(std crypto hash)

Reference

Overview

Hashes, HMAC and password hashing, from .NET’s System.Security.Cryptography.

(import (std crypto hash))

(bytes->hex (hash-string "hello"))                ; SHA-256, as hex
(hash-file "/srv/big.iso" #:algorithm Sha512)     ; read a buffer at a time
(hmac-string key "message")                       ; HMAC-SHA256

(def stored (password-hash "hunter2"))            ; "$pbkdf2-sha256$i=600000$..."
(password-verify "hunter2" stored)                ; #t

All functions take #:algorithm. The default is Sha256. A string is hashed as its UTF-8 bytes.

MD5 and SHA-1 are broken. Use them only for a protocol or a checksum that needs them. Do not use them to make a signature or to find out if data was changed by an attacker.

A digest is not a password hash. A digest is fast, so an attacker who has the digests can try many passwords each second. Use password-hash for passwords. It is slow on purpose.

Passwords. password-hash uses PBKDF2-HMAC with a random 16-byte salt from (std random). The result is a PHC string:

$pbkdf2-sha256$i=600000$<salt>$<hash>

The salt and the hash are base64 without padding. The string holds the algorithm and the iteration count, so password-verify needs only the password and the string. When you increase the iteration count, old hashes still verify. password-needs-rehash? tells you which ones to replace after the next correct login.

The password is hashed as its UTF-8 bytes, and it is not normalized. Two strings that look the same, but use different Unicode code points, give different hashes.

Password hashing takes time. At 600000 iterations, one hash takes a few hundred milliseconds of one core. In a bjoroutine, password-hash and password-verify run on a thread of their own, so the fiber pool continues to run. A login page must limit the number of attempts, because each attempt costs this time.

The salt comes from current-random. A salt must be unique, but it need not be secret. In a with-random-seed, two runs make the same salts. This is correct for a test, but do not hash real passwords there.

Compare secrets with constant-time-equal?. The = on arrays stops at the first difference. An attacker who can measure the time can then find a token one byte at a time.

See also: hash-bytes, hmac-bytes, make-hasher, password-hash, constant-time-equal?

Reference

Types

Functions

Values

Types

HashAlgorithm

union ↑ Types

Md5, Sha1, Sha256, Sha384, Sha512, Sha3-256, Sha3-384 or Sha3-512.

MD5 and SHA-1 are for protocols and checksums only. SHA-3 is not available on all platforms: see hash-algorithm-supported?.

See also: hash-algorithm-supported?, digest-length

Hasher

type alias ↑ Types

A hash or an HMAC that you give data to in parts.

A hasher is mutable. Do not use one from two fibers at the same time. It is .NET’s IncrementalHash.

See also: make-hasher, make-hmac-hasher, hasher-add!, hasher-finish!

Functions

hash-algorithm-supported?

function ↑ Functions

(: hash-algorithm-supported? (-> HashAlgorithm bool))
(hash-algorithm-supported? a)

True when this platform can compute the algorithm.

a
The algorithm.
returns
False only for SHA-3 on a platform without it: OpenSSL older than 1.1.1, or Windows older than 11.

digest-length

function ↑ Functions

(: digest-length (-> HashAlgorithm int))
(digest-length a)

The length of the algorithm’s digest, in bytes.

a
The algorithm.
returns
16 for MD5, 20 for SHA-1, and 32, 48 or 64 for the others.

hash-bytes

function ↑ Functions

(: hash-bytes (-> (Array byte) (#:algorithm HashAlgorithm) (Array byte)))
(hash-bytes data #:algorithm Sha256)

The digest of a byte array.

data
The bytes to hash.
#:algorithm
The algorithm. The default is Sha256.
returns
A new array of (digest-length algorithm) bytes.

See also: hash-string, bytes->hex

hash-string

function ↑ Functions

(: hash-string (-> string (#:algorithm HashAlgorithm) (Array byte)))
(hash-string text #:algorithm Sha256)

The digest of a string’s UTF-8 bytes.

text
The string to hash.
#:algorithm
The algorithm. The default is Sha256.
returns
A new array of (digest-length algorithm) bytes.
(bytes->hex (hash-string "abc"))
;; "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"

See also: hash-bytes, bytes->hex

hash-file

function ↑ Functions

(: hash-file (-> string (#:algorithm HashAlgorithm) (Array byte)))
(hash-file path #:algorithm Sha256)

The digest of a file, read a buffer at a time.

path
The path of the file.
#:algorithm
The algorithm. The default is Sha256.
returns
A new array of (digest-length algorithm) bytes.

In a bjoroutine, the read suspends the fiber. Elsewhere, it blocks the thread. A file that is not there raises System.IO.FileNotFoundException.

hmac-bytes

function ↑ Functions

(: hmac-bytes (-> (Array byte) (Array byte) (#:algorithm HashAlgorithm) (Array byte)))
(hmac-bytes key data #:algorithm Sha256)

The HMAC of a byte array.

key
The secret key. Use at least as many bytes as the digest has.
data
The bytes to authenticate.
#:algorithm
The algorithm. The default is Sha256.
returns
The tag: a new array of (digest-length algorithm) bytes.

To check a tag that you received, compute the tag again and compare the two with constant-time-equal?.

See also: hmac-string, constant-time-equal?

hmac-string

function ↑ Functions

(: hmac-string (-> (Array byte) string (#:algorithm HashAlgorithm) (Array byte)))
(hmac-string key text #:algorithm Sha256)

The HMAC of a string’s UTF-8 bytes.

key
The secret key.
text
The string to authenticate.
#:algorithm
The algorithm. The default is Sha256.
returns
The tag: a new array of (digest-length algorithm) bytes.

See also: hmac-bytes, constant-time-equal?

make-hasher

function ↑ Functions

(: make-hasher (-> (#:algorithm HashAlgorithm) Hasher))
(make-hasher #:algorithm Sha256)

A new, empty hasher.

#:algorithm
The algorithm. The default is Sha256.
returns
A hasher that has no data yet.
(def h (make-hasher))
(hasher-add-string! h "hello, ")
(hasher-add-string! h "world")
(hasher-finish! h)   ; the same as (hash-string "hello, world")

See also: hasher-add!, hasher-finish!

make-hmac-hasher

function ↑ Functions

(: make-hmac-hasher (-> (Array byte) (#:algorithm HashAlgorithm) Hasher))
(make-hmac-hasher key #:algorithm Sha256)

A new, empty HMAC hasher with a key.

key
The secret key.
#:algorithm
The algorithm. The default is Sha256.
returns
A hasher that has no data yet. It keeps the key after each hasher-finish!.

See also: hasher-add!, hasher-finish!, hmac-bytes

hasher-add!

function ↑ Functions

(: hasher-add! (-> Hasher (Array byte) void))
(hasher-add! h data)

Gives bytes to a hasher.

h
The hasher.
data
The bytes.

hasher-add-string!

function ↑ Functions

(: hasher-add-string! (-> Hasher string void))
(hasher-add-string! h text)

Gives a string’s UTF-8 bytes to a hasher.

h
The hasher.
text
The string.

hasher-finish!

function ↑ Functions

(: hasher-finish! (-> Hasher (Array byte)))
(hasher-finish! h)

The digest of all the data given since the hasher was made or last finished.

h
The hasher.
returns
A new array of digest bytes.

The hasher is then empty, and you can use it again. An HMAC hasher keeps its key.

constant-time-equal?

function ↑ Functions

(: constant-time-equal? (-> (Array byte) (Array byte) bool))
(constant-time-equal? a b)

Compares two byte arrays in a time that does not depend on their contents.

a
One array.
b
The other.
returns
True when the arrays have the same length and the same bytes.

Use this to compare a secret: an HMAC tag, a token, a digest of a token. The time depends on the lengths, so an attacker can find the length. That is not a problem when the length is fixed, as it is for a digest.

bytes->hex

function ↑ Functions

(: bytes->hex (-> (Array byte) string))
(bytes->hex bytes)

The bytes as lower-case hex, two digits each.

bytes
The bytes.
returns
A string of two lower-case hex digits for each byte.

See also: hex->bytes

hex->bytes

function ↑ Functions

(: hex->bytes (-> string (Option (Array byte))))
(hex->bytes text)

The bytes that a hex string gives.

text
Hex digits, upper or lower case, two for each byte.
returns
None when the length is odd or a character is not a hex digit.

See also: bytes->hex

password-hash

function ↑ Functions

(: password-hash (-> string (#:algorithm HashAlgorithm) (#:iterations int) string))
(password-hash password #:algorithm Sha256 #:iterations default-password-iterations)

A slow, salted hash of a password, as a PHC string to store.

password
The password.
#:algorithm
Sha256, Sha384 or Sha512. The default is Sha256. Other algorithms raise ArgumentException.
#:iterations
The PBKDF2 iteration count. The default is default-password-iterations.
returns
A string such as $pbkdf2-sha256$i=600000$<salt>$<hash>.

Each call uses a new salt, so two hashes of the same password are different. In a bjoroutine, the hash is computed on a thread of its own.

See also: password-verify, password-needs-rehash?

password-verify

function ↑ Functions

(: password-verify (-> string string bool))
(password-verify password stored)

True when the password gives the stored hash.

password
The password to check.
stored
A string from password-hash.
returns
True or false. The hashes are compared in constant time.

A stored string that is not a PHC string from password-hash raises ArgumentException. That is an error in the stored data, not a wrong password.

In a bjoroutine, the hash is computed on a thread of its own.

See also: password-hash, password-needs-rehash?

password-needs-rehash?

function ↑ Functions

(: password-needs-rehash? (-> string (#:algorithm HashAlgorithm) (#:iterations int) bool))
(password-needs-rehash? stored #:algorithm Sha256 #:iterations default-password-iterations)

True when a stored hash uses another algorithm or fewer iterations.

stored
A string from password-hash.
#:algorithm
The algorithm that hashes must use now. The default is Sha256.
#:iterations
The iteration count that hashes must have now. The default is default-password-iterations.
returns
True when you must replace stored with a new hash. A stored string that is not a PHC string raises ArgumentException.
(when (password-verify password stored)
  (when (password-needs-rehash? stored)
    (save-hash! user (password-hash password)))
  (log-in user))

Use it after a correct password-verify. Only then do you have the password to make a new hash.

Values

default-password-iterations

value ↑ Values

(: default-password-iterations int)

600000: the OWASP recommendation for PBKDF2-HMAC-SHA256 in 2023.