Home

Awesome

hairnet

A take on fernet using AES-GCM (AEAD) rather than fernet's AES-128 in CBC mode + HMAC.

The idea is to take what is now a fairly weak crypto library and bring it up to a higher standard while maintaining a similar interface.

The objective is to:

"takes a user-provided message (an arbitrary sequence of bytes), a key (256 bits), and the current time, and produces a token, which contains the message in a form that can't be read or altered without the key."

In terms of AEAD, the PlainText will be the user-provided message, the key should be any Erlang binary (please use a different one from the one in fernet if you are upgrading), and the current time is going to be used as the additional authenticated data (AAD), which can be used by the party doing decryption to validate for staleness.

Interface

1> Key = hairnet:generate_encoded_key().
<<"BVt1_R20scbTwz9t05PrtE4EFAauMeRKTxbwYmUiafY=">>
2> Token = hairnet:generate_token("hello", Key).
<<"AQAAAABXH4wKw8DqUtDjJxAX3BuEHGP9xke0tfY-73uzVCpa1iT5f1wgAAAABbhBUeFl">>
3> hairnet:verify_and_decrypt_token(Token, Key, infinity).
{ok, <<"hello">>}
4> TTL = 10. % 10 Seconds
10
5> hairnet:verify_and_decrypt_token(Token, Key, TTL).
{error, too_old}

Difference from fernet

Build

$ rebar3 compile

Test

$ rebar3 do eunit, dialyzer

Warnings

The publications from NIST on the algorithm have the following specifications:

The following requirement applies to all implementations that use either 1) the deterministic construction with IVs whose length is not 96, or 2) the RBG-based construction, for IVs of any length. In other words, unless an implementation only uses 96-bit IVs that are generated by the deterministic construction:

The total number of invocations of the authenticated encryption function shall not exceed 2^32, including all IV lengths and all instances of the authenticated encryption function with the given key.

This is a “global” requirement that can be achieved by appropriate “local” limits on each instance of the authenticated encryption function with a given key. For example, suppose an implementation consists of 2^10 devices that only support 64-bit, 96-bit, and 128-bit IVs. One way to satisfy the above requirement would be to limit each device to 2^20 invocations with 64-bit IVs, 2^21 invocations with 96-bit IVs, and 2^20 invocations with 128-bit IVs.

This implementation uses Erlang's crypto:strong_rand_bytes(16) to generate 128-bit IVs.

As such, it would be recommended to rotate the key used to encrypt content once such limits (2^21 calls locally, or 2^32 overall) are reached to avoid weakening the security of the private key.