- Author: Trevor Perrin (noise @ trevp.net)
- Date: 2015-11-19
- Revision: 19 (work in progress)
- Copyright: This document is placed in the public domain
- Introduction ================
Noise is a framework for crypto protocols based on Diffie-Hellman key agreement. Noise can describe protocols that consist of a single message as well as interactive protocols.
- Overview ============
A Noise protocol begins with a handshake phase where two parties send handshake messages. During the handshake phase the two parties perform a DH-based key agreement to agree on a shared secret key. After the handshake phase each party can send transport messages encrypted with the shared key.
The Noise framework can support any DH-based handshake where each party has a long-term static key pair and/or an ephemeral key pair. The handshake is described by patterns: A message pattern specifies the DH public keys that comprise a handshake message, and the DH operations that are performed when sending or receiving that message. A handshake pattern specifies the message patterns that comprise a handshake.
A handshake pattern can be instantiated by DH functions, cipher functions, and a hash function to give a concrete protocol. An application using Noise must handle some application responsibilities on its own, such as indicating message lengths.
- Message format ===================
All Noise messages are less than or equal to 65535 bytes in length, and can be processed without parsing, since there are no type or length fields within the message.
A handshake message begins with a sequence of one or more DH public keys. Following the public keys will be a payload which could be used to convey certificates or other handshake data. Encryption of static public keys and payloads will occur after a DH operation establishes a shared secret key between the two parties. Ephemeral public keys aren't encrypted. Zero-length payloads are allowed. If encrypted, a zero-length payload will result in a 16-byte payload ciphertext, since encryption adds a 16-byte authentication tag to each ciphertext.
A transport message consists solely of an encrypted payload.
- Crypto algorithms ======================
A Noise protocol depends on DH functions, cipher functions, and a hash function.
During a Noise handshake, the outputs from DH functions will be sequentially
mixed into a secret chaining key (ck
). Cipher keys (k
) derived from the
chaining key will be used to encrypt public keys and handshake payloads. These
ciphertexts will also be sequentially mixed into a hash variable (h
). The
h
variable will be authenticated with every handshake ciphertext, to ensure
ciphertexts are bound to earlier data.
To represent a cipher key and its associated nonce we introduce the notion
of a CipherState
which contains k
and n
variables.
To handle symmetric-key crypto during the handshake we introduce a
SymmetricState
which extends a CipherState
with ck
and h
variables. An implementation will create a SymmetricState
to handle
a single Noise handshake, and can delete it once the handshake is finished.
The below sections describe these concepts in more detail.
Noise depends on the following DH functions (and an associated constant):
-
GENERATE_KEYPAIR()
: Generates a new DH keypair. -
DH(privkey, pubkey)
: Performs a DH calculation and returns an output sequence of bytes. If the function detects an invalid public key, the output may be set to all zeros or any other value that doesn't leak information about the private key. -
DHLEN
= A constant specifying the size of public keys in bytes.
Noise depends on the following cipher functions:
-
ENCRYPT(k, n, ad, plaintext)
: Encryptsplaintext
using the cipher keyk
of 32 bytes and an 8-byte unsigned integer noncen
which must be unique for the keyk
. Encryption must be done with an "AEAD" encryption mode with the associated dataad
and must return a ciphertext that is the same size as the plaintext plus 16 bytes for an authentication tag. -
DECRYPT(k, n, ad, ciphertext)
: Decryptsciphertext
using a cipher keyk
of 32 bytes, an 8-byte unsigned integer noncen
, and associated dataad
. If the authentication fails an error is signaled to the caller.
Noise depends on the following hash function (and associated constants):
-
HASH(data)
: Hashes some arbitrary-length data with a collision-resistant hash function and returns an output ofHASHLEN
bytes. -
HASHLEN
= A constant specifying the size in bytes of the hash output. Must be 32 or 64. -
BLOCKLEN
= A constant specifying the size in bytes that the hash function uses internally to divide its input for iterative processing. This is needed to use the hash function within the HMAC construct (BLOCKLEN
isB
in RFC 2104).
Noise defines an additional function based on the above HASH
function. The
||
operator indicates concatentation of byte sequences:
HKDF(chaining_key, input_key_material)
: Takes achaining_key
byte sequence of lengthHASHLEN
, and aninput_key_material
byte sequence of arbitrary length. Sets the valuetemp_key = HMAC-HASH(chaining_key, input_key_material)
. Sets the valueoutput1 = HMAC-HASH(temp_key, 0x01)
. Sets the valueoutput2 = HMAC-HASH(temp_key, output1 || 0x02)
. These three values are allHASHLEN
bytes in length. Returns the pair (output1
,output2
).
A CipherState
can encrypt and decrypt data based on its k
and n
variables:
-
k
: A cipher key of 32 bytes. -
n
: An 8-byte unsigned integer nonce.
A CipherState
responds to the following methods. The ++
post-increment
operator applied to n
means "use the current n
value, then increment it".
-
EncryptAndIncrement(ad, plaintext)
: ReturnsENCRYPT(k, n++, ad, plaintext)
. -
DecryptAndIncrement(ad, ciphertext)
: ReturnsDECRYPT(k, n++, ad, ciphertext)
. If an authentication failure occurs the error is signaled to the caller.
A SymmetricState
object extends a CipherState
with the following
variables:
-
has_k
: A boolean that records whetherk
has been initialized to a secret value. -
has_psk
: A boolean that records whether a preshared key was mixed in. -
ck
: A chaining key ofHASHLEN
bytes. -
h
: A hash output ofHASHLEN
bytes.
A SymmetricState
responds to the following methods:
-
InitializeSymmetric(handshake_name)
: Takes an arbitrary-lengthhandshake_name
. Leavesk
andn
uninitialized, and setshas_k
andhas_psk
toFalse
. Ifhandshake_name
is less than or equal toHASHLEN
bytes in length, setsh
equal tohandshake_name
with zero bytes appended to makeHASHLEN
bytes. Otherwise setsh = HASH(handshake_name)
. Setsck = h
. -
MixKey(input_key_material)
: Setsck, k = HKDF(ck, input_key_material)
. IfHASHLEN
is not 32, then the second output fromHKDF()
is truncated to 32 bytes to matchk
. Setsn = 0
andhas_k = True
. -
MixHash(data)
: Setsh = HASH(h || data)
. -
MixPresharedKey(preshared_key)
: Setsck, temp = HKDF(ck, preshared_key)
. CallsMixHash(temp)
. Setshas_psk = True
. -
EncryptAndHash(plaintext)
: Ifhas_k == True
setsciphertext = EncryptAndIncrement(h, plaintext)
, callsMixHash(ciphertext)
, and returnsciphertext
. Otherwise callsMixHash(plaintext)
and returnsplaintext
. -
DecryptAndHash(data)
: Ifhas_k == True
setsplaintext = DecryptAndIncrement(h, data)
, callsMixHash(data)
, and returnsplaintext
. Otherwise callsMixHash(data)
and returnsdata
. -
Split()
: Creates two childCipherState
objects by callingHKDF(ck, empty)
whereempty
is a zero-length byte sequence. The first child'sk
is set to the first output fromHKDF()
, and the second child'sk
is set to the second output fromHKDF()
. IfHASHLEN
is not 32, then each output fromHKDF()
is truncated to 32 bytes to matchk
. Both children'sn
value is set to zero. Both children are returned. The caller will use the childCipherState
objects to encrypt transport messages, as described in the next section.
- The handshake algorithm ============================
A pattern for a handshake message is some sequence of tokens from the set ("e", "s", "dhee", "dhes", "dhse", "dhss"). To send (or receive) a handshake message you iterate through the tokens that comprise the message's pattern. For each token you write (or read) the public key it specifies, or perform the DH operation it specifies.
To provide a rigorous description we introduce the notion of a HandshakeState
object. A HandshakeState
extends a SymmetricState
with DH variables.
To execute a Noise protocol you Initialize()
a HandshakeState
. During
initialization you specify any local key pairs, and any public keys for the
remote party you have knowledge of. You may optionally specify prologue
data that both parties will confirm is identical (such as previously exchanged
version negotiation messages), and/or a pre-shared key that will be used to
encrypt and authenticate.
After Initialize()
you call WriteMessage()
and ReadMessage()
to process
each handshake message. If a decryption error occurs the handshake has failed
and the HandshakeState
is deleted without sending further messages.
Processing the final handshake message returns two CipherState
objects, the
first for encrypting transport messages from initiator to responder, and the
second for messages in the other direction. At that point the HandshakeState
may be deleted. Transport messages are then encrypted and decrypted by calling
EncryptAndIncrement()
and DecryptAndIncrement()
on the relevant
CipherState
with zero-length associated data.
A HandshakeState
object extends a SymmetricState
with the following
variables, any of which may be empty:
-
s
: The local static key pair -
e
: The local ephemeral key pair -
rs
: The remote party's static public key -
re
: The remote party's ephemeral public key
A HandshakeState
also has the following variables:
-
message_patterns
: A sequence of message patterns. Each message pattern is a sequence of tokens from the set ("s", "e", "dhee", "dhes", "dhse", "dhss"). -
message_index
: An integer indicating the next pattern to fetch frommessage_patterns
.
A HandshakeState
responds to the following methods:
-
Initialize(handshake_pattern, initiator, prologue, preshared_key, new_s, new_e, new_rs, new_re)
: Takes a valid handshake pattern (see Section 6), and aninitiator
boolean specifying this party's role. Takes aprologue
byte sequence which may be zero-length, or which may contain context information that both parties want to confirm is identical, such as protocol or version negotiation messages sent previously. Takes apreshared_key
which may be empty, or a byte sequence containing secret data known only to the initiator and responder. Takes a set of DH keypairs and public keys for initializing local variables, any of which may be empty.-
Derives a
handshake_name
byte sequence by combining the names for the handshake pattern and crypto functions, as specified in Section 9. CallsInitializeSymmetric(handshake_name)
. -
Calls
MixHash(prologue)
. -
If
preshared_key
is non-empty, callsMixPresharedKey(preshared_key)
. -
Sets
s
,e
,rs
, andre
to the corresponding arguments. -
Calls
MixHash()
once for each public key listed in the pre-messages fromhandshake_pattern
, passing in that public key as input (see Section 6). If both initiator and responder have pre-messages, the initiator's public keys are hashed first. -
Sets
message_patterns
to the message patterns fromhandshake_pattern
. -
Sets
message_index = 0
(i.e. the first message pattern).
-
-
WriteMessage(payload, message_buffer)
: Takes apayload
byte sequence which may be zero-length, and amessage_buffer
to write the output into.-
Fetches the next message pattern from
message_patterns[message_index]
, incrementsmessage_index
, and sequentially processes each token from the message pattern:-
For "e": Sets
e = GENERATE_KEYPAIR()
, overwriting any previous value fore
. Appendse.public_key
to the buffer. CallsMixHash(e.public_key)
. Ifhas_psk == True
, callsMixKey(e.public_key)
. -
For "s": Appends
EncryptAndHash(s.public_key)
to the buffer. -
For "dhxy": Calls
MixKey(DH(x, ry))
.
-
-
Appends
EncryptAndHash(payload)
to the buffer. -
If there are no more message patterns returns two new
CipherState
objects by callingSplit()
.
-
-
ReadMessage(message, payload_buffer)
: Takes a byte sequence containing a Noise handshake message, and apayload_buffer
to write the message's plaintext payload into.-
Fetches the message pattern from
message_patterns[message_index]
, incrementsmessage_index
, and sequentially processes each token from the message pattern:-
For "e": Sets
re
to the nextDHLEN
bytes from the buffer. CallsMixHash(re.public_key)
. Ifhas_psk == True
, callsMixKey(re.public_key)
. -
For "s": Sets
data
to the nextDHLEN + 16
bytes of the message ifhas_k == True
, or to the nextDHLEN
bytes otherwise. Setsrs
toDecryptAndHash(data)
. -
For "dhxy": Calls
MixKey(DH(y, rx))
.
-
-
Copies the output from
DecryptAndHash(remaining_message)
into thepayload_buffer
. -
If there are no more message patterns returns two new
CipherState
objects by callingSplit()
.
-
- Handshake patterns ======================
A message pattern is some sequence of tokens from the set ("e", "s", "dhee", "dhes", "dhse", "dhss"). A handshake pattern consists of:
-
A pattern for the initiator's "pre-message" that is either "s", "e", "s, e", or empty.
-
A pattern for the responder's "pre-message" that is either "s", "e", "s, e", or empty.
-
A sequence of message patterns for the actual handshake messages
The pre-messages represent an exchange of public keys that was somehow
performed prior to the handshake, so these public keys should be inputs to
Initialize()
.
The first actual handshake message in the sequence is sent from the initiator to the responder; the next is sent by the responder, and so on. All messsages described by the handshake pattern must be sent in order.
The following handshake pattern describes an unauthenticated DH handshake:
Noise_NN():
-> e
<- e, dhee
The handshake pattern name is Noise_NN
. The empty parentheses indicate that neither
party is initialized with any key pairs. The tokens "e" and/or "s" in
parentheses would indicate that the initiator is initialized with the corresponding
key pairs. The tokens "re" and/or "rs" would indicate the same thing for the
responder.
Pre-messages are shown as patterns prior to the delimiter "------".
During Initialize()
, MixHash()
is called on any pre-message public keys in
the order they are listed.
The following pattern describes a handshake where the initiator has pre-knowledge of the responder's static public key, and performs a DH with the responder's static public key as well as the responder's ephemeral:
Noise_NK(rs):
<- s
------
-> e, dhes
<- e, dhee
Noise patterns must be valid in two senses:
-
Parties can only send static public keys they possess, or perform DH between keys they possess.
-
Because Noise uses ephemeral public keys as nonces, parties must send an ephemeral public key as the first token of the first message they send. Also, after sending an ephemeral public key, parties must never send encrypted data unless they have performed DH between their current ephemeral and all of the other party's key pairs.
Patterns failing the first check will obviously abort the program. Patterns failing the second check could result in subtle but catastrophic security flaws.
The following patterns represent "one-way" handshakes supporting a one-way stream of data from a sender to a recipient.
Following these one-way handshakes the sender can send a stream of transport
messages, encrypting them using the first CipherState
returned by Split()
.
The second CipherState
from Split()
is discarded - the recipient must not
send any messages using it.
N = no static key for sender
K = static key for sender known to recipient
X = static key for sender transmitted to recipient
Noise_N(rs):
<- s
------
-> e, dhes
Noise_K(s, rs):
-> s
<- s
------
-> e, dhes, dhss
Noise_X(s, rs):
<- s
------
-> e, dhes, s, dhss
The following 16 patterns represent protocols where the initiator and responder exchange messages to agree on a shared key.
N_ = no static key for initiator
K_ = static key for initiator known to responder
X_ = static key for initiator transmitted to responder
I_ = static key for initiator immediately transmitted to responder
_N = no static key for responder
_K = static key for responder known to initiator
_E = static key plus a semi-ephemeral key for responder known to initiator
_X = static key for responder transmitted to initiator
Noise_NN(): Noise_KN(s):
-> e -> s
<- e, dhee ------
-> e
<- e, dhee, dhes
Noise_NK(rs): Noise_KK(s, rs):
<- s -> s
------ <- s
-> e, dhes ------
<- e, dhee -> e, dhes, dhss
<- e, dhee, dhes
Noise_NE(rs, re): Noise_KE(s, rs, re):
<- s, e -> s
------ <- s, e
-> e, dhee, dhes ------
<- e, dhee -> e, dhee, dhes, dhse
<- e, dhee, dhes
Noise_NX(rs): Noise_KX(s, rs):
-> e -> s
<- e, dhee, s, dhse ------
-> e
<- e, dhee, dhes, s, dhse
Noise_XN(s): Noise_IN(s):
-> e -> e, s
<- e, dhee <- e, dhee, dhes
-> s, dhse
Noise_XK(s, rs): Noise_IK(s, rs):
<- s <- s
------ ------
-> e, dhes -> e, dhes, s, dhss
<- e, dhee <- e, dhee, dhes
-> s, dhse
Noise_XE(s, rs, re): Noise_IE(s, rs, re):
<- s, e <- s, e
------ ------
-> e, dhee, dhes -> e, dhee, dhes, s, dhse
<- e, dhee <- e, dhee, dhes
-> s, dhse
Noise_XX(s, rs): Noise_IX(s, rs):
-> e -> e, s
<- e, dhee, s, dhse <- e, dhee, dhes, s, dhse
-> s, dhse
- Handshake re-initialization and "Noise Pipes" ===============================================
A protocol may support handshake re-initialization. In this case, the
recipient of a handshake message must also receive some indication whether this
is the next message in the current handshake, or whether to re-initialize the
HandshakeState
and perform a different handshake (see discussion on "Type
fields" in Section 10).
By way of example, this section defines the Noise Pipe protocol. This
protocol uses two patterns defined in the previous section: Noise_XX
is used
for a full handshake. Noise_IK
is used for an abbreviated handshake that
allows the initiator to send some encrypted data in the first message. The
abbreviated handshake can be used if the initiator has pre-knowledge of the
responder's static public key; for example, the initiator might cache the
responder's static public key after a full handshake, and attempt the
abbreviated handshake in the future.
If the responder fails to decrypt the first Noise_IK
message (perhaps due to
changing her static key), the responder will initiate a new Noise_XXfallback
handshake identical to Noise_XX
except re-using the ephemeral public key from
the first Noise_IK
message as a pre-message public key.
Below are the three patterns used for Noise Pipes:
Noise_XX(s, rs):
-> e
<- e, dhee, s, dhse
-> s, dhse
Noise_IK(s, rs):
<- s
------
-> e, dhes, s, dhss
<- e, dhee, dhes
Noise_XXfallback(s, rs, re):
<- e
------
-> e, dhee, s, dhse
<- s, dhse
Note that in the fallback case, the initiator and responder roles are switched:
If Alice inititates a Noise_IK
handshake with Bob, Bob might
initiate a Noise_XX_fallback
handshake.
Note also that encrypted data sent in the first Noise_IK
message is
susceptible to replay attacks. Also, if the responder's static private key is
compromised, Noise_IK
initial messages can be decrypted and/or forged.
To distinguish these patterns, each handshake message will be preceded by a
type
byte:
-
If
type == 0
in the initiator's first message then the initiator is performing aNoise_XX
handshake. -
If
type == 1
in the initiator's first message then the initiator is performing aNoise_IK
handshake. -
If
type == 1
in the responder's firstNoise_IK
response then the responder failed to authenticate the initiator'sNoise_IK
message and is performing aNoise_XXfallback
handshake, using the initiator's ephemeral public key as a pre-message. -
In all other cases,
type
will be 0.
So that Noise pipes can be used with arbitrary lower-level protocols, handshake
messages are sent with the type
byte followed by a 2-byte big-endian length
field denoting the length of the following Noise message, followed by a Noise
handshake message. Transport messages are sent with only the 2-byte length
field, followed by the Noise tranport message.
- DH functions, cipher functions, and hash functions ======================================================
-
GENERATE_KEYPAIR()
: Returns a new Curve25519 keypair. -
DH(privkey, pubkey)
: Executes the Curve25519 DH function (aka "X25519" in some other specifications). If the function detects an invalid public key, the output may be set to all zeros or any other value that doesn't leak information about the private key. -
DHLEN
= 32
-
GENERATE_KEYPAIR()
: Returns a new Curve448 keypair. -
DH(privkey, pubkey)
: Executes the Curve448 DH function (aka "X448" in some other specifications). If the function detects an invalid public key, the output may be set to all zeros or any other value that doesn't leak information about the private key. -
DHLEN
= 56
ENCRYPT(k, n, ad, plaintext)
/DECRYPT(k, n, ad, ciphertext)
:AEAD_CHACHA20_POLY1305
from RFC 7539. The 96-bit nonce is formed by encoding 32 bits of zeros followed by little-endian encoding ofn
. (Earlier implementations of ChaCha20 used a 64-bit nonce, in which case it's compatible to encoden
directly into the ChaCha20 nonce).
ENCRYPT(k, n, ad, plaintext)
/DECRYPT(k, n, ad, ciphertext)
: AES256-GCM from NIST SP800-38-D with 128-bit tags. The 96-bit nonce is formed by encoding 32 bits of zeros followed by big-endian encoding ofn
.
-
HASH(input)
:SHA2-256(input)
-
HASHLEN
= 32 -
BLOCKLEN
= 64
-
HASH(input)
:SHA2-512(input)
-
HASHLEN
= 64 -
BLOCKLEN
= 128
-
HASH(input)
:BLAKE2s(input)
with digest length 32. -
HASHLEN
= 32 -
BLOCKLEN
= 64
-
HASH(input)
:BLAKE2b(input)
with digest length 64. -
HASHLEN
= 64 -
BLOCKLEN
= 128
- Handshake names =========================
To produce a handshake name for Initialize()
you concatenate the names
for the handshake pattern, the DH functions, the cipher functions, and the hash
function. For example:
-
Noise_XX_25519_AESGCM_SHA256
-
Noise_N_25519_ChaChaPoly_BLAKE2s
-
Noise_XXfallback_448_AESGCM_SHA512
-
Noise_IK_448_ChaChaPoly_BLAKE2b
If a pre-shared key is in use, then NoisePSK
is used instead of Noise
:
-
NoisePSK_XX_25519_AESGCM_SHA256
-
NoisePSK_N_25519_ChaChaPoly_BLAKE2s
-
NoisePSK_XXfallback_448_AESGCM_SHA512
-
NoisePSK_IK_448_ChaChaPoly_BLAKE2b
- Application responsibilities ================================
An application built on Noise must consider several issues:
-
Choosing crypto functions: The
25519
DH functions are recommended for most uses, along with eitherAESGCM_SHA256
orChaChaPoly_BLAKE2s
. For an extreme security margin, you could use the448
DH functions with eitherAESGCM_SHA512
orChaChaPoly_BLAKE2b
. -
Extensibility: Applications are recommended to use an extensible data format for the payloads of all messages (e.g. JSON, Protocol Buffers). This ensures that fields can be added in the future which are ignored by older implementations.
-
Padding: Applications are recommended to use a data format for the payloads of all encrypted messages that allows padding. This allows implementations to avoid leaking information about message sizes. Using an extensible data format, per the previous bullet, will typically suffice.
-
Termination: Applications must consider that a sequence of Noise transport messages could be truncated by an attacker. Applications should include explicit length fields or termination signals inside of transport payloads to signal the end of a stream of transport messages.
-
Length fields: Applications must handle any framing or additional length fields for Noise messages, considering that a Noise message may be up to 65535 bytes in length. If an explicit length field is needed, applications are recommended to add a 16-bit big-endian length field prior to each message.
-
Type fields: Applications are recommended to include a single-byte type field prior to each Noise handshake message (and prior to the length field, if one is included). Applications would reject messages with unknown type. This allows extending the handshake with handshake re-initialization or other alternative messages in the future.
- Security considerations ===========================
This section collects various security considerations:
-
Termination: Preventing attackers from truncating a stream of transport messages is an application responsibility. See above.
-
Incrementing nonces: Reusing a nonce value for
n
with the same keyk
for encryption would be catastrophic. Implementations must carefully follow the rules for nonces. -
Fresh ephemerals: Every party in a Noise protocol should send a new ephemeral public key and perform a DH with it prior to sending any encrypted data. Otherwise replay of a handshake message could trigger catastrophic key reuse. This is one rationale behind the patterns in Section 6. It's also the reason why one-way handshakes only allow transport messages from the sender, not the recipient.
-
Handshake names: The handshake name used with
Initialize()
must uniquely identify the combination of handshake pattern and crypto functions for every key it's used with (whether ephemeral key pair or static key pair). If the same secret key was reused with the same handshake name but a different set of cryptographic operations then bad interactions could occur. -
Pre-shared keys: Pre-shared keys should be secret values with 256 bits of entropy (or more).
-
Channel binding: Depending on the DH functions, it might be possible for a malicious party to engage in multiple sessions that derive the same shared secret key (e.g. if setting her public keys to invalid values causes DH outputs of zero). If a higher-level protocol wants a unique "channel binding" value for referring to a Noise session it should use the value of
h
after the final handshake message, notck
. -
Implementation fingerprinting: If this protocol is used in settings with anonymous parties, care should be taken that implementations behave identically in all cases. This may require mandating exact behavior for handling of invalid DH public keys.
- Rationale =============
This section collects various design rationale:
Noise messages are <= 65535 bytes because:
- This allows safe streaming decryption, and random access decryption of large files.
- This simplifies testing and reduces likelihood of memory or overflow errors in handling large messages.
- This restricts length fields to a standard size of 16 bits, aiding interop.
- The overhead of larger standard length fields (e.g. 32 or 64 bits) might cost something for small messages, but the overhead of smaller length fields is insignificant for large messages.
- This discourage mis-use of handshake payloads for large data transfers.
Nonces are 64 bits in length because:
- Some ciphers (e.g. Salsa20) only have 64 bit nonces.
- 64 bit nonces were used in the initial specification and implementations of ChaCha20, so Noise nonces can be used with these implementations.
- 64 bits makes it easy for the entire nonce to be treated as an integer and incremented.
- 96 bits nonces (e.g. in RFC 7539) are a confusing size where it's unclear if random nonces are acceptable.
The recommended hash function families are SHA2 and BLAKE2 because:
- SHA2 is widely available.
- SHA2 is often used alongside AES.
- BLAKE2 is similar to ChaCha20.
Hash output lengths of 256 bits are supported because:
- SHA2-256 and BLAKE2s have sufficient collision-resistance at the 128-bit security level.
- SHA2-256 and BLAKE2s require less RAM, and less calculation when processing smaller inputs (due to smaller block size), then their larger brethren (SHA2-512 and BLAKE2b).
- SHA2-256 and BLAKE2s are faster on 32-bit processors than their larger brethren.
Cipher keys are 256 bits because:
- 256 bits is a conservative length for cipher keys when considering cryptanalytic safety margins, time/memory tradeoffs, multi-key attacks, and quantum attacks.
The authentication tag is 128 bits because:
- Some algorithms (e.g. GCM) lose more security than an ideal MAC when truncated.
- Noise may be used in a wide variety of contexts, including where attackers can receive rapid feedback on whether MAC guesses are correct.
- A single fixed length is simpler than supporting variable-length tags.
Big-endian is preferred because:
- While it's true that bignum libraries, Curve25519, Curve448, and ChaCha20/Poly1305 use little-endian, these will likely be handled by specialized libraries.
- Some ciphers use big-endian internally (e.g. GCM, SHA2).
- The Noise length fields are likely to be handled by parsing code where big-endian "network byte order" is traditional.
The MixKey()
design uses HKDF
because:
- HKDF is a conservative and widely used design.
MixHash()
is used instead of MixKey()
because:
MixHash()
is more efficient thanMixKey()
.MixHash()
avoids any IPR concerns regarding mixing identity data into session keys (see KEA+).MixHash()
produces a non-secreth
value that might be useful to higher-level protocols, e.g. for channel-binding.
- IPR ========
The Noise specification (this document) is hereby placed in the public domain.
- Acknowledgements =====================
Noise is inspired by the NaCl and CurveCP protocols from Dan Bernstein et al., and also by HOMQV from Hugo Krawzcyk.
Feedback on the spec came from: Moxie Marlinspike, Jason Donenfeld, Tiffany Bennett, Jonathan Rudenberg, Stephen Touset, and Tony Arcieri.
Moxie Marlinspike, Christian Winnerlein, and Hugo Krawzcyk provided feedback on earlier versions of the key derivation.
Thanks to Karthikeyan Bhargavan for some editorial feedback.
Jeremy Clark, Thomas Ristenpart, and Joe Bonneau gave feedback on earlier versions.