[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