summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Cost.hs9
-rw-r--r--Encryption.hs202
-rw-r--r--ExpensiveHash.hs46
-rw-r--r--Shard.hs3
-rw-r--r--Tunables.hs82
-rw-r--r--Types/Cost.hs8
-rw-r--r--keysafe.cabal5
-rw-r--r--keysafe.hs6
8 files changed, 175 insertions, 186 deletions
diff --git a/Cost.hs b/Cost.hs
index c7ab9dd..31f00a3 100644
--- a/Cost.hs
+++ b/Cost.hs
@@ -15,24 +15,15 @@ import Types.Cost
-- | Cost in seconds, with the type of hardware needed.
totalCost :: Cost op -> (Seconds, [UsingHardware])
totalCost (CPUCost s) = (s, [UsingCPU])
-totalCost (GPUCost s) = (s, [UsingGPU])
-totalCost (CombinedCost a b) =
- let (s1, h1) = totalCost a
- (s2, h2) = totalCost b
- in (s1+s2, h1++h2)
raiseCostPower :: Cost c -> Entropy e -> Cost c
raiseCostPower c (Entropy e) = adjustCost c (* 2^e)
adjustCost :: Cost c -> (Seconds -> Seconds) -> Cost c
adjustCost (CPUCost s) f = CPUCost (f s)
-adjustCost (GPUCost s) f = GPUCost (f s)
-adjustCost (CombinedCost a b) f = CombinedCost (adjustCost a f) (adjustCost b f)
castCost :: Cost a -> Cost b
castCost (CPUCost s) = CPUCost s
-castCost (GPUCost s) = GPUCost s
-castCost (CombinedCost a b) = CombinedCost (castCost a) (castCost b)
-- | CostCalc for a brute force linear search through an entropy space
-- in which each step entails paying a cost.
diff --git a/Encryption.hs b/Encryption.hs
index bf2370c..8d508d8 100644
--- a/Encryption.hs
+++ b/Encryption.hs
@@ -1,4 +1,5 @@
{-# LANGUAGE OverloadedStrings, MultiParamTypeClasses, DataKinds #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
{- Copyright 2016 Joey Hess <id@joeyh.name>
-
@@ -11,9 +12,9 @@ import Types
import Tunables
import Cost
import ExpensiveHash
-import Data.Bits
import Data.Monoid
import Data.Maybe
+import Data.Word
import qualified Raaz
import qualified Raaz.Cipher.AES as Raaz
import qualified Raaz.Cipher.Internal as Raaz
@@ -24,49 +25,6 @@ import Text.Read
type AesKey = Raaz.KEY256
--- | An AES key, which is used to encrypt the key that is stored
--- in keysafe.
-data KeyEncryptionKey = KeyEncryptionKey
- { keyEncryptionKey :: AesKey
- , keyEncryptionIV :: Raaz.IV
- , keyDecryptionCost :: Cost DecryptionOp
- , keyBruteForceCalc :: CostCalc BruteForceOp UnknownPassword
- }
-
-instance Bruteforceable KeyEncryptionKey UnknownPassword where
- getBruteCostCalc = keyBruteForceCalc
-
--- | The ExpensiveHash of the Password used as the AES key.
--- Name is used as a salt, to prevent rainbow table attacks.
-genKeyEncryptionKey :: Tunables -> Name -> Password -> IO KeyEncryptionKey
-genKeyEncryptionKey tunables name (Password password) = case decryptionPuzzleTunable tunables of
- KeyBlindingLeftSide puzzlecost -> do
- prg <- Raaz.newPRG () :: IO Raaz.SystemPRG
- ob <- genRandomObstacle tunables
- randomkey <- Raaz.random prg :: IO AesKey
- let k = hashToAESKey randomkey hash
- let strongk = mixinRandomObstacle ob k
- let decryptcost = CombinedCost puzzlecost (castCost hashcost)
- iv <- genIV (Password password)
- -- To brute force data encrypted with this key,
- -- an attacker needs to pay the decryptcost for
- -- each password checked.
- let bruteforcecalc = bruteForceLinearSearch decryptcost
- return $ KeyEncryptionKey strongk iv decryptcost bruteforcecalc
- where
- hash@(ExpensiveHash hashcost _) = expensiveHash tunables salt password
- salt = Salt name
-
--- Use the sha256 of the password (truncated) as the IV.
-genIV :: Password -> IO Raaz.IV
-genIV (Password password) = do
- prg <- Raaz.newPRG () :: IO Raaz.SystemPRG
- exampleiv <- Raaz.random prg :: IO Raaz.IV
- let ivlen = B.length (Raaz.toByteString exampleiv)
- return $ fromMaybe (error "genIV fromByteString failed") $
- Raaz.fromByteString $ B.take ivlen $
- Raaz.toByteString $ Raaz.sha256 password
-
cipher :: Raaz.AES 256 'Raaz.CBC
cipher = Raaz.aes256cbc
@@ -85,21 +43,57 @@ decrypt kek (EncryptedSecretKey b _) = SecretKey <$> fromEncryptableBytes pbs
where
pbs = EncryptableBytes $ Raaz.unsafeDecrypt cipher (keyEncryptionKey kek, keyEncryptionIV kek) b
--- | A stream of all the key encryption keys that need to be tried to
--- decrypt.
+-- | An AES key, which is used to encrypt the secret key that is stored
+-- in keysafe.
+data KeyEncryptionKey = KeyEncryptionKey
+ { keyEncryptionKey :: AesKey
+ , keyEncryptionIV :: Raaz.IV
+ , keyDecryptionCost :: Cost DecryptionOp
+ , keyBruteForceCalc :: CostCalc BruteForceOp UnknownPassword
+ }
+
+instance Bruteforceable KeyEncryptionKey UnknownPassword where
+ getBruteCostCalc = keyBruteForceCalc
+
+-- | The ExpensiveHash of the Password used as the KeyEncryptionKey
+--
+-- Name is used as a salt, to prevent rainbow table attacks.
--
--- The DecryptionPuzzleTunable is used to alter the AES key
--- in some way; this stream includes every possible solution to the puzzle.
-candidateKeyEncryptionKeys :: Tunables -> KeyEncryptionKey -> [KeyEncryptionKey]
-candidateKeyEncryptionKeys tunables basekek =
- case decryptionPuzzleTunable tunables of
- KeyBlindingLeftSide _ ->
- let k = keyEncryptionKey basekek
- nrand = sizeRandomObstacle tunables
- mkcandidate b =
- let ob = mkRandomObstacle' k b
- in basekek { keyEncryptionKey = mixinRandomObstacle ob k }
- in map mkcandidate (allByteStringsOfLength nrand)
+-- A random prefix is added to the salt, to force an attacker to
+-- run the hash repeatedly.
+genKeyEncryptionKey :: Tunables -> Name -> Password -> IO KeyEncryptionKey
+genKeyEncryptionKey tunables name password = do
+ prg <- Raaz.newPRG () :: IO Raaz.SystemPRG
+ saltprefix <- genRandomSaltPrefix prg tunables
+ return $ head $
+ genKeyEncryptionKeys [saltprefix] tunables name password
+
+-- | A stream of KeyEncryptionKeys, using the specified salt prefixes.
+genKeyEncryptionKeys :: [SaltPrefix] -> Tunables -> Name -> Password -> [KeyEncryptionKey]
+genKeyEncryptionKeys saltprefixes tunables (Name name) (Password password) =
+ map mk saltprefixes
+ where
+ iv = genIV (Password password)
+ -- To brute force data encrypted with a key,
+ -- an attacker needs to pay the decryptcost for
+ -- each password checked.
+ bruteforcecalc = bruteForceLinearSearch decryptcost
+ decryptcost = castCost $ randomSaltBytesBruteForceCost kektunables
+ kektunables = keyEncryptionKeyTunable tunables
+
+ mk saltprefix = KeyEncryptionKey (hashToAESKey hash) iv decryptcost bruteforcecalc
+ where
+ salt = Salt (saltprefix <> name)
+ hash = expensiveHash (keyEncryptionKeyHash kektunables) salt password
+
+-- | A stream of all the key encryption keys that need to be tried to
+-- decrypt.
+candidateKeyEncryptionKeys :: Tunables -> Name -> Password -> [KeyEncryptionKey]
+candidateKeyEncryptionKeys tunables name password =
+ genKeyEncryptionKeys saltprefixes tunables name password
+ where
+ saltprefixes = allByteStringsOfLength $
+ randomSaltBytes $ keyEncryptionKeyTunable tunables
allByteStringsOfLength :: Int -> [B.ByteString]
allByteStringsOfLength = go []
@@ -110,84 +104,42 @@ allByteStringsOfLength = go []
w <- [0..255]
go (w:ws) (n-1)
+-- Use the sha256 of the password (truncated) as the IV.
+genIV :: Password -> Raaz.IV
+genIV (Password password) =
+ fromMaybe (error "genIV fromByteString failed") $
+ Raaz.fromByteString $ B.take ivlen $
+ Raaz.toByteString $ Raaz.sha256 password
+ where
+ ivlen = fromIntegral $ Raaz.byteSize (undefined :: Raaz.IV)
+
+type SaltPrefix = B.ByteString
+
+genRandomSaltPrefix :: Raaz.SystemPRG -> Tunables -> IO SaltPrefix
+genRandomSaltPrefix prg tunables = go []
+ (randomSaltBytes $ keyEncryptionKeyTunable tunables)
+ where
+ go ws 0 = return (B.pack ws)
+ go ws n = do
+ b <- Raaz.random prg :: IO Word8
+ go (b:ws) (n-1)
+
+instance Raaz.Random Word8
+
-- | Make an AES key out of a hash value.
--
-- Since the ExpensiveHash value is ascii encoded, and has a common prefix,
-- it does not have a high entropy in every byte, and its length is longer
-- than the AES key length. To deal with this, use the SHA256 of
-- the ExpensiveHash, as a bytestring.
-hashToAESKey :: AesKey -> ExpensiveHash -> AesKey
-hashToAESKey samplekey (ExpensiveHash _ t) =
+hashToAESKey :: ExpensiveHash -> AesKey
+hashToAESKey (ExpensiveHash _ t) =
fromMaybe (error "hashToAESKey fromByteString failed") $
Raaz.fromByteString b
where
- b = B.take (B.length (Raaz.toByteString samplekey)) $
+ b = B.take (fromIntegral $ Raaz.byteSize (undefined :: AesKey)) $
Raaz.toByteString $ Raaz.sha256 (E.encodeUtf8 t)
--- | A random value which can be mixed into an AES key to
--- require decrypting it to perform some brute-force work.
---
--- The random value is left-padded with NULL bytes, so ORing it with an AES
--- key varies the initial bytes of the key.
---
--- The AesKey also includes a random IV.
-data RandomObstacle = RandomObstacle AesKey
-
--- | 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.
---
--- 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 decryptionPuzzleTunable tunables of
- KeyBlindingLeftSide cost -> case cost 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 :: Double
- nbits
- | targetseconds < 1 = 0
- | otherwise = logBase 2 (fromIntegral $ targetseconds * triespersecond) + 1
-
-mkRandomObstacle :: AesKey -> Int -> RandomObstacle
-mkRandomObstacle k nbytes = mkRandomObstacle' k $
- B.take nbytes $ Raaz.toByteString k
-
-mkRandomObstacle' :: AesKey -> B.ByteString -> RandomObstacle
-mkRandomObstacle' examplek b = RandomObstacle $
- fromMaybe (error "mkRandomObstacle' fromByteString failed") $
- Raaz.fromByteString (b <> padding)
- where
- klen = B.length (Raaz.toByteString examplek)
- padding = B.replicate (klen - B.length b) 0
-
-genRandomObstacle :: Tunables -> IO RandomObstacle
-genRandomObstacle tunables = do
- prg <- Raaz.newPRG () :: IO Raaz.SystemPRG
- randomkey <- Raaz.random prg :: IO AesKey
- let size = sizeRandomObstacle tunables
- return $ mkRandomObstacle randomkey size
-
-mixinRandomObstacle :: RandomObstacle -> AesKey -> AesKey
-mixinRandomObstacle (RandomObstacle r) k = k'
- where
- k' = fromMaybe (error "mixinRandomObstacle fromByteString failed") $
- Raaz.fromByteString $
- Raaz.toByteString r `orBytes` Raaz.toByteString k
-
-orBytes :: B.ByteString -> B.ByteString -> B.ByteString
-orBytes a b = B.pack $ map (uncurry (.|.)) $ zip (B.unpack a) (B.unpack b)
-
-- | A bytestring that can be AES enctypted. It includes a checksum,
-- and size, and is padded to the objectSize with NULs.
--
diff --git a/ExpensiveHash.hs b/ExpensiveHash.hs
index 226fac7..3d832fb 100644
--- a/ExpensiveHash.hs
+++ b/ExpensiveHash.hs
@@ -17,6 +17,7 @@ import qualified Crypto.Argon2 as Argon2
import Raaz.Core.Encode
import Data.Time.Clock
import Control.DeepSeq
+import Control.Monad
import Data.Monoid
-- | A hash that is expensive to calculate.
@@ -29,32 +30,45 @@ data ExpensiveHash = ExpensiveHash (Cost CreationOp) T.Text
data Salt t = Salt t
--- | Would rather use haskell argon2 library, but it doesn't build
--- from source, and is buggy. https://github.com/ocharles/argon2/issues/3
-expensiveHash :: Encodable t => Tunables -> Salt t -> B.ByteString -> ExpensiveHash
-expensiveHash tunables (Salt s) b =
- case expensiveHashTunable tunables of
- UseArgon2 opts cost -> ExpensiveHash cost $
- -- Using hashEncoded here and not hash,
- -- because of this bug:
- -- https://github.com/ocharles/argon2/issues/3
- Argon2.hashEncoded opts b argonsalt
+expensiveHash :: Encodable t => ExpensiveHashTunable -> Salt t -> B.ByteString -> ExpensiveHash
+expensiveHash (UseArgon2 cost opts) (Salt s) b = ExpensiveHash cost $
+ -- Using hashEncoded here and not hash,
+ -- because of this bug:
+ -- https://github.com/ocharles/argon2/issues/3
+ Argon2.hashEncoded opts b argonsalt
where
-- argon salt cannot be shorter than 8 bytes, so pad with spaces.
argonsalt =
let sb = toByteString s
in sb <> B.replicate (8 - B.length sb ) 32
-benchmarkExpensiveHash :: Tunables -> IO (Benchmark (Cost CreationOp))
-benchmarkExpensiveHash tunables = do
+benchmarkExpensiveHash :: Int -> ExpensiveHashTunable -> Cost op -> IO (Benchmark (Cost op))
+benchmarkExpensiveHash rounds tunables expected = do
start <- getCurrentTime
- let ExpensiveHash expected t = expensiveHash tunables
- (Salt (KeyId gpgKey ("benchmark" :: B.ByteString)))
- ("himom" :: B.ByteString)
- end <- t `deepseq` getCurrentTime
+ forM_ [1..rounds] $ \_ -> do
+ let ExpensiveHash _ t = expensiveHash tunables
+ (Salt (KeyId gpgKey ("benchmark" :: B.ByteString)))
+ ("himom" :: B.ByteString)
+ t `deepseq` return ()
+ end <- getCurrentTime
let diff = floor $ end `diffUTCTime` start
let actual = CPUCost $ Seconds diff
return $ Benchmark
{ expectedBenchmark = expected
, actualBenchmark = actual
}
+
+benchmarkTunables :: Tunables -> IO ()
+benchmarkTunables tunables = do
+ putStrLn "Note that expected times are for 1 core machine."
+ putStrLn "If this machine has 2 cores, the actual times should be half the expected times."
+ putStrLn "Benchmarking 16 rounds of key generation hash..."
+ print =<< benchmarkExpensiveHash 16
+ (keyEncryptionKeyHash $ keyEncryptionKeyTunable tunables)
+ (mapCost (`div` 16) $ randomSaltBytesBruteForceCost $ keyEncryptionKeyTunable tunables)
+ putStrLn "Benchmarking 1 round of name generation hash..."
+ print =<< benchmarkExpensiveHash 1
+ (nameGenerationHash $ nameGenerationTunable tunables)
+ (getexpected $ nameGenerationHash $ nameGenerationTunable tunables)
+ where
+ getexpected (UseArgon2 cost _) = cost
diff --git a/Shard.hs b/Shard.hs
index 2af2947..ec3332e 100644
--- a/Shard.hs
+++ b/Shard.hs
@@ -41,13 +41,14 @@ shardIdents tunables (Name name) keyid =
ShardIdents idents creationcost bruteforcecalc
where
(ExpensiveHash creationcost basename) =
- expensiveHash tunables (Salt keyid) name
+ expensiveHash hashtunables (Salt keyid) name
mk n = StorableObjectIdent $ Raaz.toByteString $ mksha $
E.encodeUtf8 $ basename <> T.pack (show n)
mksha :: B.ByteString -> Raaz.Base16
mksha = Raaz.encode . Raaz.sha256
idents = map mk [1..totalObjects (head (shardParams tunables))]
bruteforcecalc = bruteForceLinearSearch creationcost
+ hashtunables = nameGenerationHash $ nameGenerationTunable tunables
genShards :: EncryptedSecretKey -> Tunables -> IO [Shard]
genShards (EncryptedSecretKey esk _) tunables = do
diff --git a/Tunables.hs b/Tunables.hs
index 0053668..49e6cd4 100644
--- a/Tunables.hs
+++ b/Tunables.hs
@@ -22,7 +22,7 @@ import qualified Crypto.Argon2 as Argon2
-- is that it prevents attacks where related objects are correlated based
-- on their tunables.
knownTunings :: [(ExpensiveHashTunable, Tunables)]
-knownTunings = map (\t -> (expensiveHashTunable t, t))
+knownTunings = map (\t -> (nameGenerationHash (nameGenerationTunable t), t))
[ defaultTunables
]
@@ -39,10 +39,11 @@ data Tunables = Tunables
, objectSize :: Int
-- ^ a StorableObject is exactly this many bytes in size
-- (must be a multiple of AES block size 16)
- , expensiveHashTunable :: ExpensiveHashTunable
+ , nameGenerationTunable :: NameGenerationTunable
+ , keyEncryptionKeyTunable :: KeyEncryptionKeyTunable
, encryptionTunable :: EncryptionTunable
- , decryptionPuzzleTunable :: DecryptionPuzzleTunable
}
+ deriving (Show)
-- | Parameters for sharding. The secret is split into
-- N objects, such that only M are needed to reconstruct it.
@@ -50,53 +51,80 @@ data ShardParams = ShardParams
{ totalObjects :: Int -- ^ N
, neededObjects :: Int -- ^ M
}
+ deriving (Show)
--- | An expensive hash, used to make it hard to crack an encrypted secret key.
-data ExpensiveHashTunable = UseArgon2 Argon2.HashOptions (Cost CreationOp)
+-- | An expensive hash, which makes brute-forcing hard.
+--
+-- The creation cost estimate must be manually tuned to match the
+-- hash options. Use benchmarkTunables to check this.
+data ExpensiveHashTunable = UseArgon2 (Cost CreationOp) Argon2.HashOptions
deriving (Show)
--- | What encryption to use.
-data EncryptionTunable = UseAES256
+data NameGenerationTunable = NameGenerationTunable
+ { nameGenerationHash :: ExpensiveHashTunable
+ }
deriving (Show)
--- | An additional puzzle that makes decryption more expensive.
-data DecryptionPuzzleTunable = KeyBlindingLeftSide (Cost DecryptionOp)
+-- | How to generate the encryption key used to encrypt the secret key.
+-- This is an expensive hash of the password, but not a super expensive
+-- hash, because a password brute forcing attacker needs to run the hash
+-- 256 times per random salt byte.
+data KeyEncryptionKeyTunable = KeyEncryptionKeyTunable
+ { keyEncryptionKeyHash :: ExpensiveHashTunable
+ , randomSaltBytes :: Int
+ , randomSaltBytesBruteForceCost :: Cost BruteForceOp
+ }
+ deriving (Show)
+
+-- | What encryption to use.
+data EncryptionTunable = UseAES256
deriving (Show)
defaultTunables :: Tunables
defaultTunables = Tunables
{ shardParams = [ShardParams { totalObjects = 3, neededObjects = 2 }]
, objectSize = 1024*64 -- 64 kb
- , expensiveHashTunable = UseArgon2 argonoptions argoncost
+ -- The nameGenerationHash was benchmarked at 661 seconds CPU time
+ -- on a 2 core Intel(R) Core(TM) i5-4210Y CPU @ 1.50GHz.
+ -- Since cost is measured per core, we double that.
+ , nameGenerationTunable = NameGenerationTunable
+ { nameGenerationHash = argon2 10000 (CPUCost (Seconds (2*600)))
+ }
+ , keyEncryptionKeyTunable = KeyEncryptionKeyTunable
+ { keyEncryptionKeyHash = argon2 100 (CPUCost (Seconds 0))
+ , randomSaltBytes = 1
+ -- The keyEncryptionKeyHash is run 256 times per
+ -- random salt byte to brute-force, and its parameters
+ -- were chosen so the total brute forcing time is 50 minutes,
+ -- on a 2 core Intel(R) Core(TM) i5-4210Y CPU @ 1.50GHz.
+ -- Since cost is measured per core, we double that.
+ , randomSaltBytesBruteForceCost = CPUCost (Seconds (2*50*60))
+ }
, encryptionTunable = UseAES256
- -- Setting this to eg, Seconds 60 only makes each password
- -- guess 60 seconds longer on a GPU. But, on a CPU, keysafe
- -- has to work for quite a long time to solve such a puzzle.
- -- So, currently disabling the puzzle with Seconds 0.
- , decryptionPuzzleTunable = KeyBlindingLeftSide (GPUCost (Seconds 0))
}
where
- argonoptions = Argon2.HashOptions
- { Argon2.hashIterations = 10000
+ argon2 i c = UseArgon2 c $ Argon2.HashOptions
+ { Argon2.hashIterations = i
, Argon2.hashMemory = 131072 -- 128 mebibtyes per thread
, Argon2.hashParallelism = 4 -- 4 threads
, Argon2.hashVariant = Argon2.Argon2i
}
- -- argon2 is GPU and ASIC resistent, so it uses CPU time.
- -- The above HashOptions were benchmarked at 661 seconds CPU time
- -- on a 2 core Intel(R) Core(TM) i5-4210Y CPU @ 1.50GHz.
- -- Since cost is measured per core, we double that.
- argoncost = CPUCost (Seconds (2*600))
--- | Dials back cryptographic difficulty, not for production use.
+-- | Dials back hash difficulty, lies about costs.
+-- Not for production use!
testModeTunables :: Tunables
testModeTunables = Tunables
{ shardParams = [ShardParams { totalObjects = 3, neededObjects = 2 }]
, objectSize = 1024*64
- , expensiveHashTunable = UseArgon2 weakargonoptions argoncost
+ , nameGenerationTunable = NameGenerationTunable
+ { nameGenerationHash = weakargon2 (CPUCost (Seconds (2*600)))
+ }
+ , keyEncryptionKeyTunable = KeyEncryptionKeyTunable
+ { keyEncryptionKeyHash = weakargon2 (CPUCost (Seconds 0))
+ , randomSaltBytes = 1
+ , randomSaltBytesBruteForceCost = CPUCost (Seconds (2*50*60))
+ }
, encryptionTunable = UseAES256
- , decryptionPuzzleTunable = KeyBlindingLeftSide (GPUCost (Seconds 0))
}
where
- UseArgon2 argonoptions argoncost = expensiveHashTunable defaultTunables
- weakargonoptions = argonoptions { Argon2.hashIterations = 1 }
+ weakargon2 c = UseArgon2 c Argon2.defaultHashOptions
diff --git a/Types/Cost.hs b/Types/Cost.hs
index a6a1160..290fa25 100644
--- a/Types/Cost.hs
+++ b/Types/Cost.hs
@@ -12,8 +12,6 @@ import Utility.HumanTime
-- | An estimated cost to perform an operation.
data Cost op
= CPUCost Seconds -- ^ using 1 CPU core
- | GPUCost Seconds
- | CombinedCost (Cost op) (Cost op)
deriving (Show)
unknownCost :: Cost op
@@ -32,9 +30,9 @@ instance Monoid (Cost t) where
mempty = CPUCost (Seconds 0)
CPUCost (Seconds a) `mappend` CPUCost (Seconds b) =
CPUCost (Seconds (a+b))
- GPUCost (Seconds a) `mappend` GPUCost (Seconds b) =
- GPUCost (Seconds (a+b))
- a `mappend` b = CombinedCost a b
+
+mapCost :: (Integer -> Integer) -> Cost op -> Cost op
+mapCost f (CPUCost (Seconds n)) = CPUCost (Seconds (f n))
-- | Operations whose cost can be measured.
data DecryptionOp
diff --git a/keysafe.cabal b/keysafe.cabal
index fc80725..3eee7c6 100644
--- a/keysafe.cabal
+++ b/keysafe.cabal
@@ -37,17 +37,22 @@ Executable keysafe
, readline == 1.0.*
, zxcvbn-c == 1.0.*
+ -- Inlined to change the finite field size to 256
+ -- for efficient serialization.
-- secret-sharing == 1.0.*
, dice-entropy-conduit >= 1.0.0.0
, binary >=0.5.1.1
, vector >=0.10.11.0
, finite-field >=0.8.0
, polynomial >= 0.7.1
+ -- Temporarily inlined due to https://github.com/ocharles/argon2/issues/3
-- argon2 == 1.1.*
Extra-Libraries: argon2
Other-Modules:
Crypto.Argon2.FFI
Crypto.Argon2
+ ExpensiveHash
+ Encryption
source-repository head
type: git
diff --git a/keysafe.hs b/keysafe.hs
index 6306511..4058487 100644
--- a/keysafe.hs
+++ b/keysafe.hs
@@ -12,6 +12,7 @@ import Tunables
import qualified CmdLine
import UI
import Encryption
+import Entropy
import Cost
import Shard
import Storage
@@ -45,7 +46,7 @@ storedemo ui keyid tunables = do
kek <- genKeyEncryptionKey tunables name password
putStrLn "Very rough estimate of cost to brute-force the password:"
print $ estimateAttack spotAWS $ estimateBruteforceOf kek
- (passwordEntropy password)
+ (passwordEntropy password [])
let esk = encrypt tunables kek secretkey
let sis = shardIdents tunables name keyid
shards <- genShards esk tunables
@@ -77,8 +78,7 @@ retrievedemo ui keyid = do
<$> mapM (uncurry (retrieveShard localFiles)) l
_ <- obscureShards localFiles
let esk = combineShards tunables shards
- basekek <- genKeyEncryptionKey tunables name password
- go esk (candidateKeyEncryptionKeys tunables basekek)
+ go esk (candidateKeyEncryptionKeys tunables name password)
where
go _ [] = error "decryption failed"
go esk (kek:rest) = case decrypt kek esk of