how-it-works: explain what 'hi' becomes (AES-GCM scramble before volume)

This commit is contained in:
Russell Ballestrini 2026-05-28 17:58:23 -04:00
parent ae3e4316fb
commit 0ca6b2b31a
No known key found for this signature in database

View file

@ -197,6 +197,46 @@
<li><strong>Pubkey</strong> &mdash; 1-to-1 ECDH (P-256) between two public
keys.</li>
</ul>
<p>
The encryption happens <em>before</em> anything becomes volume, so the carrier
never holds the readable text &mdash; it holds scrambled bytes. Here is the
journey of <code>"hi"</code> in passphrase mode:
</p>
<ol>
<li><strong>Plaintext.</strong> <code>"hi"</code> is two bytes of ASCII:
<code>0x68 0x69</code>.</li>
<li><strong>AES-256-GCM scrambles it.</strong> A fresh random 12-byte IV
(nonce) is generated and the two bytes are encrypted with the
passphrase-derived key, producing <code>IV (12)</code> +
<code>ciphertext (2)</code> + <code>auth tag (16)</code> =
<strong>30 bytes</strong>. The ciphertext is the same length as the plaintext
(GCM is a stream mode) and looks like random noise: GCM XORs a key+IV
keystream into your bytes, turning <code>0x68 0x69</code> into two
unpredictable ones. Because the IV is random every time, sending
<code>"hi"</code> twice yields completely different ciphertext, so no
patterns leak.</li>
<li><strong>Then it is framed, error-corrected, and modulated.</strong> A frame
header + CRC wrap the 30 bytes (~43 bytes); each byte becomes a 12-bit
Hamming codeword; each codeword becomes Gray-coded N-level volume steps on
the 440&nbsp;Hz carrier.</li>
</ol>
<div class="diagram">"hi" ─▶ AES-256-GCM ─▶ IV + ciphertext + tag (30 bytes, looks random)
─▶ frame + CRC (~43 B) ─▶ Hamming(12,8) codewords ─▶ volume levels</div>
<p>
By the time it is volume, the readable <code>h</code>,<code>i</code> is gone
three layers up. Someone reading the volume locally (the same-UID PulseAudio
attack this project documents) demodulates the amplitude back to bytes and gets
30 bytes of random-looking ciphertext, not <code>"hi"</code>. Without the
passphrase the encryption is irreversible, and the auth tag means tampering
fails outright &mdash; flip one bit and decryption rejects it.
</p>
<p>
And it is doubled up: that is the <strong>application layer</strong> (AES-GCM).
WebRTC independently wraps the whole audio stream in <strong>DTLS-SRTP</strong>,
so the carrier is encrypted again in transit. Two separate keys, two separate
layers &mdash; the readable <code>"hi"</code> exists only in RAM on the two
endpoints, never on the wire and never in the volume.
</p>
<p class="note">
Protects against a network observer reading chat content. Does not protect
against another process on either endpoint recording the local audio, or an
@ -227,8 +267,8 @@
<footer style="max-width:820px;margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
<span style="color:#777">page integrity</span> &nbsp;·&nbsp; built <span class="stamp-date">2026-05-28</span><br>
md5 <span class="stamp-md5">1309e6236d25cd129cb20580b6fc1171</span><br>
sha256 <span class="stamp-sha">1561e3dc07925f0c3a92672d401c646ffd5621dcf6665f6d813873a51731a5c6</span><br>
md5 <span class="stamp-md5">e4edc61635d2ae846d7bb7438742e8a7</span><br>
sha256 <span class="stamp-sha">16e749e33551edad39a1146e3809efc7486b8461eb51d1b1238b70f13301a5b6</span><br>
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span>
</footer>
</body>