crypto +linux +x86_64

Important notice: Hare's cryptography implementations have not been audited. You can contribute to the funding of an independent audit of our cryptography implementation on OpenCollective:

https://opencollective.com/hare/projects/cryptography-audit

The "crypto" module provides easy-to-use and hard-to-misuse functions for doing various high-level cryptographic operations. This is the recommended approach for most cryptographic applications. For applications which need them, direct access to supported cryptographic primitives is provided in submodules.

Cryptography is a difficult, high-risk domain of programming. The life and well-being of your users may depend on your ability to implement cryptographic applications with due care. Please carefully read all of the documentation, double-check your work, and seek second opinions and independent review of your code. Our documentation and API design aims to prevent easy mistakes from being made, but it is no substitute for a good background in applied cryptography. We recommend the "Crypto 101" course as a good general introduction to cryptography:

https://www.crypto101.io

There are a few additional modules and functions which are of interest to users of the crypto module. Access to secure random data is provided by the crypto::random module. The ability to securely erase sensitive data in RAM is provided by bytes::zero. Note also that bytes::equal is not suitable for constant-time comparisons; equality comparisons in a cryptographic context should utilize compare instead.

TODO: Add something based on mlock to deal with storing sensitive information, and add a note here about it.

We reserve the right to make breaking changes to this module in the future, which may prevent data prepared by old versions from being readable by new versions. Such changes will be accompanied with an increment of the major version of the standard library, as well as a changelog explaining what changes are required of downstream users, and a migration procedure will be prepared. The hare-announce mailing list is the appropriate way to be notified of these changes:

https://lists.sr.ht/~sircmpwn/hare-announce

The following features are offered in this module:

Submodules

Index

Types

type box;
type mac;
type nonce;
type sessionkey;

Functions

fn compare([]u8, []u8) bool;
fn decrypt(*sessionkey, *box, []u8...) ([]u8 | errors::invalid);
fn derivekey([]u8, []u8, []u8, (u32 | []u64), u32) (void | errors::nomem);
fn encrypt(*sessionkey, *nonce, []u8, []u8...) box;

Types

type box[link]

type box = (mac, nonce, []u8);

An encrypted, authenticated message.

type mac[link]

type mac = [16]u8;

A message authentication code.

type nonce[link]

type nonce = [24]u8;

A value which is only used once.

type sessionkey[link]

type sessionkey = [32]u8;

A secret session key.

Functions

fn compare[link]

fn compare(a: []u8, b: []u8) bool;

Compares two slices and returns true if they are equal. Comparison is done in constant time, meaning that the time it takes depends only on the size of the slices and not the content.

fn decrypt[link]

fn decrypt(
	key: *sessionkey,
	box: *box,
	additional: []u8...
) ([]u8 | errors::invalid);

Authenticates and decrypts a message encrypted with encrypt. If the decryption is successful, the plaintext slice is returned, and if not, errors::invalid is returned.

The 'sessionkey' parameter is the shared key. The 'box' parameter is the output of encrypt. If additional data should be authenticated, it may be provided in the variadic 'additional' parameters.

Note that the data is decrypted in-place, such that the box's ciphertext becomes overwritten with the plaintext. The return value is borrowed from this buffer. If decryption fails, this buffer will be zeroed, causing the ciphertext to be destroyed as well. It is advised to zero the plaintext buffer yourself after you are done using it, see bytes::zero.

See decrypt for the full details and a usage example.

fn derivekey[link]

fn derivekey(
	dest: []u8,
	salt: []u8,
	password: []u8,
	mem: (u32 | []u64),
	passes: u32,
) (void | errors::nomem);

Given a password, derive a key. Given the same password, salt, memory, and passes, this function will always produce the same key. This function is designed to derive cryptographic keys from user-provided passwords, or to verify a password for user logins.

The user provides a buffer for the key to be written to via the 'dest' parameter. The minimum supported length for this buffer is 4 bytes, and the recommended length is 32 bytes.

The salt parameter should be randomly generated, stored alongside the key, and used in subsequent calls to produce the same key. It must be at least 8 bytes, but 16 bytes is recommended. Use crypto::random to generate a different salt for each key.

The 'mem' and 'passes' functions are provided to tune the behavior of this algorithm. It is designed to be computationally expensive, and you must adjust these figures to suit your hardware and use-case. If you provide a u32 for 'mem', the algorithm will dynamically allocate that many kilobytes of working memory. To allocate this memory yourself, provide a []u64 instead. The number of passes controls the amount of time spent generating the key, higher numbers take longer.

To identify ideal values for these parameters, start with 100000 for 'mem' (100 MiB) and 0 for 'passes'. If it takes too long, reduce the amount of memory, and if it does not take long enough, increase the amount of memory. If you have reached the maximum amount of memory you are able to use, increase passes.

The current implementation of this function uses argon2i version 1.3 with the provided number of memory blocks, passes equal to passes + 3, and parallelism set to one.

fn encrypt[link]

fn encrypt(
	key: *sessionkey,
	nonce: *nonce,
	plaintext: []u8,
	additional: []u8...
) box;

Performs authenticated encryption on a message. The result may be authenticated and decrypted with decrypt.

To use this function, you must first establish a session key which is shared with both parties. This key must be random and secret. You may derive this key with a key exchange (such as exchange or crypto::dh), or with a key derivation function (such as derivekey), or by sharing it in person, or some other, similar means which preserves the traits of randomness and secrecy.

You must also establish a unique nonce for each message, which you must not reuse for any future messages using the same session key. It is recommended to generate this randomly with crypto::random.

The plaintext parameter provides the message to encrypt. It will be overwritten with the ciphertext. The buffer provided in the return value is borrowed from this parameter.

The optional 'additional' parameters provide additional data to be authenticated with the same MAC. This data is not encrypted, but decrypt will fail if it has been tampered with. The order of these arguments must be consistent between encrypt and decrypt.

The return value contains all of the information which should be transmitted to the other party, including the computed MAC, a copy of the nonce, and the ciphertext. It is safe to transmit these values over an unsecured connection, or to encode them with something like encoding::base64.

Any 'additional' data, if provided, is not included in the box type. The user must provide for this data to be transmitted to the other party themselves.

let key: crypto::sessionkey = [0...];
let nonce: crypto::nonce = [0...];
random::buffer(key); // Or some other means of establishing the key
random::buffer(nonce);

let buf: [64]u8 = [0...]; // Populate with your message
let box = crypto::encrypt(&key, &nonce, buf[..], buf[..]);

// To decrypt this message: let plaintext = match (crypto::decrypt(&key, &box, buf[..])) { case let buf: []u8 => yield buf; case errors::invalid => abort("Message authentication or decryption failed"); };

The current implementation of this algorithm is based on RFC 8439, but uses XChaCha20 instead of ChaCha20.