From 39707fda6289740729bef8cb214a2bf3f555b86e Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Sun, 7 Aug 2016 16:25:12 -0400 Subject: finish AES decryption puzzle implementation --- Encryption.hs | 87 ++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 25 deletions(-) (limited to 'Encryption.hs') diff --git a/Encryption.hs b/Encryption.hs index 23da288..a31c004 100644 --- a/Encryption.hs +++ b/Encryption.hs @@ -6,11 +6,13 @@ import Types import Cost import Tunables import ExpensiveHash -import qualified Data.ByteString as B -import Raaz.Core.Encode -import qualified Raaz.Cipher.AES as AES import Data.Word +import Data.Bits import Data.Monoid +import Data.Maybe +import qualified Data.ByteString as B +import Raaz +import qualified Raaz.Cipher.AES as AES -- | An AES key, which is used to encrypt the key that is stored -- in keysafe. @@ -24,38 +26,73 @@ instance Bruteforceable KeyEncryptionKey UnknownPassword where -- | The ExpensiveHash of the Password is combined with a -- RandomObstacle to form the AES key. Combination method is logical OR. -genKeyEncryptionKey :: Tunables -> KeyIdent -> Password -> KeyEncryptionKey -genKeyEncryptionKey tunables keyident password = - KeyEncryptionKey k decryptcost bruteforcecalc +genKeyEncryptionKey :: Tunables -> KeyIdent -> Password -> IO KeyEncryptionKey +genKeyEncryptionKey tunables keyident password = do + ob@(RandomObstacle ok) <- genRandomObstacle tunables + -- Truncate the hash to the AES key length. + let truncatedhashb = B.take (B.length (toByteString ok)) hashb + let k = fromMaybe (error "genKeyEncryptionKey fromByteString failed") $ + fromByteString truncatedhashb + let strongk = mixinRandomObstacle ob k + return $ KeyEncryptionKey strongk decryptcost bruteforcecalc where - k = undefined -- hashb <> ob -- TODO use logical OR (ExpensiveHash hashcost hashb) = expensiveHash tunables salt password salt = Salt keyident - (RandomObstacle ob) = genRandomObstacle decryptcost - decryptcost = CombinedCost (decryptionCost tunables) (castCost hashcost) + decryptcost = CombinedCost (decryptionPuzzleCost tunables) (castCost hashcost) -- To brute force data encrypted with this key, -- an attacker needs to pay the decryptcost for each password -- checked. bruteforcecalc = bruteForceLinearSearch decryptcost --- | A random value which adds difficulty to decrypting, since it's never --- written down anywhere and must always be brute-forced. +-- | A random value which can be mixed into an AES key to +-- require decrypting it to perform some brute-force work. -- --- It's always 64 bits long, and is left padded with 0's, --- which are followed by a series of random bits (which necessarily always --- starts with 1). Eg: --- --- > 0000000000000000000000000000000000000000000000000000000100011100 --- --- The fewer leading 0's and thus longer the random bits, --- the harder it is. -data RandomObstacle = RandomObstacle B.ByteString +-- The random value is right-padded with NULL bytes, so ORing it with an AES +-- key varies the initial bytes of the key. +data RandomObstacle = RandomObstacle AES.KEY256 --- | Generate a random obstacle that will add the specified cost to AES --- decryption. +-- | Length of the random obstacle, in bytes, that will satisfy the +-- decryptionPuzzleCost. -- -- AES can be calculated more efficiently by a GPU, so the cost must be -- a GPU cost. -genRandomObstacle :: Cost DecryptionOp -> RandomObstacle -genRandomObstacle (GPUCost c) = undefined -genRandomObstacle _ = error "decryptionCost must be a GPUCost" +-- +-- This depends on the objectSize, because to brute force the +-- RandomObstable, AES decryptions must be done repeatedly, and the +-- time needed for an AES decryption depends on the amount of data. +sizeRandomObstacle :: Tunables -> Int +sizeRandomObstacle tunables = ceiling $ nbits / 8 + where + -- in 2016, a GPU can run AES at 10 GB/s. + bytespersecond = 10*1024*1024*1024 + triespersecond = bytespersecond `div` fromIntegral (objectSize tunables) + targetseconds = case decryptionPuzzleCost tunables of + GPUCost (Seconds n) -> n + _ -> error "decryptionPuzzleCost must be a GPUCost" + -- Add one bit of entropy, because a brute-force attack will + -- on average succeed half-way through the search space. + nbits = logBase 2 (fromIntegral $ targetseconds * triespersecond) + 1 + +mkRandomObstacle :: AES.KEY256 -> Int -> AES.KEY256 +mkRandomObstacle k nbytes = + fromMaybe (error "mkRandomObstacle fromByteString failed") $ + fromByteString ob + where + kb = toByteString k + rightnulls = B.replicate (B.length kb - nbytes) 0 + ob = B.take nbytes kb <> rightnulls + +genRandomObstacle :: Tunables -> IO RandomObstacle +genRandomObstacle tunables = do + prg <- newPRG () :: IO SystemPRG + randomkey <- random prg :: IO AES.KEY256 + let size = sizeRandomObstacle tunables + return $ RandomObstacle $ mkRandomObstacle randomkey size + +mixinRandomObstacle :: RandomObstacle -> AES.KEY256 -> AES.KEY256 +mixinRandomObstacle (RandomObstacle r) k = + fromMaybe (error "mixinRandomObstacle fromByteString failed") $ + fromByteString $ toByteString r `orBytes` toByteString k + +orBytes :: B.ByteString -> B.ByteString -> B.ByteString +orBytes a b = B.pack $ map (uncurry (.|.)) $ zip (B.unpack a) (B.unpack b) -- cgit v1.2.3