Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

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.

Hardware crypto, and one honest caveat · Bare-Metal Ada on the ESP32-S3
Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

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.

Hardware crypto, and one honest caveat · Bare-Metal Ada on the ESP32-S3
Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

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.

Hardware crypto, and one honest caveat · Bare-Metal Ada on the ESP32-S3
Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

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.

Hardware crypto, and one honest caveat · Bare-Metal Ada on the ESP32-S3
Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

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.

Hardware crypto, and one honest caveat · Bare-Metal Ada on the ESP32-S3
Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

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.