Applied Cryptography for Developers Module 1 – Foundations: goals, bytes and security levels
Security levels, key sizes and attack cost
What n bits of security means, brute-force cost, NIST key sizes for AES, RSA and elliptic curves, the birthday bound for IDs and nonces, and quantum risk.
What you will learn
- Explain what n bits of security means and estimate the cost of a brute-force search
- Read NIST's comparable-strength table to pick RSA, elliptic-curve and symmetric key sizes
- Use the birthday bound to size identifiers, nonces and hash outputs
- Describe how large quantum computers would change these numbers, and what NIST plans for them
Before you start
What you’ll learn · Before you start
What you will learn
- Explain what n bits of security means and estimate the cost of a brute-force search
- Read NIST's comparable-strength table to pick RSA, elliptic-curve and symmetric key sizes
- Use the birthday bound to size identifiers, nonces and hash outputs
- Describe how large quantum computers would change these numbers, and what NIST plans for them
Before you start
On this page
A 56-bit key has about 72 quadrillion possible values, and a cluster that tests a trillion keys a second tries every one of them in 20 hours. Add 72 more bits and the same search takes longer than ten trillion years even at a million times that speed. Key sizes are a statement about that arithmetic, and so is every “at least 128 bits” in a standard.
This lesson turns it into numbers you can check. You compute brute-force times, read NIST’s table that makes AES-128, RSA-3072 and a 256-bit curve equal, use the birthday bound to size random identifiers and nonces, and see what a large quantum computer would change, ending with a two-step code lab that turns the strength table and the birthday bound into code.
What n bits of security means, in years of guessing
NIST defines security strength: The work needed to break an algorithm, in bits: s bits means about 2^s operations. NIST uses the levels 112, 128, 192 and 256 to compare unlike algorithms, such as AES-128 and RSA-3072. as the amount of work needed to break an algorithm: an algorithm with s bits of strength takes about 2ˢ operations to break, where s is 112, 128, 192 or 256 (SP 800-57 and the draft NIST IR 8547). The simplest attack is an exhaustive key search: Trying every possible key until one works, also called brute force. Each extra key bit doubles its cost; for a well-designed cipher no attack does better.: try every key until one works. For a well-designed cipher no known attack does much better, so its strength is its key size. Each extra bit doubles the number of keys, and ten extra bits multiply it by 1,024. The program below turns key sizes into time at three guessing rates. The rates are assumptions, not measurements: about one fast machine, a large cluster, and a rate far beyond both, the margin designers use to be safe. Change them and run it again:
"""How long trying every key takes, at three assumed guessing rates (estimates, not measurements)."""
YEAR = 365.25 * 24 * 3600 # seconds in a year of 365.25 days
def readable(seconds):
"""A duration in the unit that keeps its number short."""
years = seconds / YEAR
if years >= 1e12:
return f"{years:.1e} years".replace("e+", "e")
for unit, size in [(1e9, "billion years"), (1e6, "million years"), (1, "years")]:
if years >= unit:
amount = years / unit
return f"{amount:,.0f} {size}" if amount >= 10 else f"{amount:.1f} {size}"
for name, size in [("days", 86400), ("hours", 3600), ("minutes", 60), ("s", 1), ("ms", 1e-3), ("µs", 1e-6)]:
if seconds >= size:
amount = seconds / size
return f"{amount:,.0f} {name}" if amount >= 10 else f"{amount:.1f} {name}"
return f"{seconds * 1e9:.1f} ns"
RATES = [10**9, 10**12, 10**18] # guesses a second: one fast machine, a large cluster, far beyond both
print(f"{'Key':<10}{'Keys to try':<13}{'at 10^9 a second':<19}{'at 10^12 a second':<19}at 10^18 a second")
for bits in [40, 56, 64, 80, 112, 128]:
keys = 2**bits
times = [readable(keys / rate) for rate in RATES]
print(f"{str(bits) + ' bits':<10}{keys:<13.2e}{times[0]:<19}{times[1]:<19}{times[2]}")
print()
print("A search that stops at the key needs half the keys on average, so half these times.")
print("Each extra bit doubles the work: 2**129 // 2**128 =", 2**129 // 2**128) Output
Key Keys to try at 10^9 a second at 10^12 a second at 10^18 a second 40 bits 1.10e+12 18 minutes 1.1 s 1.1 µs 56 bits 7.21e+16 2.3 years 20 hours 72 ms 64 bits 1.84e+19 585 years 214 days 18 s 80 bits 1.21e+24 38 million years 38,309 years 14 days 112 bits 5.19e+33 1.6e17 years 1.6e14 years 165 million years 128 bits 3.40e+38 1.1e22 years 1.1e19 years 1.1e13 years A search that stops at the key needs half the keys on average, so half these times. Each extra bit doubles the work: 2**129 // 2**128 = 2
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute_force.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
At a trillion guesses a second, a 56-bit key falls in 20 hours and a 64-bit key in 214 days. An 80-bit key holds for 38,309 years at that rate but only 14 days at a billion billion. From 112 bits on, even the fastest rate needs 165 million years, and 128 bits needs 1.1 × 10¹³ years. A search that stops when it finds the key needs half the tries on average, which changes nothing at these scales.
Numbers this large do not fit ordinary integers, so code that computes them needs exact arithmetic. JavaScript’s
Number is exact only up to 2⁵³; BigInt keeps every digit:
// Key counts are too big for ordinary numbers: JavaScript's Number is exact only up to 2^53.
// BigInt keeps every digit, so the years below are exact whole numbers (at an assumed 10^12 guesses a second).
const YEAR = 31_557_600n; // seconds in a year of 365.25 days
const RATE = 10n ** 12n; // guesses a second, an estimate for a large cluster
console.log('2 ** 128 as a Number: ', (2 ** 128).toString());
console.log('2n ** 128n as a BigInt:', (2n ** 128n).toString());
console.log('Number keeps 2^53 + 1? ', 2 ** 53 + 1 === 2 ** 53 ? 'no, it rounds to 2^53' : 'yes');
console.log();
for (const bits of [56n, 80n, 112n, 128n]) {
const seconds = 2n ** bits / RATE; // BigInt division rounds down
const time = seconds < YEAR ? `${(seconds / 3600n).toLocaleString('en-US')} hours` : `${(seconds / YEAR).toLocaleString('en-US')} years`;
console.log(`${bits} bits: ${time} to try every key`);
} Output
2 ** 128 as a Number: 3.402823669209385e+38 2n ** 128n as a BigInt: 340282366920938463463374607431768211456 Number keeps 2^53 + 1? no, it rounds to 2^53 56 bits: 20 hours to try every key 80 bits: 38,308 years to try every key 112 bits: 164,533,958,809,758 years to try every key 128 bits: 10,782,897,524,556,318,080 years to try every key
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node brute_force.mjs
Reading the key size as the strength
Wrong: “RSA-2048 has 2,048 bits of security, far more than AES-256.” Right: RSA-2048 gives 112 bits, because factoring its modulus is far cheaper than trying 2²⁰⁴⁸ keys. Only for a good symmetric cipher do the key size and the strength match.
Checkpoint
Check: Bits of security and brute force
Why RSA needs 3072-bit keys to match AES-128
Algorithms of different kinds are broken in different ways, so equal security takes very different key sizes. NIST’s key-management recommendation, SP 800-57 Part 1, lines them up in its Table 2, and SP 800-186 places the named curves:
| Strength | Symmetric | RSA modulus (k) | Elliptic curve order (f) and named curves |
|---|---|---|---|
| 80 or less | two-key triple DES | 1024 | 160 to 223 bits, none recommended |
| 112 | three-key triple DES | 2048 | 224 to 255 bits: P-224 |
| 128 | AES-128 | 3072 | 256 to 383 bits: P-256, Curve25519, Edwards25519 |
| 192 | AES-192 | 7680 | 384 to 511 bits: P-384 |
| 256 | AES-256 | 15360 | 512 bits and more: P-521 |
AES has no known attack much better than trying keys, so AES-128 gives 128 bits. RSA falls to methods that factor its modulus far faster than any key search; SP 800-57 notes that strength and key length differ for exactly this reason. That is why RSA must grow to 3,072 bits for 128 bits of strength, and to 15,360 for 256. Elliptic curves sit in between: the best known attacks need about the square root of the size of the curve’s group, so, as SP 800-186 puts it, a curve gives about half the bit length of its group order. A 256-bit curve gives 128 bits with private keys of 32 bytes.
Hash functions get two numbers in SP 800-57’s Table 3. Where collisions matter, as in digital signatures, SHA-256 gives 128 bits; where only finding an input for a given output matters, as in HMAC and key derivation, SHA-256 gives 256. The next section shows where the halving comes from.
OWASP’s ASVS 5.0 sets a floor worth adopting for any new design: every primitive at 128 bits or more, its requirement 11.2.3, which gives a 256-bit elliptic-curve key against RSA-3072 as its own example.
Defaults for a new design
AES-128 or AES-256 for data, X25519 for key agreement, Ed25519 or P-256 for signatures, and RSA only where a system demands it, at 3,072 bits or more.
Checkpoint
Check: Comparable key sizes
The birthday bound: collisions arrive at the square root
In a room of 23 people, the chance that two share a birthday is 50.7 %, although a year has 365 days. That surprise, recorded in the Handbook of Applied Cryptography as the birthday bound: The rule that random values of b bits start to repeat after about 2^(b/2) of them. It is why a 256-bit hash gives about 128 bits of collision resistance and why random nonces have usage limits., decides the size of anything chosen at random. Among n random values of b bits, the chance that at least two are equal is about 1 − e^(−n(n − 1)/2^(b+1)), and it reaches one half after about 1.18 × 2^(b/2) values: the square root of the number of possibilities, not the number itself. Run the program, then put in your own counts:
"""The birthday bound: how likely two random values collide, exactly for small cases and by the usual formula."""
import math
def exact(n, m):
"""1 - m(m-1)...(m-n+1) / m**n: the chance that n draws from m values repeat at least once."""
unique = 1.0
for i in range(n):
unique *= (m - i) / m
return 1 - unique
def approx(n, bits):
"""1 - exp(-n(n-1) / 2^(bits+1)), computed with expm1 so that tiny chances do not round to 0."""
return -math.expm1(-n * (n - 1) / 2 ** (bits + 1))
print(f"23 people, 365 birthdays, exact: {exact(23, 365):.3f}")
print(f"30 people, 365 birthdays, exact: {exact(30, 365):.3f}")
print(f"The formula for 23 people: {-math.expm1(-23 * 22 / (2 * 365)):.3f}")
print()
counts = [10**6, 10**9, 2**32, 10**12]
print(f"{'Random bits':<13}" + "".join(f"{label:<12}" for label in ["10^6 values", "10^9", "2^32", "10^12"]) + "50 % at about")
for bits in [32, 64, 96, 122, 128]:
cells = "".join(f"{approx(n, bits):<12.2g}" for n in counts)
half = math.sqrt(2 * math.log(2)) * 2 ** (bits / 2)
print(f"{bits:<13}{cells}{half:.2g} values")
print()
x = 1e-20
print("Why expm1: 1 - exp(-1e-20) =", 1 - math.exp(-x), "but -expm1(-1e-20) =", -math.expm1(-x)) Output
23 people, 365 birthdays, exact: 0.507 30 people, 365 birthdays, exact: 0.706 The formula for 23 people: 0.500 Random bits 10^6 values 10^9 2^32 10^12 50 % at about 32 1 1 1 1 7.7e+04 values 64 2.7e-08 0.027 0.39 1 5.1e+09 values 96 6.3e-18 6.3e-12 1.2e-10 6.3e-06 3.3e+14 values 122 9.4e-26 9.4e-20 1.7e-18 9.4e-14 2.7e+18 values 128 1.5e-27 1.5e-21 2.7e-20 1.5e-15 2.2e+19 values Why expm1: 1 - exp(-1e-20) = 0.0 but -expm1(-1e-20) = 1e-20
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 birthday.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The exact product reproduces the Handbook’s 0.507 for 23 people and 0.706 for 30, and the formula gives 0.500 for 23. The table then shows why the square root matters:
- Hash outputs. Finding any two inputs with the same SHA-256 digest takes about 2¹²⁸ tries, not 2²⁵⁶, which is the 128 bits of collision resistance in SP 800-57’s Table 3.
- Nonces. AES-GCM usually takes a 96-bit nonce: A value used only once with a given key, such as the 96-bit nonce of AES-GCM. Repeating one can break the protection, so nonces come from a counter or from enough random bits., and drawn at random, 2³² of them collide with a chance of about 1.2 × 10⁻¹⁰. NIST SP 800-38D requires that chance to stay at 2⁻³² or below, and so caps a key at 2³² encryptions when its nonces are random.
- Identifiers. Random 64-bit identifiers collide with a chance of 0.39 by 2³² of them, and become even odds near 5.1 billion. A version 4 UUID has 122 random bits (RFC 9562), so a trillion of them collide with a chance of about 9.4 × 10⁻¹⁴.
The last line of the output is a trap worth knowing: for tiny chances, 1 - exp(-x) rounds to 0, while expm1
keeps the value. The second step of the code lab below asks for exactly that.
Key idea
Random values of b bits start to repeat after about 2^(b/2) of them, so size hashes, nonces and identifiers for the square root of their space, not for its full size.
Checkpoint
Check: The birthday bound
What a large quantum computer would change
None exists that can attack today’s cryptography, and RFC 9958, the IETF’s guide to post-quantum cryptography for engineers, still calls such machines theoretical. Two algorithms explain why planning has started anyway. Shor's algorithm: A quantum algorithm that factors numbers and computes discrete logarithms efficiently. On a large enough quantum computer it would break RSA, Diffie-Hellman and elliptic-curve cryptography at any key size. factors numbers and solves discrete logarithms efficiently, so it would break RSA, Diffie-Hellman and every elliptic curve outright, and longer keys would not save them. Grover's algorithm: A quantum search algorithm with a quadratic speed-up: in theory a search of 2^128 keys would take about 2^64 steps. It weakens symmetric keys and hashes only modestly in practice. speeds up search quadratically, so in theory a 128-bit key search would need about 2⁶⁴ quantum steps. RFC 9958 argues that this matters far less in practice, because the steps cannot be split well across many machines, and concludes that AES-128 remains secure, while noting that some agencies recommend AES-256 anyway.
The urgency comes from recordings. Traffic encrypted today with RSA or elliptic-curve key exchange can be stored and decrypted once a large enough machine exists, which NIST IR 8547 and RFC 9958 both call harvest now, decrypt later: Recording encrypted traffic today in order to decrypt it once a large quantum computer exists. It makes quantum-resistant key exchange urgent for secrets that must last for years.. Data that must stay secret for years needs quantum-resistant key exchange now. NIST’s draft plan for the change, step by step:
- 80 bits or less
Retired for new protection retired
SP 800-57 puts RSA-1024, two-key triple DES, curves of 160 to 223 bits and SHA-1 signatures at 80 bits or less, no longer good enough to encrypt or sign anything new. Data already protected with them may still be decrypted or verified when the owner accepts the risk.
- 112 bits
The lowest level still in use
RSA-2048 and curves of 224 to 255 bits give 112 bits. They still protect new data under NIST's rules, but they are the bottom of the scale, and ASVS 5.0 already asks for 128.
- 128 bits and up
Today's floor for new designs
AES-128, RSA-3072, P-256 and Curve25519 give 128 bits; P-384 and AES-192 give 192; AES-256 and P-521 give 256. Against classical computers, 128 bits is far beyond any search: the program puts it at about 11 trillion years at a billion billion guesses a second.
- After 2030
112-bit public keys deprecated draft
NIST IR 8547, an initial public draft, deprecates RSA, ECDSA and the Diffie-Hellman schemes at 112 bits after 2030: still usable where the data owner accepts the risk. NIST's few 112-bit symmetric standards are disallowed in 2030.
- After 2035
Quantum-vulnerable public keys disallowed draft
The same draft disallows RSA, ECDSA, EdDSA, DH and ECDH at every key size after 2035, because a large quantum computer running Shor's algorithm would break them whatever their length. AES-128 and SHA-256 stay, as post-quantum security categories 1 and 2.
The timeline has 5 events, from 80 bits or less to After 2035.
The replacements are measured against the symmetric primitives. The draft’s post-quantum security categories use AES as their yardstick: category 1 is as hard to break as AES-128, category 3 as AES-192 and category 5 as AES-256. FIPS 203 puts ML-KEM-768, the key-encapsulation mechanism NIST recommends as the default, in category 3.
Checkpoint
Check: Quantum computers and NIST's plan
Pick sizes for a new design in three questions
Three questions decide most choices: the floor your design must meet, what the key does, and how long the protection must last. Answer them in the table below, which marks what fits and says why the rest does not:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| Choice | Classical strength | Meets a 128-bit floor | A large quantum computer | NIST's draft, after 2035 |
|---|---|---|---|---|
| RSA-2048 Used for: signatures, key transport | Mixed 112 bits (SP 800-57 Table 2) | Poor 112 bits, under 128 | Poor Shor's algorithm breaks it at any size | Poor deprecated after 2030, disallowed after 2035 |
| RSA-3072 Used for: signatures, key transport | Good 128 bits | Good 128 bits, the example ASVS gives | Poor Shor's algorithm breaks it at any size | Poor disallowed after 2035 |
| P-256, X25519 or Ed25519 Used for: signatures, key agreement | Good 128 bits (SP 800-186 Table 1) | Good 128 bits, with 32-byte X25519 keys | Poor Shor's algorithm breaks elliptic curves too | Poor disallowed after 2035 |
| P-384 Used for: signatures, key agreement | Good 192 bits | Good above the floor | Poor broken like every curve, whatever its size | Poor disallowed after 2035 |
| AES-128 Used for: data under a key both sides hold | Good 128 bits | Good exactly the floor | Mixed Grover halves it in theory; RFC 9958 calls it secure in practice | Good stays: post-quantum category 1 |
| AES-256 Used for: data under a key both sides hold | Good 256 bits | Good twice the floor | Good about 128 bits even under Grover | Good stays: post-quantum category 5 |
| ML-KEM-768 with X25519 Used for: post-quantum key agreement (hybrid) | Good X25519 gives 128 bits while ML-KEM is new | Good ML-KEM-768 is category 3 (FIPS 203) | Good built to resist known quantum attacks | Good the draft's replacement; encapsulation keys of 1,184 bytes |
How the answers decide
- “Which floor must every primitive meet?” 128 bits, as ASVS 5.0 asks: rules out RSA-2048. RSA-2048 gives 112 bits in SP 800-57 Table 2, under the 128-bit floor of ASVS 5.0 requirement 11.2.3.
- “What does the key do?” Encrypts or authenticates data with a key both sides hold: rules out RSA-2048, RSA-3072, P-256, X25519 or Ed25519, P-384 and ML-KEM-768 with X25519. Public-key algorithms sign or set up keys. The data itself is encrypted and authenticated with a symmetric key such as AES.
- “What does the key do?” Signs, or agrees a key with a party you share no key with: rules out AES-128 and AES-256. AES needs a key that both sides already hold. Agreeing on one with a stranger, or signing, takes a public-key algorithm.
- “What does the key do?” Signs, or agrees a key with a party you share no key with and “Must the protection hold after 2035, against someone who records traffic today?” Yes, the data or signatures must last: rules out RSA-2048, RSA-3072, P-256, X25519 or Ed25519 and P-384. Shor's algorithm breaks RSA and curves at any size, and traffic recorded today can be decrypted then. Use ML-KEM in a hybrid with X25519; post-quantum signatures are a later module.
- “What does the key do?” Encrypts or authenticates data with a key both sides hold and “Must the protection hold after 2035, against someone who records traffic today?” Yes, the data or signatures must last: adds a caveat to AES-128. RFC 9958 judges 128-bit keys safe in practice against Grover, but some national agencies recommend AES-256 anyway, and it costs little more.
In short: at a 128-bit floor, AES-128 or AES-256 protects data under a shared key; X25519, Ed25519 or P-256 handle key agreement and signatures for data that need not outlive 2035; RSA-2048 falls short of the floor; and anything that must stay secret past 2035 against recorded traffic needs ML-KEM, in a hybrid with X25519, for its key exchange. Whatever you choose, write it into a cryptographic inventory, as ASVS 5.0 asks in requirement 11.1.2, so that the next change of algorithm or key size is a known list of places rather than a search through the code.
Checkpoint
Check: Choosing sizes for a design
Practise: encode the tables in code
Two exercises turn the lesson’s tables into functions with sample tests that run on this page. The first looks up the strength of a key the way SP 800-57 and SP 800-186 give it, and its starter code makes the key-size mistake on purpose. The second computes collision chances with the birthday bound, including the 2³² nonce limit of SP 800-38D:
Code lab · 2 steps
Build a key-size and collision checker
Look up the security strength of a key from NIST's tables, then compute the chance that random values collide.
Exercise · Easy · Python, JavaScript
Look up the security strength of a key
Write security_bits(name) in strength.py (JavaScript: securityBits(name) in strength.mjs). It returns the security strength in bits of an algorithm and key size, the way the lesson's tables give it:
AES-128,AES-192andAES-256give their key size.RSA-<k>follows Table 2 of NIST SP 800-57 Part 1: 80 bits from k = 1024, 112 from 2048, 128 from 3072, 192 from 7680 and 256 from 15360. A size between two rows gets the lower row: RSA-4096 gives 128.- The named curves follow SP 800-186:
P-224112;P-256,X25519andEd25519128;P-384192;X448andEd448224;P-521256.
Anything else, an RSA key below 1024 bits, an AES size that does not exist or a name the tables do not list, raises ValueError (JavaScript: throws an Error). The starter code makes the classic mistake of treating the key size as the strength, and the sample tests catch it at RSA-2048 and P-256.
Python · Starter code · strength.py
def security_bits(name):
"""The security strength in bits of a name such as 'AES-128', 'RSA-3072' or 'P-256'."""
# The classic mistake: the key size is not the strength. Replace this with the lesson's tables.
return int(name.split("-")[1]) The sample tests · test_strength.py
from strength import security_bits
def raises_value_error(name):
try:
security_bits(name)
except ValueError:
return True
return False
def test_symmetric_keys():
"""AES keys give their own size"""
assert security_bits("AES-128") == 128
assert security_bits("AES-256") == 256
def test_rsa_rows_of_sp_800_57():
"""RSA follows the rows of SP 800-57 Table 2, with sizes between rows rounded down"""
expected = {"RSA-1024": 80, "RSA-2048": 112, "RSA-3072": 128, "RSA-4096": 128, "RSA-7680": 192, "RSA-15360": 256}
for name, bits in expected.items():
assert security_bits(name) == bits, f"{name} gives {bits} bits"
def test_named_curves_of_sp_800_186():
"""the curves follow SP 800-186"""
expected = {"P-224": 112, "P-256": 128, "X25519": 128, "Ed25519": 128, "P-384": 192, "Ed448": 224, "P-521": 256}
for name, bits in expected.items():
assert security_bits(name) == bits, f"{name} gives {bits} bits"
def test_unknown_names_raise():
"""names and sizes the tables do not list raise ValueError"""
for name in ["RSA-512", "AES-100", "DES", "P-255", "RSA-abc", "RSA-"]:
assert raises_value_error(name), f"{name!r} should raise ValueError" JavaScript · Starter code · strength.mjs
/** The security strength in bits of a name such as 'AES-128', 'RSA-3072' or 'P-256'. */
export function securityBits(name) {
// The classic mistake: the key size is not the strength. Replace this with the lesson's tables.
return Number(name.split('-')[1]);
} The sample tests · strength.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { securityBits } from './strength.mjs';
test('AES keys give their own size', () => {
assert.equal(securityBits('AES-128'), 128);
assert.equal(securityBits('AES-256'), 256);
});
test('RSA follows the rows of SP 800-57 Table 2, with sizes between rows rounded down', () => {
const expected = { 'RSA-1024': 80, 'RSA-2048': 112, 'RSA-3072': 128, 'RSA-4096': 128, 'RSA-7680': 192, 'RSA-15360': 256 };
for (const [name, bits] of Object.entries(expected)) assert.equal(securityBits(name), bits, `${name} gives ${bits} bits`);
});
test('the curves follow SP 800-186', () => {
const expected = { 'P-224': 112, 'P-256': 128, X25519: 128, Ed25519: 128, 'P-384': 192, Ed448: 224, 'P-521': 256 };
for (const [name, bits] of Object.entries(expected)) assert.equal(securityBits(name), bits, `${name} gives ${bits} bits`);
});
test('names and sizes the tables do not list throw', () => {
for (const name of ['RSA-512', 'AES-100', 'DES', 'P-255', 'RSA-abc', 'RSA-']) assert.throws(() => securityBits(name), `"${name}" should throw`);
}); A hint
Keep the curves in a dictionary (an object in JavaScript), and the RSA rows in a list from the largest size down: return the strength of the first row whose size the key reaches. Split a name such as RSA-3072 at the hyphen and check that the part after it is all digits before you turn it into a number.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Exercise · Easy · Python, JavaScript
Size random identifiers with the birthday bound
Write collision_probability(count, bits) in birthday.py (JavaScript: collisionProbability(count, bits) in birthday.mjs): the chance that at least two of count values, each drawn at random from 2**bits possibilities, are the same. Use the lesson's formula, 1 − exp(−n(n − 1) / 2^(bits + 1)) with n = count.
Two things decide whether your answer is useful. The chances that matter in security are tiny, such as 1.5 × 10⁻²¹ for a billion 128-bit identifiers, and 1 - exp(-x) rounds them to 0, so compute it with expm1. And the result must never be negative or above 1. A count below 0 or bits below 1 raises ValueError (JavaScript: throws). The starter code makes both classic mistakes, and the sample tests include the limit of NIST SP 800-38D: 2³² random 96-bit nonces under one key.
Python · Starter code · birthday.py
import math
def collision_probability(count, bits):
"""The chance that count random values of the given number of bits are not all different."""
# Two classic mistakes: 2 ** bits where the formula has 2 ** (bits + 1), and 1 - exp(...),
# which rounds every tiny chance to 0. Fix both.
return 1 - math.exp(-count * count / 2**bits) The sample tests · test_birthday.py
import math
from birthday import collision_probability
def close(got, want):
return math.isclose(got, want, rel_tol=1e-9, abs_tol=0.0)
def raises_value_error(count, bits):
try:
collision_probability(count, bits)
except ValueError:
return True
return False
def test_nist_sp_800_38d_nonce_limit():
"""2**32 random 96-bit nonces under one key: about 2**-33, the limit of NIST SP 800-38D"""
assert close(collision_probability(2**32, 96), 1.164153217930535e-10)
def test_64_bit_identifiers():
"""2**32 random 64-bit identifiers collide with a chance of about 0.39"""
assert close(collision_probability(2**32, 64), 0.3934693402167571)
def test_tiny_chances_are_not_zero():
"""a billion and a million 128-bit identifiers: tiny chances, but not 0"""
assert close(collision_probability(10**9, 128), 1.4693679370584914e-21)
assert collision_probability(10**6, 128) > 0
def test_edges():
"""no value or one value cannot collide; huge counts give a chance of 1"""
assert collision_probability(0, 64) == 0
assert collision_probability(1, 64) == 0
assert close(collision_probability(10**12, 32), 1.0)
def test_bad_arguments_raise():
"""a negative count or fewer than 1 bit raises ValueError"""
assert raises_value_error(-1, 64)
assert raises_value_error(10, 0) JavaScript · Starter code · birthday.mjs
/** The chance that count random values of the given number of bits are not all different. */
export function collisionProbability(count, bits) {
// Two classic mistakes: 2 ** bits where the formula has 2 ** (bits + 1), and 1 - exp(...),
// which rounds every tiny chance to 0. Fix both.
return 1 - Math.exp((-count * count) / 2 ** bits);
} The sample tests · birthday.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { collisionProbability } from './birthday.mjs';
const close = (got, want) => Math.abs(got - want) <= 1e-9 * Math.abs(want);
test('2**32 random 96-bit nonces under one key: about 2**-33, the limit of NIST SP 800-38D', () => {
assert.ok(close(collisionProbability(2 ** 32, 96), 1.164153217930535e-10), String(collisionProbability(2 ** 32, 96)));
});
test('2**32 random 64-bit identifiers collide with a chance of about 0.39', () => {
assert.ok(close(collisionProbability(2 ** 32, 64), 0.3934693402167571), String(collisionProbability(2 ** 32, 64)));
});
test('a billion and a million 128-bit identifiers: tiny chances, but not 0', () => {
assert.ok(close(collisionProbability(1e9, 128), 1.4693679370584914e-21), String(collisionProbability(1e9, 128)));
assert.ok(collisionProbability(1e6, 128) > 0, 'a million 128-bit values');
});
test('no value or one value cannot collide; huge counts give a chance of 1', () => {
assert.ok(collisionProbability(0, 64) === 0, 'no values');
assert.ok(collisionProbability(1, 64) === 0, 'one value');
assert.ok(close(collisionProbability(1e12, 32), 1));
});
test('a negative count or fewer than 1 bit throws', () => {
assert.throws(() => collisionProbability(-1, 64));
assert.throws(() => collisionProbability(10, 0));
}); A hint
Python: -math.expm1(-x) equals 1 - math.exp(-x) but keeps its precision when x is tiny. JavaScript has Math.expm1 too. Check the arguments first, then compute x = count * (count - 1) / 2 ** (bits + 1).
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Then take a mixed run through all five ideas: the first questions target your weakest idea, a right answer raises the level, and anything you miss comes back before you finish:
Practice that adapts to you
Practise this lesson
12 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher to mid level): what does it mean that RSA-3072 and AES-128 both give 128 bits of security?
It means that the best known attack on each takes about 2¹²⁸ operations, so both are equally far out of reach, not that their keys are alike. For AES-128 the best attack is close to trying every key, so its 128-bit key gives 128 bits. RSA is attacked by factoring its modulus, which is much cheaper than trying keys, so it needs a 3,072-bit modulus to reach the same level, as NIST SP 800-57’s Table 2 shows; RSA-2048 only reaches 112. An elliptic curve needs about 256 bits for 128. The number lets you compare unlike algorithms and set one floor for a whole system, such as the 128 bits of ASVS 5.0. Against a large quantum computer the comparison breaks: RSA would fall outright, while AES-128 would stay secure in practice, because Grover’s search cannot be spread well across machines.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to senior
A multi-region service gives random identifiers to 10 billion records over its life, with no central check for duplicates. How many random bits do the identifiers need so that the chance of any duplicate stays below one in a million? Show the calculation.
-
Mid to staff
An audit against a 128-bit floor flags RSA-2048 in three places: TLS certificates, signatures on firmware for devices that stay in the field for 15 years, and key transport for backups that must stay confidential for 20 years. Prioritise and plan the changes.
Flashcards · 8 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
What does n bits of security mean, and how long does searching a 56-bit and a 128-bit key take?
NIST defines security strength as the work needed to break an algorithm: s bits means about 2^s operations (112, 128, 192 or 256). Each extra bit doubles the work. At a trillion guesses a second a 56-bit key falls in 20 hours; a 128-bit key needs about 1.1 × 10^13 years even at a billion billion a second.
-
Why is RSA-2048 not 2,048 bits of security?
Strength follows the best attack, not the key size. Factoring the modulus is far cheaper than trying 2^2048 keys, so RSA-2048 gives 112 bits. Only for a good symmetric cipher do the key size and the strength match.
-
Which AES, RSA and elliptic-curve sizes give 112, 128, 192 and 256 bits of security?
112: three-key triple DES, RSA-2048, P-224. 128: AES-128, RSA-3072, P-256 or Curve25519. 192: AES-192, RSA-7680, P-384. 256: AES-256, RSA-15360, P-521. These are the pairings of NIST SP 800-57 Part 1, Table 2.
-
Why does a 256-bit elliptic curve give 128 bits of security?
The best known attacks need about the square root of the size of the curve's group, so a curve gives about half the bit length of its group order (SP 800-186). A 256-bit curve therefore gives 128 bits, with private keys of 32 bytes.
-
How many bits of security does SHA-256 give, and why are there two answers?
128 bits where collisions matter, as in digital signatures, and 256 bits where only finding an input for a given output matters, as in HMAC and key derivation (SP 800-57, Table 3). Finding any two inputs with the same digest takes about 2^128 tries, not 2^256.
-
After how many random b-bit values does a repeat become likely?
After about 1.18 × 2^(b/2): the square root of the number of possibilities, not the number itself. Among 23 people the chance of a shared birthday is already 50.7 %, although a year has 365 days. Size hashes, nonces and identifiers for the square root of their space.
-
How many encryptions may one AES-GCM key make when its 96-bit nonces are random?
At most 2^32. Among 2^32 random 96-bit nonces a repeat has a chance of about 1.2 × 10^-10, and NIST SP 800-38D requires that chance to stay at 2^-32 or below. A repeated nonce with one key can break the protection.
-
What would a large quantum computer change for RSA, elliptic curves and AES, and what is 'harvest now, decrypt later'?
Shor's algorithm would break RSA, Diffie-Hellman and every elliptic curve outright; longer keys would not save them. Grover's search is only quadratic, and RFC 9958 argues AES-128 stays secure. Traffic recorded today can be decrypted later, so data that must stay secret for years needs quantum-resistant key exchange now.
Key takeaways
- n bits of security means about 2ⁿ operations to break it; each extra bit doubles the work, and 128 bits is far beyond any search, even at a billion billion guesses a second.
- Key size equals strength only for a good symmetric cipher: AES-128, RSA-3072 and a 256-bit curve all give 128 bits, while RSA-2048 gives 112.
- Random values of b bits collide after about 2^(b/2) of them, which is why a 256-bit hash gives 128 bits of collision resistance and a key of AES-GCM with random nonces is capped at 2³² encryptions.
- Shor’s algorithm would break RSA and elliptic curves at any size and Grover’s would weaken symmetric keys only modestly, so key exchange for long-lived secrets moves to ML-KEM first.
- For a new design: a 128-bit floor, AES and modern curves today, and a hybrid post-quantum key exchange wherever data must stay secret beyond 2035.
Terms in this lesson
- birthday bound
- The rule that random values of b bits start to repeat after about 2^(b/2) of them. It is why a 256-bit hash gives about 128 bits of collision resistance and why random nonces have usage limits.
- exhaustive key search
- Trying every possible key until one works, also called brute force. Each extra key bit doubles its cost; for a well-designed cipher no attack does better.
- Grover's algorithm
- A quantum search algorithm with a quadratic speed-up: in theory a search of 2^128 keys would take about 2^64 steps. It weakens symmetric keys and hashes only modestly in practice.
- harvest now, decrypt later
- Recording encrypted traffic today in order to decrypt it once a large quantum computer exists. It makes quantum-resistant key exchange urgent for secrets that must last for years.
- nonce
- A value used only once with a given key, such as the 96-bit nonce of AES-GCM. Repeating one can break the protection, so nonces come from a counter or from enough random bits.
- security strength
- The work needed to break an algorithm, in bits: s bits means about 2^s operations. NIST uses the levels 112, 128, 192 and 256 to compare unlike algorithms, such as AES-128 and RSA-3072.
- Shor's algorithm
- A quantum algorithm that factors numbers and computes discrete logarithms efficiently. On a large enough quantum computer it would break RSA, Diffie-Hellman and elliptic-curve cryptography at any key size.
References
- NIST SP 800-57 Part 1 Rev. 5: Recommendation for Key Management (section 5.6.1, Tables 2 and 3) (National Institute of Standards and Technology (NIST))
- NIST SP 800-186: Recommendations for Discrete Logarithm-based Cryptography, Elliptic Curve Domain Parameters (Table 1) (National Institute of Standards and Technology (NIST))
- NIST IR 8547 (Initial Public Draft): Transition to Post-Quantum Cryptography Standards (National Institute of Standards and Technology (NIST))
- NIST SP 800-38D: Galois/Counter Mode (GCM) and GMAC (section 8, IV uniqueness and invocation limits) (National Institute of Standards and Technology (NIST))
- FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism Standard (National Institute of Standards and Technology (NIST))
- RFC 9958: Post-Quantum Cryptography for Engineers (Internet Engineering Task Force (IETF))
- RFC 9562: Universally Unique IDentifiers (UUIDs), section 5.4 (UUID version 4) (Internet Engineering Task Force (IETF))
- OWASP Application Security Verification Standard 5.0, V11 Cryptography (requirement 11.2.3) (OWASP Foundation)
- Handbook of Applied Cryptography, chapter 2 (section 2.1.5, birthday problems) (CRC Press (authors' copy, Menezes, van Oorschot and Vanstone))
- Python 3.14 documentation: math.expm1 (Python Software Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress