Phoenix needs Python 3.10 or newer.
$ git clone https://github.com/iamv1n/phoenix.git
$ cd phoenix
$ python -m venv .venv && source .venv/bin/activate
$ pip install -e .
This installs the phoenix Python package and the phoenix command. Its two
dependencies are NumPy (polynomial arithmetic) and cryptography
(ChaCha20-Poly1305).
import phoenix
public_key, private_key = phoenix.generate_keypair()
sealed = phoenix.seal(public_key, b"The phoenix rises at dawn.")
print(phoenix.unseal(private_key, sealed))
# b'The phoenix rises at dawn.'
seal takes and returns bytes. Encode text yourself ("…".encode()), so
there is never any doubt about which encoding was used.
Anyone with the public key can seal; only the private key can unseal. If the
message was modified in transit, or you use the wrong key, unseal raises
phoenix.DecryptionError instead of returning garbage.
Associated data is authenticated but not encrypted and not stored in the message. Both sides must supply the same value:
sealed = phoenix.seal(public_key, b"pay 100 to Bob", associated_data=b"invoice-17")
phoenix.unseal(private_key, sealed, associated_data=b"invoice-17") # ok
phoenix.unseal(private_key, sealed, associated_data=b"invoice-18") # DecryptionError
Use it to stop a valid message from being replayed somewhere it does not belong.
public_bytes = phoenix.encode_public_key(public_key) # 950 bytes
private_bytes = phoenix.encode_private_key(private_key) # 1118 bytes
public_key = phoenix.decode_public_key(public_bytes)
private_key = phoenix.decode_private_key(private_bytes)
For text-friendly storage, wrap any encoded object in PEM-style armor:
text = phoenix.armor(public_bytes)
# -----BEGIN PHOENIX PUBLIC KEY-----
# UEhOWAEBAqUDAAAIAAB/AH8Af01I7WVM0ZSM1kKfsdSolh4ljmMkdD+tXMZ/5CLzri8Dq+sWMGTt
# ...
# -----END PHOENIX PUBLIC KEY-----
public_key = phoenix.decode_public_key(phoenix.dearmor(text))
If you want a key for your own protocol rather than an encrypted message, use the KEM directly:
ciphertext, alice_secret = phoenix.encapsulate(bob_public_key) # Alice
bob_secret = phoenix.decapsulate(bob_private_key, ciphertext) # Bob
assert alice_secret == bob_secret # 32 bytes
public_key, private_key = phoenix.generate_keypair(phoenix.PHOENIX821)
| Name | N | q | Public key | Sealed overhead |
|---|---|---|---|---|
PHOENIX509 |
509 | 2048 | 719 B | 735 B |
PHOENIX677 (default) |
677 | 2048 | 950 B | 966 B |
PHOENIX821 |
821 | 4096 | 1251 B | 1267 B |
TOY |
7 | 41 | 25 B | 41 B |
TOY is for studying the algorithm by hand and provides no security. A
sealed message records its parameter set, so the receiver never has to be
told. See Security for what the sizes mean.
$ phoenix keygen -o alice
public key: alice.pub
private key: alice.key (keep this secret)
$ echo "meet me at the old bridge" > note.txt
$ phoenix encrypt -k alice.pub -i note.txt -o note.phx
$ phoenix decrypt -k alice.key -i note.phx
meet me at the old bridge
More in Command line. To experiment without writing anything, run
phoenix playground (details).
examples/ has six short runnable scripts.