[noise] [PATCH] noise: update to hkdf branch

Jason A. Donenfeld Jason at zx2c4.com
Mon Oct 12 13:02:44 PDT 2015


After this is applied, the protocol looks like this:

For the following packet descriptions, refer to these functions:

   - DH(private key, public key): Curve25519 point multiplication of private
   key and public key, returning 32 bytes of output
   - DH_GENERATE(): generate a random Curve25519 private key, returning 32
   bytes of output
   - DH_PUBKEY(private key): calculate a Curve25519 public key from private
   key, returning 32 bytes of output
   - AEAD(key, counter, plain text, auth text): ChaCha20Poly1305 AEAD, as
   specified in RFC7539, with itsnonce being composed of 32 bits of zeros
   followed by the 64-bit little-endian value of counter
   - AEAD_LEN(plain len): plain len + 16
   - KDF(key, input): Blake2b(key, input), returning 32 bytes of output
   - HASH(input): Blake2b(input), returning 32 bytes of output
   - TAI64N(): TAI64N timestamp of current time which is 12 bytes

First Message: Initiator to Responder

The initiator sends this message:

msg = handshake_initiation {
    u8 message_type
    u16 sender_index
    u8 unencryped_ephemeral[32]
    u8 encrypted_static[AEAD_LEN(32)]
    u8 encrypted_timestamp[AEAD_LEN(12)]
}

The fields are populated as follows:

initiator.chaining_key = "Noise WireGuard zx2c4 2015-10-12"
initiator.hash = HASH("Noise WireGuard zx2c4 2015-10-12" ||
responder.static_public)
initiator.ephemeral_private = DH_GENERATE()
msg.message_type = 1
msg.sender_index = little_endian(initiator.sender_index)

msg.unencrypted_ephemeral = DH_PUBKEY(initiator.ephemeral_private)
initiator.hash = HASH(initiator.hash || msg.unencrypted_ephemeral)

temp = KDF(initiator.chaining_key, DH(initiator.ephemeral_private,
responder.static_public))
initiator.chaining_key = KDF(temp, 0x1)
initiator.key = KDF(temp, initiator.chaining_key || 0x2)

msg.encrypted_static = AEAD(initiator.key, 0, initiator.static_public,
initiator.hash)
initiator.hash = HASH(initiator.hash || msg.encrypted_static)

temp = KDF(initiator.chaining_key, DH(initiator.static_private,
responder.static_public))
initiator.chaining_key = KDF(temp, 0x1)
initiator.key = KDF(temp, initiator.chaining_key || 0x2)

msg.encrypted_timestamp = AEAD(initiator.key, 0, TAI64N(), initiator.hash)
initiator.hash = HASH(initiator.hash || msg.encrypted_timestamp)

When the responder receives this message, he decrypts and does all the
above operations in reverse, so that the state is identical.
Second Message: Responder to Initiator

The responder sends this message, after processing the first message above
and applying the same operations to arrive at an identical state:

msg = handshake_response {
    u8 message_type
    u16 sender_index
    u16 receiver_index
    u8 encrypted_ephemeral[AEAD_LEN(32)]
    u8 encrypted_nothing[AEAD_LEN(0)]
}

The fields are populated as follows:

responder.ephemeral_private = DH_GENERATE()
msg.message_type = 2
msg.sender_index = little_endian(responder.sender_index)
msg.receiver_index = little_endian(initiator.sender_index)

msg.encrypted_ephemeral = AEAD(responder.key, 1,
DH_PUBKEY(responder.ephemeral_private), responder.hash)
responder.hash = HASH(responder.hash || msg.encrypted_ephemeral)

temp = KDF(responder.chaining_key, DH(responder.ephemeral_private,
initiator.ephemeral_public))
responder.chaining_key = KDF(temp, 0x1)
responder.key = KDF(temp, responder.chaining_key || 0x2)

temp = KDF(responder.chaining_key, DH(responder.ephemeral_private,
initiator.static_public))
responder.chaining_key = KDF(temp, 0x1)
responder.key = KDF(temp, responder.chaining_key || 0x2)

msg.encrypted_nothing = AEAD(responder.key, 0, [empty], responder.hash)
responder.hash = HASH(responder.hash || msg.encrypted_nothing)

When the initiator receives this message, he decrypts and does all the
above operations in reverse, so that the state is identical.
Data Keys Derivation

After the above two messages have been exchanged, keys are calculated by
the initiator for sending and receiving data:

temp = KDF(initiator.chaining_key, [32 empty bytes])
initiator.sending_key = KDF(temp, 0x1)
initiator.receiving_key = KDF(temp, initiator.sending_key || 0x2)
initiator.sending_key_counter = 0
initiator.receiving_key_counter = 0

temp = KDF(responder.chaining_key, [32 empty bytes])
responder.receiving_key = KDF(temp, 0x1)
responder.sending_key = KDF(temp, responder.receiving_key || 0x2)
responder.receiving_key_counter = 0
responder.sending_key_counter = 0

And then all previous keys, chaining keys, ephemeral keys, and hashes are
zeroed out.
Subsequent Messages: Exchange of Data Packets

The initiator and the responder exchange this packet for sharing
encapsulated packet data:

msg = packet_data {
    u8 message_type
    u16 receiver_index
    u64 counter
    u8 encrypted_encapsulated_packet[]
}

The fields are populated as follows:

msg.message_type = 3
msg.receiver_index = little_endian(responder.sender_index)
encapsulated_packet = encapsulated_packet || random padding in order
to make the length a multiple of 16
counter = ++initiator.sending_key_counter
msg.counter = little_endian(counter)
msg.encrypted_encapsulated_packet = AEAD(initiator.sending_key,
counter, encapsulated_packet, [empty])

The responder uses his responder.receiving_key to read the message.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://moderncrypto.org/mail-archive/noise/attachments/20151012/ec8640a5/attachment.html>


More information about the Noise mailing list