{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-} {- Copyright 2016 Joey Hess - - Licensed under the GNU AGPL version 3 or higher. -} module Tunables where import Cost import qualified Crypto.Argon2 as Argon2 -- | To determine the tunables used for a key name the expensive hash of the -- name is calculated, using a particular configuration, and if the -- object names it generates are available, we know the tunables. -- -- Since this process is expensive, it's important that the most commonly -- used tunables come first, so that the expensive hash does not have to be -- calculated repatedly. -- -- The reason for using this expensive method of encoding the tunables -- is that it prevents attacks where related objects are correlated based -- on their tunables. knownTunings :: [(ExpensiveHashTunable, Tunables)] knownTunings = map (\t -> (nameGenerationHash (nameGenerationTunable t), t)) [ defaultTunables ] -- | keysafe stores data for a long time, and needs to be able to process -- data from a long time ago when restoring a key. We don't want to be -- locked into old choices of crypto primitives etc forever. -- -- So, every parameter that can be tuned is configured in this data -- structure. data Tunables = Tunables { shardParams :: ShardParams , objectSize :: Int -- ^ a StorableObject is exactly this many bytes in size -- (must be a multiple of AES block size 16) , nameGenerationTunable :: NameGenerationTunable , keyEncryptionKeyTunable :: KeyEncryptionKeyTunable , encryptionTunable :: EncryptionTunable } deriving (Show) -- | Parameters for sharding. The secret is split into -- N objects, such that only M are needed to reconstruct it. data ShardParams = ShardParams { totalObjects :: Int -- ^ N , neededObjects :: Int -- ^ M } deriving (Show) -- | 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) data NameGenerationTunable = NameGenerationTunable { nameGenerationHash :: ExpensiveHashTunable } deriving (Show) -- | 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 -- 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 115 (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 } where 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 } -- | Dials back hash difficulty, lies about costs. -- Not for production use! testModeTunables :: Tunables testModeTunables = Tunables { shardParams = ShardParams { totalObjects = 3, neededObjects = 2 } , objectSize = 1024*64 , 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 } where weakargon2 c = UseArgon2 c Argon2.defaultHashOptions