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#

silk
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#

silk
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#

silk
pub union ScryptError

The reason that Scrypt.derive or Scrypt.deriveWithScratch rejected its parameters. The output does not change.

InvalidCost#

silk
InvalidCost

The cost N is not a power of two above 1, or it is not below 2^(16 * r).

InvalidBlockSize#

silk
InvalidBlockSize

The block size r is 0, or r * p is not below 2^30.

InvalidParallelism#

silk
InvalidParallelism

The parallelism p is 0, or above (2^32 - 1) * 32 / (128 * r).

OutputTooLong#

silk
OutputTooLong

The output is longer than (2^32 - 1) * 32 bytes, or the memory does not fit in an address.

ScratchTooSmall#

silk
ScratchTooSmall

The scratch passed to Scrypt.deriveWithScratch is shorter than Scrypt.scratchLength.

Implementation Debug for ScryptError#

silk
impl Debug for ScryptError

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> ()
    ! WriterError
    ? &mut Writer

Writes the name of the variant, such as InvalidCost.

Scrypt#

silk
pub struct Scrypt

Functions 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#

silk
impl Scrypt

Associated function memoryLength#

silk
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#

silk
pub effect fn derive(
    password: &[u8],
    salt: &[u8],
    cost: u64,
    blockSize: u32,
    parallelism: u32,
    output: &mut [u8],
  ) -> ()
    ! OutOfMemoryError | ScryptError
    ? &mut Allocator

Fills 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.InvalidCost if cost is not a power of two above 1, or not below 2^(16 * blockSize).

  • ScryptError.InvalidBlockSize if blockSize is 0 or blockSize * parallelism is not below 2^30.

  • ScryptError.InvalidParallelism if parallelism is 0 or above (2^32 - 1) * 32 / (128 * blockSize).

  • ScryptError.OutputTooLong if output is longer than (2^32 - 1) * 32 bytes, or the memory does not fit in an address.

  • OutOfMemoryError if 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#

silk
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#

silk
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.

View the source of this page