AES (Advanced Encryption Standard) is a widely used encryption algorithm to secure data. It operates in different modes, such as AES-GCM, AES-CBC, AES-CTR, AES-ECB, AES-CFB, and AES-OFB, each with unique characteristics. This blog post explains these modes in simple terms, compares them, and provides easy-to-understand JavaScript code examples using the Web Crypto API where applicable. Each section includes an image to visually illustrate the mode’s operation and a table summarizing key features, pros, and cons.
What Are AES Encryption Modes?
AES encryption modes define how the AES algorithm processes data blocks. AES encrypts data in fixed-size blocks (128 bits), and the mode determines how these blocks are handled, especially for data larger than one block. Let’s explore six common modes: AES-GCM, AES-CBC, AES-CTR, AES-ECB, AES-CFB, and AES-OFB.
1. AES-GCM (Galois/Counter Mode)
What is AES-GCM?
AES-GCM is a mode that combines encryption with authentication. It ensures data confidentiality and verifies that the data hasn’t been tampered with. It’s widely used in secure communication protocols like TLS.
AES-GCM Characteristics
Aspect | Details |
---|---|
Key Features | Authentication with built-in tag, uses a 12-byte nonce, parallelizable, used in HTTPS and disk encryption. |
Pros | Provides encryption and authentication, resistant to padding oracle attacks, efficient for modern applications. |
Cons | Requires unique nonce to avoid security risks, slightly complex to implement correctly. |
Code Example (AES-GCM in JavaScript):
async function encryptAESGCM(plaintext, key) {
const encoder = new TextEncoder();
const data = encoder.encode(plaintext);
const iv = crypto.getRandomValues(new Uint8Array(12)); // 12-byte nonce
const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "AES-GCM" }, false, ["encrypt"]);
const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv: iv }, cryptoKey, data);
return { iv, ciphertext: new Uint8Array(encrypted) };
}
async function decryptAESGCM(ciphertext, key, iv) {
const cryptoKey = await crypto.subtle.importKey( "raw", key, { name: "AES-GCM" }, false, ["decrypt"]);
const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv: iv }, cryptoKey, ciphertext);
return new TextDecoder().decode(decrypted);
}
// Usage
(async () => {
const key = crypto.getRandomValues(new Uint8Array(16)); // 128-bit key
const plaintext = "Hello, AES-GCM!";
const { iv, ciphertext } = await encryptAESGCM(plaintext, key);
console.log("Encrypted:", ciphertext);
const decrypted = await decryptAESGCM(ciphertext, key, iv);
console.log("Decrypted:", decrypted);
})();
2. AES-CBC (Cipher Block Chaining)
What is AES-CBC?
AES-CBC is one of the oldest AES modes. It chains blocks together, where each block’s encryption depends on the previous block’s ciphertext. It requires an initialization vector (IV) to start the chain.
AES-CBC Characteristics
Aspect | Details |
---|---|
Key Features | Uses a 16-byte random IV, block dependency, used in legacy systems and file encryption. |
Pros | Simple and widely supported, secure with a random IV. |
Cons | No built-in authentication, not parallelizable, susceptible to padding oracle attacks. |
Code Example (AES-CBC in JavaScript):
async function encryptAESCBC(plaintext, key) {
const encoder = new TextEncoder();
const data = encoder.encode(plaintext);
const iv = crypto.getRandomValues(new Uint8Array(16)); // 16-byte IV
const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "AES-CBC" }, false, ["encrypt"]);
const encrypted = await crypto.subtle.encrypt({ name: "AES-CBC", iv: iv }, cryptoKey, data);
return { iv, ciphertext: new Uint8Array(encrypted) };
}
async function decryptAESCBC(ciphertext, key, iv) {
const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "AES-CBC" }, false, ["decrypt"]);
const decrypted = await crypto.subtle.decrypt({ name: "AES-CBC", iv: iv }, cryptoKey, ciphertext);
return new TextDecoder().decode(decrypted);
}
// Usage
(async () => {
const key = crypto.getRandomValues(new Uint8Array(16)); // 128-bit key
const plaintext = "Hello, AES-CBC!";
const { iv, ciphertext } = await encryptAESCBC(plaintext, key);
console.log("Encrypted:", ciphertext);
const decrypted = await decryptAESCBC(ciphertext, key, iv);
console.log("Decrypted:", decrypted);
})();
3. AES-CTR (Counter Mode)
What is AES-CTR?
AES-CTR turns AES into a stream cipher. It uses a counter combined with a nonce to encrypt data, making it suitable for streaming applications.
AES-CTR Characteristics
Aspect | Details |
---|---|
Key Features | Uses a 16-byte nonce with a counter, stream cipher, used in streaming and disk encryption. |
Pros | Fast and parallelizable, no padding required, suitable for streaming data. |
Cons | No built-in authentication, nonce reuse is catastrophic for security. |
Code Example (AES-CTR in JavaScript):
async function encryptAESCTR(plaintext, key) {
const encoder = new TextEncoder();
const data = encoder.encode(plaintext);
const iv = crypto.getRandomValues(new Uint8Array(16)); // 16-byte nonce
const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "AES-CTR" }, false, ["encrypt"]);
const encrypted = await crypto.subtle.encrypt({ name: "AES-CTR", counter: iv, length: 128 }, cryptoKey,data);
return { iv, ciphertext: new Uint8Array(encrypted) };
}
async function decryptAESCTR(ciphertext, key, iv) {
const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "AES-CTR" }, false, ["decrypt"]);
const decrypted = await crypto.subtle.decrypt({ name: "AES-CTR", counter: iv, length: 128 }, cryptoKey, ciphertext);
return new TextDecoder().decode(decrypted);
}
// Usage
(async () => {
const key = crypto.getRandomValues(new Uint8Array(16)); // 128-bit key
const plaintext = "Hello, AES-CTR!";
const { iv, ciphertext } = await encryptAESCTR(plaintext, key);
console.log("Encrypted:", ciphertext);
const decrypted = await decryptAESCTR(ciphertext, key, iv);
console.log("Decrypted:", decrypted);
})();
4. AES-ECB (Electronic Codebook)
What is AES-ECB?
AES-ECB is the simplest AES mode, where each block is encrypted independently using the same key. It does not use an IV or nonce, making it predictable for identical plaintext blocks.
AES-ECB Characteristics
Aspect | Details |
---|---|
Key Features | No IV/nonce, block independence, used in legacy systems (rarely). |
Pros | Extremely simple to implement, fast due to independent block processing. |
Cons | Insecure due to pattern leakage, no authentication, not recommended. |
Code Example (AES-ECB in JavaScript):
Note: The Web Crypto API does not support AES-ECB due to its insecurity. Below is a conceptual example using a library like crypto-js
for demonstration purposes only. Do not use ECB in production.
const CryptoJS = require("crypto-js");
function encryptAESECB(plaintext, key) {
const keyWordArray = CryptoJS.enc.Utf8.parse(key);
const encrypted = CryptoJS.AES.encrypt(plaintext, keyWordArray, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
}
function decryptAESECB(ciphertext, key) {
const keyWordArray = CryptoJS.enc.Utf8.parse(key);
const decrypted = CryptoJS.AES.decrypt(
{ ciphertext: CryptoJS.enc.Base64.parse(ciphertext) },
keyWordArray,
{ mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }
);
return decrypted.toString(CryptoJS.enc.Utf8);
}
// Usage
const key = "1234567890123456"; // 16-byte key
const plaintext = "Hello, AES-ECB!";
const ciphertext = encryptAESECB(plaintext, key);
console.log("Encrypted:", ciphertext);
const decrypted = decryptAESECB(ciphertext, key);
console.log("Decrypted:", decrypted);
5. AES-CFB (Cipher Feedback)
What is AES-CFB?
AES-CFB turns AES into a stream cipher by using the previous ciphertext to encrypt the next block. It requires an IV to start the process.
AES-CFB Characteristics
Aspect | Details |
---|---|
Key Features | Uses a 16-byte random IV, stream cipher, used in streaming and communication. |
Pros | No padding required, suitable for streaming, more secure than ECB. |
Cons | No built-in authentication, slower than CTR, error propagation in blocks. |
Code Example (AES-CFB in JavaScript):
Note: The Web Crypto API does not support AES-CFB. Below is a conceptual example using crypto-js
.
const CryptoJS = require("crypto-js");
function encryptAESCFB(plaintext, key) {
const iv = CryptoJS.lib.WordArray.random(16); // 16-byte IV
const keyWordArray = CryptoJS.enc.Utf8.parse(key);
const encrypted = CryptoJS.AES.encrypt(plaintext, keyWordArray, {
iv: iv,
mode: CryptoJS.mode.CFB,
padding: CryptoJS.pad.NoPadding
});
return { iv: iv.toString(CryptoJS.enc.Base64), ciphertext: encrypted.ciphertext.toString(CryptoJS.enc.Base64) };
}
function decryptAESCFB(ciphertext, key, iv) {
const keyWordArray = CryptoJS.enc.Utf8.parse(key);
const ivWordArray = CryptoJS.enc.Base64.parse(iv);
const decrypted = CryptoJS.AES.decrypt(
{ ciphertext: CryptoJS.enc.Base64.parse(ciphertext) },
keyWordArray,
{ iv: ivWordArray, mode: CryptoJS.mode.CFB, padding: CryptoJS.pad.NoPadding }
);
return decrypted.toString(CryptoJS.enc.Utf8);
}
// Usage
const key = "1234567890123456"; // 16-byte key
const plaintext = "Hello, AES-CFB!";
const { iv, ciphertext } = encryptAESCFB(plaintext, key);
console.log("Encrypted:", ciphertext);
const decrypted = decryptAESCFB(ciphertext, key, iv);
console.log("Decrypted:", decrypted);
6. AES-OFB (Output Feedback)
What is AES-OFB?
AES-OFB also turns AES into a stream cipher. It generates a keystream by repeatedly encrypting the IV, which is then XORed with the plaintext.
AES-OFB Characteristics
Aspect | Details |
---|---|
Key Features | Uses a 16-byte random IV, stream cipher, used in low-error environments. |
Pros | No padding required, suitable for streaming, errors affect only corresponding bits. |
Cons | No built-in authentication, nonce reuse is dangerous, less common. |
Code Example (AES-OFB in JavaScript):
Note: The Web Crypto API does not support AES-OFB. Below is a conceptual example using crypto-js
.
const CryptoJS = require("crypto-js");
function encryptAESOFB(plaintext, key) {
const iv = CryptoJS.lib.WordArray.random(16); // 16-byte IV
const keyWordArray = CryptoJS.enc.Utf8.parse(key);
const encrypted = CryptoJS.AES.encrypt(plaintext, keyWordArray, {
iv: iv,
mode: CryptoJS.mode.OFB,
padding: CryptoJS.pad.NoPadding
});
return { iv: iv.toString(CryptoJS.enc.Base64), ciphertext: encrypted.ciphertext.toString(CryptoJS.enc.Base64) };
}
function decryptAESOFB(ciphertext, key, iv) {
const keyWordArray = CryptoJS.enc.Utf8.parse(key);
const ivWordArray = CryptoJS.enc.Base64.parse(iv);
const decrypted = CryptoJS.AES.decrypt(
{ ciphertext: CryptoJS.enc.Base64.parse(ciphertext) },
keyWordArray,
{ iv: ivWordArray, mode: CryptoJS.mode.OFB, padding: CryptoJS.pad.NoPadding }
);
return decrypted.toString(CryptoJS.enc.Utf8);
}
// Usage
const key = "1234567890123456"; // 16-byte key
const plaintext = "Hello, AES-OFB!";
const { iv, ciphertext } = encryptAESOFB(plaintext, key);
console.log("Encrypted:", ciphertext);
const decrypted = decryptAESOFB(ciphertext, key, iv);
console.log("Decrypted:", decrypted);
Comparison of AES-GCM, AES-CBC, AES-CTR, AES-ECB, AES-CFB, AES-OFB
Feature | AES-GCM | AES-CBC | AES-CTR | AES-ECB | AES-CFB | AES-OFB |
---|---|---|---|---|---|---|
Type | Authenticated Encryption | Block Cipher | Stream Cipher | Block Cipher | Stream Cipher | Stream Cipher |
Authentication | Yes (built-in tag) | No | No | No | No | No |
IV/Nonce | Nonce (12 bytes) | IV (16 bytes) | Nonce (16 bytes) | None | IV (16 bytes) | IV (16 bytes) |
Parallelizable | Yes | No | Yes | Yes | No | No |
Padding Required | No | Yes | No | Yes | No | No |
Delegate | TLS, disk encryption | Legacy systems, files | Streaming, disk encryption | Rarely used (insecure) | Streaming, communication | Low-error environments |
Security Concerns | Nonce reuse | Padding oracle attacks | Nonce reuse | Pattern leakage | Error propagation | Nonce reuse |
Which Mode Should You Use?
- AES-GCM: Best for modern applications needing encryption and authentication (e.g., HTTPS).
- AES-CBC: Suitable for legacy systems but requires additional authentication.
- AES-CTR: Ideal for streaming data or when padding is undesirable, but needs authentication.
- AES-ECB: Avoid in most cases due to its insecurity (pattern leakage).
- AES-CFB: Good for streaming data but less common and requires authentication.
- AES-OFB: Suitable for low-error streaming but rarely used due to nonce reuse risks.
Always use a unique IV/nonce for each encryption and ensure proper key management to maintain security.
Conclusion
AES-GCM, AES-CBC, AES-CTR, AES-ECB, AES-CFB, and AES-OFB offer different trade-offs. AES-GCM is preferred for modern secure applications due to its authentication. AES-CBC is reliable for legacy use, while AES-CTR, AES-CFB, and AES-OFB suit streaming scenarios. AES-ECB should be avoided due to its security flaws. Understanding these modes helps you choose the right one for your project.
References
- MDN Web Docs - Web Crypto API: Official documentation for the Web Crypto API used in the code examples. See: SubtleCrypto - Web Crypto API.
- NIST Special Publication 800-38D: Standard for AES-GCM. Available at: NIST SP 800-38D.
- NIST Special Publication 800-38A: Standard for AES-CBC, AES-CTR, AES-ECB, AES-CFB, and AES-OFB. Available at: NIST SP 800-38A.
- Cryptography Stack Exchange: Community-driven Q&A on AES modes. See: Crypto Stack Exchange.