From 3b4a775d536b2b2956269a59f886487efe29ed51 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Tue, 16 Aug 2016 12:57:19 -0400 Subject: switch to random salt byte to make decryption expensive --- Tunables.hs | 82 +++++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 27 deletions(-) (limited to 'Tunables.hs') 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 -- cgit v1.2.3