silk.crypto.scrypt.
Derive keys from passwords with scrypt (RFC 7914), a memory-hard function.
When to use#
Use Scrypt.derive to hash a password for storage or to turn a password into an encryption
key, or when a format names scrypt, such as crypto.scrypt in Node.js. Use
silk.crypto.pbkdf2 when a format names PBKDF2.
Details#
The cost N (a power of two above 1), the block size r, and the parallelism p set the work.
scrypt allocates 128 * r * (N + p + 2) bytes through the ambient Allocator, which
Scrypt.memoryLength returns before the call, and frees them before it returns. Common
settings are N = 16384, r = 8, p = 1, which use 16 MiB. The output can have any length up
to (2^32 - 1) * 32 bytes. Scrypt.deriveWithScratch does the same work in a buffer that the
caller owns and can reuse, so that repeated calls do not map and clear fresh memory.
Errors#
Scrypt.derive fails with a ScryptError for parameters that RFC 7914 does not allow, and
with OutOfMemoryError when the allocator cannot supply the memory. On an error output does
not change.
Gotchas#
The memory grows with N and r, so parameters that an attacker controls can exhaust memory.
Check Scrypt.memoryLength against a limit first, as Node.js does with maxmem. The function
does not erase the password or its memory.
Examples#
Derive a key#
import silk.crypto.scrypt { Scrypt, ScryptError }
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.core.effect { Effect }
effect fn derive() -> i32 ! OutOfMemoryError | ScryptError ? &mut Allocator {
let mut key: [u8; 64] = [0; 64]
run Scrypt.derive(b"password", b"NaCl", 1024, 8, 16, &mut key)
// RFC 7914 section 12: this key starts with fdbabe1c.
if key[0] != 0xfd || key[1] != 0xba || key[2] != 0xbe || key[3] != 0x1c {
return 1
}
return 0
}
pub effect fn main() -> i32 ! OutOfMemoryError | ScryptError {
let mut allocator = Allocator.systemAllocatorProvider()
return run derive() |> Effect.provideMut<Allocator>(&mut allocator)
}Reuse one scratch for many keys#
import silk.crypto.scrypt { Scrypt, ScryptError }
import silk.core.result { Result }
pub fn main() -> i32 {
// Scrypt.scratchLength(16, 1, 1) is 32 * (16 + 1 + 2) = 608 words.
let mut scratch: [u32; 608] = [0; 608]
let mut first: [u8; 16] = [0; 16]
let mut second: [u8; 16] = [0; 16]
if let Result.Failure { error: _ } = Scrypt.deriveWithScratch(b"one", b"salt", 16, 1, 1, &mut scratch, &mut first) {
return 1
}
if let Result.Failure { error: _ } = Scrypt.deriveWithScratch(b"two", b"salt", 16, 1, 1, &mut scratch, &mut second) {
return 1
}
if first[0] == second[0] && first[1] == second[1] && first[2] == second[2] {
return 1
}
return 0
}Import as Scrypt with import silk.crypto.scrypt { Scrypt }.
Public declarations: 2.
ScryptError#
pub union ScryptErrorThe reason that Scrypt.derive or Scrypt.deriveWithScratch rejected its parameters. The output does not change.
InvalidCost#
InvalidCostThe cost N is not a power of two above 1, or it is not below 2^(16 * r).
InvalidBlockSize#
InvalidBlockSizeThe block size r is 0, or r * p is not below 2^30.
InvalidParallelism#
InvalidParallelismThe parallelism p is 0, or above (2^32 - 1) * 32 / (128 * r).
OutputTooLong#
OutputTooLongThe output is longer than (2^32 - 1) * 32 bytes, or the memory does not fit in an address.
ScratchTooSmall#
ScratchTooSmallThe scratch passed to Scrypt.deriveWithScratch is shorter than Scrypt.scratchLength.
Implementation Debug for ScryptError#
impl Debug for ScryptErrorOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> ()
! WriterError
? &mut WriterWrites the name of the variant, such as InvalidCost.
Scrypt#
pub struct ScryptFunctions that derive keys from passwords with scrypt (RFC 7914).
When to use#
Use Scrypt.derive to hash a password for storage or to derive a key from a password. Check
Scrypt.memoryLength first when the parameters come from outside the program.
Implementation Scrypt#
impl ScryptAssociated function memoryLength#
pub fn memoryLength(cost: u64, blockSize: u32, parallelism: u32) -> Option<u64>Returns the number of bytes that Scrypt.derive allocates for cost cost, block size
blockSize, and parallelism parallelism: 128 * r * (N + p + 2).
Returns None if the product does not fit in a u64. It does not check the other
parameter rules.
Associated function derive#
pub effect fn derive(
password: &[u8],
salt: &[u8],
cost: u64,
blockSize: u32,
parallelism: u32,
output: &mut [u8],
) -> ()
! OutOfMemoryError | ScryptError
? &mut AllocatorFills output with the scrypt key of password and salt for cost cost (N), block size
blockSize (r), and parallelism parallelism (p).
Any password and salt length is valid, also 0. A shorter output is a prefix of a longer one.
It allocates Scrypt.memoryLength bytes through the ambient Allocator and frees them
before it returns.
Errors#
On an error, output does not change.
ScryptError.InvalidCostifcostis not a power of two above 1, or not below 2^(16 *blockSize).ScryptError.InvalidBlockSizeifblockSizeis 0 orblockSize*parallelismis not below 2^30.ScryptError.InvalidParallelismifparallelismis 0 or above (2^32 - 1) * 32 / (128 *blockSize).ScryptError.OutputTooLongifoutputis longer than (2^32 - 1) * 32 bytes, or the memory does not fit in an address.OutOfMemoryErrorif the allocator cannot supply the memory.
Gotchas#
The memory and time grow with cost and blockSize. The function does not erase the
password or its memory.
Associated function scratchLength#
pub fn scratchLength(cost: u64, blockSize: u32, parallelism: u32) -> Option<usize>Returns the number of u32 words that Scrypt.deriveWithScratch needs in its scratch for
cost cost, block size blockSize, and parallelism parallelism: 32 * r * (N + p + 2),
a quarter of Scrypt.memoryLength.
Returns None if the length does not fit in a usize. It does not check the other
parameter rules.
Associated function deriveWithScratch#
pub fn deriveWithScratch(
password: &[u8],
salt: &[u8],
cost: u64,
blockSize: u32,
parallelism: u32,
scratch: &mut [u32],
output: &mut [u8],
) -> Result<(), ScryptError>Fills output with the scrypt key of password and salt, as Scrypt.derive does, but
works in scratch instead of allocating.
When to use#
Use deriveWithScratch to derive many keys with one buffer, such as in a worker that serves
crypto.scrypt calls, so that no call maps and clears fresh memory.
Details#
scratch needs at least Scrypt.scratchLength words, and its contents on entry do not
matter. The call overwrites that many words and leaves the rest alone. Nothing allocates.
Errors#
The same as Scrypt.derive, with ScryptError.ScratchTooSmall in place of
OutOfMemoryError when scratch is shorter than Scrypt.scratchLength. On an error,
output and scratch do not change.
Gotchas#
The scratch holds values derived from the password after the call. Clear it when that matters.
Category: Cryptography.
Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2, x86_64-pc-windows-msvc, wasm64-unknown-unknown.