<div dir="ltr">Hi,
<div><br></div><div>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.</div><div><br></div><div>Thanks,</div><div>Jason</div><div><br></div><div><br></div><div><p style="margin:15px 0px;color:rgb(51,51,51);font-family:sans-serif;font-size:14px;line-height:22.4px">For the following packet descriptions, refer to these functions:</p><ul style="margin:15px 0px;padding-left:30px;color:rgb(51,51,51);font-family:sans-serif;font-size:14px;line-height:22.4px"><li><code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">DH(private key, public key) = Curve25519-point-multiplication(private key, public key)</code> returning 32 bytes of output</li><li><code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">DH_GENERATE() = generate Curve25519 private key</code> returning 32 bytes of output</li><li><code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">DH_PUBKEY(private key) = calculate Curve25519 public key from private key</code> returning 32 bytes of output</li><li><code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">AE0(key, plain text) = ChaCha20Poly1305-RFC7539(key, nonce, plain text)</code> with <code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">nonce</code> being composed of 96bits of zeros</li><li><code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">AE(key, counter, plain text) = little_endian(counter) || ChaCha20Poly1305-RFC7539(key, nonce, plain text)</code> with <code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">nonce</code> being composed of 32bits of zeros followed by the 64bit little-endian <code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">counter</code></li><li><code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">AE0_LEN(plain len) = plain len + 16</code></li><li><code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">AE_LEN(plain len) = plain len + 16 + 8</code></li><li><code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">GETKEY(key, counter) = ChaCha20(32 bytes of zeros, key, nonce)</code> with <code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">nonce</code> being composed of 32bits of zeros followed by the 64bit little-endian <code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">counter</code></li><li><code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">KDF(key, input) = Blake2b(key, input)</code> returning 32 bytes of output</li><li><code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">HASH(input) = Blake2b(input)</code> returning 32 bytes of output</li><li><code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">TAI64N() = TAI64N timestamp of current time</code> which is 12 bytes</li></ul><h3 style="margin:20px 0px 10px;padding:0px;font-size:18px;color:rgb(51,51,51);font-family:sans-serif">First Message: Initiator to Responder</h3><p style="margin:0px 0px 15px;color:rgb(51,51,51);font-family:sans-serif;font-size:14px;line-height:22.4px">The initiator sends this message:</p><pre style="margin-top:15px;margin-bottom:15px;border:1px solid rgb(204,204,204);font-size:13px;line-height:19px;overflow:auto;padding:6px 10px;border-radius:3px;color:rgb(51,51,51);background-color:rgb(248,248,248)"><code style="margin:0px;padding:0px;border:none;border-radius:3px;background:transparent">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)]
}
</code></pre><p style="margin:15px 0px;color:rgb(51,51,51);font-family:sans-serif;font-size:14px;line-height:22.4px">The fields are populated as follows:</p><pre style="margin-top:15px;margin-bottom:15px;border:1px solid rgb(204,204,204);font-size:13px;line-height:19px;overflow:auto;padding:6px 10px;border-radius:3px;color:rgb(51,51,51);background-color:rgb(248,248,248)"><code style="margin:0px;padding:0px;border:none;border-radius:3px;background:transparent">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)
</code></pre><p style="margin:15px 0px;color:rgb(51,51,51);font-family:sans-serif;font-size:14px;line-height:22.4px">When the responder receives this message, he decrypts and does all the above operations in reverse, so that the state is identical.</p><h3 style="margin:20px 0px 10px;padding:0px;font-size:18px;color:rgb(51,51,51);font-family:sans-serif">Second Message: Responder to Initiator</h3><p style="margin:0px 0px 15px;color:rgb(51,51,51);font-family:sans-serif;font-size:14px;line-height:22.4px">The responder sends this message, after processing the first message above and applying the same operations to arrive at an identical state:</p><pre style="margin-top:15px;margin-bottom:15px;border:1px solid rgb(204,204,204);font-size:13px;line-height:19px;overflow:auto;padding:6px 10px;border-radius:3px;color:rgb(51,51,51);background-color:rgb(248,248,248)"><code style="margin:0px;padding:0px;border:none;border-radius:3px;background:transparent">msg = handshake_initiation {
    u8 message_type
    u16 sender_index
    u16 receiver_index
    u8 encrypted_ephemeral[AE0_LEN(32)]
}
</code></pre><p style="margin:15px 0px;color:rgb(51,51,51);font-family:sans-serif;font-size:14px;line-height:22.4px">The fields are populated as follows:</p><pre style="margin-top:15px;margin-bottom:15px;border:1px solid rgb(204,204,204);font-size:13px;line-height:19px;overflow:auto;padding:6px 10px;border-radius:3px;color:rgb(51,51,51);background-color:rgb(248,248,248)"><code style="margin:0px;padding:0px;border:none;border-radius:3px;background:transparent">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)
</code></pre><p style="margin:15px 0px;color:rgb(51,51,51);font-family:sans-serif;font-size:14px;line-height:22.4px">When the initiator receives this message, he decrypts and does all the above operations in reverse, so that the state is identical.</p><h3 style="margin:20px 0px 10px;padding:0px;font-size:18px;color:rgb(51,51,51);font-family:sans-serif">Data Keys Derivation</h3><p style="margin:0px 0px 15px;color:rgb(51,51,51);font-family:sans-serif;font-size:14px;line-height:22.4px">After the above two messages have been exchanged, keys are calculated by the initiator for sending and receiving data:</p><pre style="margin-top:15px;margin-bottom:15px;border:1px solid rgb(204,204,204);font-size:13px;line-height:19px;overflow:auto;padding:6px 10px;border-radius:3px;color:rgb(51,51,51);background-color:rgb(248,248,248)"><code style="margin:0px;padding:0px;border:none;border-radius:3px;background:transparent">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
</code></pre><p style="margin:15px 0px;color:rgb(51,51,51);font-family:sans-serif;font-size:14px;line-height:22.4px">And then all previous keys and ephemeral keys are zeroed out.</p><h3 style="margin:20px 0px 10px;padding:0px;font-size:18px;color:rgb(51,51,51);font-family:sans-serif">Subsequent Messages: Exchange of Data Packets</h3><p style="margin:0px 0px 15px;color:rgb(51,51,51);font-family:sans-serif;font-size:14px;line-height:22.4px">The initiator and the responder exchange this packet for sharing encapsulated packet data:</p><pre style="margin-top:15px;margin-bottom:15px;border:1px solid rgb(204,204,204);font-size:13px;line-height:19px;overflow:auto;padding:6px 10px;border-radius:3px;color:rgb(51,51,51);background-color:rgb(248,248,248)"><code style="margin:0px;padding:0px;border:none;border-radius:3px;background:transparent">msg = packet_data {
    u8 message_type
    u16 receiver_index
    u8 encrypted_encapsulated_packet[]
}
</code></pre><p style="margin:15px 0px;color:rgb(51,51,51);font-family:sans-serif;font-size:14px;line-height:22.4px">The fields are populated as follows:</p><pre style="margin-top:15px;margin-bottom:15px;border:1px solid rgb(204,204,204);font-size:13px;line-height:19px;overflow:auto;padding:6px 10px;border-radius:3px;color:rgb(51,51,51);background-color:rgb(248,248,248)"><code style="margin:0px;padding:0px;border:none;border-radius:3px;background:transparent">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)
</code></pre><p style="margin:15px 0px;color:rgb(51,51,51);font-family:sans-serif;font-size:14px;line-height:22.4px">The responder uses his <code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">responder.receiving_key</code> to read the message.</p><h3 style="margin:20px 0px 10px;padding:0px;font-size:18px;color:rgb(51,51,51);font-family:sans-serif">DoS Mitigation</h3><p style="margin:0px 0px 15px;color:rgb(51,51,51);font-family:sans-serif;font-size:14px;line-height:22.4px">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 <em>respond at all</em> 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.</p><p style="margin:15px 0px;color:rgb(51,51,51);font-family:sans-serif;font-size:14px;line-height:22.4px">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 <a href="http://cr.yp.to/libtai/tai64.html" style="color:blue;text-decoration:none">TAI64N</a> timestamp in the first message. The server keeps track of the greatest timestamp received <em>per client</em> 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.</p><h3 style="margin:20px 0px 10px;padding:0px;font-size:18px;color:rgb(51,51,51);font-family:sans-serif">Nonce Reuse & Replay Attacks</h3><p style="margin-top:0px;margin-right:0px;margin-left:0px;color:rgb(51,51,51);font-family:sans-serif;font-size:14px;line-height:22.4px;margin-bottom:0px!important">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 <em>greatest</em> counter received, as well as the <code style="margin:0px 2px;padding:0px 5px;border:1px solid rgb(234,234,234);border-radius:3px;white-space:nowrap;background-color:rgb(248,248,248)">sizeof(unsigned long) * 8</code>possible 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.</p></div></div>