Back to portfolioBack

Cryptography

Edit the message to flip bits SHA-256 digest
Bits changed
—
Digest matches the browser’s
—
Entropy
0.0 bits per character

Bits changed —, Digest matches the browser’s —, Entropy 0.0 bits per character

wrapswrapsmaster secretHKDF: tagHKDF: posttag keypost keycontent keyAES-GCMciphertext
Message bytes
—
Ciphertext bytes
—
Locks
—
Last unseal
—

Message bytes —, Ciphertext bytes —, Locks —, Last unseal —

ABCDEFGHIJKLMNOPQRSTUVWXYZ
Cipher
Caesar
Key
shift 3
Broken in
—

Cipher Caesar, Key shift 3, Broken in —

1 A hash

A hash turns any message into a fixed-size digest. Change one character and about half of the 256 bits of a SHA-256 digest change, which is why the wall keeps flipping.

0.0 bits per character

Hash

A checksum catches accidents. It is not built to resist anyone, so it can move far fewer bits than half. Pick one above, edit the last character and compare. A single edit of SHA-256 changes a number of bits spread around 128 with a standard deviation of 8, so 100 or 150 turns up now and then.

How fast is SHA-256?

The same random buffer goes through Rust, hand-written JavaScript and the browser. All three digests must match. It runs in a worker when you press the button.

2 A sealed message

This is the scheme that locks some posts on this blog. Press Seal to encrypt a message, issue a key for a tag and unseal with it. Then change one byte of the ciphertext and unseal again.

A demo master secret was created in this tab. It is discarded when you leave.

Keys made here are demo keys. They start with folio1_ because the library fixes that prefix, and they open nothing on the blog.

3 Ciphers that do not work

Three old ciphers, all weak. Caesar slides every letter along the alphabet. Substitution swaps each letter for another. XOR combines the message with a repeating key. Break the Caesar cipher to see why the first one fails: English letters are not equally common, and a shift keeps that pattern.

Caesar

3

Ciphertext Phhw ph eb wkh rog vwrqh eulgjh dw iluvw oljkw, dqg eulqj wkh pds, wkh odqwhuq dqg d olwwoh euhdg.

Substitution

Ciphertext Dttz dt wn zit gsr lzgft wkorut qz yoklz souiz, qfr wkofu zit dqh, zit sqfztkf qfr q sozzst wktqr.

XOR

Ciphertext, hex 2b0a091d4f0b0a4c0b16461b040c4f090308491c1200020c4f041d050d08034f0d1d4f00061e1a1b4603050e0712434c0801024f0e1b0608084c1d07034f01081f4a4f18010a46030d071b031d02490e080b4c084f0a06181d03034f0e1b0a070b42

Base64 is an encoding. It hides nothing.

What you are looking at

The scheme that locks some posts on this blog, running in your browser with a throwaway secret. Step 2 calls the same code the blog does.

How it works

Step 1 hashes your message with SHA-256 twice, once in Rust through the sha2 crate and once with the browser’s crypto.subtle, and ticks when the digests match. CRC32, FNV-1a and djb2 run in Rust and are checksums, not hash functions. Entropy is Shannon entropy over Unicode code points.

Step 2 derives a tag key and a post key from a master secret with HKDF-SHA-256. Each post has its own random content key, so the key that opens one post is never the key to another. That key is wrapped once per way of unlocking the post, here the post key and the tag key, so any one of them recovers it without sharing the others. Decoy locks pad the count to eight, so nobody can tell how many tags a post has. The body is encrypted with AES-GCM, which also detects changes: alter one byte and unsealing fails instead of returning garbage.

Step 3 holds three classical ciphers. Caesar is broken by trying all 26 shifts and keeping the one whose letter frequencies are closest to English. The scan is slowed so you can watch it.

The master secret here comes from crypto.getRandomValues in this tab and is never stored. The real one lives outside the repositories and never enters a build, so nothing made on this page can open a real post.

What this is not

Nothing here is a tutorial on building your own cryptography. The ciphers in step 3 are broken on purpose. For real work, use crypto.subtle. This blog does.

Next WASM benchmark