🎮 Cheat activated — skipping to text encryption
ECDSA — ECDSA: the PS3 disaster
In plain English
p =
q =
K1 =
K2 =
Showing
y² = x³ − 3x + 5 over [−3.2, 3.2]. The curve is symmetric about the x-axis because y is squared. Discriminant 4a³ + 27b² ≠ 0 guarantees the curve is non-singular — no cusps or self-intersections — so the group operations are well-defined.
P = (0, √5) ≈ (0, )
Q = (2, √7) ≈ (2, )
P + Q = (, )
Geometrically: draw the line through P and Q, find where it hits the curve a third time, reflect across the x-axis. That's P+Q.
P = (2, √7) ≈ (2, )
2P = (, )
When P = Q, the "line through them" is the tangent at P. Same recipe: find where it hits the curve a second time, reflect.
k =
k·P = (, )
Computed via double-and-add: write k in binary, walk LSB→MSB, double at each step, add P when the current bit is 1.
All points (x, y) ∈ [0, 17)² satisfying
y² ≡ x³ + 7 (mod 17). 17 affine points + the "point at infinity" (the group identity). Same point-addition rules as over the reals — just modular arithmetic. No smooth curve, but the group structure is identical.
The orbit of G = (15, 13) under repeated addition on
y² ≡ x³ + 7 (mod 17):1·G = (15, 13)
2·G = (2, 10)
3·G = (8, 3)
4·G = (12, 1)
5·G = (6, 6)
6·G = (5, 8)
7·G = (10, 15)
8·G = (1, 12)
9·G = (3, 0)
10·G = (1, 5)
11·G = (10, 2)
12·G = (5, 9)
13·G = (6, 11)
14·G = (12, 16)
15·G = (8, 14)
16·G = (2, 7)
17·G = (15, 4)
18·G = ∞ (identity)
No pattern: 1·G → 2·G goes from (15,13) to (2,10), but 7·G → 8·G goes from (10,15) to (1,12). The hops look random. Given just (15,13) and, say, (8,3), which k did that? For our 18-point orbit, you can brute-force; for secp256k1's ~2²⁵⁶ points, brute force takes longer than the age of the universe. That asymmetry — fast forward, infeasible reverse — is ECDLP, the bedrock of every elliptic-curve crypto primitive on this site.
N:
Discovered prime factor:
Other factor:
Pollard's iterations:
Brent's iterations (same N):
For 1024-bit primes (~300 decimal digits), Pollard's rho would need ~2256 iterations. Infeasible. That's why real RSA uses huge primes.
Scenario 1: sending an HTTP request body (need integrity)
Scenario 2: encrypting database fields (identical secrets must look different)
Scenario 3: per-record disk encryption with random access (no integrity required)
Or type one:
No quick picks available — type a value yourself:
Your toy lesson script
Your full Python script
Playground
Type anything (≤500 printable ASCII chars). The table re-encrypts as you type, using .
· decoded:
In the real world
What you just did is textbook RSA — the bare math. Production systems differ in three important ways:
- RSA almost never encrypts the actual message. Each RSA block can only hold a number smaller than n — for a 2048-bit key that's ~214 bytes after padding. For anything larger, you don't split-and-RSA. You generate a random AES key, RSA-encrypt that key (one small block), then AES-encrypt the message (no size limit). HTTPS, SSH, and PGP all work this way. It's called hybrid encryption.
-
Real RSA always uses padding.
Your per-character ciphertext leaks the alphabet — every
einwould encrypt to the same number, so frequency analysis breaks it instantly. Real protocols use OAEP padding to add randomness, so the same plaintext produces different ciphertexts each time. - RSA also signs. Same math, opposite roles: hash the message (SHA-256), RSA-encrypt the hash with your private key. Anyone can verify with your public key. The message itself can be any size because only the fixed-size hash is touched by RSA.
AES S-box (highlighted cell = answer for input 0x53)
Row 1 (input)
→
Row 1 (after shift left by 1)
??
One column (input)
×
[2 3 1 1]
[1 2 3 1]
[1 1 2 3]
[3 1 1 2]
(GF(2⁸))
[2 3 1 1]
[1 2 3 1]
[1 1 2 3]
[3 1 1 2]
(GF(2⁸))
→
Output column
State byte
0xED (237 = 11101101)
XOR
Round-key byte
0x2B (43 = 00101011)
=
Output
??
A real 16-byte state going through all four transformations of one round. Demo uses a fixed input + round key; values computed client-side via the actual S-box, ShiftRows offsets, MixColumns matrix, and XOR.
Last operation:
Input:
Signature (hex):
Result:
✓ Signature valid
✗ Signature invalid (tampered or wrong key)
⚠
PIN: · PAN:
| Zone | Key (fingerprint) | Ciphertext (hex) |
|---|---|---|
| Terminal (encrypted under TPK) |
… | |
| Acquirer HSM (translated to ZPK1) |
… | |
| Network HSM (translated to ZPK2) |
… | |
| Issuer HSM (verifies PIN) |
— | (verification only) |
⚠
✓ PIN matches expected — transaction approved
✗ PIN mismatch
Original:
AES-128 key:
IV (nonce):
Ciphertext:
Decrypted: ✓ matches
⚠
SHA-256("hello")
SHA-256("Hello")
Bit-level diff (red = differing bits)
— flipping a single bit of input changes ≈half the output bits. That's avalanche.
Input:
Key:
Message:
HMAC-SHA256:
Message used:
MAC checked:
✓ MAC verified — message authentic
✗ MAC mismatch — message altered or wrong key
Alice's public key:
Bob's public key:
Shared secret (from Alice's side):
✓ Both sides derived the same secret — KEX succeeded
Note: this browser doesn't support Web Crypto X25519, so the page is showing the canonical RFC 7748 §6.1 test vectors instead.
Last operation:
Input:
Signature (hex):
Result:
✓ Signature valid
✗ Signature invalid (tampered or wrong key)
⚠
Plaintext:
AAD:
Key:
Nonce:
Ciphertext:
Tag:
Decrypted: ✓ matches
✓ Tamper test: a single-byte flip in the ciphertext made decryption fail (InvalidTag). That's the AEAD guarantee.
⚠
Original (32×32 silhouette)
After ECB encryption
The outline is still visible after ECB encryption — identical plaintext blocks produced identical ciphertext blocks. ECB hides byte values, not patterns. Never use ECB.
Plaintext (3 blocks):
IV:
All three plaintext blocks are identical ("YELLOW SUBMARINE"), but the three ciphertext blocks differ — that's CBC chaining doing its job.
Scenario 1 (HTTP body): ✓
Scenario 2 (DB fields): ✓
Scenario 3 (disk, random access): ✓
Target ciphertext:
Queries sent to oracle:
Recovered byte (last byte of plaintext):
No key. No decryption-with-key. Just observing whether the server said "bad padding" or "ok" for each forged ciphertext.
Recovered plaintext: ""
Hex:
Total queries: (≈ 128 per byte × 16 bytes)
Input: "password123"
SHA-256:
Computed in <1 ms. A GPU farm tries ~10 billion such guesses per second. Plain SHA-256 is not a password hash.
Password: · Iterations:
Salt:
PBKDF2-SHA256:
Took:
(cited)
Log scale. SHA-256 is microseconds; password hashes are hundreds of milliseconds. That ≈100,000× gap is the whole point.
PRK:
client write key:
server write key:
client IV:
server IV:
All four came from the SAME PRK + different info strings. That's how one ECDH shared secret turns into a whole tree of working keys.
Last operation:
Input:
Signature (hex):
Result:
✓ Signature valid
✗ Signature invalid
⚠
Last operation:
Input:
Signature (WOTS+ chain + Merkle path, hex):
Leaf index:
Result:
✓ Signature valid
✗ Signature invalid
⚠
Last operation:
Input:
Signature (hex, 256 bytes):
Result:
✓ Signature valid
✗ Signature invalid
⚠
Toy RSA modulus: bits (n = )
Target ciphertext (encrypted under the public key):
Queries sent to vulnerable oracle:
Conforming s values found:
Recovered plaintext: ""
No knowledge of the private key, no factoring of n — just observing whether each chosen ciphertext decrypted to something that started with
0x0002. ROBOT (2017) and DROWN (2016) showed this attack is still relevant 25+ years later.Blob 1 (first 16 hex bytes):
Blob 2 (first 16 hex bytes):
Bytes that differ between the two: of
MD5(blob1) = MD5(blob2) =
✓ COLLISION CONFIRMED — different inputs, same hash
SHAttered.io published two PDFs that both hash to:
Each PDF is bytes — too big to embed inline here. Download them from shattered.io and verify locally:
Hash space: 2 = possible values
Birthday bound: 1.18 · √N ≈ attempts
You found a collision after: attempts
Input A: ""
Input B: ""
Shared 16-bit truncated hash:
For MD5 (128 bits), the same math gives 264 ≈ 1.8·1019 attempts. Nation-state budget. That's why we don't use MD5 anymore.
Password:
Cost factor: (2 = iterations)
Time:
Output (synthetic, not real bcrypt format):
Each +1 to cost doubles the work. Real bcrypt at cost 14 takes ~1 second per hash — that's 30,000× slower than plain SHA-256. Brute-forcing rainbow tables becomes infeasible.
Original request:
Observed signature:
— attacker now uses signature as resumption point, hashes only the extension —
Glue padding (hex):
Forged request:
Forged signature:
Server response:
✓ ACCEPTED — extension is now signed
✗ rejected
Attacker never learned the secret. They forged a valid signature anyway. That's why every modern protocol uses HMAC instead of
H(secret || msg).Ciphertext (Vigenère, key length 5):
Recovered key:
Recovered plaintext:
Frequency analysis split the ciphertext into 5 streams (one per key-letter position), brute-forced each as a Caesar cipher against English letter frequencies, and reassembled.
Keypair (this session): x = , X =
Last operation:
Input:
Signature (R, s): (, )
Check gs mod p == R · Xe mod p:
lhs =
rhs =
✓ Signature valid
✗ Signature invalid (tampered or wrong message)
⚠
| char | binary | ascii | encrypted |
|---|---|---|---|
Original:
Encrypted:
Decrypted:
✓ matches
Python for this step