Beveiliging · Diepgaand

De volledige cryptografische levenscyclus

Een nauwkeurige rondleiding langs wat er bij elke stap met je gegevens gebeurt, met de primitieven, parameters en vertrouwensgrenzen uitgeschreven. Elke stap begint met een samenvatting in gewone taal, zodat je zo diep kunt lezen als je wilt.

Stap 01 · Versleutelen

Je geheim wordt op je apparaat verzegeld

Simpel gezegd: het versleutelen gebeurt in je browser, voordat er iets wordt geüpload. Wat wij ontvangen is al onleesbaar.

Als je op Versleutelen drukt, haalt de browser een nieuwe 256-bit inhoudssleutel uit de WebCrypto CSPRNG en versleutelt je gegevens met AES-256-GCM. GCM is geauthenticeerd: elk blok heeft een 128-bit tag, zodat later knoeien wordt gedetecteerd in plaats van stilletjes te worden ontsleuteld.

Tekst en bestanden worden precies hetzelfde behandeld: beide worden één ondoorzichtige binaire blob, opgevuld tot een standaardgrootte, zodat een kort wachtwoord en een klein bestand er hetzelfde uitzien. De leesbare tekst komt nooit op het netwerk en verlaat nooit het geheugen van het tabblad.

CipherAES-256-GCM, blokken van 64 KiB
Inhoudssleutel256-bit, nieuw per geheim
Noncewillekeurig prefix van 7 bytes + teller
Authenticatietag128-bit per blok
OpvullingPadmé, minimaal 64 KiB

Stap 02 · Sleutelbeheer

De sleutel zit in de link, niet op onze servers

Simpel gezegd: het deel van de link na de # bevat de sleutel, en browsers sturen dat deel nooit naar een server.

Je link bevat een willekeurige 256-bit seed in het fragment van de URL: alles na de #. Volgens de URL-specificatie haalt de browser het fragment eraf en wordt het nooit meegestuurd in het verzoek aan onze servers. Uit die seed leidt de browser met HKDF een opzoekwaarde af om het geheim te vinden, plus de sleutel die de inhoudssleutel ontsluit. De server ziet alleen ooit een hash van de opzoekwaarde.

De browser van de ontvanger leest de seed uit het fragment en doet dezelfde afleiding lokaal. Dat is wat „zero-knowledge” hier betekent: wie alleen de versleutelde data heeft, kan er zonder de link niets mee.

Locatie sleutelURL-fragment #
Naar de server?nooit
Seed32 willekeurige bytes, base64url
AfleidingHKDF-SHA-256
Server bewaartalleen SHA-256(lookup)

Stap 03 · Optionele wachtwoordzin

Een tweede slot dat alleen mensen hebben

Simpel gezegd: voeg een wachtwoordzin toe en zelfs iemand met de volledige link kan het geheim zonder die niet openen.

Stel je een wachtwoordzin in, dan wordt die opgerekt met Argon2id, een geheugenintensieve functie voor sleutelafleiding, en samen met de seed gecombineerd tot de sleutel die de inhoudssleutel inpakt. De wachtwoordzin zelf wordt nooit verstuurd of opgeslagen; alleen jij en de ontvanger kennen hem. Verkeerde pogingen worden in de browser van de ontvanger opnieuw geprobeerd op gegevens die die al heeft, dus de server kan ze niet tellen.

Door de geheugenintensiviteit wordt grootschalig offline raden duur, zelfs met GPU's en ASIC's. Deel de wachtwoordzin via een ander kanaal dan de link, dan komen de twee factoren nooit op één plek samen. De Argon2-module wordt bij elk aanmaken en tonen opgehaald, zodat het netwerk niet kan zien welke geheimen er een gebruiken.

KDFArgon2id
Salt128-bit willekeurig
InpakkenAES-256-GCM, 96-bit IV

Stap 04 · Transport & opslag

Alleen versleutelde data verlaat je apparaat

Simpel gezegd: we bewaren een afgesloten doos en een vervaltijd. Verder niets: geen leesbare tekst, geen sleutel, geen gegevens over jou.

De blob wordt geüpload via TLS en opgeslagen als versleutelde data, naast gehashte toegangsgegevens, de levensduur en een afgeronde vervaltijd. We bewaren bewust geen identiteit van de afzender, ontvanger, IP-log, aanmaaktijd of toegangsgeschiedenis. Er is geen accountsysteem om een geheim aan een persoon te koppelen. Snelheidsbeperking werkt met een HMAC van het adres met een salt die regelmatig wisselt, zodat niets van vóór een wissel te koppelen is aan iets daarna.

Omdat het opgeslagen object zonder de seed uit het fragment betekenisloos is, levert een gehackte server of een gerechtelijk bevel alleen ondoorzichtige bytes en hashes op die niet terug te rekenen zijn naar iets bruikbaars.

OnderwegTLS
Opgeslagenversleutelde data + gehashte sleutels + vervaltijd
Logsgeen per geheim

Stap 05 · Ophalen & vernietigen

Wie het eerst leest, krijgt het, daarna wordt het gewist

Simpel gezegd: een eenmalige link openen levert de doos één keer af en verwijdert de kopie op de server. Of vernietig het zelf, wanneer je maar wilt.

Er wordt niets opgehaald als de pagina om het geheim te tonen laadt: het verzoek gaat pas de deur uit bij een echte klik of toetsaanslag, zodat linkscanners en chatvoorbeelden een geheim niet kunnen opgebruiken. Bij een eenmalig geheim claimt de server de rij in één voorwaardelijke update (precies één verzoek wint) en vanaf dat moment geldt het geheim als gebruikt. Dezelfde browser kan 15 minuten lang opnieuw downloaden als de verbinding wegvalt; niemand anders kan dat, en een onbevestigde claim wordt vernietigd, nooit teruggedraaid. Geheimen met een tijdslimiet zijn gewoon leesbaar tot ze verlopen.

Elk geheim heeft ook een privé-vernietigingslink. Die openen vernietigt op zich nog niets; pas na bevestiging wordt de versleutelde data direct gewist, voordat een ontvanger het kan lezen.

Eenmaligatomaire claim, daarna vernietigd
Venster voor opnieuw downloaden15 min, alleen dezelfde client
Vervaltijd15 min – 14 dagen
Controle eigenaarvernietigingslink

Dreigingsmodel

Wat shhnote wel en niet kan zien

De eerlijke versie. Hier ligt precies de vertrouwensgrens.

Verlaat je apparaat nooit

Wat wij niet kunnen zien

Je leesbare tekst · de inhoudssleutel en de seed · je wachtwoordzin · of er een wachtwoordzin is ingesteld · wie een geheim heeft aangemaakt of geopend · wanneer het is aangemaakt · toegangslogs op IP-niveau. Niets daarvan wordt verstuurd of bewaard.

Opgeslagen op onze servers

Wat we wel hebben

Een versleutelde blob die we niet kunnen openen · de opgevulde grootteklasse · eenrichtingshashes van de waarden die je browser gebruikt om het te vinden of te vernietigen · of het eenmalig is of een tijdslimiet heeft · een afgeronde vervaltijd. Allemaal nutteloos zonder de seed in je link.

Goed om te weten

De eerlijke kanttekeningen

Iedereen met de volledige link (en de wachtwoordzin, als die is ingesteld) kan een eenmalig geheim lezen: behandel de link als het geheim zelf. Nadat een geheim is getoond, overschrijft de pagina wat ze kan als je weggaat, maar JavaScript kan dat niet garanderen; sluit het tabblad als je klaar bent. Een geautomatiseerd aangestuurde browser die echte invoer simuleert, kan een eenmalig geheim openen zoals een mens dat zou doen; de ontvanger vindt dan niets, en dat is de eerlijke uitkomst. Een geheim verwijderen haalt het record weg; versleutelde restanten kunnen achterblijven in journals, vrije schijfruimte of back-ups tot ze worden overschreven, als versleutelde data zonder sleutel. Grote geheimen die buiten de database worden opgeslagen, dragen het tijdstip waarop het bestand is geschreven. En zoals bij elke webapp vertrouw je de code die naar je browser wordt gestuurd: daarom is shhnote open source en zelf te hosten.

Cryptografische specificatie

Elke primitief op één plek

Versleuteling

AES-256-GCM

Geauthenticeerde symmetrische versleuteling in blokken van 64 KiB. Een 256-bit sleutel plus een integriteitstag per blok, zodat knoeien of inkorten wordt gedetecteerd en niet alleen onleesbaar blijft.

Sleutelafleiding

HKDF · Argon2id

Sleutels en identificatoren worden met HKDF-SHA-256 uit de seed afgeleid. Optionele wachtwoordzinnen worden opgerekt met een geheugenintensieve KDF, waardoor offline brute-force traag en duur wordt, zelfs op speciale hardware.

Willekeur

WebCrypto CSPRNG

Seeds, sleutels, salts en nonces komen uit de cryptografisch veilige generator van de browser. Nooit Math.random(), nooit voorspelbaar.

Sleutelbeheer

URL-fragment #

De seed staat na de # in je link. Browsers versturen het fragment nooit, dus het bereikt onze servers nooit.

Onderweg

TLS

Alleen versleutelde data verlaat je apparaat, via moderne TLS. Wat wij ontvangen is een ondoorzichtige blob die we niet kunnen openen.

Opgeslagen

Versleutelde data + hashes

We bewaren een versleutelde blob, eenrichtingshashes van de toegangsgegevens en een afgeronde vervaltijd: geen leesbare tekst, geen sleutels, geen metadata over afzender of ontvanger.

Technische vragen

Voor de beveiligingsbewuste lezer

Hoe wordt de eenmaligheid afgedwongen bij gelijktijdige verzoeken?

Het tonen voert één voorwaardelijke update uit die de rij van „actief” naar „geclaimd” zet. Twee gelijktijdige verzoeken kunnen niet allebei slagen; het tweede ziet een record dat al geclaimd is en krijgt hetzelfde antwoord „geen geheim gevonden” als iedereen. De claim wordt nooit teruggedraaid: gaat de download verloren, dan downloadt de houder van het claimtoken opnieuw binnen het tijdvenster, en anders wordt het geheim ongelezen vernietigd.

Waarom het URL-fragment en niet een queryparameter?

Queryparameters worden naar de server gestuurd en komen terecht in toegangslogs, proxy's en analysetools. Het fragment (na de #) blijft volgens de URL-specificatie aan de kant van de client: de browser zet het nooit in het HTTP-verzoek. Op die eigenschap rust het hele zero-knowledge-ontwerp. Na het tonen haalt de pagina het fragment bovendien uit de adresbalk en de geschiedenis.

Wat houdt linkscanners tegen om eenmalige geheimen voortijdig op te branden?

De weergavepagina is voor elk geheim hetzelfde statische document en haalt bij het laden niets op. Het verzoek wordt pas verstuurd bij een echte klik of toetsaanslag; een gescripte click() of formulierverzending telt niet. Geautomatiseerde scanners (e-mailbeveiliging, linkvoorbeelden in chats, virusscanners) laden alleen die statische schil.

Kan ik dit controleren of zelf hosten?

Ja. De volledige broncode is openbaar onder AGPLv3, het protocol is vastgelegd in een specificatie met conformiteitsvectoren waar elke implementatie zichzelf tegen kan toetsen, en er is een handleiding om zelf te hosten. Je kunt de cryptografie aan de clientkant controleren en je eigen instantie draaien, zodat de vertrouwensgrens helemaal bij jou ligt.

Wordt er iets gelogd om misbruik te voorkomen?

We houden geen toegangslogs per geheim en geen IP-geschiedenis bij. Snelheidsbeperking gebruikt een gesleutelde hash van het adres met een salt die regelmatig wisselt en daarbij alle tellers wist. Misbruikmeldingen worden per dag geteld; er wordt niet bijgehouden welk geheim is gemeld.

Tips om veilig te delen

  • Stuur de wachtwoordzin op een andere manier dan de link, bijvoorbeeld de link per e-mail en de wachtwoordzin via de telefoon.
  • Kies de kortste vervaltijd die voor jou werkt.
  • Gebruik een eenmalig geheim voor wachtwoorden en andere informatie die precies één keer gelezen moet worden.

Een kwetsbaarheid melden

Denk je een beveiligingsprobleem met deze site te hebben gevonden? Mail dan naar [email protected] en laat het ons vertrouwelijk weten voordat je het aan iemand anders vertelt. Beschrijf wat je hebt gevonden en hoe het te reproduceren is.

Wij zullen:

  • binnen 5 werkdagen reageren;
  • je op de hoogte houden terwijl we het oplossen;
  • je vermelden wanneer we de oplossing publiceren, als je dat wilt.

Ga tijdens het testen niet aan de gegevens van anderen, verstoor de dienst niet en voer geen geautomatiseerde scans uit die hem zwaar belasten.

Kwetsbaarheden in de shhnote-software zelf: zie het beveiligingsbeleid van het project.

Geheim aanmaken Broncode bekijken op GitHub

Probeer het: het kost tien seconden

Schrijf iets geheims, krijg je eenmalige link en zie het verdwijnen.

Geheim aanmaken