diff --git a/beacon_chain/spec/keystore.nim b/beacon_chain/spec/keystore.nim index 175f3d962..b989db57f 100644 --- a/beacon_chain/spec/keystore.nim +++ b/beacon_chain/spec/keystore.nim @@ -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] diff --git a/tests/test_keystore.nim b/tests/test_keystore.nim index 9798cff3a..532dbd13e 100644 --- a/tests/test_keystore.nim +++ b/tests/test_keystore.nim @@ -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"