-
-
Notifications
You must be signed in to change notification settings - Fork 0
Secure Memory
Documentation for the secure memory operations module (ama_cryptography/secure_memory.py), covering SecureBuffer, memory zeroing, memory locking, and constant-time comparisons.
Cryptographic key material must be handled with care:
- Zeroed when no longer needed (prevent exposure via heap dumps, core files, swap)
- Locked in RAM (prevent exposure via swap/hibernate)
- Compared in constant time (prevent timing side-channel attacks)
The secure_memory module provides these capabilities using only the standard library, with optional acceleration via the AMA native C backend.
A context manager for automatic, guaranteed memory zeroing:
from ama_cryptography.secure_memory import SecureBuffer
import os
# Allocate a 32-byte secure buffer
with SecureBuffer(32) as buf:
# buf is a bytearray, initially zeroed
buf[:] = os.urandom(32) # Load key material
# Use buf for cryptographic operations...
key = bytes(buf)
# On context manager __exit__, buf is automatically zeroed
# (multi-pass overwrite, ensuring the key cannot be recovered)SecureBuffer.__enter__() returns a bytearray (not bytes) because bytearray supports in-place modification, allowing the buffer to be zeroed without creating new memory allocations. bytes objects in Python are immutable and cannot be zeroed in place.
Multi-pass memory overwrite for sensitive data:
from ama_cryptography.secure_memory import secure_memzero
# Load sensitive data
secret_key = bytearray(os.urandom(32))
# Use the key...
signature = sign(message, bytes(secret_key))
# Zero immediately after use
secure_memzero(secret_key)
# secret_key is now all zerosImportant: Always pass a
bytearray, notbytes.bytesobjects are immutable and cannot be zeroed.
secure_memzero() performs multiple overwrite passes:
- Fill with
0x00 - Fill with
0xFF - Final fill with
0x00
This protects against "compiler optimization" attacks where a naive memset() call to zero is optimized away because the buffer is not subsequently read.
Lock memory pages to prevent swapping to disk:
from ama_cryptography.secure_memory import secure_mlock, secure_munlock
secret = bytearray(os.urandom(32))
# Lock the memory page containing `secret` into RAM
# Raises NotImplementedError if native C or POSIX mlock unavailable
secure_mlock(secret)
print("Memory locked in RAM (will not swap)")
# ... use secret ...
# Unlock when done (allow the OS to swap it again)
secure_munlock(secret)
# Zero the memory
secure_memzero(secret)| Platform | API | Notes |
|---|---|---|
| Linux | mlock() |
Requires CAP_IPC_LOCK or ulimit -l ≥ buffer size |
| macOS | mlock() |
Requires entitlements in sandboxed environments |
| Windows | VirtualLock() |
Standard user processes have limits |
Native C backend: If the AMA native C library is available,
secure_mlock()delegates toama_secure_mlock(). Otherwise falls back to POSIXmlock().
Timing-safe byte comparison:
from ama_cryptography.secure_memory import constant_time_compare
# Timing-safe comparison (always runs in O(n) time regardless of where mismatch occurs)
hmac_expected = compute_hmac(message, key)
hmac_received = package["hmac_tag"]
# Safe: does not leak position of first mismatch
if constant_time_compare(hmac_expected, hmac_received):
print("HMAC valid")
else:
print("HMAC invalid")A naive comparison (expected == received) returns False as soon as it finds the first differing byte. An attacker making many requests can measure small timing differences to determine byte-by-byte what the correct HMAC tag is (a timing oracle attack).
constant_time_compare() always processes all bytes in equal time, preventing this.
Uses ama_consttime_memcmp() from AMA's native C library when available, with a fallback to a pure-Python XOR accumulator that pads both inputs to equal length and never short-circuits.
Encrypted storage with automatic memory management:
from ama_cryptography.key_management import SecureKeyStorage
# encryption_key is bytearray to allow in-place zeroing
encryption_key = bytearray(os.urandom(32))
with SecureKeyStorage(encryption_key) as storage:
# Store key material (encrypted with AES-256-GCM)
storage.store("signing-key-v1", os.urandom(32))
# Retrieve key material (decrypted on access)
key = storage.retrieve("signing-key-v1")
# encryption_key is zeroed on context manager exit# ✓ Correct: bytearray can be zeroed
key = bytearray(os.urandom(32))
# ... use key ...
secure_memzero(key)# ✗ Incorrect: bytes cannot be zeroed in-place
key = os.urandom(32) # bytes object
# Cannot zero this after use# ✓ Automatic zeroing even on exception
with SecureBuffer(32) as buf:
buf[:] = get_key_from_hsm()
result = encrypt(plaintext, bytes(buf))
# buf is zeroed here, even if encrypt() raised# ✓ Prevent swap exposure for long-lived keys
master_key = bytearray(load_master_key())
secure_mlock(master_key)
# ... use master_key for the session ...
secure_munlock(master_key)
secure_memzero(master_key)# ✓ Constant-time comparison for HMAC tags
if constant_time_compare(expected_mac, received_mac):
proceed()# ✗ Timing-vulnerable comparison
if expected_mac == received_mac: # DO NOT USE for secrets
proceed()When the AMA native C library is available (built via CMake), the secure memory module uses it for:
-
secure_mlock()/secure_munlock()viaama_secure_mlock()/ama_secure_munlock() -
constant_time_compare()viaama_consttime_memcmp()
Without the native C library, POSIX mlock()/munlock() and a pure-Python XOR accumulator are used as fallbacks.
See Key Management for how SecureBuffer is used in key storage, or Architecture for the security architecture overview.
AMA Cryptography — Post-Quantum Security System
Copyright 2025–2026 Steel Security Advisors LLC | Apache License 2.0
Contact: steel.sa.llc@gmail.com | Report a vulnerability | Contribute
Community-tested · Not externally audited · v3.4.0