The application encrypts sensitive fields (document numbers, phone numbers, tokens) through the Stronghold API and stores only ciphertext in the database. The encryption key never leaves Stronghold, so a leaked database dump does not expose the data.
Goal
Set up a Transit key for an application, encrypt and decrypt a field, apply envelope encryption for large objects, rotate the key, re-encrypt data (rewrap), and prohibit decryption with old key versions.
Prerequisites
To follow this example, you need:
- A Stronghold token with permissions to enable secrets engines and create policies.
- The
base64,jq,openssl, andxxdutilities for the examples.
Step 1. Create a key
Enable the Transit secrets engine and create a key:
Enable the secrets engine:
d8 stronghold secrets enable transitCreate a named key for the application:
d8 stronghold write -f transit/keys/orders type=aes256-gcm96Use a separate key for each application or data set.
Step 2. Separate permissions
Create separate policies for the application and for the re-encryption job:
Application policy: encryption and decryption only:
d8 stronghold policy write orders-app - <<'POLICY' path "transit/encrypt/orders" { capabilities = ["update"] } path "transit/decrypt/orders" { capabilities = ["update"] } path "transit/datakey/plaintext/orders" { capabilities = ["update"] } POLICYRe-encryption job policy: rewrap only, with no access to plaintext:
d8 stronghold policy write orders-rewrap - <<'POLICY' path "transit/rewrap/orders" { capabilities = ["update"] } path "transit/keys/orders" { capabilities = ["read"] } POLICY
Leave key management (transit/keys/orders/*) to administrators.
Step 3. Encrypt and decrypt a field
Data is sent to Stronghold base64-encoded.
Encrypt a value:
d8 stronghold write -field=ciphertext transit/encrypt/orders plaintext=$(echo -n "4509 123456" | base64)Store the
vault:v1:...result in a table column. Thev1prefix is the key version used for encryption.Decrypt the value:
d8 stronghold write -field=plaintext transit/decrypt/orders ciphertext="vault:v1:..." | base64 --decode
To process many records in one request, use the batch_input parameter of the encrypt, decrypt, and rewrap endpoints.
Step 4. Use envelope encryption for large data
Sending files and large objects through the API is inefficient. Instead, request a data key: Stronghold returns it in plaintext for local encryption and in encrypted form for storage.
Get a data key:
d8 stronghold write -format=json -f transit/datakey/plaintext/orders > datakey.json jq -r '.data.plaintext' datakey.json | base64 --decode > dek.bin jq -r '.data.ciphertext' datakey.json > dek.encEncrypt the file locally and delete the plaintext key:
openssl rand -hex 16 > report.pdf.iv openssl enc -aes-256-cbc -K "$(xxd -p -c 64 dek.bin)" -iv "$(cat report.pdf.iv)" -in report.pdf -out report.pdf.enc shred -u dek.binThe data key is 256 bits long and is passed to
opensslas a hexadecimal AES key with the-Kflag. Thereport.pdf.ivfile contains a random initialization vector (IV). Generate a new IV for each file. Storereport.pdf.enctogether withdek.encandreport.pdf.iv.To decrypt, recover the data key through Transit and decrypt the file with the same IV:
d8 stronghold write -field=plaintext transit/decrypt/orders ciphertext="$(cat dek.enc)" | base64 --decode > dek.bin openssl enc -d -aes-256-cbc -K "$(xxd -p -c 64 dek.bin)" -iv "$(cat report.pdf.iv)" -in report.pdf.enc -out report.pdf shred -u dek.bin
If the data key is needed only for storage (for example, another service will decrypt it), use transit/datakey/wrapped/orders: the response does not contain the plaintext key.
Step 5. Rotate the key
Rotate the key manually or set up automatic rotation:
Create a new key version:
d8 stronghold write -f transit/keys/orders/rotateNew data is encrypted with the latest version; old data can still be decrypted.
To rotate automatically, set a period:
d8 stronghold write transit/keys/orders/config auto_rotate_period=720h
Step 6. Re-encrypt data (rewrap)
Rewrap re-encrypts ciphertext with the latest key version without exposing plaintext. Run it for all table records, for example as a background job with the orders-rewrap policy:
d8 stronghold write -field=ciphertext transit/rewrap/orders ciphertext="vault:v1:..."Write the vault:v2:... result back to the column.
Step 7. Prohibit decryption with old versions
When the database no longer contains ciphertext with old versions, raise the minimum decryption version:
d8 stronghold write transit/keys/orders/config min_decryption_version=2Versions below min_decryption_version are archived and are not used for decryption. In an emergency, you can lower the value again.
To delete old versions permanently, use transit/keys/orders/trim with the min_available_version parameter. This action is irreversible.
Verification
Check the result:
View the key state:
d8 stronghold read transit/keys/ordersCheck the
latest_version,min_decryption_version, andauto_rotate_periodvalues.Try to decrypt
vault:v1:...ciphertext after step 7: the request must fail.Make sure a token with the
orders-rewrappolicy cannot calltransit/decrypt/orders.
Cleanup
Delete the objects created in this example:
Delete the policies:
d8 stronghold policy delete orders-appandd8 stronghold policy delete orders-rewrap.Delete the local files
datakey.json,dek.enc,report.pdf.iv, andreport.pdf.enc.To delete the test key, allow deletion and delete the key:
d8 stronghold write transit/keys/orders/config deletion_allowed=true d8 stronghold delete transit/keys/ordersData encrypted with this key becomes inaccessible.