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 invalid

Standard 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 invalid

Uses - 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

  • crypto is a namespace, not a type — functions are stateless, no object to create
  • Hash functions accept both [byte] and strargument-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)
Last updated on