{"id":9313,"library":"spake2","title":"SPAKE2 Password-Authenticated Key Exchange","description":"The `spake2` library is a pure-Python implementation of the SPAKE2 password-authenticated key exchange (PAKE) algorithm. It enables two parties sharing a weak password to securely derive a strong shared secret over an insecure channel, preventing passive eavesdropping and limiting active attackers to a single password guess per protocol execution. The current stable version is 0.9, released in September 2024, with an infrequent release cadence.","status":"active","version":"0.9","language":"en","source_language":"en","source_url":"https://github.com/warner/python-spake2","tags":["cryptography","security","pake","spake2","key exchange","password","authentication"],"install":[{"cmd":"pip install spake2","lang":"bash","label":"Install latest version"}],"dependencies":[{"reason":"Used internally for HKDF (HMAC-based Key Derivation Function).","package":"cryptography","optional":false}],"imports":[{"symbol":"SPAKE2_A","correct":"from spake2 import SPAKE2_A"},{"symbol":"SPAKE2_B","correct":"from spake2 import SPAKE2_B"},{"note":"Use this class if roles (A/B) cannot be pre-determined, providing a symmetric interface.","symbol":"SPAKE2_Symmetric","correct":"from spake2 import SPAKE2_Symmetric"},{"note":"Specific parameter sets are located in `spake2.parameters` submodules, typically imported from `spake2.parameters.all` for convenience or directly from e.g., `spake2.parameters.i3072`.","wrong":"from spake2.params import ParamsEd25519","symbol":"ParamsEd25519","correct":"from spake2.parameters.all import ParamsEd25519"}],"quickstart":{"code":"import os\nfrom spake2 import SPAKE2_A, SPAKE2_B\nfrom spake2.parameters.all import ParamsEd25519 # Or other parameter sets like Params3072\n\ndef run_spake2_exchange(password: bytes, idA: bytes, idB: bytes):\n    # Alice (Side A)\n    alice = SPAKE2_A(password, idA=idA, idB=idB, params=ParamsEd25519)\n    alice_msg = alice.start()\n\n    # Bob (Side B)\n    bob = SPAKE2_B(password, idA=idA, idB=idB, params=ParamsEd25519)\n    bob_msg = bob.start()\n\n    # Exchange messages\n    # In a real application, alice_msg would be sent to Bob, and bob_msg to Alice.\n    # For this example, we directly pass them.\n\n    # Alice processes Bob's message\n    alice_key = alice.finish(bob_msg)\n\n    # Bob processes Alice's message\n    bob_key = bob.finish(alice_msg)\n\n    print(f\"Alice's derived key: {alice_key.hex()}\")\n    print(f\"Bob's derived key:   {bob_key.hex()}\")\n\n    if alice_key == bob_key:\n        print(\"\\nShared secret derived successfully!\")\n        return alice_key\n    else:\n        print(\"\\nFailed to derive shared secret. Keys do not match.\")\n        return None\n\nif __name__ == \"__main__\":\n    # Example usage\n    shared_password = os.environ.get('SPAKE2_PASSWORD', 'test-password').encode('utf-8')\n    alice_id = b\"Alice\"\n    bob_id = b\"BobServer\"\n\n    print(f\"Using password: {shared_password.decode('utf-8')}\")\n    print(f\"Alice ID: {alice_id.decode('utf-8')}, Bob ID: {bob_id.decode('utf-8')}\")\n\n    derived_key = run_spake2_exchange(shared_password, alice_id, bob_id)\n    if derived_key:\n        # The derived key can then be used for symmetric encryption, HMAC, or fed into HKDF.\n        print(f\"Using derived key for subsequent secure communication.\")\n","lang":"python","description":"This quickstart demonstrates a basic SPAKE2 key exchange between two parties, Alice (role A) and Bob (role B), who share a weak password. Both parties initialize their respective SPAKE2 instances with the shared password and unique identity strings. They then exchange initial messages, process the received message, and derive a strong, shared secret key. The `idA` and `idB` strings are crucial for binding the key to specific parties and preventing replay/substitution attacks. The example uses `ParamsEd25519` for elliptic curve security."},"warnings":[{"fix":"Implement countermeasures at the application level to obscure timing differences, such as adding random delays or ensuring consistent execution paths regardless of input.","message":"The `spake2` library is not constant-time and does not inherently protect against timing attacks. Applications must ensure that attackers cannot measure the duration of key exchange operations, particularly in sensitive environments.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Ensure the operating system's cryptographic randomness facilities are robust and properly seeded. Consult system documentation or security guides for verifying `os.urandom()` strength.","message":"The security of the derived key relies on a strong source of random numbers provided by `os.urandom()`. Do not use this library on systems where `os.urandom()` is known to be weak or compromised.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Clearly define and enforce the roles (A and B) for the communicating parties. Alternatively, use the `SPAKE2_Symmetric` class if both sides need to operate identically without pre-assigned roles.","message":"Participants must correctly agree on their roles (A and B) or use `SPAKE2_Symmetric`. Using the same role (e.g., `SPAKE2_A` on both sides) will result in non-matching keys, indistinguishable from a password mismatch, making debugging difficult.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Always encode string passwords to bytes before passing them to `SPAKE2_A`, `SPAKE2_B`, or `SPAKE2_Symmetric`. For example, `password.encode('utf-8')`.","message":"The `spake2` library expects passwords to be byte strings (e.g., `b\"my_password\"`). Passing a `str` will lead to a `TypeError`.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Create new `SPAKE2_A`, `SPAKE2_B`, or `SPAKE2_Symmetric` instances for every new key exchange session. Do not reuse old messages or state objects.","message":"Each `SPAKE2` instance and the messages it generates are single-use. Reusing an instance for multiple key exchanges or replaying messages will result in protocol failure and potential security vulnerabilities.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-04-16T00:00:00.000Z","next_check":"2026-07-15T00:00:00.000Z","problems":[{"fix":"Verify that both parties use the exact same password and identity strings. Confirm that one party is `SPAKE2_A` and the other is `SPAKE2_B`, or both are `SPAKE2_Symmetric`.","cause":"This typically occurs when the shared password does not match, the `idA`/`idB` identifiers are inconsistent, or the roles (A/B) were incorrectly assigned (e.g., both sides initialized as `SPAKE2_A`).","error":"Failed to derive shared secret. Keys do not match."},{"fix":"Convert the password string to bytes using `.encode()` before passing it to the SPAKE2 constructor, e.g., `SPAKE2_A(b'my_password', ...)` or `SPAKE2_A('my_password'.encode('utf-8'), ...)`.","cause":"The `spake2` library's constructors for `SPAKE2_A`, `SPAKE2_B`, and `SPAKE2_Symmetric` require the password argument to be a byte string.","error":"TypeError: password must be bytes, not str"},{"fix":"Ensure the import path is correct. Common parameter sets can be imported from `from spake2.parameters.all import ...` or directly from their specific submodules like `from spake2.parameters.i3072 import Params3072`.","cause":"Attempting to import parameter sets from an incorrect or non-existent path. Specific parameter sets (e.g., `ParamsEd25519`, `Params3072`) are located within the `spake2.parameters` package.","error":"ModuleNotFoundError: No module named 'spake2.parameters'"}]}