AES Image Encryption¶
The EncryptImageWithAES operation encrypts an arbitrary image payload with one of eight supported AES modes. It covers the CBC variants and AES-GCM
(authenticated encryption) plus optional Additional Authenticated Data (AAD) binding.
The operation is exposed through the standard imagesigning flow:
- The reference client uploads the plaintext payload to the platform's S3 staging bucket via a presigned URL.
- It then issues an
imagesigning add EncryptImageWithAESrequest that references the upload, the target product operation, the chosenaesMode, and (optionally)aad. - After approval, the platform encrypts the payload inside the HSM-backed crypto service. The IV used by CBC modes is the product's KEK-IV — a 16-byte value generated by the platform at product creation time and stored as a product config item. Callers do not supply it.
- The client polls and downloads the resulting ciphertext, plus any unwrapping material the chosen mode produces in
service_provided_parameters(sealed key, sealed-key IV, IV, AAD).
Prerequisites¶
- A product that has been created with an operation of type
EncryptImageWithAES. The operation is keyed by an AES symmetric key generated and held inside the HSM at product creation. See the Product Configuration Guide for how to add the operation to a product. - The product / operation IDs.
- A user token and, when the operation is configured to require approval, an approver token. See the Authentication chapter.
- For CBC modes, the device or downstream pipeline that decrypts the result must know the product's KEK-IV. It is
retrieved with a registered ProductionPC client through the product secrets endpoint (
signing-tool secrets get) — the value is returned alongsideSRKHash/SRKTableasKEKIV(also surfaced in the product config items asKEK-IVCustomEncryption). See Clients for the registration and retrieval flow. The platform reuses the same KEK-IV across everyEncryptImageWithAESrequest for the product, so this fetch only needs to happen once per provisioned device class.
Supported AES modes¶
The aesMode field in the request determines which algorithm the platform uses, the key length, and whether the data
key is wrapped (PSKEK) or used directly. If the field is omitted, the platform falls back to AES-CBC-256-PSKEK.
aesMode |
Algorithm | Key length | Authenticated | Per-Session KEK? | When to use |
|---|---|---|---|---|---|
AES-CBC-128 |
AES-128-CBC | 128 bit | No | No | Compatibility with legacy bootloaders that decrypt directly with the product key. |
AES-CBC-256 |
AES-256-CBC | 256 bit | No | No | Same as above with a 256-bit key. |
AES-CBC-128-PSKEK |
AES-128-CBC + KW | 128 bit | No | Yes | Each request uses a fresh data key; the data key is wrapped with the product KEK and returned as sealed_key. |
AES-CBC-256-PSKEK |
AES-256-CBC + KW | 256 bit | No | Yes | Default. Unique-per-payload key reduces blast radius of a leaked ciphertext. |
AES-GCM-128 |
AES-128-GCM | 128 bit | Yes | No | Authenticated encryption with a long-lived product key (e.g. firmware sealed to a single device class). |
AES-GCM-256 |
AES-256-GCM | 256 bit | Yes | No | Same as above with a 256-bit key. |
AES-GCM-128-PSKEK |
AES-128-GCM + KW | 128 bit | Yes | Yes | Authenticated encryption with a unique-per-request data key. AAD covers the outer payload only — see AAD and PSKEK below. |
AES-GCM-256-PSKEK |
AES-256-GCM + KW | 256 bit | Yes | Yes | Recommended default for new firmware encryption flows. |
PSKEK explained
A *-PSKEK mode generates a fresh AES data key per request, encrypts the payload with that data key, then wraps the
data key with the product's KEK (the long-lived key in the HSM). For CBC-PSKEK the wrap is AES-CBC under the
product's KEK-IV; for GCM-PSKEK the wrap is AES-GCM with no AAD on the inner wrap (caller AAD only binds the
outer payload — see AAD and PSKEK). The platform returns the wrapped key in sealed_key so the
device can recover the data key with its KEK and then decrypt the payload.
Non-PSKEK modes encrypt the payload directly with the product key, so no sealed_key is returned and the device
must already share that product key.
IV / nonce handling¶
Callers do not supply IVs or nonces to EncryptImageWithAES. The CBC and GCM modes get them from different sources,
and the response fields that surface them mean different things in each case — read on before wiring up a decrypter.
CBC modes (AES-CBC-*, AES-CBC-*-PSKEK)¶
- The IV is 16 bytes (the AES block size) and comes from the product's KEK-IV, generated by the platform at
product creation and persisted as the
KEK-IVCustomEncryptionproduct config item. It is the same value for everyEncryptImageWithAESrequest issued against this product. - The KEK-IV is fetched out of the platform with a
ProductionPC-typed client viasigning-tool secrets get(returned as theKEKIVfield in the decrypted product secrets). Devices need it to decrypt the result. - For
AES-CBC-*-PSKEKthe platform additionally generates a random per-request 16-byte payload IV. The KEK-IV is used only to wrap the ephemeral data key under the product KEK; the payload itself is encrypted with the ephemeral data key under the random per-request IV.- The top-level
IVinservice_provided_parametersis the product KEK-IV (used to wrap the data key). - The
sealed_key_ivfield is the per-request payload IV (used to encrypt the payload with the ephemeral data key). - Devices need both:
IVto unwrapsealed_keyagainst the product KEK, thensealed_key_ivto decrypt the payload with the unwrapped data key.
- The top-level
Same KEK-IV across all CBC encryptions
For the non-PSKEK CBC modes (AES-CBC-128, AES-CBC-256) the payload is encrypted directly with the product key
under the product KEK-IV. Because both are constant per product, encrypting the same plaintext twice produces the
same ciphertext (and identical-prefix plaintexts produce identical-prefix ciphertexts). This is acceptable for
one-shot firmware images, but if your threat model needs unique-per-payload ciphertexts use a *-PSKEK mode (which
rotates the data key and the payload IV per request) or a GCM mode.
GCM modes (AES-GCM-*, AES-GCM-*-PSKEK)¶
- GCM uses a 12-byte (96-bit) nonce. This is the only nonce length AWS CloudHSM supports.
- The platform always generates the nonce. The product KEK-IV is not used for GCM modes. When the product key is
HSM-backed the nonce is generated inside the HSM (CloudHSM FIPS requirement); when the key is software-protected the
nonce is drawn from
crypto/rand. - The encrypted payload file is laid out as
nonce ‖ ciphertext ‖ tag(12 + N + 16 bytes). The nonce is prepended for self-contained recovery; readers must slice it off before passing the rest to their GCM implementation. - The top-level
IVinservice_provided_parametersis the same 12-byte payload nonce, surfaced for visibility. - For
AES-GCM-*-PSKEKthesealed_keyblob is itself a self-containednonce ‖ ciphertext ‖ tagof the wrapped data key (under the product KEK).sealed_key_ivis therefore empty for GCM-PSKEK — the wrap nonce lives insidesealed_key.
Single-shot GCM size limit
AES-GCM cannot be safely streamed through a block-cipher pipeline the way CBC can, so the platform encrypts the
full payload in a single Seal call. The hard cap is 40 MB (≈ 4096 × 1000 × 10 bytes). Requests with a
plaintext larger than this are rejected. Use a CBC mode for very large blobs, or split the payload into chunks
that the firmware reassembles after decryption.
Additional Authenticated Data (AAD)¶
AAD lets you bind the ciphertext to a piece of context (a device serial number, a firmware version, an image header) without encrypting it. On decryption the device must supply the exact same AAD or the GCM tag verification fails.
--aad <base64>is only honoured whenaesModeis one of theAES-GCM-*variants. For any CBC mode the field is ignored.- The value must be Base64-encoded raw bytes. There is no length limit imposed by the platform; keep AAD small enough that the device firmware can reproduce it byte-for-byte at decryption time.
- If
--aadis omitted in a GCM mode, the platform encrypts with empty AAD (zero bytes). Decryption then also requires empty AAD.
AAD and PSKEK¶
For the AES-GCM-*-PSKEK modes the AAD is bound only to the outer payload. The PSKEK-wrapped inner data key is
not covered by the AAD — the data key wrap is a separate step performed by the HSM. In practice this means:
- An attacker who can substitute a different
sealed_keycannot do so silently: decrypting the payload with the wrong data key will still cause GCM tag verification to fail. - AAD is not a substitute for transport integrity over the
sealed_keyfield — keep delivering both in the same signed manifest the device validates.
Reference client usage¶
The reference client subcommand is imagesigning add EncryptImageWithAES:
(venv) $ signing-tool imagesigning add EncryptImageWithAES -h
usage: signing-tool imagesigning add EncryptImageWithAES [-h] -P P --operid OPERID -N N -D D -F F -M M [--aad AAD]
options:
-h, --help show this help message and exit
-P P Product ID
--operid OPERID Product operation ID
-N N Request name
-D D Request description
-F F File to encrypt
-M M Encryption mode, (AES-CBC-128, AES-CBC-256, AES-CBC-128-PSKEK, AES-CBC-256-PSKEK,
AES-GCM-128, AES-GCM-256, AES-GCM-128-PSKEK, AES-GCM-256-PSKEK)
--aad AAD Additional Authenticated Data, base64 encoded (GCM modes only)
The reference client does not expose an --iv flag for EncryptImageWithAES. The CBC IV is the product's KEK-IV
managed by the platform (see IV / nonce handling); the GCM nonce is generated by the platform on
every request. The older EncryptImageWithAESEscrowCustom operation still has --iv, but it should not be used for
new flows.
CBC with PSKEK (default)¶
(venv) $ signing-tool -c -t $TOKEN -a https://app.laavat.io/<CustomerName>/api/v1 \
imagesigning add EncryptImageWithAES \
-P b508d600-977a-46f4-9070-5cf56646bae1 \
--operid 8736e553-d8d3-442a-a56b-4d243cf22585 \
-N firmware-v1.6.44 -D "encrypt firmware build 1.6.44" \
-F firmware.bin -M AES-CBC-256-PSKEK
# Approve if the operation requires it
(venv) $ signing-tool -c -t $APPROVERTOKEN -a https://app.laavat.io/<CustomerName>/api/v1 \
imagesigning approve -I <request-id>
# Download the ciphertext + sealed key material
(venv) $ signing-tool -c -t $TOKEN -a https://app.laavat.io/<CustomerName>/api/v1 \
imagesigning get -I <request-id> -O firmware.bin.enc
GCM without PSKEK, with AAD¶
In this example the firmware version string (v1.6.44) is bound to the ciphertext as AAD. The device must supply the
exact same bytes when it decrypts, otherwise the GCM tag check fails.
(venv) $ AAD_B64=$(printf 'v1.6.44' | base64)
(venv) $ signing-tool -c -t $TOKEN -a https://app.laavat.io/<CustomerName>/api/v1 \
imagesigning add EncryptImageWithAES \
-P b508d600-977a-46f4-9070-5cf56646bae1 \
--operid 8736e553-d8d3-442a-a56b-4d243cf22585 \
-N firmware-v1.6.44-gcm -D "GCM-256, AAD=version" \
-F firmware.bin -M AES-GCM-256 \
--aad "$AAD_B64"
GCM with PSKEK and AAD¶
(venv) $ signing-tool -c -t $TOKEN -a https://app.laavat.io/<CustomerName>/api/v1 \
imagesigning add EncryptImageWithAES \
-P b508d600-977a-46f4-9070-5cf56646bae1 \
--operid 8736e553-d8d3-442a-a56b-4d243cf22585 \
-N firmware-v1.6.44-gcm-pskek -D "GCM-256-PSKEK with header AAD" \
-F firmware.bin -M AES-GCM-256-PSKEK \
--aad "$AAD_B64"
Fetching the product KEK-IV (for CBC decryption)¶
For any of the CBC modes, the device or downstream pipeline that decrypts the result needs the product's KEK-IV. Fetch
it once with a registered ProductionPC client:
(venv) $ signing-tool -c -t $TOKEN \
-a https://app.laavat.io/<CustomerName>/api/v1/ secrets get \
-P b508d600-977a-46f4-9070-5cf56646bae1 \
-C client.private -O /tmp/prod.json
(venv) $ jq -r '.productConfigItems[] | select(.name=="KEK-IVCustomEncryption") | .value' < /tmp/prod.json
ZX75HIzTir2AsAst0BbCMw==
This is the same value that arrives back as the top-level IV in the encryption response. See
Clients for the full registration flow.
Response payload¶
The download endpoint returns the ciphertext through a presigned S3 URL plus the unwrapping material in
payload.service_provided_parameters. The exact set of fields depends on the mode:
| Mode family | IV (top level) — meaning |
sealed_key |
sealed_key_iv |
Encrypted file layout |
|---|---|---|---|---|
AES-CBC-* |
Product KEK-IV used to encrypt the payload (16 B; constant per product) | -- | -- | ciphertext (PKCS#7-padded) |
AES-CBC-*-PSKEK |
Product KEK-IV used to wrap the data key with the product KEK (16 B) | data key wrapped under KEK (CBC, 16 / 32 B) | Per-request 16 B payload IV | ciphertext (PKCS#7-padded) |
AES-GCM-* |
12 B payload nonce (informational; same as the prefix of the file) | -- | -- | nonce ‖ ciphertext ‖ tag (12 + N + 16 B) |
AES-GCM-*-PSKEK |
12 B payload nonce (informational; same as the prefix of the file) | data key wrapped under KEK as nonce ‖ ct ‖ tag |
empty (the wrap nonce lives inside sealed_key) |
nonce ‖ ciphertext ‖ tag (12 + N + 16 B) |
For the GCM variants the encrypted file is self-contained: the 12-byte nonce is prepended to the ciphertext and
the 16-byte authentication tag is appended. Readers should split the file as
nonce = file[:12], tag = file[len-16:], ct = file[12:len-16] before calling their GCM implementation.
A truncated example response for an AES-CBC-256-PSKEK request is:
{
"id": "22e2e069-335c-4e8d-8706-675d06723ef5",
"id_product": "b508d600-977a-46f4-9070-5cf56646bae1",
"id_product_operation": "8736e553-d8d3-442a-a56b-4d243cf22585",
"name": "firmware-v1.6.44",
"payload": {
"id": "6ea19a17-284a-4170-84e4-ea0368330424",
"name": "firmware.bin",
"s3_url": "<presigned-S3-URL-for-the-ciphertext>",
"service_provided_parameters": [
{
"aes_mode": "AES-CBC-256-PSKEK"
},
{
"sealed_key": "NhgPZDXsZyG89hhPUAOAYw=="
}
]
},
"state": 16
}
For an AES-GCM-256-PSKEK request the same response carries a self-contained sealed key blob and an empty
sealed_key_iv:
{
"payload": {
"service_provided_parameters": [
{
"aes_mode": "AES-GCM-256-PSKEK"
},
{
"sealed_key": "<base64 of 12-byte nonce ‖ wrapped-key ciphertext ‖ 16-byte tag>"
}
]
},
"state": 16
}
The full set of optional response fields is documented in the
Encryption response payload reference page and in the API specification under
OptionalParameters.
Audit trail¶
Each successful EncryptImageWithAES run produces an OperationCompleted audit event tagged with the product ID and
the matching productOperationType. See Audit Events for the field schema.
Comparison with EncryptImageWithAESEscrowCustom¶
EncryptImageWithAESEscrowCustom predates EncryptImageWithAES and is limited to the four AES-CBC-* modes. It is
still available for backwards compatibility, but new product operations should use EncryptImageWithAES so that
GCM modes and AAD binding are available without re-creating the product.