Packages

Gleam bindings to fast_pbkdf2 NIF of PBKDF2 (Password-Based Key Derivation Function 2) for Erlang.

Current section

Files

Jump to
pinkdf2 src pinkdf2.gleam
Raw

src/pinkdf2.gleam

//// Gleam bindings to fast_pbkdf2 NIF of PBKDF2 (Password-Based Key Derivation Function 2) for Erlang.
import gleam/bit_array
import gleam/crypto.{type HashAlgorithm}
pub type Pbkdf2Keys {
Pbkdf2Keys(raw: BitArray, base64: String)
}
pub type Pbkdf2Algorithm {
Sha224
Sha256
Sha384
Sha512
}
pub type Pbkdf2Error {
IterationsValueNotPositive
DerivedKeyLengthValueNotPositive
}
/// Derives a key from a password and salt with default settings based on the
/// (OWASP recommendations)[https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2].
pub fn with_defaults(
password: String,
salt: String,
) -> Result(Pbkdf2Keys, Pbkdf2Error) {
let raw = fp_defaults(password, salt)
Ok(Pbkdf2Keys(raw, bit_array.base64_encode(raw, False)))
}
/// Derives a key using the provided configuration.
///
/// `iterations` is the number of times to run the algorithm. Must be a positive integer.
/// `d_len` is the target derived key length in bytes. Must be a positive integer.
///
/// ## Examples
///
/// ```gleam
/// import pinkdf2.{Sha512}
///
/// let salt = pinkdf2.get_salt()
/// let assert Ok(key) = pinkdf2.with_config(Sha512, "password", salt, 210_000, 32)
/// ```
pub fn with_config(
alg: Pbkdf2Algorithm,
password: String,
salt: String,
iterations: Int,
d_len: Int,
) -> Result(Pbkdf2Keys, Pbkdf2Error) {
case is_positive(iterations), is_positive(d_len) {
False, _ -> Error(IterationsValueNotPositive)
_, False -> Error(DerivedKeyLengthValueNotPositive)
_, _ -> {
let raw =
map_algorithm(alg)
|> fp_config(password, salt, iterations, d_len)
Ok(Pbkdf2Keys(raw, bit_array.base64_encode(raw, False)))
}
}
}
/// Generates a base64-encoded salt with a minimum size of 64 bytes.
/// It is provided here for convenience, but it is based on the same underlying Erlang function as `crypto.strong_rand_bytes`.
@external(erlang, "extern", "get_salt")
pub fn get_salt() -> String
fn map_algorithm(alg: Pbkdf2Algorithm) -> HashAlgorithm {
case alg {
Sha224 -> crypto.Sha224
Sha256 -> crypto.Sha256
Sha384 -> crypto.Sha384
Sha512 -> crypto.Sha512
}
}
fn is_positive(num: Int) -> Bool {
num > 0
}
@external(erlang, "extern", "fp_with_defaults")
fn fp_defaults(password: String, salt: String) -> BitArray
@external(erlang, "fast_pbkdf2", "pbkdf2")
fn fp_config(
alg: HashAlgorithm,
password: String,
salt: String,
iterations: Int,
d_len: Int,
) -> BitArray