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.

TL;DR: Use AES-GCM for new designs — it provides authenticated encryption (AEAD) in a single operation. Use AES-CBC + HMAC only when GCM hardware is unavailable or you must match an existing protocol (e.g., older SCP02 implementations). Never use AES-CBC alone without a MAC.

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:

Padding Oracle Attack: If your system reveals whether CBC padding is valid (e.g., "bad padding" error), an attacker can decrypt the entire ciphertext one byte at a time. This is why CBC must be paired with an HMAC computed over the ciphertext (Encrypt-then-MAC).

2. How AES-GCM Works

AES-GCM (Galois/Counter Mode) is an AEAD (Authenticated Encryption with Associated Data) mode. It combines:

// 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:

Why "Galois"? GHASH uses multiplication in the Galois field GF(2¹²⁸) — this is computationally cheap in hardware (Intel PCLMULQDQ instruction, ARM PMULL) and still efficient in software on smart cards with crypto coprocessors.

3. Head-to-Head Comparison

Property AES-CBC AES-GCM
Encryption typeBlock cipher modeStream cipher (CTR) + MAC
ConfidentialityYesYes
Integrity / AuthenticationNo MISSINGYes BUILT-IN
Padding requiredYes (PKCS#7)No
IV / Nonce16-byte IV, must be unpredictable12-byte nonce (recommended), must never repeat
IV/Nonce reuse impactLeaks XOR of first blocksCatastrophic: leaks plaintext XOR + tag forgery
Encryption parallelizableNoYes
Decryption parallelizableYesYes
Tag / MACMust add HMAC separately128-bit authentication tag included
Overhead16 bytes IV + 1–16 bytes padding12 bytes nonce + 16 bytes tag (recommended)
Throughput (software)~5 cycles/byte~3.5 cycles/byte (with PCLMULQDQ)
Smart card hardwareWidely availableAvailable on GP 2.2+ cards
NIST standardSP 800-38ASP 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:

SCP03 nonce structure (12 bytes): 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:

Migration note: EMVCo has been gradually moving toward GCM in newer specifications. If you're designing a new payment protocol, use GCM. If you're implementing EMV contactless, you must follow the spec (AES-CBC + CMAC).

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:

Real-world impact: The Lucky Thirteen attack (2013) exploited timing differences in TLS CBC padding checks to decrypt HTTPS traffic. EMV contactless payment protocols with CBC must use constant-time MAC comparison.

5.2 Why GCM Nonce Reuse Is Catastrophic

If two encryptions use the same key and nonce:

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 LengthForgery ProbabilityUse Case
128 bits2-128Standard — always use this if possible
96 bits2-96Acceptable for short-lived sessions
64 bits2-64Risky — limit to <2³² messages
32 bits2-32Never use — trivially forgeable
Smart card constraint: Some resource-constrained cards may truncate the GCM tag to reduce APDU overhead. If you must use a shorter tag, limit the total number of encryptions under the same key to keep the forgery probability negligible.

6. Performance Comparison

AES-CBC + HMAC

Two passes:

  1. Encrypt plaintext with AES-CBC
  2. 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

ScenarioRecommended ModeReason
New smart card applicationGCM BESTAEAD, smaller output, faster, modern
GlobalPlatform SCP03GCMSpec requires GCM
EMV contactless (legacy)CBC + CMACSpec compliance — no choice
Very constrained card (no GCM)CBC + HMACOnly option if GCM not supported
Large file encryptionGCMParallelizable, single pass
Streaming / real-time dataGCMLow latency, no padding delay
Interoperability with old systemsCBC + HMACWidest 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

  1. Using AES-CBC without a MAC. This provides zero integrity. Always Encrypt-then-MAC.
  2. 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).
  3. Using MAC-then-Encrypt. This pattern is vulnerable to padding oracle attacks. Always use Encrypt-then-MAC or use GCM (which does this internally).
  4. Truncating the GCM tag to 32 bits. This makes tag forgery trivial — an attacker only needs ~4 billion attempts.
  5. 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
Bottom line: AES-GCM is the modern standard for smart card authenticated encryption. Use it for all new designs. Only fall back to AES-CBC + HMAC when protocol compliance or hardware limitations demand it.