implement scrypt kdf and it's tests suite (#41)
* implement scrypt kdf and it's tests suite * fix typo and misc * bump version to 0.5.1 * prevent OOM in appveyor 32 bit * remove unnecessary exceptions * fix missing pbkdf ctx * scrypt mimicking pbkdf2 API * compile time scrypt * remove allocation and macros from scrypt * fix copyright header in scrypt.nim
This commit is contained in:
parent
8fdf6bde7a
commit
029a1f0f1e
|
@ -1,6 +1,6 @@
|
|||
# Package
|
||||
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
author = "Eugene Kabanov"
|
||||
description = "Nim cryptographic library"
|
||||
license = "MIT"
|
||||
|
|
|
@ -0,0 +1,260 @@
|
|||
#
|
||||
#
|
||||
# NimCrypto
|
||||
# (c) Copyright 2020 Andri Lim
|
||||
#
|
||||
# See the file "LICENSE", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
import utils, pbkdf2
|
||||
|
||||
## This module implements
|
||||
## The scrypt Password-Based Key Derivation Function
|
||||
## https://tools.ietf.org/html/rfc7914
|
||||
|
||||
proc salsaXor(tmp: var openArray[uint32],
|
||||
src: openArray[uint32], srco: int, dest: var openArray[uint32], dsto: int) =
|
||||
## salsaXor applies Salsa20/8 to the XOR of 16 numbers from tmp and in,
|
||||
## and puts the result into both tmp and out.
|
||||
|
||||
# no macros policy
|
||||
let
|
||||
w0 = tmp[0] xor src[0 + srco]
|
||||
w1 = tmp[1] xor src[1 + srco]
|
||||
w2 = tmp[2] xor src[2 + srco]
|
||||
w3 = tmp[3] xor src[3 + srco]
|
||||
w4 = tmp[4] xor src[4 + srco]
|
||||
w5 = tmp[5] xor src[5 + srco]
|
||||
w6 = tmp[6] xor src[6 + srco]
|
||||
w7 = tmp[7] xor src[7 + srco]
|
||||
w8 = tmp[8] xor src[8 + srco]
|
||||
w9 = tmp[9] xor src[9 + srco]
|
||||
w10 = tmp[10] xor src[10 + srco]
|
||||
w11 = tmp[11] xor src[11 + srco]
|
||||
w12 = tmp[12] xor src[12 + srco]
|
||||
w13 = tmp[13] xor src[13 + srco]
|
||||
w14 = tmp[14] xor src[14 + srco]
|
||||
w15 = tmp[15] xor src[15 + srco]
|
||||
|
||||
var
|
||||
x0 = w0
|
||||
x1 = w1
|
||||
x2 = w2
|
||||
x3 = w3
|
||||
x4 = w4
|
||||
x5 = w5
|
||||
x6 = w6
|
||||
x7 = w7
|
||||
x8 = w8
|
||||
x9 = w9
|
||||
x10 = w10
|
||||
x11 = w11
|
||||
x12 = w12
|
||||
x13 = w13
|
||||
x14 = w14
|
||||
x15 = w15
|
||||
|
||||
template R(x, a, b: untyped) =
|
||||
x = x xor ROL(a, b)
|
||||
|
||||
for i in 0 ..< 4:
|
||||
R(x4, x0+x12, 7); R(x8, x4+x0, 9)
|
||||
R(x12, x8+x4, 13); R(x0, x12+x8, 18)
|
||||
|
||||
R(x9, x5+x1, 7); R(x13, x9+x5, 9)
|
||||
R(x1, x13+x9, 13); R(x5, x1+x13, 18)
|
||||
|
||||
R(x14, x10+x6, 7); R(x2, x14+x10, 9)
|
||||
R(x6, x2+x14, 13); R(x10, x6+x2, 18)
|
||||
|
||||
R(x3, x15+x11, 7); R(x7, x3+x15, 9)
|
||||
R(x11, x7+x3, 13); R(x15, x11+x7, 18)
|
||||
|
||||
R(x1, x0+x3, 7); R(x2, x1+x0, 9)
|
||||
R(x3, x2+x1, 13); R(x0, x3+x2, 18)
|
||||
|
||||
R(x6, x5+x4, 7); R(x7, x6+x5, 9)
|
||||
R(x4, x7+x6, 13); R(x5, x4+x7, 18)
|
||||
|
||||
R(x11, x10+x9, 7); R(x8, x11+x10, 9)
|
||||
R(x9, x8+x11, 13); R(x10, x9+x8, 18)
|
||||
|
||||
R(x12, x15+x14, 7); R(x13, x12+x15, 9)
|
||||
R(x14, x13+x12, 13); R(x15, x14+x13, 18)
|
||||
|
||||
x0 += w0; x1 += w1; x2 += w2; x3 += w3
|
||||
x4 += w4; x5 += w5; x6 += w6; x7 += w7
|
||||
x8 += w8; x9 += w9; x10 += w10; x11 += w11
|
||||
x12 += w12; x13 += w13; x14 += w14; x15 += w15
|
||||
|
||||
dest[0 + dsto] = x0; dest[1 + dsto] = x1
|
||||
dest[2 + dsto] = x2; dest[3 + dsto] = x3
|
||||
dest[4 + dsto] = x4; dest[5 + dsto] = x5
|
||||
dest[6 + dsto] = x6; dest[7 + dsto] = x7
|
||||
dest[8 + dsto] = x8; dest[9 + dsto] = x9
|
||||
dest[10 + dsto] = x10; dest[11 + dsto] = x11
|
||||
dest[12 + dsto] = x12; dest[13 + dsto] = x13
|
||||
dest[14 + dsto] = x14; dest[15 + dsto] = x15
|
||||
|
||||
tmp[0] = x0; tmp[1] = x1; tmp[2] = x2
|
||||
tmp[3] = x3; tmp[4] = x4; tmp[5] = x5
|
||||
tmp[6] = x6; tmp[7] = x7; tmp[8] = x8
|
||||
tmp[9] = x9; tmp[10] = x10; tmp[11] = x11
|
||||
tmp[12] = x12; tmp[13] = x13; tmp[14] = x14
|
||||
tmp[15] = x15
|
||||
|
||||
proc blockMix(tmp: var openArray[uint32], src: openArray[uint32], srco: int,
|
||||
dest: var openArray[uint32], dsto: int, r: int) =
|
||||
let
|
||||
r16 = r*16
|
||||
r2_1 = 2*r-1
|
||||
var
|
||||
i16 = srco
|
||||
i8 = dsto
|
||||
copyMem(tmp, 0, src, r2_1*16+srco, 16)
|
||||
for i in countup(0, r2_1, 2):
|
||||
salsaXor(tmp, src, i16, dest, i8)
|
||||
salsaXor(tmp, src, i16+16, dest, i8+r16)
|
||||
inc(i16, 32)
|
||||
inc(i8, 16)
|
||||
|
||||
func integer(b: openArray[uint32], boff, r: int): uint64 =
|
||||
let j = (2*r - 1) * 16 + boff
|
||||
result = uint64(b[j]) or (uint64(b[j+1]) shl 32)
|
||||
|
||||
proc blockXor(dst: var openArray[uint32], dsto: int,
|
||||
src: openArray[uint32], srco: int, n: int) =
|
||||
|
||||
## blockXor XORs numbers from dst with n numbers from src.
|
||||
for i in 0 ..< n:
|
||||
dst[i+dsto] = dst[i+dsto] xor src[i+srco]
|
||||
|
||||
proc smix(b: var openArray[byte], boffset, r, N: int,
|
||||
xyv: var openArray[uint32], voffset: int) =
|
||||
let
|
||||
r32 = r * 32
|
||||
N_1 = N - 1
|
||||
|
||||
template x: untyped = xyv
|
||||
template v: untyped = xyv
|
||||
template y: untyped = xyv
|
||||
template yoffset: untyped = r32
|
||||
|
||||
var
|
||||
# tmp: store xor'ed value for next step
|
||||
tmp: array[16, uint32]
|
||||
j = boffset
|
||||
|
||||
for i in 0 ..< r32:
|
||||
x[i] = leLoad32(b, j)
|
||||
inc(j, 4)
|
||||
|
||||
var n = voffset
|
||||
for i in countup(0, N_1, 2):
|
||||
# x[n] is an alias to v
|
||||
copyMem(v, n, x, 0, r32)
|
||||
blockMix(tmp, x, 0, y, yoffset, r)
|
||||
inc(n, r32)
|
||||
|
||||
copyMem(v, n, y, yoffset, r32)
|
||||
blockMix(tmp, y, yoffset, x, 0, r)
|
||||
inc(n, r32)
|
||||
|
||||
for i in countup(0, N_1, 2):
|
||||
j = int(integer(x, 0, r) and uint64(N_1))
|
||||
blockXor(x, 0, v, j*r32+voffset, r32)
|
||||
blockMix(tmp, x, 0, y, yoffset, r)
|
||||
|
||||
j = int(integer(y, yoffset, r) and uint64(N_1))
|
||||
blockXor(y, yoffset, v, j*r32+voffset, r32)
|
||||
blockMix(tmp, y, yoffset, x, 0, r)
|
||||
|
||||
j = boffset
|
||||
for i in 0 ..< r32:
|
||||
leStore32(b, j, x[i])
|
||||
inc(j, 4)
|
||||
|
||||
func validateParam(N, r, p: int): bool =
|
||||
# this function does not prevent DOS attack
|
||||
# in case of using huge number
|
||||
if N <= 1 or (N and (N-1)) != 0:
|
||||
# N must be > 1 and a power of 2
|
||||
return false
|
||||
|
||||
const
|
||||
maxInt = high(int64)
|
||||
maxIntd128 = maxInt div 128
|
||||
maxIntd256 = maxInt div 256
|
||||
|
||||
let
|
||||
badParam1 = uint64(r)*uint64(p) >= 1 shl 30
|
||||
badParam2 = r > maxIntd128 div p
|
||||
badParam3 = r > maxIntd256
|
||||
badParam4 = N > maxIntd128 div r
|
||||
|
||||
if badParam1 or badParam2 or badParam3 or badParam4:
|
||||
# parameters are too large
|
||||
return false
|
||||
|
||||
result = true
|
||||
|
||||
# scrypt derives a key from the password, salt, and cost parameters, returning
|
||||
# a byte slice of length keyLen that can be used as cryptographic key.
|
||||
#
|
||||
# N is a CPU/memory cost parameter, which must be a power of two greater than 1.
|
||||
# r and p must satisfy r * p < 2^30. If the parameters do not satisfy the
|
||||
# limits, the function return zero.
|
||||
#
|
||||
# Returns number of bytes stored on success, or 0 on error.
|
||||
#
|
||||
# For example, you can get a derived key for e.g. AES-256 (which needs a
|
||||
# 32-byte key) by doing:
|
||||
#
|
||||
# dk = scrypt(some_password, salt, 32768, 8, 1, 32)
|
||||
#
|
||||
# The recommended parameters for interactive logins as of 2017 are N=32768, r=8
|
||||
# and p=1. The parameters N, r, and p should be increased as memory latency and
|
||||
# CPU parallelism increases; consider setting N to the highest power of 2 you
|
||||
# can derive within 100 milliseconds. Remember to get a good random salt.
|
||||
func scrypt*[T, M](password: openArray[T], salt: openArray[M],
|
||||
N, r, p: int, xyv: var openArray[uint32],
|
||||
b, output: var openArray[byte]): int =
|
||||
|
||||
when not((M is byte) or (M is char)):
|
||||
{.fatal: "Choosen password type is not supported!".}
|
||||
|
||||
when not((T is byte) or (T is char)):
|
||||
{.fatal: "Choosen salt type is not supported!".}
|
||||
|
||||
if not validateParam(N, r, p):
|
||||
return 0
|
||||
|
||||
let
|
||||
r32 = r*32
|
||||
r64 = r32*2
|
||||
r128 = r64*2
|
||||
|
||||
if b.len < p*r128:
|
||||
return 0
|
||||
|
||||
if xyv.len < r32*(N+2):
|
||||
return 0
|
||||
|
||||
var ctx: HMAC[sha256]
|
||||
if ctx.pbkdf2(password, salt, 1, b) == 0:
|
||||
return 0
|
||||
|
||||
var n = 0
|
||||
for i in 0 ..< p:
|
||||
smix(b, n, r, N, xyv, r64)
|
||||
inc(n, r128)
|
||||
|
||||
ctx.pbkdf2(password, b, 1, output)
|
||||
|
||||
# because of NimCrypto no alloc policy,
|
||||
# users of scrypt must provide their own `xyv` and `b` array.
|
||||
# scryptCalc will return how much element needed.
|
||||
# return value -> (xyv.len of uint32s, b.len of bytes)
|
||||
func scryptCalc*(N, r, p: int): (int, int) =
|
||||
result = ((r*32*(N+2)), (p*r*128))
|
|
@ -1,6 +1,6 @@
|
|||
import testsysrand
|
||||
import testsha1, testsha2, testkeccak, testripemd, testblake2
|
||||
import testhmac, testkdf
|
||||
import testhmac, testkdf, testscrypt
|
||||
import testrijndael, testblowfish, testtwofish
|
||||
import testbcmode
|
||||
import testapi, testutils
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
import unittest
|
||||
|
||||
# use include here, because we want to test
|
||||
# internal functions too
|
||||
include ../nimcrypto/scrypt
|
||||
|
||||
when defined(nimHasUsed): {.used.}
|
||||
|
||||
const
|
||||
# these BE uint32 are copied from RFC 7914
|
||||
# test vectors octets, and I'm too lazy to
|
||||
# rewrite/reprint it into LE uint32
|
||||
|
||||
inputBE = [
|
||||
0x7e879a21'u32, 0x4f3ec986'u32, 0x7ca940e6'u32, 0x41718f26'u32,
|
||||
0xbaee555b'u32, 0x8c61c1b5'u32, 0x0df84611'u32, 0x6dcd3b1d'u32,
|
||||
0xee24f319'u32, 0xdf9b3d85'u32, 0x14121e4b'u32, 0x5ac5aa32'u32,
|
||||
0x76021d29'u32, 0x09c74829'u32, 0xedebc68d'u32, 0xb8b8c25e'u32
|
||||
]
|
||||
|
||||
outputBE = [
|
||||
0xa41f859c'u32, 0x6608cc99'u32, 0x3b81cacb'u32, 0x020cef05'u32,
|
||||
0x044b2181'u32, 0xa2fd337d'u32, 0xfd7b1c63'u32, 0x96682f29'u32,
|
||||
0xb4393168'u32, 0xe3c9e6bc'u32, 0xfe6bc5b7'u32, 0xa06d96ba'u32,
|
||||
0xe424cc10'u32, 0x2c91745c'u32, 0x24ad673d'u32, 0xc7618f81'u32
|
||||
]
|
||||
|
||||
bInBE = [
|
||||
0xf7ce0b65'u32, 0x3d2d72a4'u32, 0x108cf5ab'u32, 0xe912ffdd'u32,
|
||||
0x777616db'u32, 0xbb27a70e'u32, 0x8204f3ae'u32, 0x2d0f6fad'u32,
|
||||
0x89f68f48'u32, 0x11d1e87b'u32, 0xcc3bd740'u32, 0x0a9ffd29'u32,
|
||||
0x094f0184'u32, 0x639574f3'u32, 0x9ae5a131'u32, 0x5217bcd7'u32,
|
||||
0x89499144'u32, 0x7213bb22'u32, 0x6c25b54d'u32, 0xa86370fb'u32,
|
||||
0xcd984380'u32, 0x374666bb'u32, 0x8ffcb5bf'u32, 0x40c254b0'u32,
|
||||
0x67d27c51'u32, 0xce4ad5fe'u32, 0xd829c90b'u32, 0x505a571b'u32,
|
||||
0x7f4d1cad'u32, 0x6a523cda'u32, 0x770e67bc'u32, 0xeaaf7e89'u32
|
||||
]
|
||||
|
||||
bOutBE = [
|
||||
0xa41f859c'u32, 0x6608cc99'u32, 0x3b81cacb'u32, 0x020cef05'u32,
|
||||
0x044b2181'u32, 0xa2fd337d'u32, 0xfd7b1c63'u32, 0x96682f29'u32,
|
||||
0xb4393168'u32, 0xe3c9e6bc'u32, 0xfe6bc5b7'u32, 0xa06d96ba'u32,
|
||||
0xe424cc10'u32, 0x2c91745c'u32, 0x24ad673d'u32, 0xc7618f81'u32,
|
||||
0x20edc975'u32, 0x323881a8'u32, 0x0540f64c'u32, 0x162dcd3c'u32,
|
||||
0x21077cfe'u32, 0x5f8d5fe2'u32, 0xb1a4168f'u32, 0x953678b7'u32,
|
||||
0x7d3b3d80'u32, 0x3b60e4ab'u32, 0x920996e5'u32, 0x9b4d53b6'u32,
|
||||
0x5d2a2258'u32, 0x77d5edf5'u32, 0x842cb9f1'u32, 0x4eefe425'u32
|
||||
]
|
||||
|
||||
bhex =
|
||||
"f7ce0b653d2d72a4108cf5abe912ffdd" &
|
||||
"777616dbbb27a70e8204f3ae2d0f6fad" &
|
||||
"89f68f4811d1e87bcc3bd7400a9ffd29" &
|
||||
"094f0184639574f39ae5a1315217bcd7" &
|
||||
"894991447213bb226c25b54da86370fb" &
|
||||
"cd984380374666bb8ffcb5bf40c254b0" &
|
||||
"67d27c51ce4ad5fed829c90b505a571b" &
|
||||
"7f4d1cad6a523cda770e67bceaaf7e89"
|
||||
|
||||
bouthex =
|
||||
"79ccc193629debca047f0b70604bf6b6" &
|
||||
"2ce3dd4a9626e355fafc6198e6ea2b46" &
|
||||
"d58413673b99b029d665c357601fb426" &
|
||||
"a0b2f4bba200ee9f0a43d19b571a9c71" &
|
||||
"ef1142e65d5a266fddca832ce59faa7c" &
|
||||
"ac0b9cf1be2bffca300d01ee387619c4" &
|
||||
"ae12fd4438f203a0e4e1c47ec314861f" &
|
||||
"4e9087cb33396a6873e8f9d2539a4b8e"
|
||||
|
||||
proc swapBytes(x: var openArray[uint32]) =
|
||||
for i in 0..<x.len:
|
||||
x[i] = leSwap32(x[i])
|
||||
|
||||
proc swapBytes(a: var openArray[uint32], b: openArray[uint32]) =
|
||||
for i in 0..<a.len:
|
||||
a[i] = leSwap32(b[i])
|
||||
|
||||
suite "Scrypt KDF tests suite":
|
||||
test "salsaXor":
|
||||
var input: array[16, uint32]
|
||||
var output: array[16, uint32]
|
||||
var tmp: array[16, uint32]
|
||||
|
||||
input.swapBytes(inputBE)
|
||||
salsaXor(tmp, input, 0, output, 0)
|
||||
output.swapBytes
|
||||
check output == outputBE
|
||||
|
||||
test "blockMix":
|
||||
var bIn: array[32, uint32]
|
||||
var bOut: array[32, uint32]
|
||||
var bTmp: array[32, uint32]
|
||||
|
||||
bIn.swapBytes(bInBE)
|
||||
blockMix(bTmp, bIn, 0, bOut, 0, 1)
|
||||
bOut.swapBytes
|
||||
check bOut == bOutBE
|
||||
|
||||
test "smix":
|
||||
let
|
||||
r = 1
|
||||
N = 16
|
||||
var xy = newSeq[uint32](64*r + 32*N*r)
|
||||
|
||||
var b = utils.fromHex(bhex)
|
||||
smix(b, 0, r, N, xy, 64*r)
|
||||
var bb = utils.fromHex(bouthex)
|
||||
check b == bb
|
||||
|
||||
func scrypt[T, M](password: openArray[T], salt: openArray[M],
|
||||
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)
|
||||
|
||||
# again, these test vectors are copied from RFC 7914
|
||||
test "scrypt N=16, r=1, p=1, keyLen=64":
|
||||
let key = fromHex("77D6576238657B203B19CA42C18A0497F16B4844E3074AE8DFDFFA3FEDE21442" &
|
||||
"FCD0069DED0948F8326A753A0FC81F17E8D3E0FB2E0D3628CF35E20C38D18906")
|
||||
let dkey = scrypt(password="", salt="", N=16, r=1, p=1, keyLen=64)
|
||||
check key == dkey
|
||||
|
||||
test "scrypt N=16, r=1, p=1, keyLen=64 (compile-time)":
|
||||
const key = fromHex("77D6576238657B203B19CA42C18A0497F16B4844E3074AE8DFDFFA3FEDE21442" &
|
||||
"FCD0069DED0948F8326A753A0FC81F17E8D3E0FB2E0D3628CF35E20C38D18906")
|
||||
const dkey = scrypt(password="", salt="", N=16, r=1, p=1, keyLen=64)
|
||||
check key == dkey
|
||||
|
||||
test "scrypt N=1024, r=8, p=16, keyLen=64":
|
||||
let key = fromHex("FDBABE1C9D3472007856E7190D01E9FE7C6AD7CBC8237830E77376634B373162" &
|
||||
"2EAF30D92E22A3886FF109279D9830DAC727AFB94A83EE6D8360CBDFA2CC0640")
|
||||
let dkey = scrypt(password="password", salt="NaCl", N=1024, r=8, p=16, keyLen=64)
|
||||
check key == dkey
|
||||
|
||||
test "scrypt N=16384, r=8, p=1, keyLen=64":
|
||||
let key = fromHex("7023BDCB3AFD7348461C06CD81FD38EBFDA8FBBA904F8E3EA9B543F6545DA1F2" &
|
||||
"D5432955613F0FCF62D49705242A9AF9E61E85DC0D651E40DFCF017B45575887")
|
||||
let dkey = scrypt(password="pleaseletmein", salt="SodiumChloride", N=16384, r=8, p=1, keyLen=64)
|
||||
check key == dkey
|
||||
|
||||
when defined(cpu64):
|
||||
# these test vectors OOM with appveyor 32 bit
|
||||
# because of huge N
|
||||
test "scrypt N=1048576, r=8, p=1, keyLen=32":
|
||||
let key = fromHex("E277EA2CACB23EDAFC039D229B79DC13ECEDB601D99B182A9FEDBA1E2BFB4F58")
|
||||
let dkey = scrypt(password="Rabbit", salt="Mouse", N=1048576, r=8, p=1, keyLen=32)
|
||||
check key == dkey
|
||||
|
||||
test "scrypt N=1048576, r=8, p=1, keyLen=64":
|
||||
let key = fromHex("2101CB9B6A511AAEADDBBE09CF70F881EC568D574A2FFD4DABE5EE9820ADAA47" &
|
||||
"8E56FD8F4BA5D09FFA1C6D927C40F4C337304049E8A952FBCBF45C6FA77A41A4")
|
||||
let dkey = scrypt(password="pleaseletmein", salt="SodiumChloride", N=1048576, r=8, p=1, keyLen=64)
|
||||
check key == dkey
|
||||
|
||||
test "string vs openarray[byte]":
|
||||
let stringarg = scrypt(password="password", salt="NaCl", N=1024, r=8, p=16, keyLen=32)
|
||||
let openarrayarg = scrypt(fromHex("70617373776F7264"), fromHex("4E61436C"), N=1024, r=8, p=16, keyLen=32)
|
||||
check stringarg == openarrayarg
|
Loading…
Reference in New Issue