AES-256 is the cipher. GCM is why you can trust the result.
AES (Advanced Encryption Standard) is a block cipher: it transforms 16-byte blocks under a secret key. The “256” is the key length in bits. Longer keys raise the cost of brute force; they do not by themselves detect tampering.
A mode of operation decides how those blocks chain, and whether the ciphertext is authenticated. CBC (Cipher Block Chaining) encrypts only. An attacker who can flip bits in CBC ciphertext can often flip bits in the resulting plaintext without knowing the key. GCM (Galois/Counter Mode) is authenticated encryption: it produces ciphertext and a tag. Decrypting with the wrong tag fails. Silent modification is not an option.
That is why a credential store should use AES-256-GCM, not “AES-256” in the abstract. The key length is necessary. The authentication tag is what makes the stored blob safe to round-trip through a database.
Three numbers that actually show up in code
A typical GCM implementation in Node looks like this:
const ALGORITHM = "aes-256-gcm";
const IV_LENGTH = 16; // 128-bit initialization vector
const AUTH_TAG_LENGTH = 16; // 128-bit tag- Key. 32 bytes. Ours is a hex string pulled from AWS Secrets Manager at runtime, not from the application bundle. If the key leaks, every ciphertext sealed with it is readable. If the key is lost, every ciphertext is gone – there is no backdoor.
- IV (initialization vector). A unique random 16-byte value per encryption. Reusing an IV with the same key in GCM is a class of failure (nonce reuse) that can reveal plaintext.
crypto.randomBytes(16)on everyencryptDatacall is not optional decoration. - Auth tag. 16 bytes produced by GCM. Stored next to the ciphertext, not derived from it later. On decrypt you
setAuthTagbeforefinal(); a mismatch throws.
The on-disk format we use is three Base64 fields, colon-separated:
base64(iv) : base64(authTag) : base64(ciphertext)A string that does not split into three parts is not our ciphertext (it may be a legacy plaintext that still needs migrating). Logging that string is safe-ish – it is not the key – but it is still the wrapped secret, so it does not belong in application logs either.
Encryption is not hashing, and not TLS
- Hashing (SHA-256, bcrypt) is one-way. You cannot get the password back. API keys we must send upstream cannot be stored as hashes.
- TLS protects data on the network. It does not protect a DynamoDB item at rest.
- AES-GCM is reversible with the key. That is the point: the gateway decrypts at request time, attaches the vendor secret, and never returns the plaintext on GET.
Credential secrets in RequestRocket are encrypted before they are written to the regional proxyCredentials / targetCredentials tables. GET/PUT of a credential does not return the secret. Rotation means writing a new ciphertext, not editing a field in the UI. The product write-up of where those tables live is in the security architecture docs – this post is the algorithm.
What AES-256-GCM does not do
It does not hide which record is a credential (the table layout does). It does not replace access control on who can use the credential (rules and proxy credentials do). It does not protect a secret the caller already has in an environment variable. Putting the vendor key only in the gateway is what shrinks that last case.
It also does not replace HTTPS. Encrypting at rest and then sending the decrypted header over cleartext HTTP would undo the work. The gateway requires TLS to the caller and should only target https:// base URLs.
Next steps
If you are choosing a mode for your own store: AES-256-GCM, unique IV per record, key in a secrets manager, tag stored with the blob, decrypt only on the hot path that needs the plaintext. How RequestRocket applies that to credentials is in the security architecture guide. RequestRocket is runtime access control for AI agents and apps calling APIs you don’t own – every call gets a least-privilege credential, a policy check, and an audit record, with no code changes.