sysl

Module

sysl.crypto

Cryptographic hashing: the SHA-2 family, SHA-1 and MD5, HMAC over any of them, and PBKDF2.

Import import sysl.crypto
Requires
no alloc

A digest is bytes rather than text, a hasher streams so a caller need not hold what it is hashing, and nothing here allocates — which is what lets a board hash a firmware image with the heap given up.

What is broken is marked broken where it is declared. MD5 and SHA-1 are here because a program reading somebody else’s format has no choice about which digest that format used, and each says in its own header what it may and may not still be trusted for. verify is the constant-time comparison, and a program checking a tag should reach for it rather than ==.

Index

hkdf1 hkdf1_expand hkdf224 hkdf224_expand hkdf256 hkdf256_expand hkdf384 hkdf384_expand hkdf512 hkdf512_expand hmac1 hmac224 hmac256 hmac384 hmac512 hmacmd5 md5 new_md5 new_sha1 new_sha224 new_sha256 new_sha384 new_sha512 pbkdf2_hmac1 pbkdf2_hmac224 pbkdf2_hmac256 pbkdf2_hmac384 pbkdf2_hmac512 sha1 sha224 sha256 sha384 sha512 verify Fault Md5 Sha1 Sha224 Sha256 Sha384 Sha512 Compression for Md5C Compression for Sha1C Compression for Sha2Narrow Compression for Sha2Wide Word for u32 Word for u64

Functions

hkdf1

hkdf1(salt: []const u8, ikm: []const u8, info: []const u8, out: []u8) -> Result[unit, Fault]

HKDF (RFC 5869) in one call: a secret is condensed with the salt and then spread into out.

The secret must already be unguessable – a Diffie-Hellman result, a key from a hardware source – because nothing here raises the cost of guessing it. A password goes to pbkdf2_hmac256 instead.

info is what keeps two keys derived from one secret apart, and it is a label rather than a secret: a protocol writes its own name and the key’s purpose there, and a different info yields an unrelated key. The salt may be empty, which is the standard’s own default – HMAC pads a short key with zeroes, so an empty salt is the zero key the standard names.

At most 255 digests’ worth of key can be asked for, which the standard’s one-byte counter fixes.

Extract on its own is hmac256(salt, ikm) and needs nothing from this module: HKDF-Extract is HMAC with the salt as the key, which is why only expansion has a name of its own below.

hkdf1_expand

hkdf1_expand(prk: []const u8, info: []const u8, out: []u8) -> Result[unit, Fault]

HKDF’s expansion alone (RFC 5869 §2.3), over a pseudorandom key the caller already has.

This is the half TLS 1.3 leans on: HKDF-Expand-Label is this function with the label written into info in the shape §7.1 gives, and Derive-Secret is one of those per traffic and exporter secret in the schedule. Signal’s chains are the same shape.

The key should be a digest’s worth of unguessable bytes – what hmac256(salt, ikm), which is HKDF-Extract, answers with. At most 255 digests’ worth of key can be asked for.

hkdf224

hkdf224(salt: []const u8, ikm: []const u8, info: []const u8, out: []u8) -> Result[unit, Fault]

hkdf224_expand

hkdf224_expand(prk: []const u8, info: []const u8, out: []u8) -> Result[unit, Fault]

hkdf256

hkdf256(salt: []const u8, ikm: []const u8, info: []const u8, out: []u8) -> Result[unit, Fault]

hkdf256_expand

hkdf256_expand(prk: []const u8, info: []const u8, out: []u8) -> Result[unit, Fault]

hkdf384

hkdf384(salt: []const u8, ikm: []const u8, info: []const u8, out: []u8) -> Result[unit, Fault]

hkdf384_expand

hkdf384_expand(prk: []const u8, info: []const u8, out: []u8) -> Result[unit, Fault]

hkdf512

hkdf512(salt: []const u8, ikm: []const u8, info: []const u8, out: []u8) -> Result[unit, Fault]

hkdf512_expand

hkdf512_expand(prk: []const u8, info: []const u8, out: []u8) -> Result[unit, Fault]

hmac1

hmac1(key: []const u8, msg: []const u8) -> [20]u8

HMAC-SHA-1, answering a twenty-byte tag. A key longer than a block is replaced by its own digest; a shorter one is padded with zeroes.

This is a weaker warning than sha1 carries, and deliberately so. HMAC’s security does not rest on the hash being collision resistant, so HMAC-SHA-1 is not broken the way SHA-1 is – which is why RFC 6238 still specifies it as TOTP’s default and why a great deal of deployed request signing still uses it. It is here to talk to those. A protocol being written now wants hmac256.

hmac224

hmac224(key: []const u8, msg: []const u8) -> [28]u8

hmac256

hmac256(key: []const u8, msg: []const u8) -> [32]u8

hmac384

hmac384(key: []const u8, msg: []const u8) -> [48]u8

hmac512

hmac512(key: []const u8, msg: []const u8) -> [64]u8

hmacmd5

hmacmd5(key: []const u8, msg: []const u8) -> [16]u8

HMAC-MD5, answering a sixteen-byte tag. A key longer than a block is replaced by its own digest; a shorter one is padded with zeroes.

The same weaker warning hmac1 carries applies here, and for the same reason. HMAC’s security does not rest on the hash being collision resistant, so HMAC-MD5 is not broken the way md5 is – RFC 6151 says as much, while advising against it for anything new. It is here for what already asks: HTTP Digest with qop, CRAM-MD5, and RADIUS, none of which lets a client choose. A protocol being written now wants hmac256.

md5

md5(data: []const u8) -> [16]u8

The digest of one slice of bytes, sixteen bytes long.

MD5 is broken, and has been since 2004. A collision is buildable, and a chosen-prefix collision has been since 2007 – a pair of colliding X.509 certificates was built to show it. So this digest says nothing about a document nobody could have substituted: it must not carry a signature, a certificate, a content address or a deduplication key. sha256 is what all of those want.

It is in this module for the protocols that name it and give a client no say. PostgreSQL’s md5 authentication answers a salt with md5(md5(password + user) + salt); HTTP Digest, a Content-MD5 header and an S3 ETag are the same shape. Where an attacker gains nothing from a second document with the same digest, MD5 is adequate; everywhere else it is not, and there is no third case.

new_md5

new_md5() -> Md5

A hasher ready for its first update.

MD5 is broken. Reach for new_sha256 unless a wire protocol names this one; see md5.

new_sha1

new_sha1() -> Sha1

A hasher ready for its first update.

SHA-1 is broken. Reach for new_sha256 unless a wire protocol names this one; see sha1.

new_sha224

new_sha224() -> Sha224

new_sha256

new_sha256() -> Sha256

new_sha384

new_sha384() -> Sha384

new_sha512

new_sha512() -> Sha512

pbkdf2_hmac1

pbkdf2_hmac1(password: []const u8, salt: []const u8, iterations: u32, out: []u8) -> Result[unit, Fault]

PBKDF2 over HMAC-SHA-1, writing as much key as out has room for.

It is the one an existing protocol names: WPA2 derives its pairwise master key this way, and so does SCRAM-SHA-1 and a great deal of deployed password storage. A scheme being written now wants pbkdf2_hmac256, and something built for the purpose – Argon2, scrypt – before either.

The iteration count is the caller’s because the protocol says it: SCRAM carries the server’s count on the wire, and a client that used its own would derive a different key and be told its password was wrong. It must be at least one.

pbkdf2_hmac224

pbkdf2_hmac224(password: []const u8, salt: []const u8, iterations: u32, out: []u8) -> Result[unit, Fault]

PBKDF2 over HMAC-SHA-2, writing as much key as out has room for.

pbkdf2_hmac256 is the one to reach for, being what SCRAM-SHA-256 names – so PostgreSQL, MongoDB, Kafka and XMPP – and what WebCrypto’s deriveBits is usually asked for.

The iteration count is the caller’s, is named by the protocol, and must be at least one. What it buys is the cost of guessing, and nothing else: a password that could be guessed in a thousand tries still can be, a hundred thousand times more slowly.

pbkdf2_hmac256

pbkdf2_hmac256(password: []const u8, salt: []const u8, iterations: u32, out: []u8) -> Result[unit, Fault]

pbkdf2_hmac384

pbkdf2_hmac384(password: []const u8, salt: []const u8, iterations: u32, out: []u8) -> Result[unit, Fault]

pbkdf2_hmac512

pbkdf2_hmac512(password: []const u8, salt: []const u8, iterations: u32, out: []u8) -> Result[unit, Fault]

sha1

sha1(data: []const u8) -> [20]u8

The digest of one slice of bytes, which is the whole of what most callers want.

SHA-1 is broken, and has been publicly broken since 2017. A collision is buildable, and since 2019 a chosen-prefix collision is, so this digest says nothing about a document nobody could have substituted: it must not carry a signature, a certificate, a content address or a deduplication key. sha256 is what all of those want.

It is in this module for RFC 6455’s opening handshake, which answers a client’s key with base64(sha1(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")) and names both the algorithm and the constant in the standard, so there is no substitute. That use makes no security claim at all – the handshake shows the far end read the request and authenticates nobody – which is why an algorithm this broken still serves it. Where a second preimage would buy an attacker nothing, SHA-1 is adequate; everywhere else it is not, and there is no third case.

sha224

sha224(data: []const u8) -> [28]u8

sha256

sha256(data: []const u8) -> [32]u8

sha384

sha384(data: []const u8) -> [48]u8

sha512

sha512(data: []const u8) -> [64]u8

verify

verify(a: []const u8, b: []const u8) -> bool

Types

Fault

enum Fault
    NotStarted
    DigestTooSmall
    NoIterations
    TooMuchOutput
MemberSignatureDescription
describedescribe(self) -> string

Md5

struct Md5
    private[crypto] inner: Sha[Md5C]

An MD5 hash in progress. MD5 is broken – see md5 for what that does and does not rule out.

MemberSignatureDescription
updateupdate(*self, data: []const u8)
finishfinish(*self, digest: []u8) -> Result[unit, Fault]

Sha1

struct Sha1
    private[crypto] inner: Sha[Sha1C]

A SHA-1 hash in progress. SHA-1 is broken – see sha1 for what that does and does not rule out.

MemberSignatureDescription
updateupdate(*self, data: []const u8)
finishfinish(*self, digest: []u8) -> Result[unit, Fault]

Sha224

struct Sha224
    private[crypto] inner: Sha[Sha2Narrow]
MemberSignatureDescription
updateupdate(*self, data: []const u8)
finishfinish(*self, digest: []u8) -> Result[unit, Fault]

Sha256

struct Sha256
    private[crypto] inner: Sha[Sha2Narrow]
MemberSignatureDescription
updateupdate(*self, data: []const u8)
finishfinish(*self, digest: []u8) -> Result[unit, Fault]

Sha384

struct Sha384
    private[crypto] inner: Sha[Sha2Wide]
MemberSignatureDescription
updateupdate(*self, data: []const u8)
finishfinish(*self, digest: []u8) -> Result[unit, Fault]

Sha512

struct Sha512
    private[crypto] inner: Sha[Sha2Wide]
MemberSignatureDescription
updateupdate(*self, data: []const u8)
finishfinish(*self, digest: []u8) -> Result[unit, Fault]

Implementations

Compression for Md5C

impl Compression for Md5C

Compression for Sha1C

impl Compression for Sha1C

Compression for Sha2Narrow

impl Compression for Sha2Narrow

Compression for Sha2Wide

impl Compression for Sha2Wide

Word for u32

impl Word for u32

Word for u64

impl Word for u64

Search

Esc
↑↓ to navigate ↵ to open Esc to close