1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}
{- Copyright 2016 Joey Hess <id@joeyh.name>
-
- 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
|