[noise] Noise-inspired protocol based on HandshakeIK (noh2's Handshake IS)

Trevor Perrin trevp at trevp.net
Thu Aug 27 09:41:11 PDT 2015


On Thu, Aug 27, 2015 at 5:37 AM, Jason A. Donenfeld <Jason at zx2c4.com> wrote:
> Hi,
>
> I was hoping you could have a look at this noise-inspired handshake. It's
> extremely similar, though it avoids the use of aad and shortens some things.
> Please let me know if parts seem insecure or silly.

Looked at quickly, few things:
 * Don't see point to encrypting ephemerals
 * The response message should probably include a final MAC / "AE0"
call to confirm the responder knows the final key
 * Probably should have some way to securely signal end of message stream

Counting ops, the Noise_IS handshake can be done in 3 HASH, 4
GETKEY+KDF, and 3 AEAD.  I think you're doing 1 HASH, 9 GETKEY+KDF,
and 5 AEAD (I'm assuming you should add one AEAD for the response
message).

The GETKEY+KDF going to be, hmm, maybe 2x or 3x cost of other ops with
either HMAC-SHA256 or your Blake2b?  So your thing has maybe 1.5x to
2x symmetric crypto cost, and 16 bytes bigger handshake messages
(because of ephemeral encryption)?


Trevor


>
> Thanks,
> Jason
>
>
> For the following packet descriptions, refer to these functions:
>
> DH(private key, public key) = Curve25519-point-multiplication(private key,
> public key) returning 32 bytes of output
> DH_GENERATE() = generate Curve25519 private key returning 32 bytes of output
> DH_PUBKEY(private key) = calculate Curve25519 public key from private key
> returning 32 bytes of output
> AE0(key, plain text) = ChaCha20Poly1305-RFC7539(key, nonce, plain text) with
> nonce being composed of 96bits of zeros
> AE(key, counter, plain text) = little_endian(counter) ||
> ChaCha20Poly1305-RFC7539(key, nonce, plain text) with nonce being composed
> of 32bits of zeros followed by the 64bit little-endian counter
> AE0_LEN(plain len) = plain len + 16
> AE_LEN(plain len) = plain len + 16 + 8
> GETKEY(key, counter) = ChaCha20(32 bytes of zeros, key, nonce) with nonce
> being composed of 32bits of zeros followed by the 64bit little-endian
> counter
> 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 encryped_ephemeral[AE0_LEN(32)]
>     u8 encrypted_static[AE0_LEN(32)]
>     u8 encrypted_timestamp[AE0_LEN(12)]
> }
>
> The fields are populated as follows:
>
> initiator.ephemeral_private = DH_GENERATE()
> msg.message_type = 1
> msg.sender_index = little_endian(initiator.sender_index)
> initiator.key = HASH("WireGuard-zx2c4-20150827" || 0x0 || msg.sender_index
> || 0x0 || HASH(responder.static_public))
> msg.encrypted_ephemeral = AE0(initiator.key,
> DH_PUBKEY(initiator.ephemeral_private))
> initiator.key = KDF(GETKEY(initiator.key, 1),
> DH_PUBKEY(initiator.ephemeral_private))
> initiator.key = KDF(GETKEY(initiator.key, 0),
> DH(initiator.ephemeral_private, responder.static_public))
> msg.encrypted_static = AE0(initiator.key, initiator.static_public)
> initiator.key = KDF(GETKEY(initiator.key, 1), initiator.static_public)
> initiator.key = KDF(GETKEY(initiator.key, 0), DH(initiator.static_private,
> responder.static_public))
> msg.encrypted_timestamp = AE0(initiator.key, TAI64N())
> initiator.key = KDF(GETKEY(initiator.key, 1), msg)
>
> 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_initiation {
>     u8 message_type
>     u16 sender_index
>     u16 receiver_index
>     u8 encrypted_ephemeral[AE0_LEN(32)]
> }
>
> 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)
> responder.key = KDF(GETKEY(responder.key, 0), msg.sender_index ||
> msg.receiver_index)
> msg.encrypted_ephemeral = AE0(responder.key,
> DH_PUBKEY(responder.ephemeral_private))
> responder.key = KDF(GETKEY(responder.key, 1),
> DH(responder.ephemeral_private, initiator.ephemeral_public))
> responder.key = KDF(GETKEY(responder.key, 0),
> DH(responder.ephemeral_private, initiator.static_public))
> responder.key = KDF(GETKEY(responder.key, 0), msg)
>
> 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:
>
> initiator.sending_key = GETKEY(initiator.key, 0)
> initiator.sending_key_counter = 0
> initiator.receiving_key = GETKEY(initiator.key, 1)
> initiator.receiving_key_counter = 0
>
> responder.receiving_key = GETKEY(responder.key, 0)
> responder.receiving_key_counter = 0
> responder.sending_key = GETKEY(responder.key, 1)
> responder.sending_key_counter = 0
>
> And then all previous keys and ephemeral keys 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
>     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
> msg.encrypted_encapsulated_packet = AE(initiator.sending_key,
> ++initiator.sending_key_counter, encapsulated_packet)
>
> The responder uses his responder.receiving_key to read the message.
>
> DoS Mitigation
>
> We require authentication in the first handshake message sent because it
> does not require allocating any state on the server for potentially
> unauthentic messages. In fact, the server does not even respond at all to an
> unauthorized client; it is silent and invisible. The handshake avoids a
> denial of service vulnerability created by allowing any state to be created
> in response to packets that have not yet been authenticated.
>
> This, however, introduces the issue of having authentication in the first
> packet: it is always open to a replay attack. An attacker could replay
> initial handshake messages to trick the server into regenerating its
> ephemeral key, thereby disconnecting the legitimate client connection
> (though not affecting the security of any messages). For that reason, we
> include a TAI64N timestamp in the first message. The server keeps track of
> the greatest timestamp received per client and discards packets containing
> timestamps less than or equal to it. If the server restarts and looses this
> state, that is not a problem: an initial packet from earlier can be
> replayed, but it could not possibly disrupt any ongoing sessions, since the
> server has just restarted. Once clients reconnect to the server after its
> restart, they will be using greater timestamps, invalidating the previous
> ones. This timestamp ensures that an attacker can't disrupt a current
> session between client and server.
>
> Nonce Reuse & Replay Attacks
>
> Nonces are never reused. A 64bit counter is used, and cannot be wound
> backward. UDP, however, sometimes delivers messages out of order. For that
> reason we use a sliding window, in which we keep track of the greatest
> counter received, as well as the sizeof(unsigned long) * 8possible counter
> values possibly preceding the greatest one. This avoids replay attacks while
> ensuring nonces are never reused and that UDP can maintain out-of-order
> delivery performance.
>
>
> _______________________________________________
> Noise mailing list
> Noise at moderncrypto.org
> https://moderncrypto.org/mailman/listinfo/noise
>


More information about the Noise mailing list