TheAlgorithms/Python · error · ValueError

Invalid public key

Error message

Invalid public key

What it means

Raised by DiffieHellman.generate_shared_key when the peer's public key fails the NIST SP800-56 validity check in is_valid_public_key: it must lie in [2, p-2] and be a quadratic residue (key^((p-1)/2) mod p == 1). This blocks small-subgroup and invalid-key attacks.

Source

Thrown at ciphers/diffie_hellman.py:239

    def get_private_key(self) -> str:
        return hex(self.__private_key)[2:]

    def generate_public_key(self) -> str:
        public_key = pow(self.generator, self.__private_key, self.prime)
        return hex(public_key)[2:]

    def is_valid_public_key(self, key: int) -> bool:
        # check if the other public key is valid based on NIST SP800-56
        return (
            2 <= key <= self.prime - 2
            and pow(key, (self.prime - 1) // 2, self.prime) == 1
        )

    def generate_shared_key(self, other_key_str: str) -> str:
        other_key = int(other_key_str, base=16)
        if not self.is_valid_public_key(other_key):
            raise ValueError("Invalid public key")
        shared_key = pow(other_key, self.__private_key, self.prime)
        return sha256(str(shared_key).encode()).hexdigest()

    @staticmethod
    def is_valid_public_key_static(remote_public_key_str: int, prime: int) -> bool:
        # check if the other public key is valid based on NIST SP800-56
        return (
            2 <= remote_public_key_str <= prime - 2
            and pow(remote_public_key_str, (prime - 1) // 2, prime) == 1
        )

    @staticmethod
    def generate_shared_key_static(
        local_private_key_str: str, remote_public_key_str: str, group: int = 14
    ) -> str:
        local_private_key = int(local_private_key_str, base=16)
        remote_public_key = int(remote_public_key_str, base=16)
        prime = primes[group]["prime"]

View on GitHub (pinned to f5988cc097)

Solutions

  1. Verify both parties constructed DiffieHellman with the same group
  2. Regenerate the peer key from a real generate_public_key() output (full hex, no 0x prefix)
  3. Call is_valid_public_key(int(peer_hex, 16)) yourself and drop the connection/abort on failure

Example fix

# before
shared = dh.generate_shared_key(peer_hex)  # raises if peer_hex is invalid

# after
peer_int = int(peer_hex, 16)
if not dh.is_valid_public_key(peer_int):
    raise SystemExit("peer sent an invalid public key")
shared = dh.generate_shared_key(peer_hex)
Defensive patterns

Strategy: validation

Validate before calling

peer_int = int(peer_hex, 16)
if not dh.is_valid_public_key(peer_int):
    raise ValueError("rejecting invalid peer public key")

Try / catch

try:
    shared = dh.generate_shared_key(peer_hex)
except ValueError:
    abort_handshake("peer public key failed NIST SP800-56 validation")

Prevention

When it happens

Trigger: Passing a hex string that decodes to 0, 1, or p-1; passing a value not generated by pow(g, private, p) in the same group; truncated or corrupted hex from the peer; wrong group on one side.

Common situations: Hand-rolled transports that mangle leading zeros in hex keys; peers using different group numbers; test code with arbitrary hex strings instead of real public keys; MITM/tampered payloads.

Related errors


AI-assisted analysis of TheAlgorithms/Python@f5988cc097 (2026-08-14). Data as JSON: /api/errors/a8771b9b815fbec6. Report an issue: GitHub.