Skip to content

Key Derivation

Key derivation functions transform raw input material into well-formed cryptographic keys. This library provides two algorithms for two fundamentally different scenarios:

  • HKDF (KDF) – derives keys from high-entropy input such as key agreement output or random bytes. Fast by design.
  • PBKDF2 (password-based) – derives keys from low-entropy passwords. Deliberately slow to resist brute-force attacks.

Assumed imports

All examples on this page assume the following imports and setup:

import dev.whyoleg.cryptography.*
import dev.whyoleg.cryptography.algorithms.*
import dev.whyoleg.cryptography.random.*
import dev.whyoleg.cryptography.BinarySize.Companion.bits

val provider = CryptographyProvider.Default

HKDF

HKDF (HMAC-based Key Derivation Function) derives keys from input material that already has sufficient entropy but is not in the right format for direct use as a cryptographic key – for example, the raw shared secret from an ECDH key agreement. It is fast by design: a single HMAC pass to extract, then expand to the desired length.

See Secure Messaging for a complete example using HKDF with key agreement.

Basic Usage

Get the algorithm, configure a SecretDerivation with digest, output size, salt, and info, then deriveSecretToByteArray from input key material:

val hkdf = provider.get(HKDF)

// Input key material -- e.g., a shared secret from ECDH
val inputKeyMaterial = ByteArray(32) // placeholder for actual key material

val salt = CryptographyRandom.nextBytes(32)

val derivation = hkdf.secretDerivation(
    digest = SHA256,
    outputSize = 256.bits,
    salt = salt,
    info = "encryption-key".encodeToByteArray()
)

val derivedKey: ByteArray = derivation.deriveSecretToByteArray(inputKeyMaterial)

The SecretDerivation instance is stateless and reusable. For the same input and parameters, the output is always identical – HKDF is deterministic.

Deriving Multiple Keys

Use different info strings to derive independent keys from the same input material:

val hkdf = provider.get(HKDF)
val salt = CryptographyRandom.nextBytes(32)

// Derive an encryption key
val encryptionKey = hkdf.secretDerivation(
    digest = SHA256,
    outputSize = 256.bits,
    salt = salt,
    info = "encryption".encodeToByteArray()
).deriveSecretToByteArray(sharedSecret)

// Derive a separate MAC key from the same input
val macKey = hkdf.secretDerivation(
    digest = SHA256,
    outputSize = 256.bits,
    salt = salt,
    info = "authentication".encodeToByteArray()
).deriveSecretToByteArray(sharedSecret)

The two derived keys are cryptographically independent – knowing one reveals nothing about the other. This pattern is widely used in protocols like TLS 1.3.

PBKDF2

PBKDF2 (Password-Based Key Derivation Function 2) derives keys from passwords – short, human-chosen strings with far less entropy than a random key. It iterates HMAC hundreds of thousands of times, making each guess expensive for an attacker.

See Password-Based Encryption for a complete example using PBKDF2 with AES-GCM.

Basic Usage

Get the algorithm, configure a SecretDerivation with digest, iteration count, output size, and salt, then deriveSecretToByteArray from the password bytes:

val pbkdf2 = provider.get(PBKDF2)

val password = "user-password".encodeToByteArray()
val salt = CryptographyRandom.nextBytes(16) // store alongside ciphertext

val derivation = pbkdf2.secretDerivation(
    digest = SHA256,
    iterations = 600_000,
    outputSize = 256.bits,
    salt = salt
)

val derivedKey: ByteArray = derivation.deriveSecretToByteArray(password)

The SecretDerivation instance is deterministic – the same password, salt, iterations, digest, and output size always produce the same output. This is essential for decryption: store the salt and parameters alongside the ciphertext so the key can be reconstructed later.

Iteration Count

The iteration count is the primary tuning parameter. A higher count means more work per derivation, which slows down both the legitimate user and any attacker. The OWASP Password Storage Cheat Sheet recommends 600,000 iterations for SHA-256.

Supported Algorithms

Algorithm JDK WebCrypto Apple CryptoKit OpenSSL3
PBKDF2 ✅ ✅ ✅ ❌ ✅
HKDF ✅ ✅ ✅ ✅ ✅