RFC Reference notes

1) The quickest orientation

The mental model

Cryptographic systems usually need four layers:

  1. Identity / authentication
    - RSA-PSS, ECDSA, or Ed25519 signatures.
  2. Key establishment / key transport
    - RSA-OAEP, ECDH on P-256/P-384/P-521, X25519, or HPKE.
  3. Bulk payload protection
    - AES-GCM/EAX/CCM/OCB/SIV or ChaCha20-Poly1305.
  4. Key storage / serialization
    - PKCS#8, SubjectPublicKeyInfo, ECPrivateKey, RFC 7468 PEM text encoding.

The most common engineering failure is to pick a strong primitive but combine it badly:
- encrypt without authentication;
- use a shared secret directly instead of running a KDF;
- sign ambiguous or under-specified data;
- reuse a nonce;
- serialize keys in ad hoc formats;
- treat decode/decrypt errors inconsistently.


2) IV / nonce privacy: the rule people often get wrong

A nonce or IV usually does not need to be secret. What matters is the property required by the mode:

Construction What the value is called What is required Must it be secret? Notes
AES-CBC IV Unpredictable per encryption No Integrity of the IV still matters.
AES-CFB IV Unpredictable No Similar IV rule to CBC.
AES-OFB IV Unique No Reuse is dangerous.
AES-CTR Counter block / nonce Unique under a key No Wrong nonce usually gives garbage without an exception.
AES-GCM / EAX / CCM / OCB Nonce Unique under a key No Tag verification catches tampering.
AES-SIV Optional nonce Optional; if omitted encryption is deterministic No Misuse-resistant by design.
ChaCha20-Poly1305 / XChaCha20-Poly1305 Nonce Unique under a key No Public metadata in normal protocols.
Salsa20 Nonce Unique under a key No Add a MAC if using Salsa20 directly.
ECDSA / DSA Per-message nonce k Secret and never reused Yes Leakage or reuse can expose the private key.

The practical rule

  • Treat AEAD nonces as public protocol fields.
  • Treat CBC IVs as public but unpredictable.
  • Treat ECDSA/DSA nonces as secret key material for one message only.
  • If you want stronger misuse resistance for encryption, prefer SIV or a fully structured protocol like HPKE.

3) Standards map: what each feature is tied to

3.1 RSA encryption and signatures

RFC 8017 — PKCS #1 v2.2

Covers:
- RSAES-OAEP
- RSAES-PKCS1-v1_5
- RSASSA-PSS
- RSASSA-PKCS1-v1_5
- RSA ASN.1 structures

Why it exists:
Early RSA deployments needed interoperable definitions for encryption padding, signature encoding, and key syntax. Later revisions added OAEP and PSS to improve the security story of the older PKCS#1 v1.5 constructions.

Why it matters in practice:
- OAEP is the correct default for fresh RSA encryption.
- PSS is the correct default for fresh RSA signatures.
- PKCS#1 v1.5 remains relevant for compatibility, but it is not where new design effort should go.

What problem OAEP solved

Raw RSA is deterministic and malleable. OAEP adds randomized padding so RSA encryption behaves like a real encryption scheme instead of a fragile arithmetic transform.

What problem PSS solved

Older RSA signatures used fixed padding structures. PSS gives a stronger, modern probabilistic signature design with a cleaner security story.


3.2 ECDSA and deterministic ECDSA

FIPS 186-5 — Digital Signature Standard (DSS)

Covers:
- RSA signatures
- ECDSA
- EdDSA

Why it exists:
FIPS 186 is the NIST digital-signature baseline for federal and FIPS-constrained environments. It gives approved constructions, parameter choices, and validation rules.

RFC 6979 — Deterministic DSA/ECDSA

Covers:
- deterministic per-message nonce generation for DSA/ECDSA

Why it exists:
A large class of historical signature failures came from bad randomness. Reusing or biasing the ECDSA nonce k can reveal the private key. RFC 6979 avoids that entire failure mode by deriving k deterministically from the private key and message hash.

Practical takeaway:
If you use ECDSA in software, deterministic RFC 6979 mode is usually the safer application-level default unless a profile requires randomized signing.

RFC 3279

Covers:
- ASN.1 algorithm identifiers and DER encodings used in X.509 ecosystems, including DSA/ECDSA signature representations

Why it exists:
The algorithm may be correct but still fail interoperability if the signature or key is serialized differently. RFC 3279 helped standardize those encodings in certificate and PKI workflows.


3.3 Ed25519 / Ed448

RFC 8032 — EdDSA

Covers:
- Ed25519
- Ed448
- PureEdDSA and HashEdDSA forms

Why it exists:
EdDSA was designed to reduce common implementation pitfalls in classic ECDSA-style systems: simpler formulas, deterministic signing, strong performance, and easier constant-time implementations.

Why it matters:
Ed25519 is often the best signature choice when you do not need a strictly NIST-P-curve-only profile and you want strong usability with fewer nonce hazards.


3.4 ECDH and X25519

NIST SP 800-56A Rev. 3

Covers:
- pair-wise key-establishment schemes based on discrete logarithms
- including elliptic-curve Diffie-Hellman style constructions

Why it exists:
A raw shared secret by itself is not a session key. SP 800-56A standardizes how two parties perform key agreement and then derive usable symmetric material from the resulting secret.

Engineering consequence:
Never use the shared secret directly as an AES key. Always feed it into a KDF with context binding.

RFC 7748 — X25519 / X448

Covers:
- modern Montgomery curves for key agreement

Why it exists:
Traditional ECC deployments were often difficult to parameterize safely and to implement in constant time. RFC 7748 provided modern, simpler curves intended to improve practical security and interoperability.

Practical takeaway:
X25519 is a very strong default for modern key agreement when strict NIST-only constraints are not the overriding requirement.


3.5 AEAD and symmetric encryption modes

RFC 5116 — AEAD interface

Covers:
- the general API model of authenticated encryption with associated data

Why it exists:
Protocols kept reinventing slightly different interfaces for “encrypt + authenticate”. RFC 5116 made the model explicit: key, nonce, plaintext, AAD, ciphertext, tag.

Why it matters:
It pushes engineers away from ad hoc “encrypt, then maybe MAC something” designs and toward a consistent API surface.

NIST SP 800-38C — CCM

Why it exists:
CCM combines CTR-mode encryption with CBC-MAC authentication in a NIST-approved AEAD mode.

NIST SP 800-38D — GCM / GMAC

Why it exists:
GCM gives high-performance AEAD around counter-mode encryption and a finite-field authentication function.

RFC 5297 — AES-SIV

Why it exists:
Real systems often reuse nonces by mistake. SIV was built for misuse resistance so that accidental nonce reuse is much less catastrophic than it is in GCM/CTR-like modes.

Practical consequence:
If nonce management is error-prone and throughput is secondary, SIV is often the safer engineering choice.

RFC 8439 — ChaCha20-Poly1305

Why it exists:
A fast software-oriented AEAD construction was needed, especially for platforms where AES hardware acceleration is absent or inconsistent.

Practical consequence:
ChaCha20-Poly1305 is a first-class modern AEAD. XChaCha20-Poly1305 extends the nonce size for easier nonce management in many application designs.


3.6 Key wrapping

RFC 3394 and RFC 5649

Covers:
- AES Key Wrap (KW)
- AES Key Wrap with Padding (KWP)

NIST SP 800-38F

Covers:
- NIST-approved key wrapping modes including KW and KWP

Why they exist:
Key wrapping is narrower than general-purpose file/message encryption. The goal is to protect cryptographic keys with both confidentiality and integrity in a standardized transport/storage format.

Practical takeaway:
Use KW/KWP for keys, not arbitrary application data. If you are protecting general payloads, use AEAD instead.


3.7 HPKE

RFC 9180 — Hybrid Public Key Encryption

Covers:
- sender-to-recipient encryption using a KEM + KDF + AEAD composition
- authenticated variants with sender keys and/or PSKs

Why it exists:
Teams repeatedly built “hybrid encryption” from raw primitives, often forgetting important context binding or sequence handling. HPKE standardizes the full composition pattern.

Practical takeaway:
If you need public-key-based message encryption for arbitrary-size plaintexts and you want a modern, structured design, HPKE is much safer than inventing your own envelope format from scratch.


3.8 Key serialization and textual encodings

RFC 5480

Covers:
- SubjectPublicKeyInfo encoding for ECC public keys

RFC 5915

Covers:
- EC private key structure (ECPrivateKey)

RFC 8410

Covers:
- algorithm identifiers and key encodings for Ed25519, Ed448, X25519, and X448

RFC 5958 (obsoletes RFC 5208)

Covers:
- PKCS#8-style asymmetric private-key packages

RFC 7468

Covers:
- PEM-style textual encodings for PKIX / PKCS / CMS structures

RFC 1421-1424

Historical context:
The original PEM RFC family defined the old Privacy-Enhanced Mail ecosystem. Modern PEM text blobs used for keys and certs inherit from that historical lineage, but RFC 7468 is the cleaner modern reference for textual encodings.

Why all of this exists:
Most crypto failures in production are not mathematical failures. They are interoperability and lifecycle failures: systems cannot import each other’s keys, key metadata is lost, or developers store private keys in weak ad hoc formats.

Practical takeaway:
For private keys, default to PKCS#8 with password-based protection if a passphrase is needed. For textual transport, use RFC 7468 PEM wrappers over standardized binary structures.


3.9 Padding standards

RFC 5652 — CMS

Relevance here:
PyCryptodome’s pkcs7 padding option aligns with the PKCS#7 / CMS family used for block-oriented content padding.

Why it exists:
Block ciphers like CBC and ECB require full blocks. CMS/PKCS#7 standardized a clean way to pad arbitrary-length data up to the block boundary.

ISO/IEC 7816-4 padding

Relevance here:
PyCryptodome exposes it as iso7816.

Why it exists:
It is a simple sentinel-style padding convention often associated with smart-card ecosystems.

ANSI X9.23 padding

Relevance here:
PyCryptodome exposes it as x923.

Why it exists:
It is another conventional block-padding scheme used in financial and legacy systems.

Important note:
Padding standards are not integrity mechanisms. Padding only solves alignment. It does not authenticate the message.


4) Why these standards were created at all

The pattern is consistent across decades of cryptographic standardization:

  1. Interoperability problem
    - Different vendors encoded keys, signatures, or parameters differently.
  2. Misuse problem
    - Real engineers accidentally reused nonces, forgot to MAC ciphertexts, or generated weak randomness.
  3. Provable-security problem
    - Older constructions had weaker or less clean security analyses.
  4. Operational problem
    - Systems needed reusable, composable formats for storing keys and exchanging secure messages.
  5. Validation / procurement problem
    - Governments and large enterprises needed stable profiles to test against.

So standards are not just bureaucracy. They are the accumulated scar tissue of production failures.


5) Best-practice patterns for resilient cryptosystems

5.1 Prefer complete constructions over loose primitives

Bad design instinct:
- “I’ll do ECDH, then maybe hash the result, then AES-CTR, then maybe append a MAC.”

Better:
- HPKE for public-key message encryption.
- AES-GCM/EAX/OCB/SIV or ChaCha20-Poly1305 for payload protection.
- RSA-OAEP + AEAD if you must do classic hybrid RSA.

The fewer composition seams you invent yourself, the fewer failure cases you own.

5.2 Always authenticate ciphertext and metadata

Authenticate:
- protocol version;
- algorithm suite identifier;
- sender/recipient identity binding;
- sequence number / message number;
- content type;
- application context or purpose string.

This is exactly what AAD is for in AEAD modes.

If metadata affects interpretation, it must be authenticated.

5.3 Every DH/X25519 result goes through a KDF

Never do this:
- aes_key = shared_secret

Do this:
- session_key = HKDF(shared_secret, ...)

And bind context into the KDF:
- protocol name;
- role (client vs server);
- ciphersuite;
- transcript hash or channel binding;
- sender and recipient identifiers.

This prevents key reuse across contexts and makes cross-protocol mistakes much harder.

5.4 Use nonce discipline as a first-class system concern

For GCM/CTR/ChaCha20-style systems:
- define nonce construction explicitly;
- ensure uniqueness per key;
- document rollover behavior;
- include message counters in protocol state;
- reject replays when applicable.

If you cannot enforce nonce discipline cleanly, consider SIV or a protocol that manages sequencing structurally.

5.5 Prefer misuse-resistant signatures where possible

  • Ed25519 avoids the classic ECDSA “bad nonce destroys the private key” problem.
  • If you use ECDSA, deterministic RFC 6979 mode is usually the safer software default.
  • If you need strict NIST/FIPS alignment, P-256 ECDSA remains the most conservative choice.

5.6 Separate long-term identity keys from ephemeral/session keys

Do not use one key pair for everything.

Use separate keys for:
- signing identity;
- key agreement;
- transport/session establishment;
- key wrapping / storage roles if policy requires.

This reduces blast radius and makes future rotation easier.

5.7 Serialize keys with standard containers only

Use:
- PKCS#8 for private keys;
- SubjectPublicKeyInfo / standard public-key encodings for public keys;
- RFC 7468 PEM for text transport;
- RFC 8410 encodings for Ed25519/X25519 families.

Avoid:
- ad hoc JSON blobs with unlabeled raw bytes;
- homemade base64 fields without algorithm identifiers;
- “just store the scalar in hex” without curve context.

5.8 Handle errors in a uniform, boring way

A resilient system does not leak subtle information by varying response shape, timing, or cleanup path.

Examples:
- OAEP failure should not disclose whether the label, key, or ciphertext was wrong.
- PKCS#1 v1.5 legacy decryptions should continue processing with a sentinel in protocols that still need them.
- AEAD verification failures should abort processing before using the plaintext.

5.9 Treat negative-path tests as first-class tests

For every success path, have tests for:
- wrong passphrase;
- wrong recipient key;
- tampered ciphertext;
- tampered AAD;
- wrong signature;
- truncated message;
- reordered message;
- repeated nonce / sequence edge cases;
- malformed serialization;
- wrong curve / wrong algorithm identifier.

Most production breakages happen in the negative paths, not the happy path.

5.10 Version your crypto envelope

Your serialized ciphertext/message should normally carry:
- format version;
- suite identifier;
- nonce or message sequence number;
- sender key identifier or recipient key identifier;
- AAD-relevant metadata.

That makes rotation and migration possible without guesswork.


6) FIPS-oriented profiles that build on the same ideas

6.1 A conservative FIPS-friendly application profile

If you need to stay close to FIPS/NIST guidance, a strong baseline is:

  • Signatures: RSA-PSS or ECDSA on P-256 / P-384; EdDSA where policy and validated-module support allow it.
  • Key agreement: ECDH on P-256 / P-384 / P-521 with a NIST-aligned KDF flow.
  • Bulk encryption: AES-GCM or AES-CCM; AES-SIV when misuse resistance is more important than staying inside the older NIST AEAD set.
  • Hash/KDF/MAC: SHA-256 / SHA-384 / SHA-512 or SHA-3 family; HKDF; HMAC.
  • Key wrapping: AES-KW / KWP when the payload is itself key material.
  • Key storage: PKCS#8 for private keys; PEM only as the text envelope.

6.2 A modern engineering profile when strict NIST-only choices are not mandatory

  • Signatures: Ed25519
  • Key agreement: X25519
  • Payload protection: ChaCha20-Poly1305 or AES-GCM
  • KDF: HKDF
  • Public-key message encryption: HPKE

This profile is often easier to implement correctly, especially where software portability and safer defaults matter more than strict legacy procurement alignment.


7) Compositions worth copying

7.1 Signed software artifact

  • Ed25519 or RSA-PSS signature over a canonical artifact digest.
  • Detached signature.
  • Clear key identifier and algorithm identifier.
  • Versioned manifest.

7.2 Encrypted application message

  • X25519 or ECDH -> HKDF -> AES-GCM/ChaCha20-Poly1305
  • AAD includes protocol version, sender ID, recipient ID, and message counter.

7.3 Envelope encryption for large content

  • Random content-encryption key (CEK)
  • CEK protects bulk content with AEAD
  • CEK transported with RSA-OAEP or HPKE

7.4 Private key at rest

  • PKCS#8 protected private key
  • strong password KDF parameters
  • clear algorithm OID and standard container format

8) Anti-patterns to avoid

  • RSA-encrypting large payloads directly.
  • ECDH shared secret used directly as an AES key.
  • AES-CBC or AES-CTR without a MAC/AEAD wrapper.
  • Reusing GCM/CTR/ChaCha nonces.
  • Treating padding as authentication.
  • Reusing ECDSA nonces.
  • Storing raw private scalars without algorithm metadata.
  • Returning different error messages for different decryption failures.
  • Signing data before canonicalizing it.
  • Designing a custom message encryption scheme when HPKE already fits.

9) A short decision guide

Need a signature?

  • Strict NIST/FIPS posture: ECDSA P-256 or RSA-PSS
  • Safer modern ergonomics: Ed25519

Need sender-to-recipient encryption?

  • Modern clean composition: HPKE
  • Existing RSA infrastructure: RSA-OAEP + AEAD envelope

Need symmetric encryption?

  • Default: AES-GCM or EAX
  • Nonce management is risky: AES-SIV
  • Portable software-first: ChaCha20-Poly1305

Need to wrap keys specifically?

  • AES-KW/KWP

Need private key serialization?

  • PKCS#8, optionally armored with RFC 7468 PEM

10) Reference list

Signatures

  • FIPS 186-5 — Digital Signature Standard (DSS)
  • RFC 8017 — PKCS #1 v2.2 (RSA)
  • RFC 6979 — Deterministic DSA/ECDSA
  • RFC 8032 — EdDSA (Ed25519 / Ed448)
  • RFC 3279 — X.509 algorithm identifiers / signature encodings

Key agreement and hybrid encryption

  • NIST SP 800-56A Rev. 3 — Pair-wise key establishment
  • RFC 7748 — X25519 / X448
  • RFC 9180 — HPKE

Symmetric encryption and key wrapping

  • RFC 5116 — AEAD interface
  • NIST SP 800-38C — CCM
  • NIST SP 800-38D — GCM / GMAC
  • RFC 5297 — AES-SIV
  • RFC 8439 — ChaCha20-Poly1305
  • RFC 3394 — AES Key Wrap
  • RFC 5649 — AES Key Wrap with Padding
  • NIST SP 800-38F — Key wrapping modes

Key formats and encodings

  • RFC 5480 — ECC SubjectPublicKeyInfo
  • RFC 5915 — ECPrivateKey
  • RFC 8410 — Ed25519 / X25519 encodings
  • RFC 5958 — Asymmetric Key Packages (modern PKCS#8 form)
  • RFC 7468 — PEM textual encodings
  • RFC 1421-1424 — historical PEM family

Padding and message syntax

  • RFC 5652 — CMS / PKCS#7 family
  • ISO/IEC 7816-4 — ISO7816 padding
  • ANSI X9.23 — X9.23 padding

11) Bottom line

If you remember only five things:

  1. Prefer complete authenticated constructions.
  2. Every DH secret goes through a KDF.
  3. Nonces are usually public, but misuse can still destroy security.
  4. ECDSA nonce failures are catastrophic; Ed25519 and RFC6979 exist partly to remove that foot-gun.
  5. Standard key containers and standard message envelopes are not optional engineering polish; they are part of the security boundary.
#!/usr/bin/env python3
from __future__ import annotations

import json
import textwrap
import traceback
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Callable, Dict, Iterable, List, Optional

from Crypto.Cipher import AES, PKCS1_OAEP, PKCS1_v1_5, ChaCha20_Poly1305, Salsa20
from Crypto.Hash import HMAC, SHA256, SHA3_256, SHA512
from Crypto.IO import PEM, PKCS8
from Crypto.Protocol import HPKE
from Crypto.Protocol.DH import (
    import_x25519_private_key,
    import_x25519_public_key,
    key_agreement,
)
from Crypto.Protocol.KDF import HKDF, PBKDF2, scrypt
from Crypto.Protocol.SecretSharing import Shamir
from Crypto.PublicKey import ECC, RSA
from Crypto.Random import get_random_bytes
from Crypto.Signature import DSS, eddsa, pkcs1_15, pss
from Crypto.Util import Counter, RFC1751, number
from Crypto.Util.Padding import pad, unpad
from Crypto.Util.number import bytes_to_long, long_to_bytes
from Crypto.Util.strxor import strxor, strxor_c


SCRIPT_DIR = Path(__file__).resolve().parent
REPORT_PATH = SCRIPT_DIR / "Usage_report.md"


def _hex(data: bytes, limit: int = 64) -> str:
    value = data.hex()
    return value if len(value) <= limit else value[:limit] + "..."


def _repr_value(value: Any) -> str:
    if isinstance(value, bytes):
        return f"{_hex(value)} ({len(value)} bytes)"
    if isinstance(value, str):
        return value
    if isinstance(value, (int, float, bool)) or value is None:
        return repr(value)
    if isinstance(value, (list, tuple)):
        return json.dumps([_repr_value(item) for item in value], ensure_ascii=False, indent=2)
    if isinstance(value, dict):
        pretty = {str(k): _repr_value(v) for k, v in value.items()}
        return json.dumps(pretty, ensure_ascii=False, indent=2, sort_keys=True)
    return repr(value)


def l2b(value: int, blocksize: int = 0) -> bytes:
    if not isinstance(value, int):
        raise TypeError("value must be an int")
    if value < 0:
        raise ValueError("value must be non-negative")
    return long_to_bytes(value, blocksize) if blocksize else long_to_bytes(value)


def b2l(data: bytes) -> int:
    if not isinstance(data, (bytes, bytearray, memoryview)):
        raise TypeError("data must be bytes-like")
    return bytes_to_long(bytes(data))


def random_bytes(length: int) -> bytes:
    if length <= 0:
        raise ValueError("length must be positive")
    return get_random_bytes(length)


def xor_bytes(left: bytes, right: bytes) -> bytes:
    if len(left) != len(right):
        raise ValueError("inputs must have the same length")
    return strxor(left, right)


def xor_with_constant(data: bytes, constant: int) -> bytes:
    if not 0 <= constant <= 0xFF:
        raise ValueError("constant must be in range(0, 256)")
    return strxor_c(data, constant)


def split_blocks(data: bytes, block_size: int) -> List[bytes]:
    if block_size <= 0:
        raise ValueError("block_size must be positive")
    return [data[i : i + block_size] for i in range(0, len(data), block_size)]


def pad_bytes(data: bytes, block_size: int, style: str = "pkcs7") -> bytes:
    return pad(data, block_size, style=style)


def unpad_bytes(data: bytes, block_size: int, style: str = "pkcs7") -> bytes:
    return unpad(data, block_size, style=style)


def make_ctr_counter(
    *,
    nbits: int,
    prefix: bytes = b"",
    suffix: bytes = b"",
    initial_value: int = 1,
    little_endian: bool = False,
):
    return Counter.new(
        nbits,
        prefix=prefix,
        suffix=suffix,
        initial_value=initial_value,
        little_endian=little_endian,
    )


def derive_hkdf_key(master: bytes, length: int, salt: bytes, context: bytes = b"") -> bytes:
    return HKDF(master, length, salt, SHA256, context=context)


def derive_pbkdf2_key(password: bytes, salt: bytes, length: int, count: int = 100_000) -> bytes:
    return PBKDF2(password, salt, dkLen=length, count=count, hmac_hash_module=SHA512)


def derive_scrypt_key(password: bytes, salt: bytes, length: int, n: int = 2**14, r: int = 8, p: int = 1) -> bytes:
    return scrypt(password, salt, key_len=length, N=n, r=r, p=p)


def rfc1751_encode(key: bytes) -> str:
    return RFC1751.key_to_english(key)


def rfc1751_decode(words: str) -> bytes:
    return RFC1751.english_to_key(words)


def pem_wrap(data: bytes, marker: str, passphrase: Optional[bytes] = None) -> str:
    return PEM.encode(data, marker, passphrase=passphrase)


def pem_unwrap(pem_data: str, passphrase: Optional[bytes] = None):
    return PEM.decode(pem_data, passphrase=passphrase)


def pkcs8_wrap_der(
    private_key_der: bytes,
    key_oid: str,
    passphrase: bytes,
    protection: str = "PBKDF2WithHMAC-SHA512AndAES256-CBC",
    prot_params: Optional[Dict[str, Any]] = None,
) -> bytes:
    if prot_params is None:
        prot_params = {"iteration_count": 21_000}
    return PKCS8.wrap(
        private_key_der,
        key_oid,
        passphrase=passphrase,
        protection=protection,
        prot_params=prot_params,
    )


def pkcs8_unwrap_der(blob: bytes, passphrase: bytes):
    return PKCS8.unwrap(blob, passphrase=passphrase)


def generate_prime(bits: int = 256) -> int:
    return number.getPrime(bits)


def generate_strong_prime(bits: int = 512) -> int:
    return number.getStrongPrime(bits)


def mod_inverse(value: int, modulus: int) -> int:
    return number.inverse(value, modulus)


@dataclass
class Check:
    label: str
    passed: bool
    detail: str

    @property
    def status(self) -> str:
        return "PASS" if self.passed else "FAIL"


@dataclass
class DemoResult:
    name: str
    description: str
    usage_example: str
    outputs: Dict[str, Any] = field(default_factory=dict)
    pass_checks: List[Check] = field(default_factory=list)
    expected_failures: List[Check] = field(default_factory=list)
    notes: List[str] = field(default_factory=list)
    unexpected_error: Optional[str] = None

    @property
    def ok(self) -> bool:
        return self.unexpected_error is None and all(item.passed for item in self.pass_checks + self.expected_failures)


def condition(label: str, ok: bool, success_detail: str, failure_detail: str) -> Check:
    return Check(label=label, passed=ok, detail=success_detail if ok else failure_detail)


def expect_exception(label: str, func: Callable[[], Any], *exc_types: type[BaseException]) -> Check:
    try:
        func()
    except exc_types as exc:
        return Check(label=label, passed=True, detail=f"raised {type(exc).__name__}: {exc}")
    except Exception as exc:  # noqa: BLE001
        expected = ", ".join(exc.__name__ for exc in exc_types)
        return Check(
            label=label,
            passed=False,
            detail=f"raised unexpected {type(exc).__name__}: {exc}; expected {expected}",
        )
    expected = ", ".join(exc.__name__ for exc in exc_types)
    return Check(label=label, passed=False, detail=f"did not raise; expected {expected}")


def expect_equal(label: str, left: Any, right: Any) -> Check:
    return condition(label, left == right, "values match", f"values differ: {left!r} != {right!r}")


def expect_not_equal(label: str, left: Any, right: Any, detail: str) -> Check:
    return condition(label, left != right, detail, "values unexpectedly matched")


def expect_sentinel(label: str, value: bytes, sentinel: bytes) -> Check:
    return condition(label, value == sentinel, "returned sentinel as designed", "did not return sentinel")


@dataclass
class Context:
    rsa_key: Any
    p256_key: Any
    ed25519_key: Any
    x25519_key: Any
    passphrase: bytes


def build_context() -> Context:
    return Context(
        rsa_key=RSA.generate(2048),
        p256_key=ECC.generate(curve="P-256"),
        ed25519_key=ECC.generate(curve="Ed25519"),
        x25519_key=ECC.generate(curve="Curve25519"),
        passphrase=b"correct horse battery staple",
    )


def demo_utilities_and_low_level_helpers() -> DemoResult:
    result = DemoResult(
        name="Utilities and low-level helpers",
        description="Integer/byte conversions, XOR helpers, number theory helpers, RFC1751, PEM, and PKCS#8 wrappers.",
        usage_example=textwrap.dedent(
            """\
            from pycryptodome_showcase_runner import (
                b2l, l2b, random_bytes, xor_bytes, xor_with_constant,
                generate_prime, generate_strong_prime, mod_inverse,
                rfc1751_encode, rfc1751_decode, pem_wrap, pem_unwrap,
                pkcs8_wrap_der, pkcs8_unwrap_der,
            )

            sample = l2b(65537)
            assert b2l(sample) == 65537
            mixed = xor_bytes(b"ABCDEF12", b"12345678")
            flipped = xor_with_constant(b"AAAA", 0x20)
            """
        ),
    )

    value = 65537
    encoded = l2b(value)
    decoded = b2l(encoded)
    random_blob = random_bytes(24)
    blocks = split_blocks(random_blob, 8)

    left = b"ByteString123456"
    right = b"AnotherString789"
    xored = xor_bytes(left, right)
    xored_constant = xor_with_constant(b"AAAA", 0x20)

    prime = generate_prime(256)
    strong_prime = generate_strong_prime(512)
    inverse = mod_inverse(3, 11)

    english_key = rfc1751_encode(b"12345678")
    decoded_key = rfc1751_decode(english_key)

    pem_blob = pem_wrap(b"demo payload", "TEST DATA", passphrase=b"demo-pass")
    pem_data, pem_marker, pem_encrypted = pem_unwrap(pem_blob, passphrase=b"demo-pass")

    temp_rsa = RSA.generate(1024)
    wrapped = pkcs8_wrap_der(
        temp_rsa.export_key(format="DER"),
        "1.2.840.113549.1.1.1",
        passphrase=b"demo-pass",
    )
    oid, unwrapped_der, params = pkcs8_unwrap_der(wrapped, passphrase=b"demo-pass")

    result.outputs.update(
        {
            "l2b_65537_hex": encoded.hex(),
            "random_bytes_length": len(random_blob),
            "split_blocks_lengths": [len(block) for block in blocks],
            "xor_bytes_hex": xored.hex(),
            "xor_with_constant_hex": xored_constant.hex(),
            "prime_bits": number.size(prime),
            "strong_prime_bits": number.size(strong_prime),
            "inverse_mod_11_of_3": inverse,
            "rfc1751_words": english_key,
            "pem_marker": pem_marker,
            "pem_encrypted": pem_encrypted,
            "pkcs8_oid": oid,
            "pkcs8_inner_der_length": len(unwrapped_der),
            "pkcs8_params": params,
        }
    )

    result.pass_checks.extend(
        [
            expect_equal("l2b/b2l round-trip", decoded, value),
            condition("random_bytes returned the requested length", len(random_blob) == 24, "24 bytes returned", "length mismatch"),
            condition("split_blocks partitioned data evenly", [len(block) for block in blocks] == [8, 8, 8], "three 8-byte blocks", "unexpected block sizes"),
            condition("xor_bytes produced same-length output", len(xored) == len(left), "length preserved", "length mismatch"),
            condition("getPrime produced a prime", number.isPrime(prime), "isPrime returned True", "isPrime returned False"),
            condition("getStrongPrime produced a prime", number.isPrime(strong_prime), "isPrime returned True", "isPrime returned False"),
            expect_equal("inverse(3,11) == 4", inverse, 4),
            expect_equal("RFC1751 round-trip", decoded_key, b"12345678"),
            expect_equal("PEM decode recovered original bytes", pem_data, b"demo payload"),
            expect_equal("PKCS#8 unwrap recovered DER", RSA.import_key(unwrapped_der).n, temp_rsa.n),
        ]
    )

    result.expected_failures.extend(
        [
            expect_exception(
                "xor_bytes rejects mismatched lengths",
                lambda: xor_bytes(b"short", b"longer"),
                ValueError,
            ),
            expect_exception(
                "inverse rejects non-invertible input",
                lambda: mod_inverse(2, 4),
                ValueError,
            ),
            expect_exception(
                "RFC1751 rejects malformed word list",
                lambda: rfc1751_decode("ONE TWO THREE"),
                ValueError,
            ),
            expect_exception(
                "PEM decode rejects wrong passphrase",
                lambda: pem_unwrap(pem_blob, passphrase=b"wrong-pass"),
                ValueError,
            ),
            expect_exception(
                "PKCS#8 unwrap rejects wrong passphrase",
                lambda: pkcs8_unwrap_der(wrapped, passphrase=b"wrong-pass"),
                ValueError,
            ),
        ]
    )

    result.notes.extend(
        [
            "PEM.encode()/decode() demonstrate the low-level textual envelope. For private keys, PKCS#8 protection is the stronger default.",
            "RFC1751 is mostly historical today, but it is useful to know the module exists.",
        ]
    )
    return result


def demo_hashes_and_hmac() -> DemoResult:
    result = DemoResult(
        name="Hashes and HMAC",
        description="SHA-256, SHA3-256, and HMAC verification including expected tamper failure.",
        usage_example=textwrap.dedent(
            """\
            from pycryptodome_showcase_runner import demo_hashes_and_hmac

            outcome = demo_hashes_and_hmac()
            print(outcome.outputs["sha256"])
            """
        ),
    )

    message = b"hash me"
    sha256_hex = SHA256.new(message).hexdigest()
    sha3_hex = SHA3_256.new(message).hexdigest()

    key = get_random_bytes(32)
    signer = HMAC.new(key, digestmod=SHA256)
    signer.update(message)
    tag = signer.digest()

    verifier = HMAC.new(key, digestmod=SHA256)
    verifier.update(message)
    verifier.verify(tag)

    result.outputs.update({"sha256": sha256_hex, "sha3_256": sha3_hex, "hmac_tag": tag.hex()})
    result.pass_checks.extend(
        [
            condition("SHA-256 digest is 32 bytes", len(bytes.fromhex(sha256_hex)) == 32, "digest length is 32", "digest length mismatch"),
            condition("SHA3-256 digest is 32 bytes", len(bytes.fromhex(sha3_hex)) == 32, "digest length is 32", "digest length mismatch"),
            condition("HMAC verification succeeded", True, "tag verified", "tag did not verify"),
        ]
    )
    result.expected_failures.append(
        expect_exception(
            "HMAC verification fails on modified message",
            lambda: (lambda v: (v.update(message + b"!"), v.verify(tag)))(HMAC.new(key, digestmod=SHA256)),
            ValueError,
        )
    )
    return result


def demo_padding_and_cbc() -> DemoResult:
    result = DemoResult(
        name="Padding and AES-CBC",
        description="CBC mode with PKCS#7/ISO7816/X9.23 padding and expected padding failure on tamper.",
        usage_example=textwrap.dedent(
            """\
            from pycryptodome_showcase_runner import pad_bytes, unpad_bytes
            from Crypto.Cipher import AES
            from Crypto.Random import get_random_bytes

            key = get_random_bytes(16)
            iv = get_random_bytes(16)
            cipher = AES.new(key, AES.MODE_CBC, iv=iv)
            ciphertext = cipher.encrypt(pad_bytes(b"confidential", 16, style="pkcs7"))
            """
        ),
    )

    plaintext = b"Padding demo for CBC mode"
    styles = {}
    for style in ("pkcs7", "iso7816", "x923"):
        padded = pad_bytes(plaintext, AES.block_size, style=style)
        recovered = unpad_bytes(padded, AES.block_size, style=style)
        styles[style] = {"padded_length": len(padded), "round_trip": recovered == plaintext}

    key = get_random_bytes(16)
    iv = get_random_bytes(16)
    cipher = AES.new(key, AES.MODE_CBC, iv=iv)
    ciphertext = cipher.encrypt(pad_bytes(plaintext, AES.block_size, style="pkcs7"))

    cipher = AES.new(key, AES.MODE_CBC, iv=iv)
    recovered = unpad_bytes(cipher.decrypt(ciphertext), AES.block_size, style="pkcs7")

    tampered = bytearray(ciphertext)
    tampered[-1] ^= 1

    result.outputs.update(
        {
            "padding_styles": styles,
            "cbc_iv": iv.hex(),
            "cbc_ciphertext": ciphertext.hex(),
        }
    )
    result.pass_checks.extend(
        [
            expect_equal("CBC round-trip recovered original plaintext", recovered, plaintext),
            condition(
                "All padding styles round-tripped",
                all(item["round_trip"] for item in styles.values()),
                "all styles unpadded correctly",
                "a padding style failed",
            ),
        ]
    )
    result.expected_failures.append(
        expect_exception(
            "CBC + PKCS#7 unpad fails after last-byte tamper",
            lambda: unpad_bytes(AES.new(key, AES.MODE_CBC, iv=iv).decrypt(bytes(tampered)), AES.block_size, style="pkcs7"),
            ValueError,
        )
    )
    result.notes.append(
        "CBC provides confidentiality only. Padding errors are detectable here because the example explicitly calls unpad(); unauthenticated modification elsewhere may still decrypt to garbage."
    )
    return result


def _aead_round_trip(mode_name: str, key: bytes, plaintext: bytes, aad: bytes) -> Dict[str, Any]:
    if mode_name == "EAX":
        cipher = AES.new(key, AES.MODE_EAX)
    elif mode_name == "GCM":
        cipher = AES.new(key, AES.MODE_GCM)
    elif mode_name == "CCM":
        cipher = AES.new(key, AES.MODE_CCM)
    elif mode_name == "OCB":
        cipher = AES.new(key, AES.MODE_OCB)
    elif mode_name == "SIV":
        cipher = AES.new(key, AES.MODE_SIV)
    else:
        raise ValueError(f"unsupported mode {mode_name}")

    cipher.update(aad)
    ciphertext, tag = cipher.encrypt_and_digest(plaintext)
    nonce = getattr(cipher, "nonce", None)

    if mode_name == "EAX":
        dec = AES.new(key, AES.MODE_EAX, nonce=nonce)
    elif mode_name == "GCM":
        dec = AES.new(key, AES.MODE_GCM, nonce=nonce)
    elif mode_name == "CCM":
        dec = AES.new(key, AES.MODE_CCM, nonce=nonce)
    elif mode_name == "OCB":
        dec = AES.new(key, AES.MODE_OCB, nonce=nonce)
    else:
        dec = AES.new(key, AES.MODE_SIV)

    dec.update(aad)
    recovered = dec.decrypt_and_verify(ciphertext, tag)
    return {"nonce": nonce, "ciphertext": ciphertext, "tag": tag, "recovered": recovered}


def demo_aes_aead_modes() -> DemoResult:
    result = DemoResult(
        name="AES AEAD modes",
        description="EAX, GCM, CCM, OCB, and SIV with authenticated AAD and tamper detection.",
        usage_example=textwrap.dedent(
            """\
            from pycryptodome_showcase_runner import demo_aes_aead_modes

            report = demo_aes_aead_modes()
            for name, details in report.outputs["modes"].items():
                print(name, details["tag"])
            """
        ),
    )

    plaintext = b"AEAD-protected message"
    aad = b"header-data"
    modes_summary: Dict[str, Dict[str, Any]] = {}

    for mode_name, key in {
        "EAX": get_random_bytes(16),
        "GCM": get_random_bytes(32),
        "CCM": get_random_bytes(16),
        "OCB": get_random_bytes(16),
        "SIV": get_random_bytes(32),
    }.items():
        values = _aead_round_trip(mode_name, key, plaintext, aad)
        modes_summary[mode_name] = {
            "nonce": values["nonce"].hex() if values["nonce"] is not None else "omitted",
            "ciphertext": values["ciphertext"].hex(),
            "tag": values["tag"].hex(),
        }
        result.pass_checks.append(expect_equal(f"{mode_name} round-trip", values["recovered"], plaintext))

        bad_tag = bytearray(values["tag"])
        bad_tag[-1] ^= 1

        def fail_case() -> None:
            if mode_name == "EAX":
                dec = AES.new(key, AES.MODE_EAX, nonce=values["nonce"])
            elif mode_name == "GCM":
                dec = AES.new(key, AES.MODE_GCM, nonce=values["nonce"])
            elif mode_name == "CCM":
                dec = AES.new(key, AES.MODE_CCM, nonce=values["nonce"])
            elif mode_name == "OCB":
                dec = AES.new(key, AES.MODE_OCB, nonce=values["nonce"])
            else:
                dec = AES.new(key, AES.MODE_SIV)
            dec.update(aad)
            dec.decrypt_and_verify(values["ciphertext"], bytes(bad_tag))

        result.expected_failures.append(expect_exception(f"{mode_name} rejects bad tag", fail_case, ValueError))

    siv_key = get_random_bytes(32)
    siv_1 = AES.new(siv_key, AES.MODE_SIV)
    siv_1.update(aad)
    ct1, tag1 = siv_1.encrypt_and_digest(plaintext)
    siv_2 = AES.new(siv_key, AES.MODE_SIV)
    siv_2.update(aad)
    ct2, tag2 = siv_2.encrypt_and_digest(plaintext)

    result.outputs["modes"] = modes_summary
    result.outputs["siv_deterministic_ciphertext_match"] = ct1 <mark> ct2 and tag1 </mark> tag2
    result.pass_checks.append(
        condition(
            "SIV is deterministic when nonce is omitted",
            ct1 <mark> ct2 and tag1 </mark> tag2,
            "identical plaintext/AAD produced identical ciphertext+tag",
            "SIV unexpectedly differed without a nonce",
        )
    )
    result.notes.append("SIV is the odd one out: omitting the nonce is valid and makes encryption deterministic.")
    return result


def demo_ctr_and_counter() -> DemoResult:
    result = DemoResult(
        name="AES-CTR and Counter utility",
        description="CTR mode with custom Counter.new(), wraparound detection, and an example of silent failure when metadata is wrong.",
        usage_example=textwrap.dedent(
            """\
            from pycryptodome_showcase_runner import make_ctr_counter
            from Crypto.Cipher import AES
            from Crypto.Random import get_random_bytes

            key = get_random_bytes(16)
            prefix = get_random_bytes(8)
            ctr = make_ctr_counter(nbits=64, prefix=prefix, initial_value=1)
            cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
            """
        ),
    )

    key = get_random_bytes(16)
    prefix = get_random_bytes(8)
    plaintext = b"CTR mode does not authenticate ciphertext"

    ctr = make_ctr_counter(nbits=64, prefix=prefix, initial_value=1)
    cipher = AES.new(key, AES.MODE_CTR, counter=ctr)
    ciphertext = cipher.encrypt(plaintext)

    ctr = make_ctr_counter(nbits=64, prefix=prefix, initial_value=1)
    recovered = AES.new(key, AES.MODE_CTR, counter=ctr).decrypt(ciphertext)

    wrong_prefix = bytes([prefix[0] ^ 1]) + prefix[1:]
    ctr_wrong = make_ctr_counter(nbits=64, prefix=wrong_prefix, initial_value=1)
    wrong_plaintext = AES.new(key, AES.MODE_CTR, counter=ctr_wrong).decrypt(ciphertext)

    tiny_prefix = get_random_bytes(15)

    def overflow_case() -> None:
        ctr_local = make_ctr_counter(nbits=8, prefix=tiny_prefix, initial_value=1)
        AES.new(key, AES.MODE_CTR, counter=ctr_local).encrypt(b"A" * (16 * 260))

    result.outputs.update(
        {
            "ctr_prefix": prefix.hex(),
            "ctr_ciphertext": ciphertext.hex(),
            "wrong_prefix_plaintext": wrong_plaintext.hex(),
        }
    )
    result.pass_checks.append(expect_equal("CTR round-trip", recovered, plaintext))
    result.expected_failures.extend(
        [
            expect_exception("Counter overflow raises OverflowError", overflow_case, OverflowError),
            expect_not_equal(
                "Wrong CTR prefix silently yields wrong plaintext",
                wrong_plaintext,
                plaintext,
                "plaintext changed without an authentication exception",
            ),
        ]
    )
    result.notes.append("CTR is a confidentiality mode only. The library can detect counter wraparound, but it cannot detect ciphertext tampering without an external MAC or AEAD wrapper.")
    return result


def demo_aes_kw_kwp() -> DemoResult:
    result = DemoResult(
        name="AES Key Wrap (KW/KWP)",
        description="Deterministic authenticated key wrapping for key material.",
        usage_example=textwrap.dedent(
            """\
            from Crypto.Cipher import AES
            from Crypto.Random import get_random_bytes

            kek = get_random_bytes(16)
            wrapped = AES.new(kek, AES.MODE_KW).seal(b"12345678abcdefgh")
            """
        ),
    )

    kek = get_random_bytes(16)
    key_to_wrap = b"12345678abcdefgh"
    kw_wrapped = AES.new(kek, AES.MODE_KW).seal(key_to_wrap)
    kw_unwrapped = AES.new(kek, AES.MODE_KW).unseal(kw_wrapped)

    padded_key = b"not multiple of 8 bytes"
    kwp_wrapped = AES.new(kek, AES.MODE_KWP).seal(padded_key)
    kwp_unwrapped = AES.new(kek, AES.MODE_KWP).unseal(kwp_wrapped)

    result.outputs.update(
        {
            "kw_wrapped": kw_wrapped.hex(),
            "kwp_wrapped": kwp_wrapped.hex(),
        }
    )
    result.pass_checks.extend(
        [
            expect_equal("KW unwrapped the original key", kw_unwrapped, key_to_wrap),
            expect_equal("KWP unwrapped the original key", kwp_unwrapped, padded_key),
        ]
    )

    bad_kw = bytearray(kw_wrapped)
    bad_kw[-1] ^= 1
    bad_kwp = bytearray(kwp_wrapped)
    bad_kwp[0] ^= 1

    result.expected_failures.extend(
        [
            expect_exception(
                "KW rejects modified wrapped key",
                lambda: AES.new(kek, AES.MODE_KW).unseal(bytes(bad_kw)),
                ValueError,
            ),
            expect_exception(
                "KWP rejects modified wrapped key",
                lambda: AES.new(kek, AES.MODE_KWP).unseal(bytes(bad_kwp)),
                ValueError,
            ),
        ]
    )
    result.notes.append("KW requires wrapped key material to be a multiple of 8 bytes. KWP removes that constraint.")
    return result


def demo_rsa_primitives(ctx: Context) -> DemoResult:
    result = DemoResult(
        name="RSA encryption and signatures",
        description="OAEP, PKCS#1 v1.5 encryption, RSA-PSS, and PKCS#1 v1.5 signatures.",
        usage_example=textwrap.dedent(
            """\
            from Crypto.Hash import SHA256
            from Crypto.Cipher import PKCS1_OAEP
            from Crypto.Signature import pss

            key = RSA.generate(2048)
            ciphertext = PKCS1_OAEP.new(key.public_key(), hashAlgo=SHA256).encrypt(b"secret")
            signature = pss.new(key).sign(SHA256.new(b"message"))
            """
        ),
    )

    key = ctx.rsa_key
    message = b"RSA primitive demo"
    small_secret = b"0123456789abcdef"

    oaep_ciphertext = PKCS1_OAEP.new(key.public_key(), hashAlgo=SHA256, label=b"context").encrypt(message)
    oaep_plaintext = PKCS1_OAEP.new(key, hashAlgo=SHA256, label=b"context").decrypt(oaep_ciphertext)

    legacy_ciphertext = PKCS1_v1_5.new(key.public_key()).encrypt(small_secret)
    sentinel = get_random_bytes(len(small_secret))
    legacy_plaintext = PKCS1_v1_5.new(key).decrypt(legacy_ciphertext, sentinel, expected_pt_len=len(small_secret))

    msg_hash = SHA256.new(message)
    pss_signature = pss.new(key, salt_bytes=32).sign(msg_hash)
    pss.new(key.public_key(), salt_bytes=32).verify(SHA256.new(message), pss_signature)

    pkcs15_signature = pkcs1_15.new(key).sign(SHA256.new(message))
    pkcs1_15.new(key.public_key()).verify(SHA256.new(message), pkcs15_signature)

    result.outputs.update(
        {
            "oaep_ciphertext": oaep_ciphertext.hex(),
            "pkcs1_v1_5_ciphertext": legacy_ciphertext.hex(),
            "pss_signature": pss_signature.hex(),
            "pkcs1_v1_5_signature": pkcs15_signature.hex(),
        }
    )
    result.pass_checks.extend(
        [
            expect_equal("OAEP round-trip", oaep_plaintext, message),
            expect_equal("PKCS#1 v1.5 round-trip", legacy_plaintext, small_secret),
            condition("PSS verification succeeded", True, "signature verified", "signature did not verify"),
            condition("PKCS#1 v1.5 verification succeeded", True, "signature verified", "signature did not verify"),
        ]
    )

    bad_oaep = bytearray(oaep_ciphertext)
    bad_oaep[5] ^= 1
    bad_legacy = bytearray(legacy_ciphertext)
    bad_legacy[7] ^= 1
    bad_pss = bytearray(pss_signature)
    bad_pss[-1] ^= 1
    bad_pkcs15 = bytearray(pkcs15_signature)
    bad_pkcs15[0] ^= 1

    result.expected_failures.extend(
        [
            expect_exception(
                "OAEP rejects modified ciphertext",
                lambda: PKCS1_OAEP.new(key, hashAlgo=SHA256, label=b"context").decrypt(bytes(bad_oaep)),
                ValueError,
            ),
            expect_sentinel(
                "PKCS#1 v1.5 decryption returns sentinel on failure",
                PKCS1_v1_5.new(key).decrypt(bytes(bad_legacy), sentinel, expected_pt_len=len(small_secret)),
                sentinel,
            ),
            expect_exception(
                "PSS rejects modified signature",
                lambda: pss.new(key.public_key(), salt_bytes=32).verify(SHA256.new(message), bytes(bad_pss)),
                ValueError,
            ),
            expect_exception(
                "PKCS#1 v1.5 signature verification rejects modification",
                lambda: pkcs1_15.new(key.public_key()).verify(SHA256.new(message), bytes(bad_pkcs15)),
                ValueError,
                TypeError,
            ),
        ]
    )
    result.notes.append("PKCS#1 v1.5 encryption is kept only as a legacy interop example. OAEP is the modern RSA encryption default.")
    return result


def demo_rsa_key_io_and_parameters(ctx: Context) -> DemoResult:
    result = DemoResult(
        name="RSA key import/export and parameter extraction",
        description="PKCS#8-protected private export, DER/PEM/OpenSSH public export, parameter extraction, and key reconstruction.",
        usage_example=textwrap.dedent(
            """\
            key = RSA.generate(2048)
            private_pem = key.export_key(
                format="PEM",
                pkcs=8,
                passphrase=b"secret",
                protection="PBKDF2WithHMAC-SHA512AndAES256-CBC",
            )
            reloaded = RSA.import_key(private_pem, passphrase=b"secret")
            """
        ),
    )

    key = ctx.rsa_key
    private_pem = key.export_key(
        format="PEM",
        pkcs=8,
        passphrase=ctx.passphrase,
        protection="PBKDF2WithHMAC-SHA512AndAES256-CBC",
        prot_params={"iteration_count": 21_000},
    )
    private_der = key.export_key(
        format="DER",
        pkcs=8,
        passphrase=ctx.passphrase,
        protection="PBKDF2WithHMAC-SHA512AndAES256-CBC",
        prot_params={"iteration_count": 21_000},
    )
    public_pem = key.public_key().export_key(format="PEM")
    public_der = key.public_key().export_key(format="DER")
    public_openssh = key.public_key().export_key(format="OpenSSH")

    loaded_private = RSA.import_key(private_pem, passphrase=ctx.passphrase)
    loaded_private_der = RSA.import_key(private_der, passphrase=ctx.passphrase)
    loaded_public = RSA.import_key(public_pem)
    loaded_public_der = RSA.import_key(public_der)
    loaded_openssh = RSA.import_key(public_openssh)

    reconstructed_public = RSA.construct((key.n, key.e))
    reconstructed_private = RSA.construct((key.n, key.e, key.d, key.p, key.q, key.u))

    result.outputs.update(
        {
            "public_openssh_prefix": public_openssh.split()[0],
            "size_in_bits": key.size_in_bits(),
            "n_bits": key.n.bit_length(),
            "e": key.e,
            "u": key.u,
        }
    )
    result.pass_checks.extend(
        [
            expect_equal("PEM private import recovered modulus", loaded_private.n, key.n),
            expect_equal("DER private import recovered modulus", loaded_private_der.n, key.n),
            expect_equal("PEM public import recovered modulus", loaded_public.n, key.n),
            expect_equal("DER public import recovered modulus", loaded_public_der.n, key.n),
            expect_equal("OpenSSH public import recovered modulus", loaded_openssh.n, key.n),
            expect_equal("Public reconstruction preserved modulus", reconstructed_public.n, key.n),
            expect_equal("Private reconstruction preserved d", reconstructed_private.d, key.d),
        ]
    )
    result.expected_failures.append(
        expect_exception(
            "RSA protected private key rejects wrong passphrase",
            lambda: RSA.import_key(private_pem, passphrase=b"wrong-pass"),
            ValueError,
        )
    )
    return result


def demo_ecc_ecdsa_and_io(ctx: Context) -> DemoResult:
    result = DemoResult(
        name="ECC P-256, ECDSA, and key formats",
        description="Randomized ECDSA, deterministic RFC6979 ECDSA, SEC1/raw/OpenSSH import-export, and key reconstruction.",
        usage_example=textwrap.dedent(
            """\
            from Crypto.PublicKey import ECC
            from Crypto.Signature import DSS
            from Crypto.Hash import SHA256

            key = ECC.generate(curve="P-256")
            sig = DSS.new(key, "deterministic-rfc6979").sign(SHA256.new(b"msg"))
            """
        ),
    )

    key = ctx.p256_key
    message = b"ECDSA on P-256"
    randomized_sig = DSS.new(key, "fips-186-3").sign(SHA256.new(message))
    deterministic_sig = DSS.new(key, "deterministic-rfc6979").sign(SHA256.new(message))

    DSS.new(key.public_key(), "fips-186-3").verify(SHA256.new(message), randomized_sig)
    DSS.new(key.public_key(), "deterministic-rfc6979").verify(SHA256.new(message), deterministic_sig)

    public_pem = key.public_key().export_key(format="PEM")
    public_der = key.public_key().export_key(format="DER")
    public_sec1 = key.public_key().export_key(format="SEC1")
    public_sec1_compressed = key.public_key().export_key(format="SEC1", compress=True)
    public_raw = key.public_key().export_key(format="raw")
    public_openssh = key.public_key().export_key(format="OpenSSH")
    private_pem = key.export_key(
        format="PEM",
        use_pkcs8=True,
        passphrase=ctx.passphrase,
        protection="PBKDF2WithHMAC-SHA512AndAES256-CBC",
        prot_params={"iteration_count": 21_000},
    )

    loaded_private = ECC.import_key(private_pem, passphrase=ctx.passphrase)
    loaded_pem = ECC.import_key(public_pem)
    loaded_der = ECC.import_key(public_der)
    loaded_sec1 = ECC.import_key(public_sec1, curve_name="P-256")
    loaded_raw = ECC.import_key(public_raw, curve_name="P-256")
    loaded_openssh = ECC.import_key(public_openssh)
    reconstructed_private = ECC.construct(curve="P-256", d=key.d)
    reconstructed_public = ECC.construct(curve="P-256", point_x=key.pointQ.x, point_y=key.pointQ.y)

    result.outputs.update(
        {
            "public_sec1_length": len(public_sec1),
            "public_sec1_compressed_length": len(public_sec1_compressed),
            "public_raw_length": len(public_raw),
            "public_openssh_prefix": public_openssh.split()[0],
            "curve": key.curve,
            "private_scalar_hex_prefix": hex(int(key.d))[:34],
        }
    )
    result.pass_checks.extend(
        [
            condition("ECDSA randomized signature verified", True, "verified", "did not verify"),
            condition("ECDSA deterministic signature verified", True, "verified", "did not verify"),
            expect_equal("ECC private import recovered scalar", int(loaded_private.d), int(key.d)),
            expect_equal("PEM public import recovered X", int(loaded_pem.pointQ.x), int(key.pointQ.x)),
            expect_equal("DER public import recovered X", int(loaded_der.pointQ.x), int(key.pointQ.x)),
            expect_equal("SEC1 public import recovered X", int(loaded_sec1.pointQ.x), int(key.pointQ.x)),
            expect_equal("raw public import recovered X", int(loaded_raw.pointQ.x), int(key.pointQ.x)),
            expect_equal("OpenSSH public import recovered X", int(loaded_openssh.pointQ.x), int(key.pointQ.x)),
            expect_equal("reconstructed private scalar", int(reconstructed_private.d), int(key.d)),
            expect_equal("reconstructed public X", int(reconstructed_public.pointQ.x), int(key.pointQ.x)),
        ]
    )

    bad_sig = bytearray(deterministic_sig)
    bad_sig[-1] ^= 1
    result.expected_failures.extend(
        [
            expect_exception(
                "ECDSA verify rejects modified signature",
                lambda: DSS.new(key.public_key(), "deterministic-rfc6979").verify(SHA256.new(message), bytes(bad_sig)),
                ValueError,
            ),
            expect_exception(
                "ECC protected private key rejects wrong passphrase",
                lambda: ECC.import_key(private_pem, passphrase=b"wrong-pass"),
                ValueError,
            ),
        ]
    )
    result.notes.append("P-256 is the easiest choice when you need strong NIST/FIPS alignment for ECC signatures and ECDH.")
    return result


def demo_ed25519(ctx: Context) -> DemoResult:
    result = DemoResult(
        name="Ed25519 signatures and key formats",
        description="Ed25519 signing, raw key import/export, and PKCS#8-protected private export.",
        usage_example=textwrap.dedent(
            """\
            from Crypto.PublicKey import ECC
            from Crypto.Signature import eddsa

            key = ECC.generate(curve="Ed25519")
            signature = eddsa.new(key, "rfc8032").sign(b"message")
            """
        ),
    )

    key = ctx.ed25519_key
    message = b"Ed25519 message"
    signature = eddsa.new(key, "rfc8032").sign(message)
    eddsa.new(key.public_key(), "rfc8032").verify(message, signature)

    raw_public = key.public_key().export_key(format="raw")
    raw_private = key.seed
    imported_public = eddsa.import_public_key(raw_public)
    imported_private = eddsa.import_private_key(raw_private)

    private_pem = key.export_key(
        format="PEM",
        use_pkcs8=True,
        passphrase=ctx.passphrase,
        protection="PBKDF2WithHMAC-SHA512AndAES256-CBC",
        prot_params={"iteration_count": 21_000},
    )
    loaded_private = ECC.import_key(private_pem, passphrase=ctx.passphrase)

    result.outputs.update(
        {
            "raw_public_length": len(raw_public),
            "seed_length": len(raw_private),
            "signature_length": len(signature),
        }
    )
    result.pass_checks.extend(
        [
            condition("Ed25519 signature verified", True, "verified", "did not verify"),
            expect_equal("Raw public import preserved curve", imported_public.curve, "Ed25519"),
            expect_equal("Raw private import preserved curve", imported_private.curve, "Ed25519"),
            expect_equal("PEM import recovered the same seed", loaded_private.seed, key.seed),
        ]
    )

    bad_sig = bytearray(signature)
    bad_sig[0] ^= 1
    result.expected_failures.extend(
        [
            expect_exception(
                "Ed25519 verify rejects modified signature",
                lambda: eddsa.new(key.public_key(), "rfc8032").verify(message, bytes(bad_sig)),
                ValueError,
            ),
            expect_exception(
                "Ed25519 protected private key rejects wrong passphrase",
                lambda: ECC.import_key(private_pem, passphrase=b"wrong-pass"),
                ValueError,
            ),
        ]
    )
    result.notes.append("Ed25519 uses a seed-based private key model instead of the scalar d model used by NIST P-curves.")
    return result


def demo_ecdh_p256() -> DemoResult:
    result = DemoResult(
        name="ECDH on P-256 + HKDF + AES-GCM",
        description="Pair-wise key agreement, KDF extraction, and immediate use with AEAD.",
        usage_example=textwrap.dedent(
            """\
            from Crypto.PublicKey import ECC
            from Crypto.Protocol.DH import key_agreement

            alice = ECC.generate(curve="P-256")
            bob = ECC.generate(curve="P-256")
            """
        ),
    )

    alice = ECC.generate(curve="P-256")
    bob = ECC.generate(curve="P-256")
    salt = get_random_bytes(16)
    context = b"P-256 ECDH demo"

    def kdf(shared: bytes) -> bytes:
        return derive_hkdf_key(shared, 32, salt, context)

    alice_key = key_agreement(static_priv=alice, static_pub=bob.public_key(), kdf=kdf)
    bob_key = key_agreement(static_priv=bob, static_pub=alice.public_key(), kdf=kdf)

    cipher = AES.new(alice_key, AES.MODE_GCM)
    ciphertext, tag = cipher.encrypt_and_digest(b"hello over ECDH")
    nonce = cipher.nonce
    recovered = AES.new(bob_key, AES.MODE_GCM, nonce=nonce).decrypt_and_verify(ciphertext, tag)

    wrong_peer = ECC.generate(curve="P-256")
    wrong_key = key_agreement(static_priv=wrong_peer, static_pub=alice.public_key(), kdf=kdf)

    result.outputs.update(
        {
            "session_key": alice_key.hex(),
            "nonce": nonce.hex(),
            "ciphertext": ciphertext.hex(),
            "tag": tag.hex(),
        }
    )
    result.pass_checks.extend(
        [
            expect_equal("Alice and Bob derived the same key", alice_key, bob_key),
            expect_equal("ECDH-derived AES-GCM round-trip", recovered, b"hello over ECDH"),
        ]
    )
    result.expected_failures.extend(
        [
            expect_not_equal(
                "Wrong private key produces a different session key",
                wrong_key,
                alice_key,
                "derived key changed as expected",
            ),
            expect_exception(
                "GCM rejects modified tag after ECDH key agreement",
                lambda: AES.new(bob_key, AES.MODE_GCM, nonce=nonce).decrypt_and_verify(ciphertext, tag[:-1] + bytes([tag[-1] ^ 1])),
                ValueError,
            ),
        ]
    )
    return result


def demo_x25519(ctx: Context) -> DemoResult:
    result = DemoResult(
        name="X25519 / Curve25519 key agreement",
        description="X25519 shared secret derivation, HKDF extraction, and raw key import/export.",
        usage_example=textwrap.dedent(
            """\
            from Crypto.PublicKey import ECC
            from Crypto.Protocol.DH import key_agreement

            alice = ECC.generate(curve="Curve25519")
            bob = ECC.generate(curve="Curve25519")
            """
        ),
    )

    alice = ctx.x25519_key
    bob = ECC.generate(curve="Curve25519")
    salt = get_random_bytes(16)

    def kdf(shared: bytes) -> bytes:
        return derive_hkdf_key(shared, 32, salt, b"X25519 demo")

    alice_key = key_agreement(static_priv=alice, static_pub=bob.public_key(), kdf=kdf)
    bob_key = key_agreement(static_priv=bob, static_pub=alice.public_key(), kdf=kdf)

    raw_public = alice.public_key().export_key(format="raw")
    raw_private = alice.seed
    imported_public = import_x25519_public_key(raw_public)
    imported_private = import_x25519_private_key(raw_private)

    wrong_key = key_agreement(
        static_priv=ECC.generate(curve="Curve25519"),
        static_pub=bob.public_key(),
        kdf=kdf,
    )

    result.outputs.update(
        {
            "session_key": alice_key.hex(),
            "raw_public_length": len(raw_public),
            "seed_length": len(raw_private),
        }
    )
    result.pass_checks.extend(
        [
            expect_equal("Alice and Bob derived the same X25519 key", alice_key, bob_key),
            expect_equal("Imported public key curve", imported_public.curve, "Curve25519"),
            expect_equal("Imported private key curve", imported_private.curve, "Curve25519"),
        ]
    )
    result.expected_failures.append(
        expect_not_equal(
            "Wrong X25519 private key derives a different secret",
            wrong_key,
            alice_key,
            "derived key changed as expected",
        )
    )
    result.notes.append("X25519 is for key agreement only. It is not a signature algorithm.")
    return result


def demo_stream_ciphers() -> DemoResult:
    result = DemoResult(
        name="Stream ciphers and authenticated stream constructions",
        description="ChaCha20-Poly1305, XChaCha20-Poly1305, and Salsa20 with Encrypt-then-MAC.",
        usage_example=textwrap.dedent(
            """\
            from Crypto.Cipher import ChaCha20_Poly1305, Salsa20

            key = get_random_bytes(32)
            cipher = ChaCha20_Poly1305.new(key=key)
            """
        ),
    )

    key = get_random_bytes(32)
    aad = b"stream-header"

    cipher = ChaCha20_Poly1305.new(key=key)
    cipher.update(aad)
    chacha_ct, chacha_tag = cipher.encrypt_and_digest(b"secret stream")
    chacha_nonce = cipher.nonce
    dec = ChaCha20_Poly1305.new(key=key, nonce=chacha_nonce)
    dec.update(aad)
    chacha_pt = dec.decrypt_and_verify(chacha_ct, chacha_tag)

    xnonce = get_random_bytes(24)
    cipher = ChaCha20_Poly1305.new(key=key, nonce=xnonce)
    xct, xtag = cipher.encrypt_and_digest(b"secret stream x")
    xpt = ChaCha20_Poly1305.new(key=key, nonce=xnonce).decrypt_and_verify(xct, xtag)

    salsa = Salsa20.new(key=key)
    salsa_ct = salsa.encrypt(b"legacy stream")
    package = salsa.nonce + salsa_ct
    mac = HMAC.new(key, digestmod=SHA256)
    mac.update(package)
    salsa_tag = mac.digest()

    verifier = HMAC.new(key, digestmod=SHA256)
    verifier.update(package)
    verifier.verify(salsa_tag)
    recovered = Salsa20.new(key=key, nonce=salsa.nonce).decrypt(salsa_ct)

    result.outputs.update(
        {
            "chacha20_poly1305_nonce": chacha_nonce.hex(),
            "xchacha20_poly1305_nonce": xnonce.hex(),
            "salsa20_nonce": salsa.nonce.hex(),
            "salsa20_tag": salsa_tag.hex(),
        }
    )
    result.pass_checks.extend(
        [
            expect_equal("ChaCha20-Poly1305 round-trip", chacha_pt, b"secret stream"),
            expect_equal("XChaCha20-Poly1305 round-trip", xpt, b"secret stream x"),
            expect_equal("Salsa20 + HMAC round-trip", recovered, b"legacy stream"),
        ]
    )

    result.expected_failures.extend(
        [
            expect_exception(
                "ChaCha20-Poly1305 rejects bad tag",
                lambda: (lambda c: (c.update(aad), c.decrypt_and_verify(chacha_ct, chacha_tag[:-1] + bytes([chacha_tag[-1] ^ 1]))))(ChaCha20_Poly1305.new(key=key, nonce=chacha_nonce)),
                ValueError,
            ),
            expect_exception(
                "Salsa20 package HMAC rejects tampering",
                lambda: (lambda v: (v.update(package[:-1] + bytes([package[-1] ^ 1])), v.verify(salsa_tag)))(HMAC.new(key, digestmod=SHA256)),
                ValueError,
            ),
        ]
    )
    result.notes.append("Salsa20 alone is unauthenticated. The example deliberately wraps it in Encrypt-then-MAC.")
    return result


def demo_secret_sharing_and_kdfs() -> DemoResult:
    result = DemoResult(
        name="Secret sharing and KDFs",
        description="Shamir split/combine plus HKDF, PBKDF2, and scrypt helper outputs.",
        usage_example=textwrap.dedent(
            """\
            from Crypto.Protocol.SecretSharing import Shamir

            secret = get_random_bytes(16)
            shares = Shamir.split(2, 5, secret)
            restored = Shamir.combine(shares[:2])
            """
        ),
    )

    secret = get_random_bytes(16)
    shares = Shamir.split(2, 5, secret)
    restored = Shamir.combine(shares[:2])

    bad_shares = list(shares[:2])
    index, share = bad_shares[1]
    bad_shares[1] = (index, bytes([share[0] ^ 1]) + share[1:])
    wrong_restored = Shamir.combine(bad_shares)

    salt = get_random_bytes(16)
    hkdf_key = derive_hkdf_key(secret, 32, salt, b"demo")
    pbkdf2_key = derive_pbkdf2_key(b"passphrase", salt, 32, count=50_000)
    scrypt_key = derive_scrypt_key(b"passphrase", salt, 32)

    result.outputs.update(
        {
            "shares_preview": [(idx, piece.hex()) for idx, piece in shares[:3]],
            "hkdf_key": hkdf_key.hex(),
            "pbkdf2_key": pbkdf2_key.hex(),
            "scrypt_key": scrypt_key.hex(),
        }
    )
    result.pass_checks.extend(
        [
            expect_equal("Shamir combine restored the secret", restored, secret),
            condition("HKDF length is 32 bytes", len(hkdf_key) == 32, "32 bytes", "wrong length"),
            condition("PBKDF2 length is 32 bytes", len(pbkdf2_key) == 32, "32 bytes", "wrong length"),
            condition("scrypt length is 32 bytes", len(scrypt_key) == 32, "32 bytes", "wrong length"),
        ]
    )
    result.expected_failures.append(
        expect_not_equal(
            "Modified Shamir share silently reconstructs the wrong secret",
            wrong_restored,
            secret,
            "reconstructed output changed without authenticity protection",
        )
    )
    result.notes.append("Shamir split/combine gives threshold reconstruction, not authenticity. Authenticate shares if hostile modification is possible.")
    return result


def demo_hpke() -> DemoResult:
    result = DemoResult(
        name="HPKE (base, Auth, and PSK modes)",
        description="RFC 9180-style hybrid public-key encryption using PyCryptodome's HPKE module.",
        usage_example=textwrap.dedent(
            """\
            from Crypto.Protocol import HPKE

            receiver = ECC.generate(curve="P-256")
            encryptor = HPKE.new(receiver_key=receiver.public_key(), aead_id=HPKE.AEAD.AES128_GCM)
            ciphertext = encryptor.seal(b"message")
            decryptor = HPKE.new(receiver_key=receiver, aead_id=HPKE.AEAD.AES128_GCM, enc=encryptor.enc)
            """
        ),
    )

    receiver = ECC.generate(curve="P-256")
    sender = ECC.generate(curve="P-256")
    psk_pair = (b"psk-id", get_random_bytes(32))

    base_sender = HPKE.new(receiver_key=receiver.public_key(), aead_id=HPKE.AEAD.AES128_GCM, info=b"ctx")
    base_ct = base_sender.seal(b"base mode", auth_data=b"aad")
    base_receiver = HPKE.new(receiver_key=receiver, aead_id=HPKE.AEAD.AES128_GCM, enc=base_sender.enc, info=b"ctx")
    base_pt = base_receiver.unseal(base_ct, auth_data=b"aad")

    auth_sender = HPKE.new(
        receiver_key=receiver.public_key(),
        sender_key=sender,
        aead_id=HPKE.AEAD.CHACHA20_POLY1305,
        info=b"ctx",
    )
    auth_ct = auth_sender.seal(b"auth mode")
    auth_receiver = HPKE.new(
        receiver_key=receiver,
        sender_key=sender.public_key(),
        aead_id=HPKE.AEAD.CHACHA20_POLY1305,
        enc=auth_sender.enc,
        info=b"ctx",
    )
    auth_pt = auth_receiver.unseal(auth_ct)

    psk_sender = HPKE.new(
        receiver_key=receiver.public_key(),
        aead_id=HPKE.AEAD.AES256_GCM,
        psk=psk_pair,
        info=b"ctx",
    )
    psk_ct = psk_sender.seal(b"psk mode")
    psk_receiver = HPKE.new(
        receiver_key=receiver,
        aead_id=HPKE.AEAD.AES256_GCM,
        psk=psk_pair,
        enc=psk_sender.enc,
        info=b"ctx",
    )
    psk_pt = psk_receiver.unseal(psk_ct)

    result.outputs.update(
        {
            "base_enc_length": len(base_sender.enc),
            "auth_enc_length": len(auth_sender.enc),
            "psk_enc_length": len(psk_sender.enc),
        }
    )
    result.pass_checks.extend(
        [
            expect_equal("HPKE base mode round-trip", base_pt, b"base mode"),
            expect_equal("HPKE auth mode round-trip", auth_pt, b"auth mode"),
            expect_equal("HPKE PSK mode round-trip", psk_pt, b"psk mode"),
        ]
    )
    result.expected_failures.extend(
        [
            expect_exception(
                "HPKE base mode rejects wrong AAD",
                lambda: HPKE.new(receiver_key=receiver, aead_id=HPKE.AEAD.AES128_GCM, enc=base_sender.enc, info=b"ctx").unseal(base_ct, auth_data=b"wrong"),
                ValueError,
            ),
            expect_exception(
                "HPKE auth mode rejects wrong sender public key",
                lambda: HPKE.new(
                    receiver_key=receiver,
                    sender_key=ECC.generate(curve="P-256").public_key(),
                    aead_id=HPKE.AEAD.CHACHA20_POLY1305,
                    enc=auth_sender.enc,
                    info=b"ctx",
                ).unseal(auth_ct),
                ValueError,
            ),
            expect_exception(
                "HPKE PSK mode rejects wrong PSK",
                lambda: HPKE.new(
                    receiver_key=receiver,
                    aead_id=HPKE.AEAD.AES256_GCM,
                    psk=(psk_pair[0], get_random_bytes(32)),
                    enc=psk_sender.enc,
                    info=b"ctx",
                ).unseal(psk_ct),
                ValueError,
            ),
        ]
    )
    result.notes.append("HPKE is the cleanest high-level way in PyCryptodome to combine asymmetric setup with AEAD-protected payload transport.")
    return result


def demo_hybrid_rsa_aes(ctx: Context) -> DemoResult:
    result = DemoResult(
        name="Hybrid envelope: RSA-OAEP + AES-EAX",
        description="A common real-world composition: RSA protects a random session key and AES-EAX protects the bulk plaintext.",
        usage_example=textwrap.dedent(
            """\
            session_key = get_random_bytes(16)
            wrapped_session_key = PKCS1_OAEP.new(rsa_public).encrypt(session_key)
            cipher = AES.new(session_key, AES.MODE_EAX)
            ciphertext, tag = cipher.encrypt_and_digest(plaintext)
            """
        ),
    )

    plaintext = b"Large payloads should use hybrid encryption"
    session_key = get_random_bytes(16)
    wrapped_session_key = PKCS1_OAEP.new(ctx.rsa_key.public_key(), hashAlgo=SHA256).encrypt(session_key)

    cipher = AES.new(session_key, AES.MODE_EAX)
    ciphertext, tag = cipher.encrypt_and_digest(plaintext)
    nonce = cipher.nonce

    recovered_session_key = PKCS1_OAEP.new(ctx.rsa_key, hashAlgo=SHA256).decrypt(wrapped_session_key)
    recovered_plaintext = AES.new(recovered_session_key, AES.MODE_EAX, nonce=nonce).decrypt_and_verify(ciphertext, tag)

    result.outputs.update(
        {
            "wrapped_session_key": wrapped_session_key.hex(),
            "nonce": nonce.hex(),
            "ciphertext": ciphertext.hex(),
            "tag": tag.hex(),
        }
    )
    result.pass_checks.extend(
        [
            expect_equal("Hybrid session key unwrap", recovered_session_key, session_key),
            expect_equal("Hybrid ciphertext round-trip", recovered_plaintext, plaintext),
        ]
    )

    bad_tag = tag[:-1] + bytes([tag[-1] ^ 1])
    result.expected_failures.extend(
        [
            expect_exception(
                "Hybrid AEAD layer rejects tampered tag",
                lambda: AES.new(recovered_session_key, AES.MODE_EAX, nonce=nonce).decrypt_and_verify(ciphertext, bad_tag),
                ValueError,
            ),
            expect_exception(
                "Hybrid unwrap rejects wrong RSA private key",
                lambda: PKCS1_OAEP.new(RSA.generate(2048), hashAlgo=SHA256).decrypt(wrapped_session_key),
                ValueError,
            ),
        ]
    )
    return result


def demo_unexpected_failure_capture() -> DemoResult:
    return DemoResult(
        name="Internal runner health",
        description="A trivial sanity record proving the report generator itself executed.",
        usage_example="outcome = demo_unexpected_failure_capture()",
        outputs={"runner": "alive"},
        pass_checks=[condition("Runner reached the final demo", True, "yes", "no")],
    )


def run_showcase() -> List[DemoResult]:
    ctx = build_context()
    demos = [
        lambda: demo_utilities_and_low_level_helpers(),
        lambda: demo_hashes_and_hmac(),
        lambda: demo_padding_and_cbc(),
        lambda: demo_aes_aead_modes(),
        lambda: demo_ctr_and_counter(),
        lambda: demo_aes_kw_kwp(),
        lambda: demo_rsa_primitives(ctx),
        lambda: demo_rsa_key_io_and_parameters(ctx),
        lambda: demo_ecc_ecdsa_and_io(ctx),
        lambda: demo_ed25519(ctx),
        lambda: demo_ecdh_p256(),
        lambda: demo_x25519(ctx),
        lambda: demo_stream_ciphers(),
        lambda: demo_secret_sharing_and_kdfs(),
        lambda: demo_hpke(),
        lambda: demo_hybrid_rsa_aes(ctx),
        lambda: demo_unexpected_failure_capture(),
    ]

    results: List[DemoResult] = []
    for demo in demos:
        try:
            results.append(demo())
        except Exception:  # noqa: BLE001
            results.append(
                DemoResult(
                    name=getattr(demo, "__name__", demo.__class__.__name__),
                    description="This demo crashed unexpectedly.",
                    usage_example="",
                    unexpected_error=traceback.format_exc(),
                )
            )
    return results


def summarize(results: Iterable[DemoResult]) -> Dict[str, int]:
    totals = {"demos": 0, "all_green": 0, "unexpected_errors": 0, "checks": 0, "checks_passed": 0}
    for result in results:
        totals["demos"] += 1
        checks = result.pass_checks + result.expected_failures
        totals["checks"] += len(checks)
        totals["checks_passed"] += sum(1 for item in checks if item.passed)
        if result.ok:
            totals["all_green"] += 1
        if result.unexpected_error:
            totals["unexpected_errors"] += 1
    return totals


def write_usage_report(results: List[DemoResult], path: Path = REPORT_PATH) -> Path:
    totals = summarize(results)
    lines: List[str] = []
    lines.append("# Usage Report")
    lines.append("")
    lines.append("This report was generated by running `pycryptodome_showcase_runner.py` against the local PyCryptodome installation.")
    lines.append("")
    lines.append("## Summary")
    lines.append("")
    lines.append(f"- Demos executed: **{totals['demos']}**")
    lines.append(f"- Demos fully green: **{totals['all_green']}**")
    lines.append(f"- Total checks: **{totals['checks_passed']} / {totals['checks']}**")
    lines.append(f"- Unexpected runner errors: **{totals['unexpected_errors']}**")
    lines.append("")
    lines.append("## How to use this script")
    lines.append("")
    lines.append("```bash")
    lines.append("python pycryptodome_showcase_runner.py")
    lines.append("```")
    lines.append("")
    lines.append("That command re-runs the demonstrations and rewrites this report in-place.")
    lines.append("")

    for idx, result in enumerate(results, 1):
        lines.append(f"## {idx}. {result.name}")
        lines.append("")
        lines.append(result.description)
        lines.append("")
        lines.append(f"Overall status: **{'PASS' if result.ok else 'FAIL'}**")
        lines.append("")
        if result.usage_example:
            lines.append("### Example usage")
            lines.append("")
            lines.append("```python")
            lines.append(result.usage_example.rstrip())
            lines.append("```")
            lines.append("")
        if result.outputs:
            lines.append("### Observed outputs")
            lines.append("")
            for key, value in result.outputs.items():
                if isinstance(value, (dict, list, tuple)):
                    lines.append(f"- **{key}**:")
                    lines.append("")
                    lines.append("```json")
                    lines.append(json.dumps(value, indent=2, ensure_ascii=False, default=str))
                    lines.append("```")
                else:
                    lines.append(f"- **{key}**: `{_repr_value(value)}`")
            lines.append("")
        if result.pass_checks:
            lines.append("### Success-path checks")
            lines.append("")
            for item in result.pass_checks:
                lines.append(f"- **{item.status}** — {item.label}: {item.detail}")
            lines.append("")
        if result.expected_failures:
            lines.append("### Expected failure-path checks")
            lines.append("")
            for item in result.expected_failures:
                lines.append(f"- **{item.status}** — {item.label}: {item.detail}")
            lines.append("")
        if result.notes:
            lines.append("### Notes")
            lines.append("")
            for note in result.notes:
                lines.append(f"- {note}")
            lines.append("")
        if result.unexpected_error:
            lines.append("### Unexpected error")
            lines.append("")
            lines.append("```text")
            lines.append(result.unexpected_error.rstrip())
            lines.append("```")
            lines.append("")

    path.write_text("\n".join(lines), encoding="utf-8")
    return path


def main() -> int:
    results = run_showcase()
    report_path = write_usage_report(results)
    totals = summarize(results)
    print(f"Wrote report to {report_path}")
    print(json.dumps(totals, indent=2, sort_keys=True))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Usage Report

This report was generated by running pycryptodome_showcase_runner.py against the local PyCryptodome installation.

Summary

  • Demos executed: 17
  • Demos fully green: 17
  • Total checks: 103 / 103
  • Unexpected runner errors: 0

How to use this script

That command re-runs the demonstrations and rewrites this report in-place.

1. Utilities and low-level helpers

Integer/byte conversions, XOR helpers, number theory helpers, RFC1751, PEM, and PKCS#8 wrappers.

Overall status: PASS

Example usage

from pycryptodome_showcase_runner import (
    b2l, l2b, random_bytes, xor_bytes, xor_with_constant,
    generate_prime, generate_strong_prime, mod_inverse,
    rfc1751_encode, rfc1751_decode, pem_wrap, pem_unwrap,
    pkcs8_wrap_der, pkcs8_unwrap_der,
)

sample = l2b(65537)
assert b2l(sample) == 65537
mixed = xor_bytes(b"ABCDEF12", b"12345678")
flipped = xor_with_constant(b"AAAA", 0x20)

Observed outputs

  • l2b_65537_hex: 010001
  • random_bytes_length: 24
  • split_blocks_lengths:
[
  8,
  8,
  8
]
  • xor_bytes_hex: 03171b113b11003a1a15585c54030d0f
  • xor_with_constant_hex: 61616161
  • prime_bits: 256
  • strong_prime_bits: 512
  • inverse_mod_11_of_3: 4
  • rfc1751_words: OWN GUSH RAYS COME CARE HUGH
  • pem_marker: TEST DATA
  • pem_encrypted: True
  • pkcs8_oid: 1.2.840.113549.1.1.1
  • pkcs8_inner_der_length: 608
  • pkcs8_params: None

Success-path checks

  • PASS — l2b/b2l round-trip: values match
  • PASS — random_bytes returned the requested length: 24 bytes returned
  • PASS — split_blocks partitioned data evenly: three 8-byte blocks
  • PASS — xor_bytes produced same-length output: length preserved
  • PASS — getPrime produced a prime: isPrime returned True
  • PASS — getStrongPrime produced a prime: isPrime returned True
  • PASS — inverse(3,11) == 4: values match
  • PASS — RFC1751 round-trip: values match
  • PASS — PEM decode recovered original bytes: values match
  • PASS — PKCS#8 unwrap recovered DER: values match

Expected failure-path checks

  • PASS — xor_bytes rejects mismatched lengths: raised ValueError: inputs must have the same length
  • PASS — inverse rejects non-invertible input: raised ValueError: base is not invertible for the given modulus
  • PASS — RFC1751 rejects malformed word list: raised ValueError: list.index(x): x not in list
  • PASS — PEM decode rejects wrong passphrase: raised ValueError: Padding is incorrect.
  • PASS — PKCS#8 unwrap rejects wrong passphrase: raised ValueError: Error decoding PKCS#8 (PBES1[Unknown OID for PBES1],PBES2[Invalid])

Notes

  • PEM.encode()/decode() demonstrate the low-level textual envelope. For private keys, PKCS#8 protection is the stronger default.
  • RFC1751 is mostly historical today, but it is useful to know the module exists.

2. Hashes and HMAC

SHA-256, SHA3-256, and HMAC verification including expected tamper failure.

Overall status: PASS

Example usage

from pycryptodome_showcase_runner import demo_hashes_and_hmac

outcome = demo_hashes_and_hmac()
print(outcome.outputs["sha256"])

Observed outputs

  • sha256: eb201af5aaf0d60629d3d2a61e466cfc0fedb517add831ecac5235e1daa963d6
  • sha3_256: 8d474817ce261f93fcb4ee35f2d903e6751bbe179e9d7c77423c76acd0dba9e5
  • hmac_tag: 9dbf3946c380433c7f0bda8e546660f794196ee2584a635b24cb1c28f21021a2

Success-path checks

  • PASS — SHA-256 digest is 32 bytes: digest length is 32
  • PASS — SHA3-256 digest is 32 bytes: digest length is 32
  • PASS — HMAC verification succeeded: tag verified

Expected failure-path checks

  • PASS — HMAC verification fails on modified message: raised ValueError: MAC check failed

3. Padding and AES-CBC

CBC mode with PKCS#7/ISO7816/X9.23 padding and expected padding failure on tamper.

Overall status: PASS

Example usage

from pycryptodome_showcase_runner import pad_bytes, unpad_bytes
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes

key = get_random_bytes(16)
iv = get_random_bytes(16)
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
ciphertext = cipher.encrypt(pad_bytes(b"confidential", 16, style="pkcs7"))

Observed outputs

  • padding_styles:
{
  "pkcs7": {
    "padded_length": 32,
    "round_trip": true
  },
  "iso7816": {
    "padded_length": 32,
    "round_trip": true
  },
  "x923": {
    "padded_length": 32,
    "round_trip": true
  }
}
  • cbc_iv: 9cf5e978c3eecede6c299605a245c3a2
  • cbc_ciphertext: adc3c7bbb570769cd3e43042f8d87b790d05b8136847db0e87a2344300f536b9

Success-path checks

  • PASS — CBC round-trip recovered original plaintext: values match
  • PASS — All padding styles round-tripped: all styles unpadded correctly

Expected failure-path checks

  • PASS — CBC + PKCS#7 unpad fails after last-byte tamper: raised ValueError: Padding is incorrect.

Notes

  • CBC provides confidentiality only. Padding errors are detectable here because the example explicitly calls unpad(); unauthenticated modification elsewhere may still decrypt to garbage.

4. AES AEAD modes

EAX, GCM, CCM, OCB, and SIV with authenticated AAD and tamper detection.

Overall status: PASS

Example usage

from pycryptodome_showcase_runner import demo_aes_aead_modes

report = demo_aes_aead_modes()
for name, details in report.outputs["modes"].items():
    print(name, details["tag"])

Observed outputs

  • modes:
{
  "EAX": {
    "nonce": "a7c128c7071541bd8fd095207e91466e",
    "ciphertext": "587ba7eba150fe36d1b631f0002fa80a51220a998f88",
    "tag": "d53a4c21c384d65ef18a6d33280e1ad2"
  },
  "GCM": {
    "nonce": "eda49c23c238cd8c27d2649aa306c542",
    "ciphertext": "be52b900fae67ff8989f99ac1b42c793cb2d71303aac",
    "tag": "96369a3fb82899c20e7deb1b5dc15b61"
  },
  "CCM": {
    "nonce": "f48a16c5972d0e807eae2d",
    "ciphertext": "c8469f241ac7ccfc05a863f1b8a0a17f8dbdb429996f",
    "tag": "9cbdf6566866bc090e0ca4c2c5bbafd9"
  },
  "OCB": {
    "nonce": "55528831d6e534af29bf337445d10e",
    "ciphertext": "0f3aff4f3bb26a211ad8c9421960ca4fc424d1a4e784",
    "tag": "8d19ebe4c9869b16a9df42aecf435ce8"
  },
  "SIV": {
    "nonce": "omitted",
    "ciphertext": "d9c1de911fdb21341cbf5f3715d90df79aafd678090a",
    "tag": "0170bd18ebfcd7de77657bf24d966010"
  }
}
  • siv_deterministic_ciphertext_match: True

Success-path checks

  • PASS — EAX round-trip: values match
  • PASS — GCM round-trip: values match
  • PASS — CCM round-trip: values match
  • PASS — OCB round-trip: values match
  • PASS — SIV round-trip: values match
  • PASS — SIV is deterministic when nonce is omitted: identical plaintext/AAD produced identical ciphertext+tag

Expected failure-path checks

  • PASS — EAX rejects bad tag: raised ValueError: MAC check failed
  • PASS — GCM rejects bad tag: raised ValueError: MAC check failed
  • PASS — CCM rejects bad tag: raised ValueError: MAC check failed
  • PASS — OCB rejects bad tag: raised ValueError: MAC check failed
  • PASS — SIV rejects bad tag: raised ValueError: MAC check failed

Notes

  • SIV is the odd one out: omitting the nonce is valid and makes encryption deterministic.

5. AES-CTR and Counter utility

CTR mode with custom Counter.new(), wraparound detection, and an example of silent failure when metadata is wrong.

Overall status: PASS

Example usage

from pycryptodome_showcase_runner import make_ctr_counter
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes

key = get_random_bytes(16)
prefix = get_random_bytes(8)
ctr = make_ctr_counter(nbits=64, prefix=prefix, initial_value=1)
cipher = AES.new(key, AES.MODE_CTR, counter=ctr)

Observed outputs

  • ctr_prefix: 085d815c509d534f
  • ctr_ciphertext: 894b2c410ec621dbcf34e8b62dd99af32e33fadb55a11a53919828fb969695d7b3872f096a6782db00
  • wrong_prefix_plaintext: 5c7b0aa1de75c9a7b731b5d93ea787e7db6f4c634a4b4733b99f0a9d51d076e84f6a0a42f42003cc86

Success-path checks

  • PASS — CTR round-trip: values match

Expected failure-path checks

  • PASS — Counter overflow raises OverflowError: raised OverflowError: The counter has wrapped around in CTR mode
  • PASS — Wrong CTR prefix silently yields wrong plaintext: plaintext changed without an authentication exception

Notes

  • CTR is a confidentiality mode only. The library can detect counter wraparound, but it cannot detect ciphertext tampering without an external MAC or AEAD wrapper.

6. AES Key Wrap (KW/KWP)

Deterministic authenticated key wrapping for key material.

Overall status: PASS

Example usage

from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes

kek = get_random_bytes(16)
wrapped = AES.new(kek, AES.MODE_KW).seal(b"12345678abcdefgh")

Observed outputs

  • kw_wrapped: 2f2f45b4e43b0ac26d9b9fa2a3983ea9c804a9a3e29293b2
  • kwp_wrapped: 955befec402df043e529fb81ffee58a80656402a2ba9b997d4822f65e86c6d19

Success-path checks

  • PASS — KW unwrapped the original key: values match
  • PASS — KWP unwrapped the original key: values match

Expected failure-path checks

  • PASS — KW rejects modified wrapped key: raised ValueError: Incorrect integrity check value
  • PASS — KWP rejects modified wrapped key: raised ValueError: Incorrect decryption

Notes

  • KW requires wrapped key material to be a multiple of 8 bytes. KWP removes that constraint.

7. RSA encryption and signatures

OAEP, PKCS#1 v1.5 encryption, RSA-PSS, and PKCS#1 v1.5 signatures.

Overall status: PASS

Example usage

from Crypto.Hash import SHA256
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Signature import pss

key = RSA.generate(2048)
ciphertext = PKCS1_OAEP.new(key.public_key(), hashAlgo=SHA256).encrypt(b"secret")
signature = pss.new(key).sign(SHA256.new(b"message"))

Observed outputs

  • oaep_ciphertext: 5fa3281865096b9d5356afe8f95500570c8669f7031af729a7a146faa0ba23c75ae7b38b474bb69ecf0eae70bb272fd4a8fb0685ef4224c65d75e9799d8b491f1d2c121de77d24629fe544c6a2b4fc727accca91c5d4f9ccff23ceaeb650198351eda95ee7b08376cadac41b9fc374909a46c14e39adefe64fca5e57dd4e3f75d88692ff7389a6256c4a9063fa37df54219524b86067f010578bea9eacd3333385589a605f81127861be58a5b1d2b25967961e2cd30ae1d2af793d8fda7cf41318977bf668b891bdcb515d6de369b0e707d99ff5652cb3858c43f684342f5b522826d8abb561ebd06a1bf753c79d8adcef5536c6489669e8fdf7a28eafc09987
  • pkcs1_v1_5_ciphertext: 2892e86f86fe4ef55a70b5ebb53b3e6ce8800aa313f38dbe2a9393ea8b151715a5297fa4cc6123b7eab3dc49b83d8cd813990444dd96337116c56f5e1e3fcef623257332eaa39a344c9141e1d314ba9bac90e4508808c29e47237bd9cff6bd108499124c3acc2d76ed6a2992ba6dca84886e5896a5194dfe2032df5c6d1b048bf6470e417796c8ca50bf7d7f8b758f9f241e01083df2d22b1433a1261d9c9f90b35a6fbf94a5cc54ec24fe28fa20bbc4dd9dc650ee20bdf9d6c6ec6103e96bed35539695011d7bb75e5d4ffbd4c9c121b6dfe2587ca9a920c2a0c53895e91d7c600539c8188b82148e24f45617ecc29a6a73829a86c54861bee32f2a4544574d
  • pss_signature: 5cb42cb196b551cead0e6db2747634ffd64cdb8a80e52cd02823ac73edc76283e2b4d3091f8ce7e1c74cb3354b5861c610da4186bc847c08e9004eee5c36702e85bbe887fcf38a51f717da895402bf811b663e43c041e7ec2e5aa757bd5cc3a812c3916d5d01b5f1a7e68d2c06e70627daebbbe363295b377ea36e64e22f40c14e1c68afef394285ea78559f6a8a49ae20ee41c9f46c20dc6998c8a7581814787e757c8756aeee140681f44537e850d181238566683ecfd2c9075e5a95d2e85a1cd0444f2434a045a54f230a6b79f978bbd1977b470b2eaa122e2f6cc6c8488eeceb71818c3a3c3f5b98dd19b4768c2789e968d9868d4634c214fa84bfbeb6cd
  • pkcs1_v1_5_signature: 9714b818e5760d2c6dec0390bec99ee849029ba2ce0ac02a72ae096bdd295655d0a6cdfb3b317d447ea9fc9eb70d81ba9460a3b463c63b0847cd9e125bf5f017c6c6f8267fb3a2f80528e9d0a5e73139185429fbe400903fcf4ea5eb5ba636c679168111088bbc32a8e18864ed00e50a2c9b929be6c96376a17fb5e4ad65e34e0661d8a0040d91263052fb4935ec5bb71475b0dee236fbed632b07b630d66848e5f650211a430b5eb34188a2df362b38760183b3a741a390c3176b7fd9b3c5d77dc662fe344454c8d21697f4612b1c200d6138c0e49d5fce6a224e65699f4f20cda223b543f79c087409a68e246dfd0d597d6199da5ee2ea700902553847845c

Success-path checks

  • PASS — OAEP round-trip: values match
  • PASS — PKCS#1 v1.5 round-trip: values match
  • PASS — PSS verification succeeded: signature verified
  • PASS — PKCS#1 v1.5 verification succeeded: signature verified

Expected failure-path checks

  • PASS — OAEP rejects modified ciphertext: raised ValueError: Incorrect decryption.
  • PASS — PKCS#1 v1.5 decryption returns sentinel on failure: returned sentinel as designed
  • PASS — PSS rejects modified signature: raised ValueError: Incorrect signature
  • PASS — PKCS#1 v1.5 signature verification rejects modification: raised ValueError: Invalid signature

Notes

  • PKCS#1 v1.5 encryption is kept only as a legacy interop example. OAEP is the modern RSA encryption default.

8. RSA key import/export and parameter extraction

PKCS#8-protected private export, DER/PEM/OpenSSH public export, parameter extraction, and key reconstruction.

Overall status: PASS

Example usage

key = RSA.generate(2048)
private_pem = key.export_key(
    format="PEM",
    pkcs=8,
    passphrase=b"secret",
    protection="PBKDF2WithHMAC-SHA512AndAES256-CBC",
)
reloaded = RSA.import_key(private_pem, passphrase=b"secret")

Observed outputs

  • public_openssh_prefix: 7373682d727361 (7 bytes)
  • size_in_bits: 2048
  • n_bits: 2048
  • e: 65537
  • u: 90242810392949031556374249264499853062881081549265497848261025820272742729513870212914793515343341636629172660092497801362239585160571478609041524131260449846854374683933588852439531685848468562455833567091412324532302761086988914764084246641025398715350130596080252173331757050739764409857895268648297835669

Success-path checks

  • PASS — PEM private import recovered modulus: values match
  • PASS — DER private import recovered modulus: values match
  • PASS — PEM public import recovered modulus: values match
  • PASS — DER public import recovered modulus: values match
  • PASS — OpenSSH public import recovered modulus: values match
  • PASS — Public reconstruction preserved modulus: values match
  • PASS — Private reconstruction preserved d: values match

Expected failure-path checks

  • PASS — RSA protected private key rejects wrong passphrase: raised ValueError: RSA key format is not supported

9. ECC P-256, ECDSA, and key formats

Randomized ECDSA, deterministic RFC6979 ECDSA, SEC1/raw/OpenSSH import-export, and key reconstruction.

Overall status: PASS

Example usage

from Crypto.PublicKey import ECC
from Crypto.Signature import DSS
from Crypto.Hash import SHA256

key = ECC.generate(curve="P-256")
sig = DSS.new(key, "deterministic-rfc6979").sign(SHA256.new(b"msg"))

Observed outputs

  • public_sec1_length: 65
  • public_sec1_compressed_length: 33
  • public_raw_length: 65
  • public_openssh_prefix: ecdsa-sha2-nistp256
  • curve: NIST P-256
  • private_scalar_hex_prefix: 0xc8b906448df5720b91fc254c22f7e700

Success-path checks

  • PASS — ECDSA randomized signature verified: verified
  • PASS — ECDSA deterministic signature verified: verified
  • PASS — ECC private import recovered scalar: values match
  • PASS — PEM public import recovered X: values match
  • PASS — DER public import recovered X: values match
  • PASS — SEC1 public import recovered X: values match
  • PASS — raw public import recovered X: values match
  • PASS — OpenSSH public import recovered X: values match
  • PASS — reconstructed private scalar: values match
  • PASS — reconstructed public X: values match

Expected failure-path checks

  • PASS — ECDSA verify rejects modified signature: raised ValueError: The signature is not authentic
  • PASS — ECC protected private key rejects wrong passphrase: raised ValueError: Invalid DER encoding inside the PEM file

Notes

  • P-256 is the easiest choice when you need strong NIST/FIPS alignment for ECC signatures and ECDH.

10. Ed25519 signatures and key formats

Ed25519 signing, raw key import/export, and PKCS#8-protected private export.

Overall status: PASS

Example usage

from Crypto.PublicKey import ECC
from Crypto.Signature import eddsa

key = ECC.generate(curve="Ed25519")
signature = eddsa.new(key, "rfc8032").sign(b"message")

Observed outputs

  • raw_public_length: 32
  • seed_length: 32
  • signature_length: 64

Success-path checks

  • PASS — Ed25519 signature verified: verified
  • PASS — Raw public import preserved curve: values match
  • PASS — Raw private import preserved curve: values match
  • PASS — PEM import recovered the same seed: values match

Expected failure-path checks

  • PASS — Ed25519 verify rejects modified signature: raised ValueError: The signature is not authentic
  • PASS — Ed25519 protected private key rejects wrong passphrase: raised ValueError: Invalid DER encoding inside the PEM file

Notes

  • Ed25519 uses a seed-based private key model instead of the scalar d model used by NIST P-curves.

11. ECDH on P-256 + HKDF + AES-GCM

Pair-wise key agreement, KDF extraction, and immediate use with AEAD.

Overall status: PASS

Example usage

from Crypto.PublicKey import ECC
from Crypto.Protocol.DH import key_agreement

alice = ECC.generate(curve="P-256")
bob = ECC.generate(curve="P-256")

Observed outputs

  • session_key: 7e1c7d747c2d9f45270d15863972d842ea762f62a8425f296eb9486263e6475f
  • nonce: 8d70c5c3221fead5bf9e232cbc549779
  • ciphertext: 4ea41cb14e45f4c0ce64e04bf32ccf
  • tag: af98272d8ba51b288b0831e0d04c2d65

Success-path checks

  • PASS — Alice and Bob derived the same key: values match
  • PASS — ECDH-derived AES-GCM round-trip: values match

Expected failure-path checks

  • PASS — Wrong private key produces a different session key: derived key changed as expected
  • PASS — GCM rejects modified tag after ECDH key agreement: raised ValueError: MAC check failed

12. X25519 / Curve25519 key agreement

X25519 shared secret derivation, HKDF extraction, and raw key import/export.

Overall status: PASS

Example usage

from Crypto.PublicKey import ECC
from Crypto.Protocol.DH import key_agreement

alice = ECC.generate(curve="Curve25519")
bob = ECC.generate(curve="Curve25519")

Observed outputs

  • session_key: 4625530d9f34f0e11ac41b1e59eae2aeb89d8cf6013a8115ef2848058db5c4e4
  • raw_public_length: 32
  • seed_length: 32

Success-path checks

  • PASS — Alice and Bob derived the same X25519 key: values match
  • PASS — Imported public key curve: values match
  • PASS — Imported private key curve: values match

Expected failure-path checks

  • PASS — Wrong X25519 private key derives a different secret: derived key changed as expected

Notes

  • X25519 is for key agreement only. It is not a signature algorithm.

13. Stream ciphers and authenticated stream constructions

ChaCha20-Poly1305, XChaCha20-Poly1305, and Salsa20 with Encrypt-then-MAC.

Overall status: PASS

Example usage

from Crypto.Cipher import ChaCha20_Poly1305, Salsa20

key = get_random_bytes(32)
cipher = ChaCha20_Poly1305.new(key=key)

Observed outputs

  • chacha20_poly1305_nonce: 96f2d7b12503fa25d039c2e4
  • xchacha20_poly1305_nonce: 881c9217ee5159890caf98c5dcee6984df620469f6d77abd
  • salsa20_nonce: df3f3a90d4140e66
  • salsa20_tag: 7a90ada91400e0bed92d0979f91b61fc118c9cbbf051ca053526911fc0f3888d

Success-path checks

  • PASS — ChaCha20-Poly1305 round-trip: values match
  • PASS — XChaCha20-Poly1305 round-trip: values match
  • PASS — Salsa20 + HMAC round-trip: values match

Expected failure-path checks

  • PASS — ChaCha20-Poly1305 rejects bad tag: raised ValueError: MAC check failed
  • PASS — Salsa20 package HMAC rejects tampering: raised ValueError: MAC check failed

Notes

  • Salsa20 alone is unauthenticated. The example deliberately wraps it in Encrypt-then-MAC.

14. Secret sharing and KDFs

Shamir split/combine plus HKDF, PBKDF2, and scrypt helper outputs.

Overall status: PASS

Example usage

from Crypto.Protocol.SecretSharing import Shamir

secret = get_random_bytes(16)
shares = Shamir.split(2, 5, secret)
restored = Shamir.combine(shares[:2])

Observed outputs

  • shares_preview:
[
  [
    1,
    "bb173f8cac9b011f513e80fcec8770e0"
  ],
  [
    2,
    "ead3615c93f9e72831ab7b18e25b68ce"
  ],
  [
    3,
    "da6f54ec86d8453aee27d244e7ef60d4"
  ]
]
  • hkdf_key: 144f5faf3572a649c0c448682b597dab91cd5211d9571bbcc81ebbfd28079429
  • pbkdf2_key: e3837c929dba44da5a123ae0378b6d30187ed25566c9b9650d32a525b7ef63c8
  • scrypt_key: 5d3958476f0279f4abc82e2daceecba0116a01d05207d13332f703b9809c9a62

Success-path checks

  • PASS — Shamir combine restored the secret: values match
  • PASS — HKDF length is 32 bytes: 32 bytes
  • PASS — PBKDF2 length is 32 bytes: 32 bytes
  • PASS — scrypt length is 32 bytes: 32 bytes

Expected failure-path checks

  • PASS — Modified Shamir share silently reconstructs the wrong secret: reconstructed output changed without authenticity protection

Notes

  • Shamir split/combine gives threshold reconstruction, not authenticity. Authenticate shares if hostile modification is possible.

15. HPKE (base, Auth, and PSK modes)

RFC 9180-style hybrid public-key encryption using PyCryptodome’s HPKE module.

Overall status: PASS

Example usage

from Crypto.Protocol import HPKE

receiver = ECC.generate(curve="P-256")
encryptor = HPKE.new(receiver_key=receiver.public_key(), aead_id=HPKE.AEAD.AES128_GCM)
ciphertext = encryptor.seal(b"message")
decryptor = HPKE.new(receiver_key=receiver, aead_id=HPKE.AEAD.AES128_GCM, enc=encryptor.enc)

Observed outputs

  • base_enc_length: 65
  • auth_enc_length: 65
  • psk_enc_length: 65

Success-path checks

  • PASS — HPKE base mode round-trip: values match
  • PASS — HPKE auth mode round-trip: values match
  • PASS — HPKE PSK mode round-trip: values match

Expected failure-path checks

  • PASS — HPKE base mode rejects wrong AAD: raised ValueError: Incorrect HPKE keys/parameters or invalid message (wrong MAC tag)
  • PASS — HPKE auth mode rejects wrong sender public key: raised ValueError: Incorrect HPKE keys/parameters or invalid message (wrong MAC tag)
  • PASS — HPKE PSK mode rejects wrong PSK: raised ValueError: Incorrect HPKE keys/parameters or invalid message (wrong MAC tag)

Notes

  • HPKE is the cleanest high-level way in PyCryptodome to combine asymmetric setup with AEAD-protected payload transport.

16. Hybrid envelope: RSA-OAEP + AES-EAX

A common real-world composition: RSA protects a random session key and AES-EAX protects the bulk plaintext.

Overall status: PASS

Example usage

session_key = get_random_bytes(16)
wrapped_session_key = PKCS1_OAEP.new(rsa_public).encrypt(session_key)
cipher = AES.new(session_key, AES.MODE_EAX)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)

Observed outputs

  • wrapped_session_key: 646ff50566f0b4a1b160a27ca7dbcce3d6c8ab975cad731dc6de4d3f98d4cb6bda0909dd488cbc411baa475666d7e1af1ef71e8994941f1ff40e7e5f7900e799ddb4645e0698bb049e19cf715b15e698bb9de55298364ab89448f884bb6a766dd7f5750ac04be01786082f9094504bb960d6c6da08f90190b1f49cd4071ca1f892c04ee16a1dbdf1ffb42368f578ed10deef503a925632440bec1a13aeff4ae5d2dbd69b65411269c7f7408ce675040686248a946b32aaa13487348d1ef92aed0e75a9b3b57f67ac45d563fb5559a6ea5202d6a21ade19e14b1f3599ec7035175c40f7744a6735298bb5f56d4fd86012174ea37307f2b0478d1b2d191eefb6bd
  • nonce: 7ad89ea05fb53f36e72efb3665dea78d
  • ciphertext: f988bce129985adf292d12986fd15b2d2a5af845bb447640bc40b5623b913a0046d89acf8a805159c962e7
  • tag: abdf2cc434ec64c8c407ec3084572a84

Success-path checks

  • PASS — Hybrid session key unwrap: values match
  • PASS — Hybrid ciphertext round-trip: values match

Expected failure-path checks

  • PASS — Hybrid AEAD layer rejects tampered tag: raised ValueError: MAC check failed
  • PASS — Hybrid unwrap rejects wrong RSA private key: raised ValueError: Incorrect decryption.

17. Internal runner health

A trivial sanity record proving the report generator itself executed.

Overall status: PASS

Example usage

outcome = demo_unexpected_failure_capture()

Observed outputs

  • runner: alive

Success-path checks

  • PASS — Runner reached the final demo: yes