AES-GCM vs AES-CBC: Authenticated Encryption for Smart Cards
Which AES mode should you use for smart card secure messaging? The answer depends on whether you need authentication (integrity + confidentiality) or just confidentiality. This guide compares AES-GCM and AES-CBC in depth — how they work, security trade-offs, smart card usage (GlobalPlatform SCP03, EMV contactless), and practical code examples.
1. How AES-CBC Works
AES-CBC (Cipher Block Chaining) is a block cipher mode that provides confidentiality only. Each 16-byte plaintext block is XORed with the previous ciphertext block before encryption, creating a chain:
C₀ = AES_Enc(K, IV)
C₁ = AES_Enc(K, P₁ ⊕ C₀)
C₂ = AES_Enc(K, P₂ ⊕ C₁)
...
Key properties:
- Requires padding — plaintext must be padded to a multiple of AES block size (16 bytes). PKCS#7 is standard.
- Sequential encryption — each block depends on the previous one; cannot be parallelized during encryption (but decryption can be parallelized).
- No integrity — an attacker can flip bits in the ciphertext, and decryption will produce modified plaintext without any error. This is the padding oracle attack vulnerability.
- IV must be unpredictable — reusing or predicting the IV leaks information about the first block (XOR of two plaintexts).
2. How AES-GCM Works
AES-GCM (Galois/Counter Mode) is an AEAD (Authenticated Encryption with Associated Data) mode. It combines:
- AES-CTR for encryption — a counter-based stream cipher mode (parallelizable).
- GHASH (Galois field multiplication) for authentication — computes a 128-bit authentication tag over both the ciphertext and any additional authenticated data (AAD).
// Encryption
For i = 0, 1, 2, ...:
key_stream[i] = AES_Enc(K, IV || i)
C[i] = P[i] ⊕ key_stream[i]
// Authentication tag
tag = GHASH(H, AAD || C || len(AAD) || len(C))
where H = AES_Enc(K, 0ⁱ²⁸)
Key properties:
- No padding — CTR mode is a stream cipher; plaintext can be any length.
- Fully parallelizable — both encryption and authentication can run in parallel.
- Built-in authentication — the 128-bit tag guarantees integrity. If any bit of ciphertext or AAD is modified, tag verification fails.
- Nonce must never repeat — reusing a nonce with the same key catastrophically reveals the XOR of two plaintexts and allows tag forgery.
3. Head-to-Head Comparison
| Property | AES-CBC | AES-GCM |
|---|---|---|
| Encryption type | Block cipher mode | Stream cipher (CTR) + MAC |
| Confidentiality | Yes | Yes |
| Integrity / Authentication | No MISSING | Yes BUILT-IN |
| Padding required | Yes (PKCS#7) | No |
| IV / Nonce | 16-byte IV, must be unpredictable | 12-byte nonce (recommended), must never repeat |
| IV/Nonce reuse impact | Leaks XOR of first blocks | Catastrophic: leaks plaintext XOR + tag forgery |
| Encryption parallelizable | No | Yes |
| Decryption parallelizable | Yes | Yes |
| Tag / MAC | Must add HMAC separately | 128-bit authentication tag included |
| Overhead | 16 bytes IV + 1–16 bytes padding | 12 bytes nonce + 16 bytes tag (recommended) |
| Throughput (software) | ~5 cycles/byte | ~3.5 cycles/byte (with PCLMULQDQ) |
| Smart card hardware | Widely available | Available on GP 2.2+ cards |
| NIST standard | SP 800-38A | SP 800-38D |
4. Smart Card Usage
4.1 GlobalPlatform SCP03 (AES-GCM)
GlobalPlatform SCP03 is the modern secure channel protocol that uses AES-GCM for both encryption and authentication of APDU commands and responses. Key features:
- Keys: 3× AES-128 keys (ENC, MAC, DEK) or AES-192/AES-256 variants
- Command APDU: CLS||INS||P1||P2 as AAD, data field encrypted with GCM, tag appended
- Response APDU: data encrypted with GCM, tag included before SW
- Counter-based nonce: each command uses an incrementing 12-byte counter as the GCM nonce, ensuring uniqueness
02 (1 byte) || command counter (5 bytes, big-endian) || 00 00 00 00 00 00 (6 bytes padding). The command counter increments with each APDU, guaranteeing nonce uniqueness within a session.
4.2 EMV Contactless (AES-CBC + HMAC)
EMV contactless transactions typically use AES-CBC for encryption combined with a separate MAC algorithm:
- EMV C2 (Contactless Level 2): Uses AES-128-CBC for session key derivation and data encryption
- MAC computation: AES-CBC-MAC (CMAC) or retail MAC over the command data
- Why CBC? Legacy compatibility — the EMV contact specifications were finalized before GCM adoption became widespread in the payment terminal ecosystem
4.3 Java Card API
Java Card 3.0.5+ provides both modes through Cipher and AEADCipher:
// AES-CBC (Java Card)
Cipher cipherCBC = Cipher.getInstance(
Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false);
// AES-GCM (Java Card 3.1+)
AEADCipher cipherGCM = AEADCipher.getInstance(
AEADCipher.ALG_AES_GCM, false);
cipherGCM.init(key, nonce, aad);
cipherGCM.update(plaintext, 0, plainLen, ciphertext, 0);
cipherGCM.doFinal(tag, 0); // get 16-byte tag
5. Security Analysis
5.1 Why CBC Without HMAC Is Dangerous
AES-CBC provides only confidentiality. Without a MAC, attackers can:
- Flip bits in the plaintext by modifying ciphertext blocks (bit-flipping attack)
- Reorder blocks — CBC doesn't protect block order
- Truncate ciphertext — remove trailing blocks undetected
- Padding oracle — if padding errors are distinguishable from decryption errors, full plaintext recovery is possible
5.2 Why GCM Nonce Reuse Is Catastrophic
If two encryptions use the same key and nonce:
- Plaintext XOR revealed:
C₁ ⊕ C₂ = P₁ ⊕ P₂— identical to a two-time pad - Tag forgery: an attacker can recover the GHASH key
Hand forge valid authentication tags for any message
This is why SCP03 uses an incrementing counter as the nonce — the counter is monotonic within a session, making nonce reuse impossible under normal operation.
5.3 Tag Length Considerations
AES-GCM typically produces a 128-bit tag, but shorter tags (96, 64, 32 bits) are possible:
| Tag Length | Forgery Probability | Use Case |
|---|---|---|
| 128 bits | 2-128 | Standard — always use this if possible |
| 96 bits | 2-96 | Acceptable for short-lived sessions |
| 64 bits | 2-64 | Risky — limit to <2³² messages |
| 32 bits | 2-32 | Never use — trivially forgeable |
6. Performance Comparison
AES-CBC + HMAC
Two passes:
- Encrypt plaintext with AES-CBC
- Compute HMAC-SHA256 over ciphertext
Overhead: 16B IV + padding (1–16B) + 32B HMAC tag = 49–64 bytes
Throughput: ~3.5 cycles/byte (AES) + ~4 cycles/byte (HMAC) = ~7.5 cycles/byte total
AES-GCM
One pass: encryption and authentication happen simultaneously.
Overhead: 12B nonce + 16B tag = 28 bytes
Throughput: ~3.5 cycles/byte (single pass with hardware GHASH)
Result: GCM is approximately 2× faster than CBC+HMAC on hardware with GHASH acceleration (Intel, ARM, smart card crypto coprocessors) and produces smaller output (28 bytes vs 49–64 bytes overhead).
7. When to Use Each Mode
| Scenario | Recommended Mode | Reason |
|---|---|---|
| New smart card application | GCM BEST | AEAD, smaller output, faster, modern |
| GlobalPlatform SCP03 | GCM | Spec requires GCM |
| EMV contactless (legacy) | CBC + CMAC | Spec compliance — no choice |
| Very constrained card (no GCM) | CBC + HMAC | Only option if GCM not supported |
| Large file encryption | GCM | Parallelizable, single pass |
| Streaming / real-time data | GCM | Low latency, no padding delay |
| Interoperability with old systems | CBC + HMAC | Widest compatibility |
8. Code Examples
8.1 AES-GCM (Python)
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
key = AESGCM.generate_key(bit_length=128) # 16-byte key
aesgcm = AESGCM(key)
# Encrypt
nonce = os.urandom(12) # 12-byte nonce
plaintext = b"Hello, smart card secure messaging!"
aad = b"SCP03-INIT" # Associated data (not encrypted, but authenticated)
ciphertext = aesgcm.encrypt(nonce, plaintext, aad)
# ciphertext = encrypted_data + 16-byte tag
# Decrypt
decrypted = aesgcm.decrypt(nonce, ciphertext, aad)
print(decrypted.decode()) # "Hello, smart card secure messaging!"
8.2 AES-CBC + HMAC (Python)
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import hashes, hmac, padding
import os
key_enc = os.urandom(16) # AES-128 encryption key
key_mac = os.urandom(32) # HMAC-SHA256 key
iv = os.urandom(16) # 16-byte IV (unpredictable)
# Encrypt
padder = padding.PKCS7(128).padder()
padded = padder.update(b"Hello, smart card!") + padder.finalize()
cipher = Cipher(algorithms.AES(key_enc), modes.CBC(iv))
encryptor = cipher.encryptor()
ciphertext = encryptor.update(padded) + encryptor.finalize()
# Compute HMAC over (IV + ciphertext)
h = hmac.HMAC(key_mac, hashes.SHA256())
h.update(iv + ciphertext)
mac = h.finalize()
# Transmit: IV + ciphertext + MAC
message = iv + ciphertext + mac
# Decrypt + Verify (receiver)
iv_r = message[:16]
ciphertext_r = message[16:-32]
mac_r = message[-32:]
h2 = hmac.HMAC(key_mac, hashes.SHA256())
h2.update(iv_r + ciphertext_r)
h2.verify(mac_r) # Raises InvalidSignature if tampered
decryptor = Cipher(algorithms.AES(key_enc), modes.CBC(iv_r)).decryptor()
padded_r = decryptor.update(ciphertext_r) + decryptor.finalize()
unpadder = padding.PKCS7(128).unpadder()
plaintext = unpadder.update(padded_r) + unpadder.finalize()
8.3 AES-GCM (JavaScript — Web Crypto API)
async function aesGcmEncrypt(plaintext, key) {
const nonce = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: nonce, tagLength: 128 },
key,
new TextEncoder().encode(plaintext)
);
return { nonce, ciphertext }; // ciphertext includes the 16-byte tag
}
async function aesGcmDecrypt(nonce, ciphertext, key) {
const plaintext = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: nonce, tagLength: 128 },
key,
ciphertext
);
return new TextDecoder().decode(plaintext);
}
9. Common Mistakes
- Using AES-CBC without a MAC. This provides zero integrity. Always Encrypt-then-MAC.
- Reusing GCM nonces. This is equivalent to reusing a one-time pad. Use a counter or ensure random nonces have enough entropy (>2⁶⁴ messages before collision at 2⁻³² probability).
- Using MAC-then-Encrypt. This pattern is vulnerable to padding oracle attacks. Always use Encrypt-then-MAC or use GCM (which does this internally).
- Truncating the GCM tag to 32 bits. This makes tag forgery trivial — an attacker only needs ~4 billion attempts.
- Using predictable CBC IVs. The IV must be cryptographically random or derived from a secure source. Never use a counter as a CBC IV.
10. Summary
Choose AES-GCM When...
- You need both confidentiality and integrity
- You're building new protocols (SCP03, custom secure messaging)
- Performance matters (single pass, parallelizable)
- You want smaller output overhead (28 vs 49+ bytes)
- Your platform supports AEAD (Java Card 3.1+, GP 2.2+)
Choose AES-CBC + HMAC When...
- You must comply with legacy specs (EMV contactless)
- Your card doesn't support GCM
- You need maximum interoperability
- You can guarantee Encrypt-then-MAC discipline
- You're maintaining an existing CBC-based system