BACK TO NEWS
defense #grover #aes #symmetric #migration #key-size

Grover bites AES: why 128 is dead and 256 is your new baseline

Grover's algorithm gives a quadratic speed-up on brute-force key search. AES-128 drops to ~64-bit effective security. AES-256 stays at ~128-bit. The fix is one config line — but every place stores cipher state, you need to find.

by · · 2 min read

TL;DR

A quantum adversary running Grover's algorithm against an n-bit symmetric cipher needs about 2^(n/2) quantum operations to recover the key. AES-128 falls to ~2⁶⁴ effective work — within reach of a serious adversary in the 2030s. AES-256 falls to ~2¹²⁸ effective — beyond every projected budget. Move every AES-128 key to AES-256 now. This is the cheapest PQ upgrade you will ever do.

1. The math, compressed

Grover gives a square-root speedup on unstructured search. For a key space of size 2^n:

  • Classical brute-force: 2^n trials, ~2^n quantum gates.
  • Quantum (Grover): ~2^(n/2) iterations, each costing ~O(n) reversible gates plus an oracle evaluation.

Concretely, for AES:

Cipher Classical effort Grover effort Verdict
AES-128 2¹²⁸ 2⁶⁴ Falls in 2030s
AES-192 2¹⁹² 2⁹⁶ Borderline
AES-256 2²⁵⁶ 2¹²⁸ Safe

The 2⁶⁴ number is roughly the cost of mining the Bitcoin chain twice. Not trivial — but predictable, parallelizable, and within reach of a state-level adversary by the late 2020s.

NSA's CNSA 2.0 advisory (2022) explicitly drops AES-128 from approved suites for "long-term confidentiality of national security information". The migration target is AES-256-GCM.

2. Why hashes are mostly OK

The same speedup applies to hash preimages. SHA-256 preimage cost drops from 2²⁵⁶ to ~2¹²⁸ under Grover — still safe. Collision attacks (birthday) are harder under quantum: Brassard-Høyer-Tapp gives a cubic-root, so SHA-256 collisions drop from 2¹²⁸ to ~2⁸⁵.

That collision number matters for long-lived commitments — Merkle roots in append-only logs, certificate-transparency entries, deduplicated storage. For those use cases, migrate to SHA-384 or SHA3-384. SHA-256 is fine for short-lived integrity (TLS record MAC, HMAC of session tokens).

3. Migration: every place you wrote AES-128, sed it

Most teams have AES-128 sprinkled across three places:

  1. TLS cipher suites (server config, load balancer config).
  2. Application-level envelope encryption (KMS calls, database column encryption, file-at-rest).
  3. Internal RPC encryption (Wireguard, Tailscale, custom HMAC + AES).

3a. Python (cryptography)

PYTHON
1# cryptography ≥ 41
2from cryptography.hazmat.primitives.ciphers.aead import AESGCM
3import os
4 
5# BEFORE: AES-128 — Grover-vulnerable
6key128 = AESGCM.generate_key(bit_length=128)
7aead128 = AESGCM(key128)
8 
9# AFTER: AES-256 — Grover-safe
10key256 = AESGCM.generate_key(bit_length=256)
11aead256 = AESGCM(key256)
12 
13nonce = os.urandom(12)
14ct = aead256.encrypt(nonce, b'plaintext', b'associated-data')
15pt = aead256.decrypt(nonce, ct, b'associated-data')

3b. Node.js

JAVASCRIPT
1const crypto = require('node:crypto');
2 
3// BEFORE: aes-128-gcm
4function encryptOld(key, plaintext) {
5 const iv = crypto.randomBytes(12);
6 const cipher = crypto.createCipheriv('aes-128-gcm', key, iv);
7 const ct = Buffer.concat([cipher.update(plaintext), cipher.final()]);
8 return { iv, tag: cipher.getAuthTag(), ct };
9}
10 
11// AFTER: aes-256-gcm (only the algorithm string and key length change)
12function encryptNew(key, plaintext) {
13 // key must be 32 bytes
14 const iv = crypto.randomBytes(12);
15 const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
16 const ct = Buffer.concat([cipher.update(plaintext), cipher.final()]);
17 return { iv, tag: cipher.getAuthTag(), ct };
18}
19 
20// Generate a fresh 256-bit key
21const key256 = crypto.randomBytes(32);

3c. PHP (libsodium)

PHP
1<?php
2// libsodium ships with PHP 7.2+ — built-in, no extension to install.
3// XChaCha20-Poly1305 is a near-drop-in for AES-256-GCM and is the
4// quantum-safe symmetric primitive recommended in CNSA 2.0.
5 
6// BEFORE: openssl AES-128-GCM
7$key128 = random_bytes(16);
8$iv = random_bytes(12);
9$tag = '';
10$ct128 = openssl_encrypt('hello', 'aes-128-gcm', $key128, OPENSSL_RAW_DATA, $iv, $tag);
11 
12// AFTER: libsodium XChaCha20-Poly1305 (effectively 256-bit, quantum-safe symmetric)
13$key256 = sodium_crypto_aead_xchacha20poly1305_ietf_keygen(); // 32 bytes
14$nonce = random_bytes(SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
15$ct256 = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt('hello', '', $nonce, $key256);

3d. TLS — Apache / nginx

NGINX
1# nginx: drop AES-128, keep AES-256 AEADs only
2ssl_protocols TLSv1.2 TLSv1.3;
3ssl_ciphers "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384";
4ssl_prefer_server_ciphers off;
APACHE
1# Apache mod_ssl
2SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
3SSLCipherSuite TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
4SSLHonorCipherOrder off

4. One-shot sweep across a repo

BASH
1# aes256-migrate.sh — flag every AES-128 reference in a tree.
2# Conservative: never auto-edits, only lists. Manual review required.
3set -eu
4 
5ROOT="${1:-.}"
6 
7echo "=== AES-128 references in $ROOT ==="
8grep -rnE 'AES[-_]?128[-_]?(GCM|CBC|CTR|ECB)?|aes-128-(gcm|cbc|ctr|ecb)|aes_128'
9 --include='*.{py,js,ts,php,go,rb,java,kt,rs,c,cpp,sh,yaml,yml,conf,nginx,htaccess}'
10 "$ROOT" 2>/dev/null
11 
12echo
13echo "=== bit_length=128 generators ==="
14grep -rnE 'bit_lengths*=s*128|key.*=.*16s*[);]|randomBytes(s*16s*)'
15 --include='*.{py,js,ts,php,go,rb,rs}' "$ROOT" 2>/dev/null
16 
17echo
18echo "=== TLS configs referencing CBC suites ==="
19grep -rnE 'AES.*CBC|CBC-SHA'
20 --include='*.{conf,nginx,htaccess,yaml,yml}' "$ROOT" 2>/dev/null

Make it executable and run against your repo: chmod +x aes256-migrate.sh && ./aes256-migrate.sh /srv/app. Triage by file.

5. Cost of waiting

A document encrypted with AES-128 in 2026 will be openable by a state-level adversary with quantum capacity around 2032-2034. If your retention policy says "encrypted for 7 years", the math says you are already late.

The migration to AES-256 has no protocol breakage. No new dependencies. No performance regression worth measuring (AES-NI on Intel/AMD makes 256 nearly indistinguishable from 128 at the throughput you care about). This is the single cheapest post-quantum upgrade in the entire stack.

6. References

  • Bernstein, Cost analysis of hash collisions: will quantum computers make SHARCS obsolete? (2009)
  • Grassl, Langenberg, Roetteler & Steinwandt, Applying Grover's algorithm to AES: quantum resource estimates (2016)
  • NSA CNSA 2.0 (2022, updated 2024) — mandates AES-256-GCM for new builds
  • NIST SP 800-131A Rev. 2 — retires AES-128 for "transition" use cases

Migrate symmetric first. It costs nothing. The expensive PQ work (asymmetric, signatures, KEMs) deserves your attention after.

// related ops

RELATED OPS