Crypto
Luma provides cryptographic operations through the crypto module.
Hash functions, HMAC, secure random generation, and byte utilities live under the crypto.* namespace.
Encoding functions (hex, base64) are type methods on [byte] and str — not part of crypto.
Hash functions
All hash functions accept both [byte] and str arguments and always return [byte].
MD5
hash: [byte] = crypto.md5(data)
hash: [byte] = crypto.md5("hello")Returns 16 bytes (128 bits).
Not secure for cryptographic purposes but widely used for checksums, content fingerprinting, and cache keys.
content: str = file("data.csv").read()
checksum: str = crypto.md5(content).to_hex()
print("MD5: ${checksum}")SHA1
hash: [byte] = crypto.sha1(data)
hash: [byte] = crypto.sha1("hello")Returns 20 bytes (160 bits).
Required for MySQL mysql_native_password authentication. Also used in Git object hashing.
SHA256
hash: [byte] = crypto.sha256(data)
hash: [byte] = crypto.sha256("hello")Returns 32 bytes (256 bits).
The modern standard for cryptographic hashing. Required for MySQL caching_sha2_password authentication.
payload: str = '{"action":"transfer","amount":100}'
signature: str = crypto.sha256(payload).to_hex()SHA224
hash: [byte] = crypto.sha224(data)Returns 28 bytes (224 bits). A truncated variant of SHA256.
SHA384
hash: [byte] = crypto.sha384(data)Returns 48 bytes (384 bits). A truncated variant of SHA512.
SHA512
hash: [byte] = crypto.sha512(data)Returns 64 bytes (512 bits). The strongest SHA-2 hash.
Hash output sizes
| Function | Output | Bits |
|---|---|---|
crypto.md5() |
16 bytes | 128 |
crypto.sha1() |
20 bytes | 160 |
crypto.sha224() |
28 bytes | 224 |
crypto.sha256() |
32 bytes | 256 |
crypto.sha384() |
48 bytes | 384 |
crypto.sha512() |
64 bytes | 512 |
HMAC
HMAC combines a hash function with a secret key to produce an authentication code. Unlike plain hashing, HMAC proves that the message was created by someone who knows the key.
HMAC-SHA256
mac: [byte] = crypto.hmac_sha256(key, data)
mac: [byte] = crypto.hmac_sha256(key, "message")Returns 32 bytes. The modern standard for HMAC.
Used in GitHub webhooks, Stripe webhooks, AWS Signature V4, and JWT HS256.
secret: [byte] = "webhook_secret".to_bytes()
payload: str = request.body
expected: str = "sha256=" + crypto.hmac_sha256(secret, payload).to_hex()
if expected == signature_header {
print("Webhook verified")
}HMAC-SHA512
mac: [byte] = crypto.hmac_sha512(key, data)Returns 64 bytes. Used in JWT HS512 and high-security applications.
HMAC-SHA1
mac: [byte] = crypto.hmac_sha1(key, data)Returns 20 bytes. Used in OAuth 1.0 and some TOTP implementations.
HMAC-MD5
mac: [byte] = crypto.hmac_md5(key, data)Returns 16 bytes. Legacy but still used in some older APIs.
HMAC summary
| Function | Output | Primary use |
|---|---|---|
crypto.hmac_sha256() |
32 bytes | Modern standard, webhooks, JWT |
crypto.hmac_sha512() |
64 bytes | High-security, JWT HS512 |
crypto.hmac_sha1() |
20 bytes | OAuth 1.0, TOTP |
crypto.hmac_md5() |
16 bytes | Legacy APIs |
All HMAC functions take key: [byte] as the first argument and data: [byte] or data: str as the second.
Secure random
data: [byte] = crypto.rand(32)Returns N cryptographically secure random bytes.
Uses the operating system’s secure random source. Never fails.
// Generate a session token
token: str = crypto.rand(32).to_hex()
print("Session: ${token}")
// Generate a salt for password hashing
salt: [byte] = crypto.rand(16)
hash: [byte] = crypto.sha256(salt + password.to_bytes())Byte utilities
XOR
result: [byte] = crypto.xor(a, b)Byte-wise XOR of two equal-length byte slices.
Panics if lengths differ.
// MySQL mysql_native_password: final step
auth_response: [byte] = crypto.xor(sha1_hash, scramble_hash)Constant-time comparison
equal: bool = crypto.equal(a, b)Compares two byte slices in constant time.
Unlike normal ==, this takes the same time regardless of where bytes differ, preventing timing attacks.
Use for comparing secret values — HMAC signatures, password hashes, authentication tokens:
expected: [byte] = crypto.hmac_sha256(secret, payload)
received: [byte] = signature_header.from_hex()
if crypto.equal(expected, received) {
print("Signature valid")
}Normal == is fine for non-secret values like checksums or cache keys.
Encoding methods
Encoding is not a crypto operation — it lives as type methods on [byte] and str.
Hex
hex: str = data.to_hex() // [byte] -> "48656c6c6f"
data: [byte] = "48656c6c6f".from_hex() // "48656c6c6f" -> [byte]
data: ?[byte] = input.from_hex() // nil for invalid hex.to_hex() on [byte] always succeeds. Each byte becomes two lowercase hex characters.
.from_hex() on str panics on invalid input. Use ?[byte] for safe decoding.
hash: [byte] = crypto.sha256("hello")
print(hash.to_hex())
key: [byte] = "deadbeef".from_hex()Base64
encoded: str = data.to_base64() // [byte] -> standard base64
data: [byte] = "SGVsbG8=".from_base64() // standard base64 -> [byte]
data: ?[byte] = input.from_base64() // nil for invalidStandard base64 with padding (RFC 4648).
URL-safe base64
encoded: str = data.to_base64url() // [byte] -> URL-safe base64
data: [byte] = encoded.from_base64url() // URL-safe base64 -> [byte]
data: ?[byte] = input.from_base64url() // nil for invalidUses - and _ instead of + and /, omits padding =.
Used in JWTs, URL parameters, and filenames.
Encoding summary
| Method | On type | Direction |
|---|---|---|
.to_hex() |
[byte] |
bytes -> hex string |
.from_hex() |
str |
hex string -> bytes |
.to_base64() |
[byte] |
bytes -> base64 |
.from_base64() |
str |
base64 -> bytes |
.to_base64url() |
[byte] |
bytes -> URL-safe base64 |
.from_base64url() |
str |
URL-safe base64 -> bytes |
Error handling
| Operation | Behavior |
|---|---|
| All hash functions | Always succeeds |
| All HMAC functions | Always succeeds |
crypto.rand() |
Always succeeds |
crypto.xor(a, b) |
Panics if lengths differ |
crypto.equal(a, b) |
Always succeeds |
.to_hex(), .to_base64(), .to_base64url() |
Always succeeds |
.from_hex() |
Panics on invalid input, ?[byte] returns nil |
.from_base64() |
Panics on invalid input, ?[byte] returns nil |
.from_base64url() |
Panics on invalid input, ?[byte] returns nil |
Examples
File checksum
content: str = file("data.csv").read()
md5: str = crypto.md5(content).to_hex()
sha256: str = crypto.sha256(content).to_hex()
print("MD5: ${md5}")
print("SHA256: ${sha256}")Webhook verification
secret: [byte] = "whsec_abc123".to_bytes()
payload: str = request_body
expected: str = "sha256=" + crypto.hmac_sha256(secret, payload).to_hex()
if crypto.equal(expected.to_bytes(), received_sig.to_bytes()) {
print("Webhook signature valid")
}Session token
token: str = crypto.rand(32).to_hex()
print("Session: ${token}")
// Or URL-safe
token_url: str = crypto.rand(32).to_base64url()
print("URL-safe: ${token_url}")Password hashing with salt
fn hash_password(password: str) -> str {
salt: [byte] = crypto.rand(16)
salted: buffer = buffer()
salted.write(salt)
salted.write(password)
hash: [byte] = crypto.sha256(salted.to_bytes())
return salt.to_hex() + ":" + hash.to_hex()
}
fn verify_password(password: str, stored: str) -> bool {
parts: [str] = stored.split(":")
salt: [byte] = parts[0].from_hex()
stored_hash: [byte] = parts[1].from_hex()
salted: buffer = buffer()
salted.write(salt)
salted.write(password)
computed: [byte] = crypto.sha256(salted.to_bytes())
return crypto.equal(stored_hash, computed)
}MySQL authentication
// mysql_native_password
password: str = "secret123"
nonce: [byte] = server_nonce
sha1_hash: [byte] = crypto.sha1(password)
double_hash: [byte] = crypto.sha1(sha1_hash)
combined: buffer = buffer()
combined.write(nonce)
combined.write(double_hash)
scramble: [byte] = crypto.sha1(combined.to_bytes())
auth_response: [byte] = crypto.xor(sha1_hash, scramble)Method summary
| Method | Arguments | Returns | Description |
|---|---|---|---|
crypto.md5() |
[byte] or str |
[byte] (16) |
MD5 hash |
crypto.sha1() |
[byte] or str |
[byte] (20) |
SHA1 hash |
crypto.sha224() |
[byte] or str |
[byte] (28) |
SHA224 hash |
crypto.sha256() |
[byte] or str |
[byte] (32) |
SHA256 hash |
crypto.sha384() |
[byte] or str |
[byte] (48) |
SHA384 hash |
crypto.sha512() |
[byte] or str |
[byte] (64) |
SHA512 hash |
crypto.hmac_md5() |
key: [byte], data |
[byte] (16) |
HMAC-MD5 |
crypto.hmac_sha1() |
key: [byte], data |
[byte] (20) |
HMAC-SHA1 |
crypto.hmac_sha256() |
key: [byte], data |
[byte] (32) |
HMAC-SHA256 |
crypto.hmac_sha512() |
key: [byte], data |
[byte] (64) |
HMAC-SHA512 |
crypto.rand() |
n: int |
[byte] |
Secure random bytes |
crypto.xor() |
a: [byte], b: [byte] |
[byte] |
Byte-wise XOR |
crypto.equal() |
a: [byte], b: [byte] |
bool |
Constant-time comparison |
Design notes
cryptois a namespace, not a type — functions are stateless, no object to create- Hash functions accept both
[byte]andstr— argument-type dispatch, no.to_bytes()needed - Hash functions always return
[byte]— chain.to_hex()when you need a string - Encoding (hex, base64) lives as type methods, not under crypto — encoding is not a crypto operation
- Crypto functions never fail at runtime — hashing, HMAC, and random generation always succeed
- MD5 is included despite being cryptographically weak — it’s everywhere in practice (checksums, caching, legacy protocols)