Hashing, verification, and randomness
The hash library provides cryptographic hashing, secure comparison, random generation, and UUIDs. Everything you need for authentication, data integrity, and tokens.
Function reference
| Function | Example | Description |
|---|---|---|
hash.md5(s) |
hash.md5("hello") |
MD5 hash (hex) — fast but not secure |
hash.sha1(s) |
hash.sha1("hello") |
SHA-1 hash (hex) |
hash.sha256(s) |
hash.sha256("hello") |
SHA-256 hash (hex) — recommended |
hash.sha512(s) |
hash.sha512("hello") |
SHA-512 hash (hex) |
hash.crc32(s) |
hash.crc32("hello") |
CRC32 checksum — returns a number, not a hex string |
hash.hmac(key, data) |
hash.hmac("secret", "msg") |
HMAC-SHA256 (hex) |
hash.random(bytes?) |
hash.random(16) |
Secure random hex (default 32 bytes) |
hash.equals(a, b) |
hash.equals("a", "a") |
Timing-safe comparison |
hash.uuid() |
hash.uuid() |
RFC 4122 UUID v4 |
Hashing passwords
-- Hash a password before storing (use SHA-256 minimum)
local hashed = hash.sha256(password .. user_salt)
api.users.create({
username = username,
password = hashed
})
Note: For production user passwords, use
hash.hmacwith a site-specific secret, or bcrypt viahash.sha512with multiple rounds.
Verifying webhook signatures
app:post("/webhook/github", function(ctx)
local signature = req.headers["x-hub-signature-256"]
local computed = "sha256=" .. hash.hmac(GITHUB_SECRET, req.body)
if not hash.equals(signature, computed) then
return ctx:error("Invalid signature", 401)
end
-- Process the webhook...
end, { csrf = false })
hash.equals uses timing-safe comparison — it takes the same time regardless of how many characters match. This prevents timing attacks.
Generating tokens
-- API token
local token = hash.random(32) -- 64 hex characters
-- Password reset token
local reset_token = hash.uuid() -- "7d39eb22-1b4a-426c-867e-b01ba24aa702"
-- Session ID
local session = hash.random(16) -- 32 hex characters
Creating UUIDs
-- Unique identifier for a record
local product_id = hash.uuid()
api.dataset.create("products", {
id = product_id,
name = "Widget"
})
-- Or let the database assign IDs and use UUID for external references
local order = api.dataset.create("orders", {
reference = hash.uuid(),
items = cart_items
})
File checksums
-- Verify file integrity
local content = req.body
local checksum = hash.md5(content)
-- Compare against expected
if checksum ~= expected_checksum then
return ctx:error("File corrupted", 400)
end
Practical: password reset flow
-- Generate reset token
app:post("/forgot-password", function(ctx)
local email = req.post.email
local result = api.users.list({ search = email })
if result.total == 0 then
-- Don't reveal whether user exists
ctx:flash("success", "If that email exists, we sent a reset link.")
return ctx:redirect("/login")
end
local user = result.items[1]
local token = hash.uuid()
-- Save token + expiry in dataset
api.dataset.create("password_resets", {
user_id = user.id,
token = token,
expires = os.time() + 3600 -- 1 hour
})
-- Send email with link containing token
return ctx:redirect("/login")
end)
Next: String Utilities — split, trim, contains, and more.