PyNaCl SecretBox encryption + Ed25519 signing: working versions (tested 2026-10-18)
Authenticated symmetric encryption with SecretBox and Ed25519 signing with SigningKey/VerifyKey, showing tamper detection, nonce rules and Base64/Hex key encoding round-trips.
passed6 passed · 0 failed2026-10-18 03:57 UTC- Status
- passed
- Last passed
- 2026-10-18 03:57 UTC
- Tests
- 6 / 6
- Language
- Python
- Runtime
- Python 3.13.16
pinned versions2
pynacl@1.6.2pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
test environment
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 39095370682
The code
Exactly the files that passed. Your agent can pull them with get_recipe("pynacl-secretbox-signing") (install), or see them on GitHub.
requirements.txt
pynacl==1.6.2 pytest==9.1.1
crypto.py
from nacl import encoding
from nacl.secret import SecretBox
from nacl.signing import SigningKey, VerifyKey
from nacl.utils import random
def new_secret_key() -> bytes:
return random(SecretBox.KEY_SIZE)
def encrypt(secret_key: bytes, plaintext: bytes) -> bytes:
return bytes(SecretBox(secret_key).encrypt(plaintext))
def decrypt(secret_key: bytes, blob: bytes) -> bytes:
return SecretBox(secret_key).decrypt(blob)
def new_signing_key() -> SigningKey:
return SigningKey.generate()
def sign(signing_key: SigningKey, message: bytes) -> bytes:
return bytes(signing_key.sign(message))
def verify(verify_key: VerifyKey, signed: bytes) -> bytes:
return verify_key.verify(signed)
def encode_key(key, encoder: encoding.Encoder = encoding.Base64Encoder) -> bytes:
return key.encode(encoder=encoder)
def decode_signing_key(
encoded: bytes, encoder: encoding.Encoder = encoding.Base64Encoder
) -> SigningKey:
return SigningKey(encoded, encoder=encoder)
def decode_verify_key(
encoded: bytes, encoder: encoding.Encoder = encoding.Base64Encoder
) -> VerifyKey:
return VerifyKey(encoded, encoder=encoder)
tests/test_crypto.py
import pytest
from nacl import encoding
from nacl.exceptions import BadSignatureError, CryptoError
from nacl.secret import SecretBox
from nacl.utils import random
from crypto import (
decode_signing_key,
decode_verify_key,
decrypt,
encode_key,
encrypt,
new_secret_key,
new_signing_key,
sign,
verify,
)
def test_secretbox_roundtrip_and_blob_layout():
key = new_secret_key()
blob = encrypt(key, b"attack at dawn")
assert decrypt(key, blob) == b"attack at dawn"
box = SecretBox(key)
enc = box.encrypt(b"attack at dawn", random(SecretBox.NONCE_SIZE))
assert bytes(enc) == enc.nonce + enc.ciphertext
with pytest.raises(CryptoError):
box.decrypt(bytes(enc), enc.nonce)
assert box.decrypt(enc.ciphertext, enc.nonce) == b"attack at dawn"
def test_secretbox_tamper_detected():
key = new_secret_key()
blob = bytearray(encrypt(key, b"transfer $10"))
blob[-1] ^= 0x01
with pytest.raises(CryptoError):
decrypt(key, bytes(blob))
with pytest.raises(CryptoError):
decrypt(new_secret_key(), encrypt(key, b"transfer $10"))
def test_secretbox_nonce_rules():
key = new_secret_key()
box = SecretBox(key)
nonce = random(SecretBox.NONCE_SIZE)
assert bytes(box.encrypt(b"same", nonce)) == bytes(box.encrypt(b"same", nonce))
assert bytes(box.encrypt(b"same")) != bytes(box.encrypt(b"same", nonce))
with pytest.raises(ValueError):
box.encrypt(b"data", b"too-short")
def test_ed25519_sign_verify_and_message_layout():
sk = new_signing_key()
sm = sk.sign(b"release v1")
assert bytes(sm) == sm.signature + sm.message
assert len(sm.signature) == 64
assert verify(sk.verify_key, bytes(sm)) == b"release v1"
assert sk.verify_key.verify(sm.message, sm.signature) == b"release v1"
def test_ed25519_tamper_detected():
sk = new_signing_key()
signed = bytearray(sign(sk, b"pay bob $5"))
signed[-1] ^= 0x01
with pytest.raises(BadSignatureError):
verify(sk.verify_key, bytes(signed))
with pytest.raises(BadSignatureError):
verify(new_signing_key().verify_key, sign(sk, b"pay bob $5"))
def test_key_encoding_roundtrip():
sk = new_signing_key()
restored = decode_signing_key(encode_key(sk))
assert restored.verify_key.encode() == sk.verify_key.encode()
assert sign(restored, b"hi") == sign(sk, b"hi")
vk = sk.verify_key
hex_vk = encode_key(vk, encoding.HexEncoder)
assert decode_verify_key(hex_vk, encoding.HexEncoder).encode() == vk.encode()
pytest.ini
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.