A hands-on guide to the built-in Crypto class: AES encryption in CBC and GCM modes, hashing, HMACs, and digital signatures. Follow the animated path, run the live demo, flip through every method, then test yourself.
Every AES call in Apex walks the same path. Watch it advance — or click any stage to read what happens there.
With managed IV, Salesforce packs the IV into the output for you — but the layout differs by mode:
encryptWithManagedIV('AES256', ...)
GCM · encryptWithManagedIV('AES256-GCM', ...)
With GCM you never supply an IV yourself — passing any non-null IV to encrypt() throws an error. With CBC + encrypt(), your IV must be exactly 16 bytes.
| Mode | Variants | Description |
|---|---|---|
| CBC | AES128 / AES128-CBC | AES 128-bit, CBC mode, PKCS7 padding |
AES192 / AES192-CBC | AES 192-bit, CBC mode, PKCS7 padding | |
AES256 / AES256-CBC | AES 256-bit, CBC mode, PKCS7 padding | |
| GCM | AES256-GCM | AES 256-bit, GCM mode, no padding. Only 256-bit is currently supported for GCM |
generateMac over the ciphertext with key #2, and verifyHMac before decrypting) — or simply use a GCM algorithm.| Type | Variants | Description |
|---|---|---|
| RSA | RSA / RSA-SHA1 | RSA signature of a SHA1 hash |
RSA-SHA256 | RSA signature of a SHA256 hash | |
RSA-SHA384 | RSA signature of a SHA384 hash | |
RSA-SHA512 | RSA signature of a SHA512 hash | |
| ECDSA (DER) | ECDSA-SHA256 | ECDSA signature of a SHA256 hash |
ECDSA-SHA384 | ECDSA signature of a SHA384 hash | |
ECDSA-SHA512 | ECDSA signature of a SHA512 hash | |
| ECDSA (P1363) | ECDSA-SHA256-P1363 | ECDSA SHA256, P1363 format |
ECDSA-SHA256-PLAIN | P1363 format — the fix when a JWT flow returns invalid_client | |
ECDSA-SHA384-P1363 | ECDSA, P1363 format | |
ECDSA-SHA512-P1363 | ECDSA, P1363 format |
JWT gotcha from the docs: if you sign with ECDSA (P-256 curve, P1363 format) and verify with ECDSA-SHA256, the JWT service can return invalid_client. Switch to ECDSA-SHA256-PLAIN and the error is resolved.
| Method | Accepted algorithm values |
|---|---|
generateDigest | MD5 · SHA1 · SHA-256 · SHA-512 · SHA3-256 · SHA3-384 · SHA3-512 |
generateMac / verifyHMac | hmacMD5 · hmacSHA1 · hmacSHA256 · hmacSHA512 — key up to 4 KB |
If you Base64-encode the private key for generateMac, you must supply the same Base64-encoded key to verifyHMac.
Runs real AES-256-CBC in your browser and mimics Crypto.encryptWithManagedIV('AES256', ...) — including prepending the 16-byte IV to the output, exactly like the byte diagram above. Nothing leaves this page.
🧪 Tamper a byte flips one byte of the ciphertext so you can watch decryption fail — the browser's CBC throws just like Apex raises a SecurityException ("Given final block isn't properly padded").
String algorithmName = 'AES128'; Blob key = Crypto.generateAesKey(128); Blob data = Blob.valueOf('Data to be encrypted'); Blob encrypted = Crypto.encryptWithManagedIV(algorithmName, key, data); Blob decrypted = Crypto.decryptWithManagedIV(algorithmName, key, encrypted); Assert.areEqual('Data to be encrypted', decrypted.toString()); // store ciphertext in a text field String stored = EncodingUtil.base64Encode(encrypted);
Best default when Salesforce is on both sides. Key sizes must match the algorithm: 16 / 24 / 32 bytes for AES 128 / 192 / 256. Input max: 1,048,576 bytes (1 MB).
String algorithmName = 'AES256-GCM'; // No IV if you specify AES256-GCM Blob key = Crypto.generateAesKey(256); Blob data = Blob.valueOf('Data to be encrypted'); Blob aad = Blob.valueOf('Additional tag'); Blob encrypted = Crypto.encryptWithManagedIV(algorithmName, key, data, aad); Blob decrypted = Crypto.decryptWithManagedIV(algorithmName, key, encrypted, aad); Assert.areEqual('Data to be encrypted', decrypted.toString());
AAD is bound to the ciphertext but not encrypted — change it and decryption fails. The AAD overloads support only AES256-GCM.
// 16-byte value (doc sample uses a literal; make it random in production) Blob exampleIv = Blob.valueOf('Example of IV123'); Blob key = Crypto.generateAesKey(128); Blob data = Blob.valueOf('Data to be encrypted'); Blob encrypted = Crypto.encrypt('AES128', key, exampleIv, data); Blob decrypted = Crypto.decrypt('AES128', key, exampleIv, encrypted); System.debug(decrypted.toString());
You must transmit or store the IV alongside the ciphertext. Never reuse a fixed IV with the same key in production.
// Digest: one-way fingerprint Blob hash = Crypto.generateDigest('SHA-256', Blob.valueOf('hello world')); System.debug(EncodingUtil.convertToHex(hash)); // HMAC: keyed hash — webhook verification, integrity checks Blob macKey = Crypto.generateAesKey(256); Blob body = Blob.valueOf(requestBody); Blob mac = Crypto.generateMac('hmacSHA256', body, macKey); // verify — don't hand-roll a comparison Boolean ok = Crypto.verifyHMac('hmacSHA256', body, macKey, mac);
// 1) Sign with an org certificate (Setup → Certificate and Key Management) Blob input = Blob.valueOf('Test Sign With Certificate.'); Blob sig = Crypto.signWithCertificate('RSA', input, 'your-cert-unique-name'); // 2) Sign with a raw PKCS #8 key (base64Decoded, max 4 KB, no BEGIN/END lines) Blob privateKey = EncodingUtil.base64Decode(rawPkcs8Key); Blob sig2 = Crypto.sign('RSA', input, privateKey); // 3) Verify with a public key or an org certificate Boolean ok = Crypto.verify('RSA-SHA256', input, sig2, publicKeyBlob); // 4) Envelop a signature inside an XML document Crypto.signXML('RSA', doc.getRootElement(), null, 'your-cert-unique-name');
Prep a PKCS8 key with openssl: openssl genrsa -out k.pem 1024 then openssl pkey -in k.pem -out k.pkcs8.pem.
👆 Click or tap any card to flip it.
128 / 192 / 256 bits. Returns a Blob. Generate once, store in a protected custom setting, reuse forever.aaData overload is GCM-only.InvalidParameterValue if the blob is too short to contain the IV. AAD used to encrypt must match at decrypt.null — anything else errors. For third-party handshakes.-PLAIN). Key: base64Decoded PKCS #8, ≤ 4 KB, no BEGIN/END PRIVATE KEY lines.Dom.XmlNode. Overload with refChild inserts before a chosen child node.| Exception | Typical message | Cause & fix |
|---|---|---|
| InvalidParameterValue | "Unable to parse the initialization vector…" | Managed-IV ciphertext shorter than the IV header — check you stored the full blob |
| InvalidParameterValue | "Invalid private key. Must be size bytes." | Key length ≠ algorithm: 16/24/32 bytes for AES 128/192/256 |
| InvalidParameterValue | "Invalid initialization vector…" | CBC needs a 16-byte IV; GCM's IV is 12 bytes and Salesforce-generated |
| InvalidParameterValue | "AAD can only be used with AESGCM algorithms." | You passed aaData with CBC — switch to AES256-GCM |
| InvalidParameterValue | "…exceeds the limit of 1,048,576 bytes." | Input over 1 MB — chunk the payload |
| NullPointerException | "Argument can't be null." | A required argument was null |
| SecurityException | "Given final block isn't properly padded." | Wrong key, wrong IV, or corrupted ciphertext — try the Tamper button above! |
Crypto.encrypt('AES256-GCM', key, myIv, data) with a non-null IV…encryptWithManagedIV('AES256', ...) put the IV?