SPAKE2 Password-Authenticated Key Exchange
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.
Common errors
-
Failed to derive shared secret. Keys do not match.
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`).fixVerify 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`. -
TypeError: password must be bytes, not str
cause The `spake2` library's constructors for `SPAKE2_A`, `SPAKE2_B`, and `SPAKE2_Symmetric` require the password argument to be a byte string.fixConvert 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'), ...)`. -
ModuleNotFoundError: No module named 'spake2.parameters'
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.fixEnsure 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`.
Warnings
- gotcha 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.
- gotcha 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.
- gotcha 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.
- gotcha The `spake2` library expects passwords to be byte strings (e.g., `b"my_password"`). Passing a `str` will lead to a `TypeError`.
- gotcha 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.
Install
-
pip install spake2
Imports
- SPAKE2_A
from spake2 import SPAKE2_A
- SPAKE2_B
from spake2 import SPAKE2_B
- SPAKE2_Symmetric
from spake2 import SPAKE2_Symmetric
- ParamsEd25519
from spake2.params import ParamsEd25519
from spake2.parameters.all import ParamsEd25519
Quickstart
import os
from spake2 import SPAKE2_A, SPAKE2_B
from spake2.parameters.all import ParamsEd25519 # Or other parameter sets like Params3072
def run_spake2_exchange(password: bytes, idA: bytes, idB: bytes):
# Alice (Side A)
alice = SPAKE2_A(password, idA=idA, idB=idB, params=ParamsEd25519)
alice_msg = alice.start()
# Bob (Side B)
bob = SPAKE2_B(password, idA=idA, idB=idB, params=ParamsEd25519)
bob_msg = bob.start()
# Exchange messages
# In a real application, alice_msg would be sent to Bob, and bob_msg to Alice.
# For this example, we directly pass them.
# Alice processes Bob's message
alice_key = alice.finish(bob_msg)
# Bob processes Alice's message
bob_key = bob.finish(alice_msg)
print(f"Alice's derived key: {alice_key.hex()}")
print(f"Bob's derived key: {bob_key.hex()}")
if alice_key == bob_key:
print("\nShared secret derived successfully!")
return alice_key
else:
print("\nFailed to derive shared secret. Keys do not match.")
return None
if __name__ == "__main__":
# Example usage
shared_password = os.environ.get('SPAKE2_PASSWORD', 'test-password').encode('utf-8')
alice_id = b"Alice"
bob_id = b"BobServer"
print(f"Using password: {shared_password.decode('utf-8')}")
print(f"Alice ID: {alice_id.decode('utf-8')}, Bob ID: {bob_id.decode('utf-8')}")
derived_key = run_spake2_exchange(shared_password, alice_id, bob_id)
if derived_key:
# The derived key can then be used for symmetric encryption, HMAC, or fed into HKDF.
print(f"Using derived key for subsequent secure communication.")