Beta documentation. This is an early preview — content is still in active development. Feedback helps shape the final release. Share your thoughts or join the discussion.

Hash & Crypto

The `hash` library provides cryptographic hashing, secure comparison, random generation, and UUIDs. Everything you need for authentication, data integrity, a...

On this page

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.hmac with a site-specific secret, or bcrypt via hash.sha512 with 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.

Previous Decoder Next String Utilities