Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Cryptography (std:crypto)

Hash functions and HMAC-SHA256, returned as lowercase hex digests.

Overview

std:crypto exports five functions. Every argument must be a string, and every result is a lowercase hex string.

FunctionDigestHex length
md5(text)128-bit32
sha1(text)160-bit40
sha256(text)256-bit64
sha512(text)512-bit128
hmac_sha256(key, text)256-bit64

There is no generic crypto:hmac, no other HMAC variant, no raw/binary output mode, no incremental hashing and no password-hashing function such as bcrypt or argon2.

Quick Start

import std:crypto
import std:println

println(crypto:sha256("Hello, World!"))
# dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f

println(crypto:hmac_sha256("secret-key", "message to sign"))
# 5e2a3d8758df91e8fb93c09d4141c12ccc1f986cc67edccad3ebc5463c9bb136

Hash Functions

md5(text)

import std:crypto
import std:println

println(crypto:md5("Hello, World!"))  # 65a8e27d8879283831b664bd8b7f0ad4
println(crypto:md5(""))               # d41d8cd98f00b204e9800998ecf8427e

MD5 is cryptographically broken. Use it only for non-security purposes such as cache keys or change detection.

sha1(text)

import std:crypto
import std:println

println(crypto:sha1("Hello, World!"))  # 0a0a9f2a6772942557ab5355d76af442f8f65e01

SHA-1 is also considered weak; prefer SHA-256 for anything new.

sha256(text)

The general-purpose choice.

import std:crypto
import std:println

println(crypto:sha256("Hello, World!"))
# dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f

println(crypto:sha256(""))
# e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

sha512(text)

import std:crypto
import std:println

digest = crypto:sha512("Hello, World!")
println(digest::length())  # 128
println(digest[0;32])      # 374d794a95cdcfd8b35993185fef9ba3

hmac_sha256(key, text)

Computes a keyed message authentication code. Both arguments are strings; the key comes first.

import std:crypto
import std:println

signature = crypto:hmac_sha256("secret-key", "message to sign")
println(signature)
# 5e2a3d8758df91e8fb93c09d4141c12ccc1f986cc67edccad3ebc5463c9bb136

verify = |key, message, expected| crypto:hmac_sha256(key, message) == expected

println(verify("secret-key", "message to sign", signature))  # true
println(verify("wrong-key", "message to sign", signature))   # false

Keep the key out of the source and read it from the environment:

import std:crypto
import std:env
import std:println

env:var["WEBHOOK_SECRET"] = "s3cr3t"  # normally set outside the script

secret = env:var::get("WEBHOOK_SECRET", "")
payload = '{"event":"push"}'

println(crypto:hmac_sha256(secret, payload)::length())  # 64

Comparison with == is not constant time, so it can in principle leak timing information. Suji offers no constant-time comparison primitive.

Text and Encoding

The input is a Suji string, hashed as UTF-8, so non-ASCII text hashes consistently:

import std:crypto
import std:println

println(crypto:sha256("héllo"))
# 3c48591d8d098a4538f5e013dfcf406e948eac4d3277b10bf614e295d6068179

Non-string arguments raise Type error: argument must be string, so convert first:

import std:crypto
import std:println

println(crypto:md5(42::to_string()))  # a1d0c6e83f027327d8461063f4ac58a6

For base64 or hex conversions of the data itself — rather than a digest of it — see std:encoding.

Examples

File Checksum

import std:crypto
import std:io
import std:println

p = `mktemp`
f = io:open(p, true, true)
f::write("important contents\n")
f::close()

checksum = |path| {
    stream = io:open(path)
    content = stream::read_all()
    stream::close()
    crypto:sha256(content)
}

println(checksum(p))
# 8dd64c9b0c49e80ad4361b4604284ec5c2b20613faed29af2fc5fd9cd922db3a

Content-Addressed Storage

import std:crypto
import std:io
import std:os
import std:path
import std:println

root = `mktemp -d`

store = |content| {
    key = crypto:sha256(content)
    out = io:open(path:join([root, key]), true, true)
    out::write(content)
    out::close()
    key
}

load = |key| {
    f = io:open(path:join([root, key]))
    content = f::read_all()
    f::close()
    content
}

key = store("Important data")
println(key::length())      # 64
println(load(key))          # Important data

Deduplicating by Digest

import std:crypto
import std:println

documents = ["alpha", "beta", "alpha", "gamma"]

seen = []
unique = []
loop through documents with doc {
    digest = crypto:sha256(doc)
    match {
        seen::contains(digest) => { continue }
        _ => {
            seen::push(digest)
            unique::push(doc)
        }
    }
}

println(unique)  # [alpha, beta, gamma]

Cache Key from Parameters

Because json:generate sorts keys, two equal maps produce the same key regardless of the order they were built in:

import std:crypto
import std:json
import std:println

cache_key = |name, params| crypto:md5("${name}:${json:generate(params)}")

a = cache_key("search", {"q": "suji", "page": 1})
b = cache_key("search", {"page": 1, "q": "suji"})

println(a == b)         # true
println(a::length())    # 32

Signed Payload

import std:crypto
import std:json
import std:println

sign = |secret, data| {
    body = json:generate(data)
    {
        "body": body,
        "signature": crypto:hmac_sha256(secret, body),
    }
}

check = |secret, envelope| {
    crypto:hmac_sha256(secret, envelope:body) == envelope:signature
}

envelope = sign("shared-secret", {"user": "alice", "action": "update"})
println(check("shared-secret", envelope))  # true
println(check("other-secret", envelope))   # false

Gotchas

  • All arguments must be strings; numbers and maps raise a type error.
  • Digests are hex text, so ::length() is 32/40/64/128 characters, not bytes.
  • Raw hashes are unsuitable for password storage; there is no salted key derivation function in the standard library.
  • Hashing is one shot over a whole string, so a very large file must be read fully into memory first.

See Also