ddn.crypto.kdf.bcrypt_pbkdf

bcrypt_pbkdf: the OpenBSD bcrypt-based password-based key derivation function.

bcrypt_pbkdf is a PBKDF2-style construction around the "bcrypt hash" (EksBlowfish with a fixed cost) rather than HMAC. OpenSSH uses it to derive the cipher key and IV for encrypted openssh-key-v1 private key files; see OpenSSH PROTOCOL.key and bcrypt_pbkdf(3) from OpenBSD.

The bcrypt hash function is derived from the bcrypt password hashing function with the following modifications (quoting the OpenBSD reference):

  1. The input password and salt are preprocessed with SHA-512.
  2. The output length is expanded to 256 bits.
  3. The magic string to be encrypted is lengthened and modified

    to "OxychromaticBlowfishSwatDynamite".

  4. The hash function performs 64 rounds of initial state

    expansion.

One deviation from PBKDF2: instead of outputting key material linearly, the bytes of the per-block outputs are interleaved (striped) across the whole derived key, so assembling any subkey requires computing the entirety of the key material.

The Blowfish core here is a private EksBlowfish implementation, separate from the deprecated legacy Blowfish data-encryption cipher (ddn.crypto.legacy.cipher.blowfish): bcrypt uses Blowfish strictly as a one-way compression schedule over 64-bit blocks, which carries none of the 64-bit-block encryption risk that deprecates legacy Blowfish for data.

Constant-time note: the key schedule and Feistel rounds are branch-free; the only secret-dependent data flow is S-box indexing (memory access), which is inherent to Blowfish and identical in the OpenBSD reference. Secret intermediates are wiped with secureZero on every exit path.

Types 2

bcrypt_pbkdf implementation (OpenBSD bcrypt_pbkdf(3)).

Example:

auto kdf = new BcryptPbkdf(16);
auto keyMaterial = kdf.derive(passphraseBytes, saltBytes, 48);

Fields
private size_t _rounds
Methods
ubyte[] derive(const(ubyte)[] secret, const(ubyte)[] salt, size_t length)Derives key material using the constructor's round count.
ubyte[] derive(const(ubyte)[] secret, const(ubyte)[] salt, size_t length, KDFParams params)Derives key material, letting params.iterations override the constructor's round count (zero means "unset").
private ubyte[] deriveRounds(const(ubyte)[] pass, const(ubyte)[] salt, size_t keylen, size_t rounds) static
private ubyte[] sha512(const(ubyte)[] data) staticComputes SHA-512 of data and returns the fresh digest.
Constructors
this(size_t rounds)Constructs a BcryptPbkdf with a fixed round count.
private structEksBlowfish

Self-contained EksBlowfish ("expensive key setup") state: the Blowfish P-array and S-boxes plus the key-schedule variants bcrypt needs. Kept private and separate from the deprecated legacy Blowfish block cipher; this type never encrypts user data.

Fields
private uint[18] p
private uint[256][4] s
Methods
void initialize() @safe pure nothrow @nogcLoads the pi-digit initial state (Blowfish_initstate).
uint stream2word(scope const(ubyte)[] data, ref uint j) static @safe pure nothrow @nogcReads the next big-endian 32-bit word from data, cycling j over the buffer (Blowfish_stream2word).
void expand0state(scope const(ubyte)[] key) @safe pure nothrow @nogcPlain key expansion from the current state (Blowfish_expand0state): XORs the cycled key into the P-array, then repeatedly enciphers the chained (l) register pair to replace the P-array and S-boxes.
void expandState(scope const(ubyte)[] key, scope const(ubyte)[] data) @safe pure nothrow @nogcSalted key expansion (Blowfish_expandstate): XORs the cycled key into the P-array, then evolves the (l) register pair, mixing one word of data into each register per step, to replace the P-array an...
void encipherBlock(ref uint xl, ref uint xr) @safe pure nothrow @nogcEncrypts one 64-bit block in place with the 16-round Feistel network (Blowfish_encipher).
private uint f(uint x) @safe pure nothrow @nogcThe Blowfish F function: ((S0[a] + S1[b] ^ S2[c]) + S3[d]).
void wipe() nothrow @nogc @trustedWipes the (secret-derived) key schedule.

Functions 2

private fnubyte[bcryptPbkdfBlock] bcryptHash(const(ubyte)[] sha2pass, const(ubyte)[] sha2salt) @safe nothrowOne bcrypt hash evaluation: 32 output bytes from 64-byte SHA-512 compressed password and salt images.
private fnubyte[] sha512Of(const(ubyte)[] data)Test-only SHA-512 helper (the class member is private).

Variables 6

enumvarbcryptPbkdfBlock = 32

Hash size of the inner bcrypt hash and the KDF block size in bytes.

enumvarbcryptPbkdfMaxKey = 1024

Maximum derived key length in bytes (32 * 32, the reference bound; OpenSSH only ever needs 48).

enumvarbcryptPbkdfMaxSalt = 1 << 20

Maximum accepted salt length in bytes (the reference bound).

private enumvarbcryptMagic = "OxychromaticBlowfishSwatDynamite"

The magic constant encrypted by the bcrypt hash.

private varuint[18] INIT_P
private varuint[256][4] INIT_S