Step 27 of 56
Hardware crypto, and one honest caveat
SHA, AES and RSA acceleration behind protected objects, MD5 for a specific non-cryptographic job — and a random number generator that is not a CSPRNG on this runtime.
SHA
function Hash_1 (Data : Byte_Array) return SHA1_Digest; -- 20 bytes
function Hash_224 (Data : Byte_Array) return SHA224_Digest;
function Hash_256 (Data : Byte_Array) return SHA256_Digest;
One shared accelerator, so a protected object serialises the message-load /
start / read handshake and concurrent Hash calls from different
tasks are safe. All three variants share the 512-bit block and padding, differing
only in hardware mode and digest length. The block also does SHA-384/512, which
use a 1024-bit block and are not exposed here.
AES, and a silicon limitation worth a contract
type Block is array (0 .. 15) of Interfaces.Unsigned_8;
subtype Key_128 is Key_Bytes (0 .. 15);
subtype Key_256 is Key_Bytes (0 .. 31);
function Supported_Key (Key : Key_Bytes) return Boolean;
function Encrypt_ECB (Key : Key_Bytes; Plain : Block) return Block;
The S3's AES supports 128- and 256-bit keys only.
There is no 192-bit support on this silicon — selecting "AES-192" makes the
engine silently fall back to AES-128 on the first 16 key bytes, which
would produce ciphertext that looks fine and is not what you asked for. The
Supported_Key precondition turns that into a contract violation
instead. 192-bit keys exist only on the original ESP32.
What is exposed is single-block ECB, deliberately: it is the primitive, not a mode you should be encrypting messages with directly. Build CBC, CTR or GCM on top of it.
RSA
type Word_Array is array (Natural range <>) of Word; -- little-endian limbs
procedure Mod_Exp (X, Y, M, R2 : Word_Array; Z : out Word_Array; Ok : out Boolean);
procedure Mod_Exp (X, Y, M : Word_Array; Z : out Word_Array; Ok : out Boolean);
Big-integer modular exponentiation, Z = X**Y mod M, up to
4096-bit — the core of RSA signature verification. The hardware does
Montgomery exponentiation; the driver computes M' itself, and the
second overload derives R2 for you as well. Operands are
little-endian 32-bit limb arrays, all the same length. It is lock-free and
ZFP-safe: a sequence of register accesses with no tasking and no heap.
MD5, and why it is here
MD5 is long broken as a cryptographic hash and this package does
not pretend otherwise. It earns its place because the ESP32 ROM loader's
SPI_FLASH_MD5 command speaks it: after writing an image, the target
hashes what its flash actually holds, this computes what it should hold,
and comparing them is what lets "programmed OK" mean something. An integrity
check against accident, not against an adversary.
It is streaming (Reset / Update /
Hex_Digest), pure Ada, no heap, no tasking — so it runs on the
light runtime too.
The RNG caveat
-- Read is a single atomic 32-bit register load; Fill writes a buffer.
Each read of the RNG data register returns a fresh 32-bit value. It is Preelaborate, heap-free, finalization-free and task-safe by construction — the one peripheral that keeps every one of those properties at once.
Do not treat it as a CSPRNG as it stands. The TRM wants an active entropy source for cryptographic-quality output — the RF subsystem, which this bare runtime does not start, or SAR-ADC bootloader entropy. Without one it still produces varying values from internal clock jitter, which is fine for dithering, non-secret identifiers, test data or seeding a software PRNG. It is not fine for keys. Also avoid reading in a tight loop faster than the hardware refreshes, or successive words may correlate.
A related trap: this is the RNG peripheral register, not
esp-idf's WDEV_RND_REG. That one only yields entropy with the RF
clock domain up, so on this runtime it reads a constant — a mistake that
would look like working code returning the same "random" number forever.