Add Scrypt support in the Keystores

This commit is contained in:
Zahary Karadjov 2020-08-02 21:46:12 +03:00 committed by zah
parent 1aba7aed6d
commit c882b7c2f3
2 changed files with 73 additions and 29 deletions

View File

@ -9,7 +9,7 @@ import
math, strutils, strformat, typetraits, bearssl,
stew/[results, byteutils, bitseqs, bitops2], stew/shims/macros,
eth/keyfile/uuid, blscurve, faststreams/textio, json_serialization,
nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, utils],
nimcrypto/[sha2, rijndael, pbkdf2, bcmode, hash, utils, scrypt],
./datatypes, ./crypto, ./digest, ./signatures
export
@ -130,8 +130,8 @@ const
scryptParams = ScryptParams(
dklen: keyLen,
n: 2^18,
r: 1,
p: 8
p: 1,
r: 8
)
pbkdf2Params = Pbkdf2Params(
@ -393,6 +393,13 @@ template writeValue*(w: var JsonWriter,
template bytes(value: Pbkdf2Salt|SimpleHexEncodedTypes|Aes128CtrIv): seq[byte] =
distinctBase value
func scrypt(password: openArray[char], salt: openArray[byte],
N, r, p, keyLen: static[int]): array[keyLen, byte] =
let (xyvLen, bLen) = scryptCalc(N, r, p)
var xyv = newSeq[uint32](xyvLen)
var b = newSeq[byte](bLen)
discard scrypt(password, salt, N, r, p, xyv, b, result)
proc decryptCryptoField*(crypto: Crypto, password: KeystorePass): seq[byte] =
## Returns 0 bytes if the supplied password is incorrect
@ -401,7 +408,19 @@ proc decryptCryptoField*(crypto: Crypto, password: KeystorePass): seq[byte] =
template params: auto = crypto.kdf.pbkdf2Params
sha256.pbkdf2(password.string, params.salt.bytes, params.c, params.dklen)
of kdfScrypt:
raiseAssert "Scrypt is not supported yet"
template params: auto = crypto.kdf.scryptParams
if params.dklen != scryptParams.dklen or
params.n != scryptParams.n or
params.r != scryptParams.r or
params.p != scryptParams.p:
# TODO This should be reported in a better way
return
@(scrypt(password.string,
params.salt.bytes,
scryptParams.n,
scryptParams.r,
scryptParams.p,
scryptParams.dklen))
let derivedChecksum = shaChecksum(decKey.toOpenArray(16, 31),
crypto.cipher.message.bytes)
@ -441,7 +460,7 @@ proc createCryptoField(kdfKind: KdfKind,
iv: openarray[byte] = @[]): Crypto =
type AES = aes128
let kdfSalt = Pbkdf2Salt:
let kdfSalt =
if salt.len > 0:
doAssert salt.len == keyLen
@salt
@ -454,17 +473,22 @@ proc createCryptoField(kdfKind: KdfKind,
else:
getRandomBytes(rng, AES.sizeBlock)
let decKey = sha256.pbkdf2(password.string,
kdfSalt.bytes,
pbkdf2Params.c,
pbkdf2Params.dklen)
var decKey: seq[byte]
let kdf = case kdfKind
of kdfPbkdf2:
decKey = sha256.pbkdf2(password.string,
kdfSalt,
pbkdf2Params.c,
pbkdf2Params.dklen)
var params = pbkdf2Params
params.salt = kdfSalt
params.salt = Pbkdf2Salt kdfSalt
Kdf(function: kdfPbkdf2, pbkdf2Params: params, message: "")
of kdfScrypt:
raiseAssert "Scrypt is not implemented yet"
decKey = @(scrypt(password.string, kdfSalt,
scryptParams.n, scryptParams.r, scryptParams.p, keyLen))
var params = scryptParams
params.salt = ScryptSalt kdfSalt
Kdf(function: kdfScrypt, scryptParams: params, message: "")
var
aesCipher: CTR[AES]

View File

@ -35,14 +35,14 @@ const
"checksum": {
"function": "sha256",
"params": {},
"message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb"
"message": "d2217fe5f3e9a1e34581ef8a78f7c9928e436d36dacc5e846690a5581e8ea484"
},
"cipher": {
"function": "aes-128-ctr",
"params": {
"iv": "264daa3f303d7259501c93d997d84fe6"
},
"message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30"
"message": "06ae90d55fe0a6e9c5c3bc5b170827b2e5cce3929ed3f116c2811e6366dfe20f"
}
},
"description": "This is a test keystore that uses scrypt to secure the secret.",
@ -50,7 +50,7 @@ const
"path": "m/12381/60/3141592653/589793238",
"uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f",
"version": 4
}""" #"
}"""
pbkdf2Vector = """{
"crypto": {
@ -67,29 +67,28 @@ const
"checksum": {
"function": "sha256",
"params": {},
"message": "18b148af8e52920318084560fd766f9d09587b4915258dec0676cba5b0da09d8"
"message": "8a9f5d9912ed7e75ea794bc5a89bca5f193721d30868ade6f73043c6ea6febf1"
},
"cipher": {
"function": "aes-128-ctr",
"params": {
"iv": "264daa3f303d7259501c93d997d84fe6"
},
"message": "a9249e0ca7315836356e4c7440361ff22b9fe71e2e2ed34fc1eb03976924ed48"
"message": "cee03fde2af33149775b7223e7845e4fb2c8ae1792e5f99fe9ecf474cc8c16ad"
}
},
"description": "This is a test keystore that uses scrypt to secure the secret.",
"description": "This is a test keystore that uses PBKDF2 to secure the secret.",
"pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07",
"path": "m/12381/60/0/0",
"uuid": "64625def-3331-4eea-ab6f-782f3ed16a83",
"version": 4
}""" #"
}"""
password = "testpassword"
secretBytes = hexToSeqByte("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")
salt = hexToSeqByte("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
iv = hexToSeqByte("264daa3f303d7259501c93d997d84fe6")
password = string.fromBytes hexToSeqByte("7465737470617373776f7264f09f9491")
secretBytes = hexToSeqByte "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
description = "This is a test keystore that uses scrypt to secure the secret."
salt = hexToSeqByte "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
iv = hexToSeqByte "264daa3f303d7259501c93d997d84fe6"
let
rng = newRng()
@ -106,11 +105,19 @@ suiteReport "Keystore":
check decrypt.isOk
check secret == decrypt.get()
timedTest "Scrypt decryption":
let
keystore = Json.decode(scryptVector, Keystore)
decrypt = decryptKeystore(keystore, KeystorePass password)
check decrypt.isOk
check secret == decrypt.get()
timedTest "Pbkdf2 encryption":
let keystore = createKeystore(kdfPbkdf2, rng[], secret,
KeystorePass password,
salt=salt, iv=iv,
description = description,
description = "This is a test keystore that uses PBKDF2 to secure the secret.",
path = validateKeyPath "m/12381/60/0/0")
var
encryptJson = parseJson Json.encode(keystore)
@ -120,6 +127,20 @@ suiteReport "Keystore":
check encryptJson == pbkdf2Json
timedTest "Scrypt encryption":
let keystore = createKeystore(kdfScrypt, rng[], secret,
KeystorePass password,
salt=salt, iv=iv,
description = "This is a test keystore that uses scrypt to secure the secret.",
path = validateKeyPath "m/12381/60/3141592653/589793238")
var
encryptJson = parseJson Json.encode(keystore)
scryptJson = parseJson(scryptVector)
encryptJson{"uuid"} = %""
scryptJson{"uuid"} = %""
check encryptJson == scryptJson
timedTest "Pbkdf2 errors":
expect Defect:
echo createKeystore(kdfPbkdf2, rng[], secret, salt = [byte 1])
@ -140,13 +161,12 @@ suiteReport "Keystore":
KeystorePass "").isErr
template checkVariant(remove): untyped =
check decryptKeystore(JsonString pbkdf2Vector.replace(remove, ""),
check decryptKeystore(JsonString pbkdf2Vector.replace(remove, "1234"),
KeystorePass password).isErr
checkVariant "d4e5" # salt
checkVariant "18b1" # checksum
checkVariant "264d" # iv
checkVariant "a924" # cipher
checkVariant "f876" # salt
checkVariant "75ea" # checksum
checkVariant "b722" # cipher
var badKdf = parseJson(pbkdf2Vector)
badKdf{"crypto", "kdf", "function"} = %"invalid"