Skip to content

Instantly share code, notes, and snippets.

@jedisct1
Created July 26, 2023 22:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jedisct1/f38e3a262cfa5b9b0399cd62beb1441d to your computer and use it in GitHub Desktop.
Save jedisct1/f38e3a262cfa5b9b0399cd62beb1441d to your computer and use it in GitHub Desktop.

std.crypto changes

New features

  • Salsa20: round-reduced variants can now be used.
  • The POLYVAL universal hash function was added.
  • AEGIS: support for 256-bit tags was added.
  • A MAC API was added to AEGIS (std.crypto.auth.aegis) - AEGIS can be used as a high-performance MAC on systems with hardware AES support. Note that this is not a hash function; a secret key is absolutely required in order to authenticate untrusted messages.
  • Edwards25519: a rejectLowOrder() function was added to quickly reject low-order points.
  • HKDF: with extractInit(), a PRK can now be initialized with only a salt, the keying material being added later, possibly as multiple chunks.
  • Hash functions that returns a fixed-length digest now include a finalResult() function that returns the digest as an array, as well as a peek() function that returns it without changing the state.
  • AES-CMAC has been implemented, and is availble in crypto.auth.cmac.
  • std.crypto.ecc: the isOdd() function was added to return the parity of a field element.
  • bcrypt: bcrypt has a slightly annoying limitation: passwords are limited to 72 bytes, and additional bytes are silently ignored. A new option, silently_truncate_password, can be set to true to transparently pre-hash the passwords and overcome this limitation.

Breaking changes

  • A HMAC key size can have any length, and crypto.Hmac*.key_size was previously set to 256 bits for general guidance. This has been changed to match the actual security level of each function.
  • secp256k1: the mulPublic() and verify() functions can now return a NonCanonicalError in addition to existing errors.
  • Ed25519: the top-level Ed25519.sign(), Ed25519.verify(), key_blinding.sign() and key_blinding.unblindPublicKey() functions, that were already deprecated in version 0.10.0, have been removed. For consistency with other signature schemes, these functions have been moved to the KeyPair, PublicKey, BlindKeyPair and BlindPublicKey structures.

Keccak

The Keccak permutation was only used internally for sha3. It was completely revamped and has now its dedicated public interface in crypto.core.keccak.

keccak.KeccakF is the permutation itself, which now supports sizes between 200 and 1600 bits, as well as a configurable number of rounds. And keccak.State offers an API for standard sponge-based constructions.

Taking advantage of this, the SHAKE extendable output function (XOF) has been added, and can be found in std.crypto.hash.sha3.Shake128 and std.crypto.hash.sha3.Sha256. SHAKE is based on SHA-3, NIST-approved, and the output of can be of any length, which has many applications and is something we were missing in the standard library.

The more recent TurboSHAKE variant is also available, as crypto.hash.sha3.TurboShake128 and crypto.hash.sha3.TurboShake256. TurboSHAKE benefits from the extensive analysis of SHA-3, its output can also be of any length, and it has good performance across all platforms. In fact, on CPUs without SHA-256 acceleration, and when using WebAssembly, TurboSHAKE is the fastest function we have in the standard library. If you need a modern, portable, secure, overall fast hash function / XOF, that is not vulnerable to length-extension attacks (unlike SHA-256), TurboSHAKE should be your go-to choice.

Kyber

Kyber is a post-quantum public key encryption and key exchange machanism. It was selected by NIST for the first post-quantum cryptography standard.

It is available in the standard library, in the std.crypto.kem namespace, making Zig the first language with post-quantum cryptography available right in the standard library.

Kyber512, Kyber768 and Kyber1024, as specified in the current draft, are supported.

The TLS client (std.crypto.tls.Client) also supports the hybrid X25519Kyber768 post-quantum key agreement mechanism by default.

Thanks a lot to Bas Westerbaan for contributing this!

Constant-time, allocation-free field arithmetic

Cryptography frequently requires computations over arbitrary finite fields.

This is why a new namespace made its appearance: std.crypto.ff.

Functions from this namespace never require dynamic allocations, are designed to run in constant time, and transparently perform conversions from/to the Montgomery domain.

This allowed us to implement RSA verification without using any allocators.

Configurable side channels mitigations

Side channels in cryptographic code can be exploited to leak secrets.

And mitigations are useful but also come with a performance hit.

For some applications, performance is critical, and side channels may not be part of the threat model. For other applications, hardware-based attacks is a concern, and mitigations should go beyond constant-time code.

Zig 0.11 introduces the std_options.side_channels_mitigations global setting to accomodate the different use cases.

It can have 4 different values:

  • none: which doesn't enable additional mitigations. "Additional", because it only disables mitigations that don't have a big performance cost. For example, checking authentication tags will still be done in constant time.

  • basic: which enables mitigations protecting against attacks in a common scenario, where an attacker doesn't have physical access to the device, cannot run arbitrary code on the same thread, and cannot conduct brute-force attacks without being throttled.

  • medium: which enables additional mitigations, targeting protection against practictal attacks even in a shared environement.

  • full: which enables all the available mitigations, going beyond ensuring that the code runs in constant time.

The more mitigations are enabled, the bigger the performance hit will be. But this let applications choose what's best for their use case.

medium is the default.

Hello Ascon, farewell to Gimli and Xoodoo

Gimli and Xoodoo have been removed from the standard library, in favor of Ascon.

These are great permutations, and there's nothing wrong with them from a practical security perspective.

However, both were competing in the NIST lightweight crypto competition.

Gimli didn't pass the 3rd selection round, and was not used much in the wild besides Zig and libhydrogen. It will never be standardized and is unlikely to get more traction in the future.

The Xoodyak mode, that Xoodoo is the permutation of, brought some really nice ideas. There are discusisons to standardize a Xoodyak-like mode, but without Xoodoo.

So, the Zig implementations of these permutations are better maintained outside the standard library.

For lightweight crypto, Ascon is the one that we know NIST will standardize and that we can safely rely on from a usage perspective.

So, Ascon was added instead (in crypto.core.Ascon). We support the 128 and 128a variants, both with Little-Endian or Big-Endian encoding.

Note that we currently only ship the permutation itself, as the actual constructions are very likely to change a lot during the ongoing standardization process.

The default CSPRNG (std.rand.DefaultCsprng) used to be based on Xoodoo. It was replaced by a traditional ChaCha-based random number generator, that also improves performance on most platforms.

For constrained environments, std.rand.Ascon is also available as an alternative. As the name suggest, it's based on Ascon, and has very low memory requirements.

Bug fixes

  • HKDF: expansion to <hash size> * 255 bytes not an error any more.
  • Curve25519: when compiled to WebAssembly, scalar multiplication emitted too many local variables for some runtimes. This has been fixed. The code is also significantly smaller in ReleaseSmall mode.
  • Prime-order curves: points whose X coordinate was 0 used to be rejected with an IdentityElement error. These points were also not properly serialized. That has been fixed.
  • Argon2: outputs larger than 64 bytes are now correctly handled.

Performance improvements

  • GHASH was reimplemented and is now ~3x faster.
  • AES encryption now takes advantage of the EOR3 instruction on Apple Silicon for a slight performance boost.
  • The ChaCha20 implementation can now take advantage of CPUs with 256 and 512 bit vector registers for a significant speedup.
  • Poly1305 got a little bit faster, too.
  • SHA256 can take advantage of hardware acceleration on x86_64 and aarch64.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment