ProvenPin Install

cryptography MultiFernet key ring with rotation: working versions (tested 2026-10-15)

Wrap cryptography's MultiFernet in a small key ring that encrypts with the newest key, still decrypts tokens from older keys, re-encrypts them with rotate(), and rejects tokens once their key is dropped, all tested offline.

passed6 passed · 0 failed2026-10-15 18:59 UTC
Status
passed
Last passed
2026-10-15 18:59 UTC
Tests
6 / 6
Language
Python
Runtime
Python 3.13.16
pinned versions2
cryptography@50.0.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 38790162748

The code

Exactly the files that passed. Your agent can pull them with get_recipe("cryptography-fernet-multi-key") (install), or see them on GitHub.

requirements.txt
cryptography==50.0.2
pytest==9.1.1
rotation.py
from cryptography.fernet import Fernet, InvalidToken, MultiFernet


def new_key() -> str:
    return Fernet.generate_key().decode()


class KeyRing:
    def __init__(self, keys: list[str]):
        self.keys = list(keys)
        self._mf = MultiFernet([Fernet(k.encode()) for k in self.keys])

    @classmethod
    def generate(cls) -> "KeyRing":
        return cls([new_key()])

    def add(self, key: str) -> None:
        self.keys.insert(0, key)
        self._mf = MultiFernet([Fernet(k.encode()) for k in self.keys])

    def drop(self, key: str) -> None:
        self.keys.remove(key)
        self._mf = MultiFernet([Fernet(k.encode()) for k in self.keys])

    def encrypt(self, data: str) -> str:
        return self._mf.encrypt(data.encode()).decode()

    def decrypt(self, token: str) -> str:
        return self._mf.decrypt(token.encode()).decode()

    def rotate(self, token: str) -> str:
        return self._mf.rotate(token.encode()).decode()
tests/test_crypto.py
import pytest
from cryptography.fernet import InvalidToken
from rotation import KeyRing, new_key


def test_roundtrip():
    ring = KeyRing.generate()
    assert ring.decrypt(ring.encrypt("secret")) == "secret"


def test_encrypt_is_nondeterministic():
    ring = KeyRing.generate()
    a, b = ring.encrypt("x"), ring.encrypt("x")
    assert a != b
    assert ring.decrypt(a) == ring.decrypt(b) == "x"


def test_old_key_still_decrypts_during_rotation():
    old = KeyRing.generate()
    token = old.encrypt("payload")
    ring = KeyRing([new_key(), *old.keys])
    assert ring.decrypt(token) == "payload"


def test_rotate_moves_token_to_primary_key():
    old = KeyRing.generate()
    token = old.encrypt("payload")
    ring = KeyRing([new_key(), *old.keys])
    rotated = ring.rotate(token)
    assert KeyRing([ring.keys[0]]).decrypt(rotated) == "payload"


def test_dropping_key_invalidates_old_tokens():
    old = KeyRing.generate()
    token = old.encrypt("payload")
    ring = KeyRing([new_key(), *old.keys])
    ring.drop(old.keys[0])
    with pytest.raises(InvalidToken):
        ring.decrypt(token)


def test_tampered_token_rejected():
    ring = KeyRing.generate()
    token = ring.encrypt("payload")
    bad = token[:-2] + ("AA" if token[-2:] != "AA" else "BB")
    with pytest.raises(InvalidToken):
        ring.decrypt(bad)
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

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.