crypto
Cryptographic primitives: SHA-256 / SHA-512 / BLAKE2b / MD5 hashing,
HMAC message authentication, an OS-backed CSPRNG, and a timing-safe
byte-comparison routine. Most functions consume and produce Bytes
values — pipe through bytes.to_hex / bytes.to_base64 when you need
a string representation.
crypto.random_bytes reads directly from the operating system’s
cryptographically secure pseudo-random number generator (getrandom(2)
on Linux, BCryptGenRandom on Windows, SecRandomCopyBytes on macOS;
see the getrandom crate for the
full platform list). The crypto.constant_time_eq comparison is
timing-safe for equal-length inputs: the running time is independent of
where a mismatch occurs. Length mismatches short-circuit to false,
matching the convention used by Python’s hmac.compare_digest and
Rust’s subtle crate — this means lengths can leak via timing but the
contents of equal-length buffers cannot. Pad inputs to a fixed width
beforehand if length privacy matters for your protocol.
Summary
| Function | Signature | Description |
|---|---|---|
sha256 | (Bytes) -> Bytes | SHA-256 digest (32 bytes) |
sha512 | (Bytes) -> Bytes | SHA-512 digest (64 bytes) |
md5 | (Bytes) -> Bytes | MD5 digest (16 bytes) — legacy / non-security use only |
blake2b | (Bytes) -> Bytes | BLAKE2b-512 digest (64 bytes), RFC 7693 |
hmac_sha256 | (Bytes, Bytes) -> Bytes | HMAC-SHA256 over (key, msg) (32 bytes) |
hmac_sha512 | (Bytes, Bytes) -> Bytes | HMAC-SHA512 over (key, msg) (64 bytes) |
random_bytes | (Int) -> Result(Bytes, CryptoError) | OS CSPRNG, 0..=1_048_576 bytes |
constant_time_eq | (Bytes, Bytes) -> Bool | Timing-safe comparison (lengths leak) |
Examples
import bytes
import crypto
fn main() {
-- Hashing
let msg = bytes.from_string("abc")
let digest = crypto.sha256(msg)
println(bytes.to_hex(digest))
-- ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
-- HMAC authentication
let key = bytes.from_string("secret key")
let tag = crypto.hmac_sha256(key, bytes.from_string("hello"))
println(bytes.length(tag)) -- 32
-- CSPRNG: 32 random bytes (256-bit token)
match crypto.random_bytes(32) {
Ok(token) -> println(bytes.to_hex(token))
Err(e) -> println(e)
}
-- Timing-safe comparison for auth tag verification
let expected = crypto.hmac_sha256(key, bytes.from_string("hello"))
let received = crypto.hmac_sha256(key, bytes.from_string("hello"))
println(crypto.constant_time_eq(expected, received)) -- true
}open in playgroundErrors
Only random_bytes is fallible at the type level — the others are
total functions over their Bytes inputs. It returns
Result(Bytes, CryptoError):
| Variant | Fields | Meaning |
|---|---|---|
InvalidLength(Int) | the length asked for | n < 0 or n > 1_048_576 |
Other(String) | message | the OS CSPRNG failed |
Notes
- Digest outputs have fixed widths:
16bytes (md5),32bytes (sha256,hmac_sha256),64bytes (sha512,hmac_sha512,blake2b). The typechecker has no dependent-type support for fixed-widthBytes, so the returned type is the same opaqueBytesthe rest of the module uses. - MD5 is not collision-resistant. Use it only for interop with
legacy systems (etags, Git-style content hashing, cache keys where
an adversary isn’t in play). Never use MD5 for signatures,
certificates, or any security decision — prefer
sha256orblake2b. blake2bis BLAKE2b-512 (the 512-bit-output variant from RFC 7693). It’s faster than SHA-512 on 64-bit hardware and is a sound default for new protocols that don’t need to match a SHA-family spec.random_bytes(0)returns an emptyBytes(not an error). This keeps caller code simpler when the length comes from a variable.- The 1 MiB cap on
random_bytesis a sanity guard against accidental huge allocations; it is not a security boundary. Loop if you really need more than a mebibyte of entropy at a time. - Use
crypto.constant_time_eq— not the==operator — whenever you compare a user-supplied value against a secret (MAC tag, password hash, token).==short-circuits on the first differing byte and leaks the matching prefix length via timing.