Search code examples
gowireguard

Why different Wireguard private keys resulting in the same public key?


I discovered that different Wireguard private keys could result in the same public key. Under "different keys" I mean four last bits in a first byte of a private key are ignored during curve25519 calculation for a public one.

I will appreciate if someone can explain this strange(?) behaviour.

Example:

❯ echo "Mp4S2elbVWEo2xzGtefU8eIccYMkX3XD8y3yNGeOMXE=" | wg pubkey
YG8wJtIMuqTmO7l4OMDBkT516y8NigBilaqZt3fDdCU=
❯ echo "MJ4S2elbVWEo2xzGtefU8eIccYMkX3XD8y3yNGeOMXE=" | wg pubkey
YG8wJtIMuqTmO7l4OMDBkT516y8NigBilaqZt3fDdCU=
❯ echo "MZ4S2elbVWEo2xzGtefU8eIccYMkX3XD8y3yNGeOMXE=" | wg pubkey
YG8wJtIMuqTmO7l4OMDBkT516y8NigBilaqZt3fDdCU=
❯ echo "M54S2elbVWEo2xzGtefU8eIccYMkX3XD8y3yNGeOMXE=" | wg pubkey
YG8wJtIMuqTmO7l4OMDBkT516y8NigBilaqZt3fDdCU=

Solution

  • This is due to Curve25519 clamping. You can read all about it in this blog article by Neil Madden:

    https://neilmadden.blog/2020/05/28/whats-the-curve25519-clamping-all-about/

    You are using a 256-bit private key (32 bytes). This is a completely random number. Not all of these possible numbers are suitable as scalar values for a curve25519 private key, for good security-related reasons.

    • the value needs to be between 2251 and 2252-1
    • the lower 3 bits need to be zero

    What clamping function on a 256-bit random number performs these steps:

    // clear least-significant 3 bits
    t[0] &= 248; 
    // clear most significant 1 bit (ensure t < 2^252)
    t[31] &= 127; 
    // set the second-most significant bit (ensures t >= 2^251)
    t[31] |= 64; 
    

    It performs these steps before deriving the public key. Even though you have a 256-bit key, there are only 2251 distinct keys.

    So, not only does the value of four last bits in the first byte not matter, but the value of the two first bits in the last byte don't matter either.