| Property | Value |
|---|---|
| Document Version | 4.0.0 |
| Last Updated | 2026-08-01 |
| Classification | Public |
| Maintainer | Steel Security Advisors LLC |
AMA Cryptography is a quantum-resistant cryptographic protection system released under the Apache License 2.0 as free and open-source software. As of v2.0, all production cryptographic primitives are implemented natively in C with zero runtime cryptographic dependencies. Security is our highest priority. We take all vulnerabilities seriously and appreciate responsible disclosure from the security research community.
We actively maintain and provide security updates for the following versions:
| Version | Supported | Status |
|---|---|---|
| 4.0.x | Yes | Active development and security updates |
| 3.5.x | No | Superseded by v4.0 (six breaking changes — see CHANGELOG [4.0.0]) |
| 3.4.x | No | Superseded by v3.5; no public API removals |
| 3.3.x | No | Superseded by v3.4; no public API removals |
| 3.2.x | No | Superseded by v3.3; no public API removals |
| 3.1.x | No | Superseded by v3.2; no public API removals |
| 3.0.x | No | Superseded by v3.1; no public API removals |
| 2.1.x | No | Superseded by v3.0 (legacy_compat Argon2id shim available for one-shot migration; see CHANGELOG [3.0.0] → ### BREAKING) |
| 2.0.x | No | Superseded by v2.1 |
| 1.0.x | No | Superseded by v2.0 |
AMA Cryptography implements defense-in-depth with multiple independent security layers — four core cryptographic operations supported by key derivation, plus an optional timestamp binding that is not an independent security layer (see item 6):
- SHA3-256 Content Hashing (NIST FIPS 202)
- HMAC-SHA3-256 Authentication (RFC 2104)
- Ed25519 Digital Signatures (RFC 8032, C11 atomics hardened)
- ML-DSA-65 Quantum-Resistant Signatures (NIST FIPS 204)
- HKDF-SHA3-256 Key Derivation (RFC 5869, NIST SP 800-108)
- RFC 3161 Timestamp Binding — not an independent layer. AMA verifies the §2.4.2 message-imprint binding only. It does not verify the TSA's CMS
SignerInfosignature or validate its certificate chain, so an adversary who can supply a token satisfies this check unaided, with anygenTimethey choose, using no key. It contributes no adversarial resistance and must not be counted toward the security bound. See INVARIANT-37 and ARCHITECTURE.md § Scope: RFC 3161 attestation is not implemented.
- AES-256-GCM Authenticated Encryption (NIST SP 800-38D)
- ML-KEM-1024 Key Encapsulation (NIST FIPS 203)
- SLH-DSA-SHA2-256f and SLH-DSA-SHAKE-128s Hash-Based Signatures (NIST FIPS 205)
- Adaptive Cryptographic Posture System (runtime threat-level response)
- Hybrid KEM Combiner (IND-CCA2 binding construction per Bindel et al.)
- Agent-Instance Key/Signature Binding (INVARIANT-30) — see below
An autonomous agent driving this library needs two cryptographic capabilities to outlive its own instance: key material that persists, and signatures a later instance would treat as authoritative. Ordinary per-message signing and ephemeral session keys are neither.
A binding names an agent instance, the lifetime of the material it may derive
(EPHEMERAL / SESSION / PERSISTENT), and the capabilities it may exercise
(DATA_SIGN, KEY_EXCHANGE, PERSISTENCE, SELF_REPLICATE, DELEGATE). Any
non-EPHEMERAL lifetime or restricted capability requires a non-zero
ethical-profile hash and an HMAC-SHA3-256 authorization tag verifying
under an operator-held authority key. The canonical 88-byte encoding is folded
into HKDF's info and into the ML-DSA / SLH-DSA signature context, so material
derived under one binding is cryptographically unrelated to the same input under
any other — including one differing in a single capability bit.
This is domain separation and policy over the existing SHA3-256 /
HMAC-SHA3-256 / HKDF primitives; no new algorithm is introduced
(INVARIANT-1 intact). Refusal is fail-closed: no output bytes, a distinct error
code (AMA_ERROR_ETHICAL_BINDING / EthicalBindingError), and no partial
state. The policy check is constant-time — verified by a strict dudect lane —
so neither whether nor which clause refused is observable by timing.
Operational guidance. Hold the authority key outside the reach of any agent that calls the library (an HSM, a separate supervisor process, or an operator workstation). The binding constrains derivations made through it: route persistence-material derivation through a binding to realise the protection. It is not a sandbox and does not restrain code that never calls this library.
Two advisory 3R detectors (VolumeSpikeDetector, NoteArtifactDetector, both
on by default) surface the corresponding runtime behaviour — operation bursts
and signed payloads shaped like instructions for a successor — for human
review. They never block an operation, and a negative result is not a statement
that a payload is benign.
Throughput is a continuously-improving axis, not a ceiling. On x86-64
AVX2 AMA's checked-in benchmark artifacts and generated reports provide the
only supported throughput claims; avoid quoting relative speedups unless the
source artifact and host class are named. Recent work (see CHANGELOG.md from v2.1 onward) added 4-way Keccak
batching, Ed25519 signed-window combs, merged-layer ML-DSA NTT, and
Ed25519 verify via Shamir/Straus joint scalar multiplication with a
width-5 wNAF — roughly doubling verify throughput.
Ed25519 verify dominates wall-clock time in three protocol families that AMA consumers run at scale:
- X.509 certificate-chain validation (TLS handshake, code-signing). A typical chain is 3–4 certificates deep; each certificate signature is one Ed25519 verify. Doubling per-verify throughput halves the CPU budget per chain validation, which on a busy gateway is the difference between handling N and 2N concurrent handshakes per core.
- Noise Protocol Framework handshakes (WireGuard, Lightning,
Nym). The XX, IK and IKpsk2 patterns each do at least one Ed25519
verify of the responder's static key per handshake; mixed-PQ
patterns (e.g.
Noise_XXhfs_25519+ML-KEM-1024_*) keep the same verify on the classical leg. Faster verify shortens the Diffie-Hellman-bound handshake critical path. - MLS (RFC 9420) group operations. Each Welcome / Commit message carries one or more Ed25519 signatures over the GroupContext and KeyPackages. In a 1000-member group an Add/Remove can require verifying O(log N) signatures along the ratchet-tree path; verify speed sets the floor on group-rekey latency.
The change is purely algorithmic — same group-element math, no new
external dependency, no new dispatch slot — and is gated behind
AMA_ED25519_VERIFY_SHAMIR (default ON) and AMA_ED25519_VERIFY_WINDOW
(default 5) so a single recompile reverts to the prior layout if a
consumer needs the old throughput envelope for
deterministic regression purposes.
The deliberate choices that constitute AMA's security posture —
zero external cryptographic dependencies, in-tree constant-time
implementations audited under INVARIANT-12, and a vendored
build-from-source supply chain — auto-bound peak ops/sec against
libraries that lean on AVX-512 or hand-tuned assembly across 500 k+
LoC of audit surface. Readers who value raw speed over supply-chain
minimalism are unlikely to be the target audience for this library;
readers who need auditable, small-surface post-quantum primitives
with headroom to keep improving should find the trade-off
acceptable. Measured ops/sec numbers are in benchmark-report.md
and benchmarks/README.md — any claim to the contrary elsewhere in
the repository should be treated as aspirational and reported as a
documentation bug.
If you discover a security vulnerability in AMA Cryptography, please report it responsibly:
DO NOT open a public GitHub issue for security vulnerabilities.
Instead, please use one of the following private channels:
- Preferred — GitHub Private Vulnerability Reporting: Open a private advisory. GitHub encrypts the report in transit and storage; no PGP key management is required on either side.
- Email fallback: steel.sa.llc@gmail.com with subject
[SECURITY] AMA Cryptography Vulnerability Report.
Include in your report:
- Detailed description of the vulnerability
- Steps to reproduce the issue
- Potential impact assessment
- Proof-of-concept code (if applicable)
- Suggested remediation (if available)
- Your contact information for follow-up
We consider the following to be security vulnerabilities worthy of immediate attention:
Critical:
- Cryptographic primitive failures (hash collision, signature forgery)
- Key extraction or recovery attacks
- Authentication bypass
- Arbitrary code execution
- Privilege escalation
- Cryptographic oracle attacks
High:
- Side-channel attacks (timing, power analysis)
- Denial of service affecting cryptographic operations
- Information disclosure of sensitive cryptographic material
- Dependency vulnerabilities in cryptographic libraries
Medium:
- Input validation issues leading to unexpected behavior
- Insufficient entropy in key generation
- Weak random number generation
- Implementation deviations from cryptographic standards
Low:
- Documentation inconsistencies affecting security
- Missing security headers or best practices
- Informational security improvements
The following are generally not considered security vulnerabilities:
- Theoretical attacks requiring impractical computational resources (e.g., 2^128 operations)
- Issues in third-party dependencies (report to upstream maintainers)
- Social engineering attacks
- Physical access attacks on user systems
- Issues requiring user misconfiguration or ignoring documentation
- Performance or availability issues without security impact
- Missing features (use GitHub Issues instead)
We are committed to responding to security reports promptly:
| Severity | Initial Response | Status Update | Resolution Target |
|---|---|---|---|
| Critical | 24 hours | Every 48 hours | 7 days |
| High | 48 hours | Weekly | 30 days |
| Medium | 5 business days | Bi-weekly | 60 days |
| Low | 10 business days | Monthly | 90 days |
Initial Response: Acknowledgment of receipt and initial severity assessment Status Update: Progress reports and estimated resolution timeline Resolution Target: Expected timeframe for patch release (may vary based on complexity)
We follow coordinated disclosure principles:
- Report Received: We acknowledge receipt within the timeframes above
- Validation: We validate the vulnerability and assess severity
- Fix Development: We develop and test a security patch
- Advisory Preparation: We prepare a security advisory with CVE (if applicable)
- Coordinated Release: We coordinate disclosure timing with the reporter
- Public Disclosure: We publish the advisory and release the patch
Typical disclosure timeline: 90 days from initial report, or earlier if:
- A fix is available and tested
- The vulnerability is being actively exploited
- Other parties have independently discovered the issue
- The reporter and maintainers mutually agree
Security updates are released as follows:
- Critical vulnerabilities: Emergency patch release within 7 days
- High vulnerabilities: Patch in next minor version (within 30 days)
- Medium vulnerabilities: Patch in next scheduled release
- Low vulnerabilities: Addressed in regular development cycle
Security advisories are published:
- GitHub Security Advisories (https://github.com/Steel-SecAdv-LLC/AMA-Cryptography/security/advisories)
- Release notes with [SECURITY] tag
- Email notification to users who have starred the repository (when critical)
We deeply appreciate security researchers who help keep AMA Cryptography secure. Reporters who follow responsible disclosure will be:
- Credited in the security advisory (unless anonymity is requested)
- Acknowledged in the CHANGELOG and release notes
- Thanked publicly on our GitHub repository
- Recognized in our Hall of Fame for significant contributions
We do not currently offer a bug bounty program but may consider recognition rewards for exceptional discoveries.
Users deploying AMA Cryptography in production should:
- REQUIRED: Store master secrets in FIPS 140-2 Level 3+ HSMs for production
- REQUIRED: Implement key rotation every 90 days
- REQUIRED: Use hardware security modules (AWS CloudHSM, YubiKey, etc.)
- NEVER: Store private keys in plain text or version control
- NEVER: Reuse keys across different Omni-Code packages
- REQUIRED: Build native C library (
cmake -B build -DAMA_USE_NATIVE_PQC=ON && cmake --build build) - All production cryptographic primitives (SHA3, HKDF, Ed25519, AES-256-GCM, ML-DSA-65, ML-KEM-1024, SLH-DSA, X25519, ChaCha20-Poly1305, Argon2id, secp256k1) are native C — no external cryptographic dependencies required
- Optional: numpy/scipy for 3R monitoring, PyKCS11 for HSM
- REQUIRED: Build native PQC C library (
cmake -B build -DAMA_USE_NATIVE_PQC=ON && cmake --build build) - REQUIRED: Enable all cryptographic layers (no fallbacks in production)
- REQUIRED (if timestamps are relied upon): Establish the token's issuer through a control outside AMA — an authenticated channel to the TSA, or out-of-band validation of the token before it is stored. AMA verifies the RFC 3161 §2.4.2 message-imprint binding only; configuring a reputable TSA has no verification consequence here, because no TSA signature is checked and a forged token is accepted identically (INVARIANT-37).
- RECOMMENDED: Use multiple TSAs for redundancy of availability. This is not redundancy of trust: AMA does not verify any of them.
- RECOMMENDED: Verify all signatures before trusting package contents
- NOTE: v2.0 has zero core cryptographic dependencies — all primitives are native C
- REQUIRED: Keep optional dependencies up to date (numpy, scipy, pynacl if used)
- REQUIRED: Enable Dependabot for automated security updates
- RECOMMENDED: Pin dependency versions for reproducible builds
- RECOMMENDED: Verify package signatures from PyPI
- REQUIRED: Log all cryptographic operations for audit trails
- REQUIRED: Monitor for signature verification failures
- REQUIRED: Alert on quantum library unavailability
- RECOMMENDED: Implement rate limiting for signature operations
- RECOMMENDED: Regular security audits of deployment configuration
The integrity check verifies that an installed wheel's .py files
and its native library have not been tampered with after build. It
is not a supply-chain identity check (PyPI's existing PGP / sigstore
mechanisms cover that) and it does not prove anything about a
malicious build pipeline — both the digest and the signing key are
produced by the same build that produced the code being signed. The
contract is:
-
The wheel build computes SHA3-256 over the package's
.pyfiles and a second SHA3-256 over the native library (libama_cryptography) it is about to ship, generates an ephemeral, per-build Ed25519 key by default (or usesAMA_INTEGRITY_SIGNING_SEED_HEXin release CI), and signs the compositeSHA3-256(domain ‖ py_digest ‖ native_digest)— so the two digests are inseparable. It embeds the signature, the public verification key, and both digests as Python literals inama_cryptography/_integrity_signature.py, then discards the private key before publishing the wheel. -
At import,
_self_test._verify_signed_integrity()re-hashes the.pyfiles, loads the embedded(pubkey, signature)pair, verifies the signature over the recomputed composite withama_ed25519_verifyfrom the in-tree C kernel (via ctypes — INVARIANT-1 forbids a PyCA dependency), and then re-hashes the shared object it actually loaded and requires it to match the signed native digest. Any mismatch — edited.pyfile, edited.so, or a swapped signature — transitions the module to the ERROR state and refuses every cryptographic operation.Binding the native library closes the gap where the Python wrapper was tamper-evident but the code doing the cryptography was not: a back-doored
libama_cryptographyused to leave the.pydigest, the signature and the trust anchor all verifying. Because_build_signcan only produce a signature by calling the nativeama_ed25519_sign, a working native library is present at signing time by construction, so every signed artefact carries the native digest — there is no unsigned-native downgrade path. The one exception is an explicitAMA_CRYPTO_LIB_PATHoverride (see below): the operator has deliberately substituted the backend, so the loaded object is recorded as unverified (a warning, andfully_verifiedisFalse) rather than treated as tampering.
There is no long-lived signing key in developer builds. Each
default build generates, signs once, and discards. Release CI may
compile the expected public key into the native library with CMake's
AMA_INTEGRITY_TRUST_ANCHOR_PUBKEY_HEX option and set
AMA_INTEGRITY_REQUIRE_TRUST_ANCHOR=1; then the build signer refuses
to emit an artefact whose derived public key does not match the native
anchor, and the import-time verifier fails closed if the embedded
public key is not that anchor. This gives release packaging a stable
trust-anchor gate without adding any external crypto dependency or
trusting mutable Python source for the anchor.
The signed digest above covers the package's .py source. CPython
does not execute source; it executes the compiled bytecode in
__pycache__/*.pyc. A timestamp-based .pyc (the default) is honoured
by the interpreter whenever its stored (mtime, size) match the source,
and an attacker with write access to the installed package sets exactly
those. So a poisoned .pyc can run while every .py stays pristine —
the source digest and its signature both still verify.
A POST stage (execution-integrity, run right after the source-integrity
stage) closes this by making on-disk bytecode subordinate to the signed
source: it recompiles each signed .py and refuses any cached .pyc
whose bytecode is not a faithful compile of it. The comparison is by
executed surface — the instructions and constants, recursively into
nested functions — and ignores the file path and line tables, so a
legitimately relocated wheel is not a false positive while a single
altered instruction is caught. It covers every signed source file, not
only the ones imported so far, so a poisoned .pyc for a lazily-imported
module is caught at POST rather than on first use; a source-only run
(no .pyc on disk) has nothing to poison and is reported as such. A
second pass rejects any loaded ama_cryptography module served from
outside the verified package directory (module substitution). Pinned by
tests/test_execution_integrity.py, including an end-to-end case where a
poisoned-but-loadable .pyc fails the import while the source digest
stays valid. This is INVARIANT-40.
Boundary (shared with the trust anchor). A self-check written in
Python cannot vouch for the bytecode of its own module if that was
already poisoned before the check ran, just as the trust anchor lives in
a shared object the same attacker could swap. This stage raises the cost
from "poison any .pyc" to "poison the checker's own .pyc without
tripping the source signature its source is bound by", but the residual
class is real and is not something an in-process check can eliminate. The
control that does is out-of-band: OS / package-manager code signing (dpkg
/ rpm signatures, a signed wheel verified by the installer, an immutable
or verified-boot filesystem) that authenticates the files before the
Python interpreter loads them. Deploy AMA behind one of those where the
.pyc/.so tampering threat is in scope; the in-process checks are
defense in depth beneath it, not a substitute for it.
python -m ama_cryptography.integrity --update is gated behind
AMA_BUILD_PIPELINE=1. Users who modify .py files after install
must rebuild the wheel — running --update locally would silently
re-bless tampered code and defeat the tamper-detection contract.
Both halves ship together in the AArch64-completeness PR (2026-05):
--updategate —python -m ama_cryptography.integrity --updaterequiresAMA_BUILD_PIPELINE=1in the environment. Outside that gate the command exits 2 with a remediation message. Seeama_cryptography/integrity.py.- Signing pipeline —
python -m ama_cryptography._build_sign(invoked by the build pipeline, e.g.setup.pypost-build hook / CMake post-install step / wheel CI workflow) generates an ephemeral Ed25519 keypair (or usesAMA_INTEGRITY_SIGNING_SEED_HEXin release CI) via the in-treeama_ed25519_keypairC symbol (INVARIANT-1: no PyCA dependency), checks the resulting public key against the C-compiled trust anchor when one is present, signs the SHA3-256 digest withama_ed25519_sign, writesama_cryptography/_integrity_signature.pywith the embedded pubkey- signature + digest, and discards the private key before exit.
See
ama_cryptography/_build_sign.py.
- signature + digest, and discards the private key before exit.
See
- Import-time verifier —
_self_test._verify_integrity()calls_verify_signed_integrity()first. When the signature artefact is present (the normal post-wheel-build state), it recomputes the digest, reads any native trust anchor viaama_integrity_trust_anchor_pubkey_hex(), callsama_ed25519_verifyvia ctypes with the embedded pubkey and signature, and accepts only on a positive verify plus trust-anchor match when configured. When the artefact is absent (editable installs, source checkouts, wheels built withoutAMA_BUILD_PIPELINE=1), the behaviour depends onAMA_INTEGRITY_REQUIRE_TRUST_ANCHOR:- Unset (developer / editable / source-checkout path): the
module falls back to digest-only verification against
_integrity_digest.txtwith a logged WARNING. Developer ergonomics do not require a full wheel build on every edit; packagers still see the missing signature in CI logs. =1(release path): the module refuses to fall back and transitions to ERROR state — release wheels MUST ship the Ed25519-signed artefact or every crypto call is rejected. This makes a forgottenAMA_BUILD_PIPELINE=1in the release pipeline a hard failure instead of a silent posture downgrade.
- Unset (developer / editable / source-checkout path): the
module falls back to digest-only verification against
State this plainly, because the distinction decides whether the mechanism is load-bearing for your deployment:
- Detected: accidental corruption, partial or interrupted installs,
drift between the built wheel and the files on disk, and a tampered
.pyfile when the attacker cannot also rewrite the signature artefact. - NOT detected without a compiled trust anchor: deliberate tampering
by anyone who can write to the installed package directory. The
verifying public key is read from
ama_cryptography/_integrity_signature.py, which sits beside the code it attests. An attacker who edits a module can regenerate the digest, sign it with a keypair of their own, and overwrite that artefact; the check then passes. Deleting the artefact instead falls back to_integrity_digest.txt, a plaintext file with no signature at all. This is inherent to any self-contained self-check — the anchor is what breaks the circularity. - NOT covered at all: the native shared library. The digest is
computed over the package's
.pyfiles only (_self_test._compute_module_digest), so a substituted or patchedlibama_cryptographyis invisible to it. See theAMA_CRYPTO_LIB_PATHnote below.
A build is only tamper-evident against a write-capable adversary when
AMA_INTEGRITY_TRUST_ANCHOR_PUBKEY_HEX is compiled into the native
library (CMakeLists.txt) so the embedded pubkey must match an anchor
the attacker cannot rewrite by editing Python, and
AMA_INTEGRITY_REQUIRE_TRUST_ANCHOR=1 forbids the unsigned fallback.
Neither is set by default, so a stock developer build is in the
unanchored state described above.
Generate the keypair with AMA's own Ed25519 kernel — no third-party crypto tool is needed, and using one would contradict INVARIANT-1:
umask 077
python3 -c "
from ama_cryptography.pqc_backends import native_ed25519_keypair
pk, sk = native_ed25519_keypair()
open('seed.txt','w').write(sk[:32].hex())
print('PUBLIC KEY:', pk.hex())
"
native_ed25519_keypair() returns a 64-byte secret key that is
seed || public_key; only the 32-byte seed (sk[:32]) is the value
to store. Both stored values are exactly 64 hex characters.
Store them on the repository (Settings → Secrets and variables → Actions):
| Value | Kind | Name |
|---|---|---|
| the seed | Secret | AMA_INTEGRITY_SIGNING_SEED_HEX |
| the public key | Variable | AMA_INTEGRITY_TRUST_ANCHOR_PUBKEY_HEX |
Then delete seed.txt and keep a copy of the seed somewhere durable: the
public half is compiled into published binaries, so the key cannot be
rotated without invalidating the anchor those releases expect.
Run the Integrity anchor check workflow (manual trigger) to confirm
the two are a matching pair before tagging a release; a mismatch is
otherwise only surfaced by a failing release build. release.yml picks
both up automatically and sets AMA_INTEGRITY_REQUIRE_TRUST_ANCHOR=1
only when the anchor variable is non-empty, so forks and
not-yet-configured repositories continue to build unanchored wheels
rather than failing on a missing secret.
This environment variable overrides the search for the native library and
loads the named shared object directly. It is a developer convenience for
pointing at an out-of-tree build, and it is a code-execution boundary: a
shared object runs its constructors the moment it is mapped, before the
power-on self-test executes. Because the override is by definition not the
signed, shipped object, the integrity check records the loaded library as
unverified (the signature over the artefact still verifies, but the
loaded bytes are not bound to it) — module_attestation()["fully_verified"]
is False and a warning names the override. Treat the ability to set it as
equivalent to the ability to run code in the process.
It is ignored, with a warning, when the process is running set-uid or
set-gid, matching the dynamic loader's refusal to honour LD_PRELOAD and
LD_LIBRARY_PATH in secure-execution mode. When it is honoured, the
override is logged at WARNING so a substituted backend is visible in
operational logs.
The native-library search also consults LD_LIBRARY_PATH /
DYLD_LIBRARY_PATH, which are caller-controlled and steer backend
selection with the same power as AMA_CRYPTO_LIB_PATH. The dynamic loader
strips these from a set-uid/set-gid or file-capability process before it
maps anything, but this module reads them directly with os.getenv, which
bypasses that stripping — so on a privileged binary a less-privileged
caller's LD_LIBRARY_PATH could otherwise steer the cryptographic backend
the loader had already protected. Under secure-execution mode these
variables are therefore ignored for backend discovery, with a WARNING,
matching the loader's own rule. On Windows the concept has no referent and
PATH-based DLL resolution is unaffected.
A shared object can export some primitives and not others — a build with
AMA_USE_NATIVE_PQC=OFF, a stale library from a previous major version, or
a cross-architecture mismatch. Such a library now surfaces the families it
does not provide in native_backend_diagnostics()["missing_families"]
and a one-time WARNING at import, rather than presenting as a clean load
with a scattering of unrelated failures at first use. The POST known-answer
tests additionally call each covered primitive (ML-KEM, ML-DSA, SLH-DSA,
Ed25519, SHA3-256, HMAC-SHA3-256, AES-256-GCM) on a fixed input, so a
mismatched ABI there produces a wrong answer the KAT catches — the check a
bare symbol-presence probe cannot perform. Under AMA_FIPS_STRICT=1 a
missing covered family hard-fails POST via its skipped KAT.
End-to-end smoke test (from the AArch64-completeness PR's CI):
AMA_BUILD_PIPELINE=1 python -m ama_cryptography.integrity --update --sign
python -m ama_cryptography.integrity --verify # → "OK (signed integrity verified, ...)"
# Now edit a .py file and re-import WITHOUT re-running the signer:
python -c "import ama_cryptography; ama_cryptography._self_test._run_self_tests()"
# → ERROR state, all crypto operations refused
# (Re-running the signer over the edited tree makes the check pass again —
# see "What the integrity check does and does not defend against" above.)
Release-anchor smoke test:
export AMA_BUILD_PIPELINE=1
export AMA_INTEGRITY_REQUIRE_TRUST_ANCHOR=1
export AMA_INTEGRITY_TRUST_ANCHOR_PUBKEY_HEX=<32-byte expected Ed25519 pubkey hex>
export AMA_INTEGRITY_SIGNING_SEED_HEX=<32-byte release signing seed hex>
python -m ama_cryptography.integrity --update --sign
AMA_INTEGRITY_REQUIRE_TRUST_ANCHOR=1 python -m ama_cryptography.integrity --verify
The release job normally supplies the trust anchor via the compiled
AMA_INTEGRITY_TRUST_ANCHOR_PUBKEY_HEX CMake option; the environment
form above is a reproducible local equivalent used by tests to prove
the strict path rejects unanchored artefacts.
Released wheels and FIPS-validated deployments should set
AMA_FIPS_STRICT=1 so a missing backend cannot silently degrade the
approved-algorithm self-test set:
- Unset (default, developer / docs / CI matrix builds): when a
KAT cannot run because its backend is unavailable (e.g. SPHINCS+
was not built into the C library), the POST runner logs a WARNING,
records the skip with
passed=Noneinmodule_self_test_results(), and continues. Skip is NOT a pass — consumers filtering for "everything passed" must comparepassed is True. =1(release / FIPS-validated path): a skipped KAT is escalated to a hard POST failure. The module enters ERROR state and refuses every cryptographic operation until the missing backend is built and the process restarted. This makes a forgottencmake -DAMA_USE_NATIVE_PQC=ONin the release build a visible failure rather than a silent posture downgrade.
The strict-mode flag applies uniformly to every KAT (SHA3-256, HMAC-SHA3-256, AES-256-GCM, ML-KEM-1024, ML-DSA-65, SLH-DSA, SLH-DSA-SHAKE-128s, Ed25519) and the constant-time timing oracle.
secure_memzero refuses to operate when the native C
zero-on-erase backend is unavailable; the Python multi-pass loop is
opt-in via AMA_ALLOW_PYTHON_MEMZERO=1 (development / test) or
AMA_SPHINX_BUILD=1 / SPHINX_BUILD=1 (docs builds). Production
deployments should never need the opt-in because INVARIANT-7 already
refuses to import the library without the native HMAC/HKDF backend,
so the native memzero is also present. The gate is defence-in-depth
against a partial-build state where the constant-time primitives are
linked but the secure-memzero symbol is not.
AESGCMProvider.encrypt() reserves its per-key counter slot
atomically via an inter-process file lock before any nonce is
generated or AEAD is invoked. Concurrent processes sharing the
same AES-GCM key reserve disjoint slots — a previous race window
where two processes could load the same baseline and write back
max(N+a, N+b) instead of N+a+b is closed. Ephemeral mode
(configure_ephemeral(True) or ephemeral=True in the constructor)
bypasses disk I/O for hermetic test runs; in that mode the
multi-process race surface is the same as the historical
single-process design and callers MUST partition keys per-process.
If the host cannot provide a working fcntl.flock (POSIX) or
msvcrt.locking (Windows) primitive, encryption fails closed instead
of logging and continuing with degraded nonce safety.
The persisted counter path no longer keeps a dirty counter or batching
interval: every successful reservation writes the slot+1 high-water
mark atomically, so there is no deferred flush state to lose on crash.
INVARIANT-22 requires that exceeding a per-key nonce safety limit force
re-keying or hard failure, never a wrap, reset, or warn-and-continue.
SecureSession auto-generates a nonce per message and is therefore in scope.
SecureSession.encrypt() draws a fresh random 96-bit nonce for every
message, so nonce reuse within one (key, rekey_epoch) pair is a birthday
problem rather than a counter overflow: after n encryptions the collision
probability is about n² / 2⁹⁷.
MAX_ENCRYPTIONS_PER_EPOCH (2²⁰) bounds it, checked before the nonce is
drawn, and encrypt() raises RekeyRequiredError on reaching it. Recovery is
the caller's: call rekey() on both peers, or close the session. The
library deliberately does not rekey for you — the AEAD associated data binds
rekey_epoch, so a unilateral rekey desynchronises the pair and every
subsequent message fails authentication at the far end.
Crossing the advisory REKEY_INTERVAL (1000 messages, counting both
directions) logs one WARNING per epoch. The ceiling binds the sender only:
decrypt() generates no nonces, so enforcing it on receive would break a peer
running an older build without improving this side's margin.
Round-1 nonces are derived as
SHA-512(label || random(32) || share_secret(32)) mod l, so nonce secrecy does
not rest on the CSPRNG alone: an adversary who can predict the RNG output
still cannot compute the nonce without the participant's secret share. The two
nonces in a pair use distinct domain-separation labels and therefore cannot
collide with each other.
It does not protect against an RNG that repeats. The derivation is a pure function of its three inputs and holds no state, so a participant handed the same random bytes twice emits the identical nonce — a VM restored from a snapshot, a fork inheriting a buffered pool, several hosts re-seeded from one image. Two partial signatures over different messages under one Schnorr nonce disclose that participant's secret share by subtraction, so an RNG replay is a full compromise of the share, and no amount of hashing inside the derivation can prevent it.
RFC 9591's own nonce_generate has the same property. Only per-signature state
would change it — a counter, or binding the message in — and neither is
available to a round-1 API that runs before the message is known. Preventing
RNG-state rollback is therefore a deployment obligation, not a property this
library provides: do not snapshot-and-restore a signing host, and do not clone
a VM image that has already signed. tests/c/test_frost.c pins the repeat
behaviour as a known limit so it cannot be mistaken for a defect later, or
quietly assumed away.
.kdf_metadata.json names the algorithm and cost that turn the master
password into the storage key, and it is an unauthenticated file in the key
directory. Anyone who can write it can name a cheaper derivation.
This does not expose keys already stored — those were encrypted under a key derived with the old parameters, so a swapped file simply fails to decrypt them. What it governs is every key written afterwards, and on a store that is initialised but not yet populated the downgrade leaves no trace at all.
SecureKeyStorage therefore clamps the parameters from below on read and
raises KDFPolicyError rather than deriving a weak key:
| Parameter | Floor | Source |
|---|---|---|
| Algorithm | Argon2id, where the build provides it | this section |
| PBKDF2-HMAC-SHA256 iterations | 600,000 | OWASP 2024 |
Argon2id t_cost |
3 | RFC 9106 §4 |
Argon2id m_cost |
65536 KiB (64 MiB) | RFC 9106 §4 |
Argon2id parallelism |
1 | RFC 9106 §4 |
The algorithm row is load-bearing and not a formality. Clamping costs within
an algorithm leaves the cheapest attack intact: name a different one. The
metadata's version field selects the Argon2id branch, so deleting that single
field re-routes derivation to PBKDF2 — and PBKDF2 at exactly 600,000 iterations
satisfies every cost floor above while discarding memory-hardness entirely,
which is the property Argon2id is chosen for and the one that costs a GPU or
ASIC attacker real money. On a build with native Argon2id, metadata naming
PBKDF2 is therefore refused. Builds without native Argon2id are unaffected,
because PBKDF2 is what such a build legitimately creates stores with.
Storage format v3 also binds the KDF parameters into the AEAD associated data
and records them in each key file. The parameters already influence the derived
key, so this adds provenance, not confidentiality: the recorded cost cannot
be edited without invalidating the tag, and a mismatch is reported as a named
KDFPolicyError rather than an unexplained authentication failure. Format v2
records (which bound key_id alone) are still read.
To open a genuine legacy store, pass allow_legacy_kdf=True — which warns
instead of raising — then call migrate_kdf(password) to re-encrypt at current
strength and reopen without the flag.
| Algorithm | Classical Security | Quantum Security | Status |
|---|---|---|---|
| SHA3-256 | 2^128 | 2^128 | ✓ Secure |
| HMAC-SHA3-256 | 2^128 | 2^128 | ✓ Secure |
| Ed25519 | 2^126 | ~10^7 gates* | ⚠ Quantum-vulnerable |
| ML-DSA-65 (Dilithium-3) | 2^207 | 2^192 | ✓ Quantum-secure |
| ML-KEM-1024 (Kyber) | 2^256 | 2^128 | ✓ Quantum-secure |
| SPHINCS+-SHA2-256f | 2^256 | 2^128 | ✓ Quantum-secure |
| AES-256-GCM | 2^256 | 2^128 | ✓ Quantum-secure |
| HKDF | 2^128 | 2^128 | ✓ Secure |
| X25519 | 2^128 | ~10^7 gates* | ⚠ Quantum-vulnerable |
| ChaCha20-Poly1305 | 2^256 | 2^128 | ✓ Quantum-secure |
| Argon2id | Memory-hard | Memory-hard | ✓ Secure |
*Ed25519 and X25519 are vulnerable to sufficiently large quantum computers, but ML-DSA-65 provides quantum-resistant backup.
We will deprecate cryptographic algorithms when:
- Practical attacks reduce security below 112-bit classical security
- NIST or other authoritative bodies recommend deprecation
- Quantum computers pose imminent threat to classical algorithms
- More efficient quantum-resistant alternatives become available
30 days notice will be provided before deprecating any algorithm, with migration guides and backwards compatibility support.
The library ships an optional VAES + VPCLMULQDQ AES-256-GCM kernel
(PR A, 2026-04) behind runtime CPUID + XCR0 gating
(ama_cpuid_has_vaes_aesgcm()). The VAES + VPCLMULQDQ AES-GCM path
targets YMM (256-bit), not ZMM: Zen 3+ / Ice Lake+ CPUs execute
these without the AVX-512 ZMM frequency penalty documented for
Skylake-SP / Cascade Lake. Cloud VM variance on shared hosts is still
the dominant noise source; published throughput numbers are from
bare-metal runs, not CI. Hosts without VAES — or any non-x86-64
host — automatically route through the AVX2 AES-NI + PCLMULQDQ path
shipped in #253 / #254 / #260 / #261, which remains the regression
baseline tracked in benchmarks/baseline.json.
AMA Cryptography has undergone internal security analysis documented in this file. We welcome:
- Independent security audits from qualified cryptographers
- Academic review of our mathematical proofs
- Penetration testing of the implementation
- Code reviews focusing on cryptographic correctness
Please contact us at steel.sa.llc@gmail.com to coordinate security audit efforts.
AMA Cryptography is designed to comply with:
- NIST FIPS 202 - SHA-3 Standard (SHA3-256, SHAKE128, SHAKE256)
- NIST FIPS 203 - Module-Lattice-Based Key-Encapsulation Mechanism (ML-KEM / Kyber)
- NIST FIPS 204 - Module-Lattice-Based Digital Signature Standard (ML-DSA / Dilithium)
- NIST FIPS 205 - Stateless Hash-Based Digital Signature Standard (SLH-DSA / SPHINCS+)
- NIST SP 800-38D - Recommendation for Block Cipher Modes: GCM (AES-256-GCM)
- NIST SP 800-108 - Recommendation for Key Derivation Using Pseudorandom Functions
- NIST SP 800-57 - Recommendation for Key Management
- RFC 2104 - HMAC: Keyed-Hashing for Message Authentication
- RFC 5869 - HMAC-based Extract-and-Expand Key Derivation Function (HKDF)
- RFC 8032 - Edwards-Curve Digital Signature Algorithm (EdDSA)
- RFC 3161 - Internet X.509 Public Key Infrastructure Time-Stamp Protocol
Non-compliance with these standards should be reported as a high-severity security issue.
Preferred channel: GitHub Private Vulnerability Reporting
Email fallback: steel.sa.llc@gmail.com
security.txt: .well-known/security.txt (RFC 9116)
Response Time: 24-48 hours for critical issues
Organization: Steel Security Advisors LLC
docs/DESIGN_NOTES.md— Security arguments for original constructions (double-helix engine, adaptive posture, composition protocol)THREAT_MODEL.md— System threat model and risk assessmentCRYPTOGRAPHY.md— Cryptographic algorithm overviewARCHITECTURE.md— System architecture and invariantsCONSTANT_TIME_VERIFICATION.md— Timing-side-channel validation.github/INVARIANTS.md— Library invariants (canonical)
| Version | Date | Changes |
|---|---|---|
| 1.0.0 | 2025-11-26 | Initial professional release |
| 1.1.0 | 2026-01-09 | Version alignment, terminology updates |
| 2.0.0 | 2026-03-08 | Zero-dependency native C architecture, FIPS 203/204/205 compliance, AES-256-GCM, adaptive posture system, hybrid KEM combiner, Ed25519 atomics hardening, Phase 2 primitives, fuzzing harnesses, threat model documentation |
| 2.1.0 | 2026-03-25 | Hand-written AVX2/NEON/SVE2 SIMD for 8 algorithms, runtime dispatch, security fixes S1-S6, bitsliced constant-time AES default |
| 2.1.5 | 2026-04-17 | Security audit fixes (length-prefixed HKDF encoding, constant-time ops, finding C6/C7/H2), HSM support via PyKCS11, fd leak protection (CodeQL #297), secure channel protocol v2 with rekey_epoch AAD, INVARIANT-13 restoration |
| 3.0.0 | 2026-04-27 | RFC 9106 Argon2id byte-identity fix (BREAKING — legacy verify-only shim provided) and out_len cap at AMA_ARGON2ID_MAX_TAG_LEN = 1024; in-house AVX-512 4-way Keccak permutation kernel (opt-in via -DAMA_ENABLE_AVX512=ON, XCR0 5+6+7 gated) with docs/AVX512_KECCAK_ADR.md ADR; X25519 fe64 (radix-2⁶⁴) ladder + hand-written MULX+ADX inline-asm kernel under BMI2∧ADX bundle gate; X25519 4-way AVX2 batch API (ama_x25519_scalarmult_batch, opt-in); VAES YMM AES-256-GCM; Ed25519 verify-path SWE rectification + base-point comb + merged NTT + AVX2 rejection; batch ML-DSA-65 / ML-KEM-1024 sampling via 4-way SHAKE; ChaCha20-Poly1305 AVX2 (≥ 512 B) and Argon2 BlaMka G AVX2; SHA-3 auto-tune hysteresis; NIST ACVP self-attestation (815/815 AFT) under continuous validation; D-1…D-10 distribution / tooling audit (wheel SONAME bundling, Cython/numpy build pins, setuptools≥78.1.1 / wheel≥0.46.2, dudect AES-GCM tag-compare redesign, .semgrep.yml 341 FP → 0, X25519 dispatch-policy contract test, ed25519-donna fallthrough annotations) |
| 3.1.0 | 2026-05-14 | Security hygiene release documentation alignment for current consumers, v3.1.0 tag legitimacy, INVARIANT-14 CVE-ignore review, and no public API changes since v3.0.0 |
| 3.2.0 | 2026-05-20 | Mercury Agent v1.7.0 alignment; per-slot SIMD auto-tune + file-based cross-process dispatch cache with dispatch-cache safety; NTT benchmark overflow guard; dudect CI hygiene; native HMAC-SHA-256 Python bindings; no breaking public API changes |
| 3.3.0 | 2026-07-05 | Native one-shot SHA-256; documented public MAC/KDF surface (quick_hmac / quick_hkdf, native HMAC/HKDF SHA-2/3, AmaCryptographyError exception root); SLH-DSA-SHA2-256f signer consolidation; native-hashing purity in crypto_api; SLSA provenance permissions + CodeQL unused-static resolution |
| 3.4.0 | 2026-07-25 | Support matrix rolled (3.4.x active); vendored Wycheproof gate; Ed25519 canonical-S enforcement (INVARIANT-26) and X25519 u-coordinate canonicalization (INVARIANT-27); agent-instance binding (INVARIANT-30) with 3R detectors; Ascon-AEAD128/Hash256 (SP 800-232) |
| 3.5.0 | 2026-07-30 | Support matrix rolled (3.5.x active, 3.4.x superseded — no public API removals); INVARIANT-22 nonce-counter rollback residual risk documented; NIST P-256/384/521 ECDSA (FIPS 186-5, INVARIANT-34 low-s policy), ML-KEM/ML-DSA parameter sets, HSS/LMS verification enter the supported surface |
Copyright 2025-2026 Steel Security Advisors LLC. Licensed under Apache License 2.0.