std.hash

import std.hash

hash.sha256("abc")                  # hex digest
hash.sha256(stream)                 # a stream, read a chunk at a time
hash.hmac_sha256(key, message)      # hex; the message may be a stream too
hash.sha256_bytes(data)             # raw 32 bytes
hash.hmac_sha256_bytes(key, msg)

hash.password("pw")                 # Argon2id PHC string
hash.verify("pw", stored)           # constant-time -> Bool
hash.constant_eq(a, b)              # compare a MAC or token, no early exit

Inputs are a string, bytes or a stream.

Hashing a file without reading it

import std.fs
import std.hash

digest = hash.sha256(fs.open("release.tar.gz"))

The memory this costs is one chunk, whatever the file's size. sha256, sha1, their _bytes forms and the message argument of hmac_sha256 accept any stream: a file, a socket, a child's output, an HTTP body. The stream is read to its end and consumed, so a read after the digest returns null.

The answer is the same as hashing the same content as a string, so a digest taken this way compares with one taken any other way. There is no hash.file, because hash.sha256(fs.open(path)) already is one, and it works on everything else too.

A line stream from io.lines is refused. Its terminators have been removed, so its digest would be of something that was never the input - which is the kind of mismatch that costs an afternoon.

Passwords: use password and verify

stored = hash.password(reveal(plain))     # store this
ok = hash.verify(reveal(attempt), stored)

Argon2id with a per-call random salt, producing a self-describing PHC string that carries its own parameters - so verification keeps working when you change the cost settings later.

verify is constant-time and returns false rather than raising on a malformed stored hash, so a login path cannot be crashed by bad data in the database.

Never hash a password with sha256. A digest is designed to be fast, which is exactly wrong for a password: fast means a stolen table can be brute-forced at enormous rates. Argon2id is deliberately slow and memory-hard. Salting a sha256 yourself does not close that gap.

HMAC for authentication

hmac_sha256(key, message) proves a message was produced by someone holding the key. This is what signs a cookie or a webhook:

mac = hash.hmac_sha256(reveal(secret_key), payload)

Compare MACs with constant_eq, never ==. A comparison that returns on the first differing byte leaks how much of a forged MAC was correct, which is enough to forge one byte at a time.

if hash.constant_eq(mac, expected) { accept() }

constant_eq takes strings or bytes, so a hex digest and its _bytes form both work, and a string compares as its UTF-8. Reach for it for anything secret you check for equality: a MAC, a CSRF token, a session id, an API key.

It compares content in constant time but does not hide length - values of different lengths answer false immediately. Everything it is meant for is fixed-length, so the length reveals nothing.

It is native for the same reason password and verify are. A loop written in Ecko allocates, bounds-checks every index and can be descheduled between iterations, so it cannot promise anything about timing.

sha1 is legacy only

hash.sha1 and hash.sha1_bytes exist for legacy protocols that require it - the MySQL authentication scramble, for instance. SHA-1 is broken for collision resistance. Do not choose it for anything new.

Not encryption

Hashing is one-way. There is no decrypt. For confidentiality use TLS in transit (std.net, std.http) and your platform's facilities at rest.