Skip to content

Commit

Permalink
crypto.scrypt: add a new scrypt module to vlib/crypto (#22216)
Browse files Browse the repository at this point in the history
  • Loading branch information
kimshrier authored Sep 14, 2024
1 parent 26ab7d4 commit 423f804
Show file tree
Hide file tree
Showing 2 changed files with 470 additions and 0 deletions.
241 changes: 241 additions & 0 deletions vlib/crypto/scrypt/scrypt.v
Original file line number Diff line number Diff line change
@@ -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]
}
Loading

0 comments on commit 423f804

Please sign in to comment.