repl: explain PBKDF2 + AES-GCM on the lock-screen modal
This commit is contained in:
parent
656f206c40
commit
b3bcff5f08
6 changed files with 183 additions and 6 deletions
|
|
@ -44,8 +44,37 @@
|
|||
<div id="lock-screen" class="lock-screen">
|
||||
<div class="lock-card">
|
||||
<h2>unlock</h2>
|
||||
<p>enter a password — transcripts are encrypted with it locally.
|
||||
a fresh password starts a fresh vault.</p>
|
||||
<p>type a password to unlock or create a vault. every keystroke and
|
||||
transcript line stays inside your browser — nothing leaves
|
||||
this tab.</p>
|
||||
<details class="lock-explain">
|
||||
<summary>how the encryption works</summary>
|
||||
<ul>
|
||||
<li>your password is run through <strong>PBKDF2</strong> (200,000
|
||||
iterations, SHA-256) with a random 32-byte salt generated
|
||||
once per device and stored in <code>localStorage</code>.</li>
|
||||
<li>that derives an <strong>AES-GCM 256-bit key</strong>. all
|
||||
transcripts are encrypted with this key before any
|
||||
<code>localStorage.setItem()</code> call.</li>
|
||||
<li>the <strong>vault id</strong> is
|
||||
<code>SHA-256(password || salt)</code> — different
|
||||
passwords on the same device address completely independent
|
||||
vaults. wrong password = decryption fails, you don't see
|
||||
other vaults' contents.</li>
|
||||
<li>same protocol as <a href="https://unsandbox.com/blog/vault-encryption-design"
|
||||
target="_blank">unsandbox's vault design</a>, implemented
|
||||
on the native <code>crypto.subtle</code> API instead of a
|
||||
shipped CryptoJS bundle. source:
|
||||
<a href="crypto.js" target="_blank"><code>crypto.js</code></a>.</li>
|
||||
<li>passwords are never sent anywhere — not to lumbda.com,
|
||||
not to a backend, not to a worker. nothing to leak in
|
||||
transit because nothing transits.</li>
|
||||
<li><strong>ephemeral</strong> skips the vault entirely —
|
||||
nothing is written to disk; closing the tab loses everything.
|
||||
use this on a shared machine when persistence would be a
|
||||
footgun.</li>
|
||||
</ul>
|
||||
</details>
|
||||
<input id="password" type="password" autocomplete="off" autofocus placeholder="password">
|
||||
<div class="lock-row">
|
||||
<button id="unlock" class="primary">unlock vault</button>
|
||||
|
|
|
|||
|
|
@ -78,6 +78,36 @@ body.repl {
|
|||
font-size: 0.8rem;
|
||||
min-height: 1.2em;
|
||||
}
|
||||
.lock-explain {
|
||||
margin: 0.4rem 0 0;
|
||||
font-size: 0.78rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
.lock-explain summary {
|
||||
cursor: pointer;
|
||||
color: var(--green);
|
||||
user-select: none;
|
||||
}
|
||||
.lock-explain summary:hover { text-decoration: underline; }
|
||||
.lock-explain ul {
|
||||
margin: 0.5rem 0 0;
|
||||
padding-left: 1.1rem;
|
||||
display: grid;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
.lock-explain li { line-height: 1.45; }
|
||||
.lock-explain code {
|
||||
background: var(--code-bg);
|
||||
padding: 0 0.2em;
|
||||
border-radius: 2px;
|
||||
font-size: 0.92em;
|
||||
}
|
||||
.lock-explain a {
|
||||
color: var(--green);
|
||||
text-decoration: none;
|
||||
}
|
||||
.lock-explain a:hover { text-decoration: underline; }
|
||||
.lock-explain strong { color: var(--fg); }
|
||||
|
||||
/* ─── Tab bar ──────────────────────────────────────────────────── */
|
||||
|
||||
|
|
|
|||
|
|
@ -44,8 +44,37 @@
|
|||
<div id="lock-screen" class="lock-screen">
|
||||
<div class="lock-card">
|
||||
<h2>unlock</h2>
|
||||
<p>enter a password — transcripts are encrypted with it locally.
|
||||
a fresh password starts a fresh vault.</p>
|
||||
<p>type a password to unlock or create a vault. every keystroke and
|
||||
transcript line stays inside your browser — nothing leaves
|
||||
this tab.</p>
|
||||
<details class="lock-explain">
|
||||
<summary>how the encryption works</summary>
|
||||
<ul>
|
||||
<li>your password is run through <strong>PBKDF2</strong> (200,000
|
||||
iterations, SHA-256) with a random 32-byte salt generated
|
||||
once per device and stored in <code>localStorage</code>.</li>
|
||||
<li>that derives an <strong>AES-GCM 256-bit key</strong>. all
|
||||
transcripts are encrypted with this key before any
|
||||
<code>localStorage.setItem()</code> call.</li>
|
||||
<li>the <strong>vault id</strong> is
|
||||
<code>SHA-256(password || salt)</code> — different
|
||||
passwords on the same device address completely independent
|
||||
vaults. wrong password = decryption fails, you don't see
|
||||
other vaults' contents.</li>
|
||||
<li>same protocol as <a href="https://unsandbox.com/blog/vault-encryption-design"
|
||||
target="_blank">unsandbox's vault design</a>, implemented
|
||||
on the native <code>crypto.subtle</code> API instead of a
|
||||
shipped CryptoJS bundle. source:
|
||||
<a href="crypto.js" target="_blank"><code>crypto.js</code></a>.</li>
|
||||
<li>passwords are never sent anywhere — not to lumbda.com,
|
||||
not to a backend, not to a worker. nothing to leak in
|
||||
transit because nothing transits.</li>
|
||||
<li><strong>ephemeral</strong> skips the vault entirely —
|
||||
nothing is written to disk; closing the tab loses everything.
|
||||
use this on a shared machine when persistence would be a
|
||||
footgun.</li>
|
||||
</ul>
|
||||
</details>
|
||||
<input id="password" type="password" autocomplete="off" autofocus placeholder="password">
|
||||
<div class="lock-row">
|
||||
<button id="unlock" class="primary">unlock vault</button>
|
||||
|
|
|
|||
|
|
@ -78,6 +78,36 @@ body.repl {
|
|||
font-size: 0.8rem;
|
||||
min-height: 1.2em;
|
||||
}
|
||||
.lock-explain {
|
||||
margin: 0.4rem 0 0;
|
||||
font-size: 0.78rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
.lock-explain summary {
|
||||
cursor: pointer;
|
||||
color: var(--green);
|
||||
user-select: none;
|
||||
}
|
||||
.lock-explain summary:hover { text-decoration: underline; }
|
||||
.lock-explain ul {
|
||||
margin: 0.5rem 0 0;
|
||||
padding-left: 1.1rem;
|
||||
display: grid;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
.lock-explain li { line-height: 1.45; }
|
||||
.lock-explain code {
|
||||
background: var(--code-bg);
|
||||
padding: 0 0.2em;
|
||||
border-radius: 2px;
|
||||
font-size: 0.92em;
|
||||
}
|
||||
.lock-explain a {
|
||||
color: var(--green);
|
||||
text-decoration: none;
|
||||
}
|
||||
.lock-explain a:hover { text-decoration: underline; }
|
||||
.lock-explain strong { color: var(--fg); }
|
||||
|
||||
/* ─── Tab bar ──────────────────────────────────────────────────── */
|
||||
|
||||
|
|
|
|||
|
|
@ -44,8 +44,37 @@
|
|||
<div id="lock-screen" class="lock-screen">
|
||||
<div class="lock-card">
|
||||
<h2>unlock</h2>
|
||||
<p>enter a password — transcripts are encrypted with it locally.
|
||||
a fresh password starts a fresh vault.</p>
|
||||
<p>type a password to unlock or create a vault. every keystroke and
|
||||
transcript line stays inside your browser — nothing leaves
|
||||
this tab.</p>
|
||||
<details class="lock-explain">
|
||||
<summary>how the encryption works</summary>
|
||||
<ul>
|
||||
<li>your password is run through <strong>PBKDF2</strong> (200,000
|
||||
iterations, SHA-256) with a random 32-byte salt generated
|
||||
once per device and stored in <code>localStorage</code>.</li>
|
||||
<li>that derives an <strong>AES-GCM 256-bit key</strong>. all
|
||||
transcripts are encrypted with this key before any
|
||||
<code>localStorage.setItem()</code> call.</li>
|
||||
<li>the <strong>vault id</strong> is
|
||||
<code>SHA-256(password || salt)</code> — different
|
||||
passwords on the same device address completely independent
|
||||
vaults. wrong password = decryption fails, you don't see
|
||||
other vaults' contents.</li>
|
||||
<li>same protocol as <a href="https://unsandbox.com/blog/vault-encryption-design"
|
||||
target="_blank">unsandbox's vault design</a>, implemented
|
||||
on the native <code>crypto.subtle</code> API instead of a
|
||||
shipped CryptoJS bundle. source:
|
||||
<a href="crypto.js" target="_blank"><code>crypto.js</code></a>.</li>
|
||||
<li>passwords are never sent anywhere — not to lumbda.com,
|
||||
not to a backend, not to a worker. nothing to leak in
|
||||
transit because nothing transits.</li>
|
||||
<li><strong>ephemeral</strong> skips the vault entirely —
|
||||
nothing is written to disk; closing the tab loses everything.
|
||||
use this on a shared machine when persistence would be a
|
||||
footgun.</li>
|
||||
</ul>
|
||||
</details>
|
||||
<input id="password" type="password" autocomplete="off" autofocus placeholder="password">
|
||||
<div class="lock-row">
|
||||
<button id="unlock" class="primary">unlock vault</button>
|
||||
|
|
|
|||
|
|
@ -78,6 +78,36 @@ body.repl {
|
|||
font-size: 0.8rem;
|
||||
min-height: 1.2em;
|
||||
}
|
||||
.lock-explain {
|
||||
margin: 0.4rem 0 0;
|
||||
font-size: 0.78rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
.lock-explain summary {
|
||||
cursor: pointer;
|
||||
color: var(--green);
|
||||
user-select: none;
|
||||
}
|
||||
.lock-explain summary:hover { text-decoration: underline; }
|
||||
.lock-explain ul {
|
||||
margin: 0.5rem 0 0;
|
||||
padding-left: 1.1rem;
|
||||
display: grid;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
.lock-explain li { line-height: 1.45; }
|
||||
.lock-explain code {
|
||||
background: var(--code-bg);
|
||||
padding: 0 0.2em;
|
||||
border-radius: 2px;
|
||||
font-size: 0.92em;
|
||||
}
|
||||
.lock-explain a {
|
||||
color: var(--green);
|
||||
text-decoration: none;
|
||||
}
|
||||
.lock-explain a:hover { text-decoration: underline; }
|
||||
.lock-explain strong { color: var(--fg); }
|
||||
|
||||
/* ─── Tab bar ──────────────────────────────────────────────────── */
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue