crypto.scrypt: add a new scrypt module to vlib/crypto (#22216)

This commit is contained in:
Kim Shrier 2024-09-14 00:54:04 -06:00 committed by GitHub
parent 26ab7d4fe1
commit 423f804b07
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 470 additions and 0 deletions

241
vlib/crypto/scrypt/scrypt.v Normal file
View File

@ -0,0 +1,241 @@
// Copyright (c) 2023 Kim Shrier. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
// Package scrypt implements the key derivation functions as
// described in https://datatracker.ietf.org/doc/html/rfc7914
module scrypt
import crypto.pbkdf2
import crypto.sha256
import encoding.binary
import math.bits
pub const max_buffer_length = ((u64(1) << 32) - 1) * 32
pub const max_blocksize_parallal_product = u64(1 << 30)
// salsa20_8 applies the salsa20/8 core transformation to a block
// of 64 u8 bytes. The block is modified in place.
fn salsa20_8(mut block []u8) {
mut block_words := []u32{len: 16}
mut scratch := [16]u32{}
for i in 0 .. 16 {
block_words[i] = binary.little_endian_u32_at(block, i * 4)
scratch[i] = block_words[i]
}
for i := 8; i > 0; i -= 2 {
// processing columns
scratch[4] ^= bits.rotate_left_32(scratch[0] + scratch[12], 7)
scratch[8] ^= bits.rotate_left_32(scratch[4] + scratch[0], 9)
scratch[12] ^= bits.rotate_left_32(scratch[8] + scratch[4], 13)
scratch[0] ^= bits.rotate_left_32(scratch[12] + scratch[8], 18)
scratch[9] ^= bits.rotate_left_32(scratch[5] + scratch[1], 7)
scratch[13] ^= bits.rotate_left_32(scratch[9] + scratch[5], 9)
scratch[1] ^= bits.rotate_left_32(scratch[13] + scratch[9], 13)
scratch[5] ^= bits.rotate_left_32(scratch[1] + scratch[13], 18)
scratch[14] ^= bits.rotate_left_32(scratch[10] + scratch[6], 7)
scratch[2] ^= bits.rotate_left_32(scratch[14] + scratch[10], 9)
scratch[6] ^= bits.rotate_left_32(scratch[2] + scratch[14], 13)
scratch[10] ^= bits.rotate_left_32(scratch[6] + scratch[2], 18)
scratch[3] ^= bits.rotate_left_32(scratch[15] + scratch[11], 7)
scratch[7] ^= bits.rotate_left_32(scratch[3] + scratch[15], 9)
scratch[11] ^= bits.rotate_left_32(scratch[7] + scratch[3], 13)
scratch[15] ^= bits.rotate_left_32(scratch[11] + scratch[7], 18)
// processing rows
scratch[1] ^= bits.rotate_left_32(scratch[0] + scratch[3], 7)
scratch[2] ^= bits.rotate_left_32(scratch[1] + scratch[0], 9)
scratch[3] ^= bits.rotate_left_32(scratch[2] + scratch[1], 13)
scratch[0] ^= bits.rotate_left_32(scratch[3] + scratch[2], 18)
scratch[6] ^= bits.rotate_left_32(scratch[5] + scratch[4], 7)
scratch[7] ^= bits.rotate_left_32(scratch[6] + scratch[5], 9)
scratch[4] ^= bits.rotate_left_32(scratch[7] + scratch[6], 13)
scratch[5] ^= bits.rotate_left_32(scratch[4] + scratch[7], 18)
scratch[11] ^= bits.rotate_left_32(scratch[10] + scratch[9], 7)
scratch[8] ^= bits.rotate_left_32(scratch[11] + scratch[10], 9)
scratch[9] ^= bits.rotate_left_32(scratch[8] + scratch[11], 13)
scratch[10] ^= bits.rotate_left_32(scratch[9] + scratch[8], 18)
scratch[12] ^= bits.rotate_left_32(scratch[15] + scratch[14], 7)
scratch[13] ^= bits.rotate_left_32(scratch[12] + scratch[15], 9)
scratch[14] ^= bits.rotate_left_32(scratch[13] + scratch[12], 13)
scratch[15] ^= bits.rotate_left_32(scratch[14] + scratch[13], 18)
}
for i in 0 .. 16 {
scratch[i] += block_words[i]
binary.little_endian_put_u32_at(mut block, scratch[i], i * 4)
}
}
@[inline]
fn blkcpy(mut dest []u8, src []u8, len u32) {
for i in 0 .. len {
dest[i] = src[i]
}
}
@[inline]
fn blkxor(mut dest []u8, src []u8, len u32) {
for i in 0 .. len {
dest[i] ^= src[i]
}
}
// block_mix performs the block_mix operation using salsa20_8
//
// The block input must be 128 * r in length. The temp array
// has to be the same size, 128 * r. r is a positive integer
// value > 0. The block is modified in place.
fn block_mix(mut block []u8, mut temp []u8, r u32) {
mut scratch := []u8{len: 64, cap: 64}
blkcpy(mut scratch, block[(((2 * r) - 1) * 64)..], 64)
for i in 0 .. 2 * r {
start := i * 64
stop := start + 64
blkxor(mut scratch, block[start..stop], 64)
salsa20_8(mut scratch)
blkcpy(mut temp[start..stop], scratch, 64)
}
for i in 0 .. r {
start := i * 64
stop := start + 64
temp_start := (i * 2) * 64
temp_stop := temp_start + 64
blkcpy(mut block[start..stop], temp[temp_start..temp_stop], 64)
}
for i in 0 .. r {
start := (i + r) * 64
stop := start + 64
temp_start := ((i * 2) + 1) * 64
temp_stop := temp_start + 64
blkcpy(mut block[start..stop], temp[temp_start..temp_stop], 64)
}
}
fn smix(mut block []u8, r u32, n u64, mut v_block []u8, mut temp_block []u8) {
blkcpy(mut temp_block, block, 128 * r)
y_start := 128 * r
for i in 0 .. n {
v_start := i * (128 * r)
v_stop := v_start + (128 * r)
blkcpy(mut v_block[v_start..v_stop], temp_block, 128 * r)
block_mix(mut temp_block, mut temp_block[y_start..], r)
}
for _ in 0 .. n {
j := binary.little_endian_u64_at(temp_block, ((2 * r) - 1) * 64) & (n - 1)
v_start := j * (128 * r)
v_stop := v_start + (128 * r)
blkxor(mut temp_block, v_block[v_start..v_stop], 128 * r)
block_mix(mut temp_block, mut temp_block[y_start..], r)
}
blkcpy(mut block, temp_block, 128 * r)
}
struct OutputBufferLengthError {
Error
length u64
}
fn (err OutputBufferLengthError) msg() string {
return 'the output buffer length, ${err.length}, is greater than ${max_buffer_length}'
}
struct BlocksizeParallelProductError {
Error
blocksize u32
parallel u32
product u64
}
fn (err BlocksizeParallelProductError) msg() string {
return 'the product of blocksize ${err.blocksize} * parallel ${err.parallel} = ${err.product}, is greater than ${max_blocksize_parallal_product}'
}
struct CpuMemoryCostError {
Error
cost u64
}
fn (err CpuMemoryCostError) msg() string {
return 'the CPU/memory cost ${err.cost} must be greater than 0 and also a power of 2'
}
// scrypt performs password based key derivation using the scrypt algorithm.
//
// The input parameters are:
//
// password - a slice of bytes which is the password being used to
// derive the key. Don't leak this value to anybody.
// salt - a slice of bytes used to make it harder to crack the key.
// n - CPU/Memory cost parameter, must be larger than 0, a power of 2,
// and less than 2^(128 * r / 8).
// r - block size parameter.
// p - parallelization parameter, a positive integer less than or
// equal to ((2^32-1) * hLen) / MFLen where hLen is 32 and
// MFlen is 128 * r.
// dk_len - intended output length in octets of the derived key;
// a positive integer less than or equal to (2^32 - 1) * hLen
// where hLen is 32.
//
// Reasonable values for n, r, and p are n = 1024, r = 8, p = 16.
pub fn scrypt(password []u8, salt []u8, n u64, r u32, p u32, dk_len u64) ![]u8 {
if dk_len > max_buffer_length {
return OutputBufferLengthError{
length: dk_len
}
}
if u64(r) * u64(p) >= max_blocksize_parallal_product {
return BlocksizeParallelProductError{
blocksize: r
parallel: p
product: u64(r) * u64(p)
}
}
// the following is a sneaky way to determine if a number is a
// power of 2. Also, a value of 0 is not allowed.
if (n & (n - 1)) != 0 || n == 0 {
return CpuMemoryCostError{
cost: n
}
}
mut b := pbkdf2.key(password, salt, 1, 128 * r * p, sha256.new())!
mut xy := []u8{len: int(256 * r), cap: int(256 * r), init: 0}
mut v := []u8{len: int(128 * r * n), cap: int(128 * r * n), init: 0}
for i in u32(0) .. p {
smix(mut b[i * 128 * r..], r, n, mut v, mut xy)
}
result := pbkdf2.key(password, b, 1, 128 * r * p, sha256.new())!
return result[..dk_len]
}

View File

@ -0,0 +1,229 @@
module scrypt
import crypto.pbkdf2
import crypto.sha256
fn test_salsa20_8() {
// The input_block and output_block values are taken from
// [RFC7914](https://datatracker.ietf.org/doc/html/rfc7914#section-8)
// section 8.
// vfmt off
mut input_block := [
u8(0x7e), 0x87, 0x9a, 0x21, 0x4f, 0x3e, 0xc9, 0x86,
0x7c, 0xa9, 0x40, 0xe6, 0x41, 0x71, 0x8f, 0x26,
0xba, 0xee, 0x55, 0x5b, 0x8c, 0x61, 0xc1, 0xb5,
0x0d, 0xf8, 0x46, 0x11, 0x6d, 0xcd, 0x3b, 0x1d,
0xee, 0x24, 0xf3, 0x19, 0xdf, 0x9b, 0x3d, 0x85,
0x14, 0x12, 0x1e, 0x4b, 0x5a, 0xc5, 0xaa, 0x32,
0x76, 0x02, 0x1d, 0x29, 0x09, 0xc7, 0x48, 0x29,
0xed, 0xeb, 0xc6, 0x8d, 0xb8, 0xb8, 0xc2, 0x5e]
output_block := [
u8(0xa4), 0x1f, 0x85, 0x9c, 0x66, 0x08, 0xcc, 0x99,
0x3b, 0x81, 0xca, 0xcb, 0x02, 0x0c, 0xef, 0x05,
0x04, 0x4b, 0x21, 0x81, 0xa2, 0xfd, 0x33, 0x7d,
0xfd, 0x7b, 0x1c, 0x63, 0x96, 0x68, 0x2f, 0x29,
0xb4, 0x39, 0x31, 0x68, 0xe3, 0xc9, 0xe6, 0xbc,
0xfe, 0x6b, 0xc5, 0xb7, 0xa0, 0x6d, 0x96, 0xba,
0xe4, 0x24, 0xcc, 0x10, 0x2c, 0x91, 0x74, 0x5c,
0x24, 0xad, 0x67, 0x3d, 0xc7, 0x61, 0x8f, 0x81]
// vfmt on
salsa20_8(mut input_block)
for i in 0 .. 64 {
assert input_block[i] == output_block[i], 'assertion failed for i: ${i}'
}
}
fn test_block_mix() {
// The input_block and output_block values are taken from
// [RFC7914](https://datatracker.ietf.org/doc/html/rfc7914#section-9)
// section 9.
// vfmt off
mut input_block := [
// B[0] - the first 64 bytes of input
u8(0xf7), 0xce, 0x0b, 0x65, 0x3d, 0x2d, 0x72, 0xa4,
0x10, 0x8c, 0xf5, 0xab, 0xe9, 0x12, 0xff, 0xdd,
0x77, 0x76, 0x16, 0xdb, 0xbb, 0x27, 0xa7, 0x0e,
0x82, 0x04, 0xf3, 0xae, 0x2d, 0x0f, 0x6f, 0xad,
0x89, 0xf6, 0x8f, 0x48, 0x11, 0xd1, 0xe8, 0x7b,
0xcc, 0x3b, 0xd7, 0x40, 0x0a, 0x9f, 0xfd, 0x29,
0x09, 0x4f, 0x01, 0x84, 0x63, 0x95, 0x74, 0xf3,
0x9a, 0xe5, 0xa1, 0x31, 0x52, 0x17, 0xbc, 0xd7,
// B[1] - the second 64 bytes of input
0x89, 0x49, 0x91, 0x44, 0x72, 0x13, 0xbb, 0x22,
0x6c, 0x25, 0xb5, 0x4d, 0xa8, 0x63, 0x70, 0xfb,
0xcd, 0x98, 0x43, 0x80, 0x37, 0x46, 0x66, 0xbb,
0x8f, 0xfc, 0xb5, 0xbf, 0x40, 0xc2, 0x54, 0xb0,
0x67, 0xd2, 0x7c, 0x51, 0xce, 0x4a, 0xd5, 0xfe,
0xd8, 0x29, 0xc9, 0x0b, 0x50, 0x5a, 0x57, 0x1b,
0x7f, 0x4d, 0x1c, 0xad, 0x6a, 0x52, 0x3c, 0xda,
0x77, 0x0e, 0x67, 0xbc, 0xea, 0xaf, 0x7e, 0x89]
output_block := [
// B'[0] - the first 64 bytes of output
u8(0xa4), 0x1f, 0x85, 0x9c, 0x66, 0x08, 0xcc, 0x99,
0x3b, 0x81, 0xca, 0xcb, 0x02, 0x0c, 0xef, 0x05,
0x04, 0x4b, 0x21, 0x81, 0xa2, 0xfd, 0x33, 0x7d,
0xfd, 0x7b, 0x1c, 0x63, 0x96, 0x68, 0x2f, 0x29,
0xb4, 0x39, 0x31, 0x68, 0xe3, 0xc9, 0xe6, 0xbc,
0xfe, 0x6b, 0xc5, 0xb7, 0xa0, 0x6d, 0x96, 0xba,
0xe4, 0x24, 0xcc, 0x10, 0x2c, 0x91, 0x74, 0x5c,
0x24, 0xad, 0x67, 0x3d, 0xc7, 0x61, 0x8f, 0x81,
// B'[1] - the second 64 bytes of output
0x20, 0xed, 0xc9, 0x75, 0x32, 0x38, 0x81, 0xa8,
0x05, 0x40, 0xf6, 0x4c, 0x16, 0x2d, 0xcd, 0x3c,
0x21, 0x07, 0x7c, 0xfe, 0x5f, 0x8d, 0x5f, 0xe2,
0xb1, 0xa4, 0x16, 0x8f, 0x95, 0x36, 0x78, 0xb7,
0x7d, 0x3b, 0x3d, 0x80, 0x3b, 0x60, 0xe4, 0xab,
0x92, 0x09, 0x96, 0xe5, 0x9b, 0x4d, 0x53, 0xb6,
0x5d, 0x2a, 0x22, 0x58, 0x77, 0xd5, 0xed, 0xf5,
0x84, 0x2c, 0xb9, 0xf1, 0x4e, 0xef, 0xe4, 0x25]
// vfmt on
// an array capable of holding r * 128 bytes used during
// the block_mix operation.
mut temp_block := []u8{len: 128, cap: 128}
// for this test, r = 1
block_mix(mut input_block, mut temp_block, 1)
for i in 0 .. 128 {
assert input_block[i] == output_block[i], 'assertion failed for i: ${i}'
}
}
fn test_smix() {
// The input_block and output_block values are taken from
// [RFC7914](https://datatracker.ietf.org/doc/html/rfc7914#section-10)
// section 10.
// vfmt off
mut input_block := [
u8(0xf7), 0xce, 0x0b, 0x65, 0x3d, 0x2d, 0x72, 0xa4,
0x10, 0x8c, 0xf5, 0xab, 0xe9, 0x12, 0xff, 0xdd,
0x77, 0x76, 0x16, 0xdb, 0xbb, 0x27, 0xa7, 0x0e,
0x82, 0x04, 0xf3, 0xae, 0x2d, 0x0f, 0x6f, 0xad,
0x89, 0xf6, 0x8f, 0x48, 0x11, 0xd1, 0xe8, 0x7b,
0xcc, 0x3b, 0xd7, 0x40, 0x0a, 0x9f, 0xfd, 0x29,
0x09, 0x4f, 0x01, 0x84, 0x63, 0x95, 0x74, 0xf3,
0x9a, 0xe5, 0xa1, 0x31, 0x52, 0x17, 0xbc, 0xd7,
0x89, 0x49, 0x91, 0x44, 0x72, 0x13, 0xbb, 0x22,
0x6c, 0x25, 0xb5, 0x4d, 0xa8, 0x63, 0x70, 0xfb,
0xcd, 0x98, 0x43, 0x80, 0x37, 0x46, 0x66, 0xbb,
0x8f, 0xfc, 0xb5, 0xbf, 0x40, 0xc2, 0x54, 0xb0,
0x67, 0xd2, 0x7c, 0x51, 0xce, 0x4a, 0xd5, 0xfe,
0xd8, 0x29, 0xc9, 0x0b, 0x50, 0x5a, 0x57, 0x1b,
0x7f, 0x4d, 0x1c, 0xad, 0x6a, 0x52, 0x3c, 0xda,
0x77, 0x0e, 0x67, 0xbc, 0xea, 0xaf, 0x7e, 0x89]
output_block := [
u8(0x79), 0xcc, 0xc1, 0x93, 0x62, 0x9d, 0xeb, 0xca,
0x04, 0x7f, 0x0b, 0x70, 0x60, 0x4b, 0xf6, 0xb6,
0x2c, 0xe3, 0xdd, 0x4a, 0x96, 0x26, 0xe3, 0x55,
0xfa, 0xfc, 0x61, 0x98, 0xe6, 0xea, 0x2b, 0x46,
0xd5, 0x84, 0x13, 0x67, 0x3b, 0x99, 0xb0, 0x29,
0xd6, 0x65, 0xc3, 0x57, 0x60, 0x1f, 0xb4, 0x26,
0xa0, 0xb2, 0xf4, 0xbb, 0xa2, 0x00, 0xee, 0x9f,
0x0a, 0x43, 0xd1, 0x9b, 0x57, 0x1a, 0x9c, 0x71,
0xef, 0x11, 0x42, 0xe6, 0x5d, 0x5a, 0x26, 0x6f,
0xdd, 0xca, 0x83, 0x2c, 0xe5, 0x9f, 0xaa, 0x7c,
0xac, 0x0b, 0x9c, 0xf1, 0xbe, 0x2b, 0xff, 0xca,
0x30, 0x0d, 0x01, 0xee, 0x38, 0x76, 0x19, 0xc4,
0xae, 0x12, 0xfd, 0x44, 0x38, 0xf2, 0x03, 0xa0,
0xe4, 0xe1, 0xc4, 0x7e, 0xc3, 0x14, 0x86, 0x1f,
0x4e, 0x90, 0x87, 0xcb, 0x33, 0x39, 0x6a, 0x68,
0x73, 0xe8, 0xf9, 0xd2, 0x53, 0x9a, 0x4b, 0x8e]
// vfmt on
r := u32(1)
n := u64(16)
// len and cap are 128 * r * n = 2048
mut v_block := []u8{len: 2048, cap: 2048}
// len and cap are 256 * r = 246
mut temp_block := []u8{len: 256, cap: 256}
smix(mut input_block, r, n, mut v_block, mut temp_block)
for i in 0 .. 128 {
assert input_block[i] == output_block[i], 'assertion failed for i: ${i}'
}
}
fn test_pbkdf2_hmac_sha256() {
// The input_block and output_block values are taken from
// [RFC7914](https://datatracker.ietf.org/doc/html/rfc7914#section-11)
// section 11.
// vfmt off
output_block := [
[u8(0x55), 0xac, 0x04, 0x6e, 0x56, 0xe3, 0x08, 0x9f,
0xec, 0x16, 0x91, 0xc2, 0x25, 0x44, 0xb6, 0x05,
0xf9, 0x41, 0x85, 0x21, 0x6d, 0xde, 0x04, 0x65,
0xe6, 0x8b, 0x9d, 0x57, 0xc2, 0x0d, 0xac, 0xbc,
0x49, 0xca, 0x9c, 0xcc, 0xf1, 0x79, 0xb6, 0x45,
0x99, 0x16, 0x64, 0xb3, 0x9d, 0x77, 0xef, 0x31,
0x7c, 0x71, 0xb8, 0x45, 0xb1, 0xe3, 0x0b, 0xd5,
0x09, 0x11, 0x20, 0x41, 0xd3, 0xa1, 0x97, 0x83
],
[u8(0x4d), 0xdc, 0xd8, 0xf6, 0x0b, 0x98, 0xbe, 0x21,
0x83, 0x0c, 0xee, 0x5e, 0xf2, 0x27, 0x01, 0xf9,
0x64, 0x1a, 0x44, 0x18, 0xd0, 0x4c, 0x04, 0x14,
0xae, 0xff, 0x08, 0x87, 0x6b, 0x34, 0xab, 0x56,
0xa1, 0xd4, 0x25, 0xa1, 0x22, 0x58, 0x33, 0x54,
0x9a, 0xdb, 0x84, 0x1b, 0x51, 0xc9, 0xb3, 0x17,
0x6a, 0x27, 0x2b, 0xde, 0xbb, 0xa1, 0xd0, 0x78,
0x47, 0x8f, 0x62, 0xb3, 0x97, 0xf3, 0x3c, 0x8d
]
]
// vfmt on
d0 := pbkdf2.key('passwd'.bytes(), 'salt'.bytes(), 1, 64, sha256.new())!
assert d0 == output_block[0]
d1 := pbkdf2.key('Password'.bytes(), 'NaCl'.bytes(), 80000, 64, sha256.new())!
assert d1 == output_block[1]
}
struct ScryptTestData {
name string
password []u8
salt []u8
n u64
r u32
p u32
dk_len u64
expected_result []u8
}
const scrypt_test_cases = [
ScryptTestData{
name: 'test case 1'
password: ''.bytes()
salt: ''.bytes()
n: 16
r: 1
p: 1
dk_len: 64
expected_result: [u8(0x77), 0xd6, 0x57, 0x62, 0x38, 0x65, 0x7b, 0x20, 0x3b, 0x19, 0xca,
0x42, 0xc1, 0x8a, 0x04, 0x97, 0xf1, 0x6b, 0x48, 0x44, 0xe3, 0x07, 0x4a, 0xe8, 0xdf,
0xdf, 0xfa, 0x3f, 0xed, 0xe2, 0x14, 0x42, 0xfc, 0xd0, 0x06, 0x9d, 0xed, 0x09, 0x48,
0xf8, 0x32, 0x6a, 0x75, 0x3a, 0x0f, 0xc8, 0x1f, 0x17, 0xe8, 0xd3, 0xe0, 0xfb, 0x2e,
0x0d, 0x36, 0x28, 0xcf, 0x35, 0xe2, 0x0c, 0x38, 0xd1, 0x89, 0x06]
},
// test cases 2, 3, and 4 are moved to the slow test repo.
]
fn test_scrypt() {
for c in scrypt_test_cases {
results := scrypt(c.password, c.salt, c.n, c.r, c.p, c.dk_len)!
assert results == c.expected_result, '${c.name} failed'
}
}