summaryrefslogtreecommitdiffhomepage
path: root/Encryption.hs
diff options
context:
space:
mode:
authorJoey Hess <joeyh@joeyh.name>2016-08-07 16:25:12 -0400
committerJoey Hess <joeyh@joeyh.name>2016-08-07 16:25:12 -0400
commit39707fda6289740729bef8cb214a2bf3f555b86e (patch)
tree98fa699a499b2ef679f88785d65aaee445c748c4 /Encryption.hs
parent460edfad8ed45412050822dfdf84f2d54015fb04 (diff)
downloadkeysafe-39707fda6289740729bef8cb214a2bf3f555b86e.tar.gz
finish AES decryption puzzle implementation
Diffstat (limited to 'Encryption.hs')
-rw-r--r--Encryption.hs87
1 files changed, 62 insertions, 25 deletions
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)