Boxes - noiseprotocol/noise_spec GitHub Wiki

Ciphersuites

Noise assumes the following variables and functions are defined by a ciphersuite:

SUITE_NAME = ? # 24-byte string uniquely naming the ciphersuite
DH_LEN     = ? # Length in bytes of DH private, public keys, and DH outputs
CC_LEN     = ? # Length in bytes of cipher context
MAC_LEN    = ? # Length in bytes that ciphertext is enlarged due to MAC

DH(privkey, pubkey):
  # Calculates a DH result.
  # Returns a DH secret of length DH_LEN.

ENCRYPT(cc, plaintext, authtext):
  # Takes a cipher context, some additional authenticated data, and plaintext.
  # Returns an "authenticated encryption" ciphertext of length equal to 
  # plaintext plus MAC_LEN.
  # Modifies the value of 'cc'.

Key Derivation

All Noise ciphersuites use the following HMAC-SHA2-512 based key derivation functions:

CV_LEN = 48
H_LEN = 64  # output length of hash, >= 32

KDF(secret, extra_secret, info, output_len):
  # Outputs a byte sequence that the caller typically splits into multiple variables
  # such as a chain variable and cipher context, or two cipher contexts.
  #
  # The 'extra_secret' is used to pass a chaining variable to mix into the KDF.
  # The 'info' ensures that applying the KDF to the same secret values will 
  # produce independent output, provided 'info' is different.

  output = []
  t = zeros[H_LEN]
  for c = 0...(ceil(output_len / H_LEN) - 1)
    t = HMAC-SHA2-512(secret, info || (byte)c || t[0:32] || extra_secret)
    output = output || t
  return output

Box Format

struct {
    bytes encrypted_plaintext[plaintext_len];
    bytes encrypted_padding[padding_len];
    bytes encrypted_padding_len[4];
    bytes mac[MAC_LEN];
} NoiseBody;

struct {
    bytes ephemeral_pubkey[DH_LEN];
    bytes encrypted_sender_pubkey[DH_LEN];
    bytes mac[MAC_LEN];
    NoiseBody body;
} NoiseBox;

Box Creation Functions

noise_body(cc, pad_len, app_data, authtext=""):
  plaintext = app_data || random(pad_len) || (uint32_little_endian)pad_len
  body = ENCRYPT(cc, plaintext, authtext)
  return body

noise_box(eph_key, sender_key, target_pubkey, pad_len, app_data, 
          kdf_num=0x00, cv=zeros[CV_LEN]):
  dh1 = DH(eph_key.priv, target_pubkey)
  dh2 = DH(sender_key.priv, target_pubkey)
  cv1 || cc1 = KDF(dh1, cv, SUITE_NAME || (byte)kdf_num, CV_LEN + CC_LEN)
  cv2 || cc2 = KDF(dh2, cv1, SUITE_NAME || (byte)(kdf_num + 1), CV_LEN + CC_LEN)
  header = eph_key.pub || ENCRYPT(cc1, sender_key.pub, target_pubkey || eph_key.pub)
  body = noise_body(cc2, pad_len, app_data, target_pubkey || header)
  return (header || body), cv2

Notes

  • Box size: The box size is not encoded, so must be determined by other means (e.g. file length). Boxes add a minimum of (2*DH_LEN + 2*MAC_LEN + 4) bytes of overhead to the payload.
  • Ephemeral key reuse: Ephemeral keys SHALL NOT be reused for multiple Noise boxes, since the ephemeral key functions as a "nonce".
  • Anonymous boxes: If the sender doesn't have a sender key, the ephemeral key MAY be reused as a sender key. This allows the sender and recipient to skip the second ECDH calculation.

Home

Next (Pipes)

⚠️ **GitHub.com Fallback** ⚠️