Confidex
Secties

Hoe kan een server onze geheimen opslaan en synchroniseren zonder de sleutels in handen te hebben om ze te lezen? We zullen volgen hoe Confidex zijn sleutels aanmaakt, deze in een slot beschermt en deze gebruikt om gegevens te versleutelen en wijzigingen te verifiëren.

Slot

Alleen de Uitverkorene kan de deur openen

De matrix opnieuw geladen

Stel je voor dat we een virtuele vault bouwen om geheime gegevens op te slaan. Het moet die gegevens op een laptop bewaren nadat de applicatie is gesloten en ons deze op een ander apparaat laten openen. De kluis heeft zijn eigen sleutels nodig. We moeten ze zo opslaan dat het kopiëren van de bestanden een buitenstaander geen mogelijkheid geeft om de inhoud ervan te lezen of te wijzigen.

Een lock beschermt de sleutels: een klein gecodeerd bestand. Om het te openen zijn een hoofdwachtwoord en een geheime code nodig, secret_key; geen van beide staat in het bestand zelf. We zouden de hele sluis in de ochtendkrant kunnen afdrukken. Lezers zouden een kopie krijgen, maar het publiceren ervan zou de privésleutels niet onthullen.

Stel dat een buitenstaander dit bestand in handen krijgt en zelfs het hoofdwachtwoord leert. Ze kunnen nu elke berekening herhalen waarvoor alleen het wachtwoord nodig is, maar die berekeningen zullen niet de sleutel opleveren waarmee het slot wordt geopend: secret_key is ook een invoer. Het bestand bevat geen afzonderlijke controle van alleen het wachtwoord. Voor het openen ervan is nog steeds de onbekende geheime code vereist.

Die code is afkomstig van cryptografisch willekeurige gegevens; een persoon verzint het niet. Zolang secret_key onbekend is, kan zelfs een correct geraden hoofdwachtwoord het slot niet openen: de aanvaller heeft nog steeds geen willekeurig geheim. Een zwak wachtwoord alleen maakt de serverkopie dus niet tot een praktische woordenboekaanval.

Als de aanvaller ook secret_key te pakken krijgt, kan hij of zij het hoofdwachtwoord offline testen. Dit is kostbaar, maar de uiteindelijke moeilijkheid hangt af van de sterkte van het wachtwoord.

Sleutels

Heren, wat doen sleutels? Sleutels... dingen ontgrendelen?

Pirates of the Caribbean: De kist van de dode man

De applicatie haalt 32 willekeurige bytes op van de cryptografische generator van het besturingssysteem en bereidt deze voor op gebruik als sleutel. Deze voorbereide bytes zijn de privéhoofdsleutel, master_key.

De applicatie leidt altijd dezelfde sleutels af van master_key: het herhalen van dezelfde berekeningen op dezelfde bytes geeft hetzelfde resultaat. Een laptop en een telefoon kunnen dus identieke sleutels terugvinden. Om master_key op een nieuw apparaat te verkrijgen, moet de applicatie eerst het slot openen met het hoofdwachtwoord en secret_key.

Het eerste paar: encryptie

Ik ben de sleutelmeester.

Ghostbusters

Om versleutelde gegevens op te slaan, berekent de applicatie een openbare sleutel uit master_key: secret_key. Het kan gegevens voor de kluis coderen, terwijl voor decodering master_key vereist is.

‘Openbaar’ beschrijft de rol van deze sleutel in het algoritme, en niet de verplichting om deze aan iedereen te geven. In Confidex blijft het bij de gebruiker. Het is ook het tweede geheim dat nodig is om het slot te openen, dus het blijft lokaal en wordt niet over het netwerk verzonden. Met secret_key kan iemand op zichzelf de gegevens voor de kluis versleutelen, maar deze niet ontsleutelen of namens de kluis wijzigingen ondertekenen.

X25519 is het algoritme dat master_key neemt en secret_key berekent. Het herstellen van master_key van secret_key is praktisch onhaalbaar. Een klassieke aanval op een denkbeeldige supercomputer die een miljard miljard curve-bewerkingen per seconde uitvoert, zou gemiddeld ongeveer 2,3 biljoen jaar duren (SafeCurves-schatting).

public bytes Random bytes32 bytes master_key secret_key public bytes Random bytes32 bytes master_key secret_key
Van willekeurige bytes tot de coderingssleutels.

Het tweede paar: handtekeningen

Het enige originele is de handtekening

Vang me als je kunt

Het eerste paar helpt de inhoud te verbergen. We hebben nu een manier nodig om te bevestigen dat de eigenaar van de kluis een wijziging heeft goedgekeurd. De server moet ook handtekeningen kunnen controleren, dus we moeten hem een ​​openbare verificatiesleutel sturen. Het openbare deel van het eerste paar is secret_key, dat de server niet mag ontvangen. We creëren daarom een ​​tweede, permanent sleutelpaar voor handtekeningen.

In plaats van nog een onafhankelijk geheim op te slaan, leiden we een privé-ondertekeningssleutel af van master_key en noemen deze signing_key. De applicatie gebruikt het om gegevens en het slot te ondertekenen. Andere apparaten en de server gebruiken het openbare deel om handtekeningen te controleren zonder de inhoud te lezen.

We kunnen dit paar terughalen uit dezelfde master_key. De applicatie ontleent er een startwaarde aan: 32 bytes die op unieke wijze de ondertekeningssleutel bepalen.

Om verschillende sleutels voor verschillende taken af te leiden, nemen we een vast label op in de berekening: hier confidex/vault/ed25519/v1. Dezelfde master_key en hetzelfde label produceren altijd dezelfde sleutel.

Het hoofdwachtwoord heeft deze berekeningen niet ingevoerd. Beide paren zijn afhankelijk van de willekeurige bytes van master_key, niet van een string die door een persoon is uitgevonden. Dit onderscheid zal later van belang zijn: het wachtwoord beschermt de toegang tot sleutels die al bestaan, zodat we het kunnen wijzigen zonder de sleutels zelf te wijzigen.

De afleiding maakt gebruik van een op hash gebaseerde sleutelafleidingsfunctie: HKDF, HMAC-based Extract-and-Expand Key Derivation Function. Confidex gebruikt het met SHA-256, vandaar de naam HKDF-SHA256. De resulterende 32 bytes zijn de seed voor het Ed25519 paar. Het openbare deel van signing_key onthult secret_key niet, dus we kunnen het naar de server sturen ter verificatie van de handtekening.

public bytes HKDF-SHA256keymaster_keyinfo"confidex/vault/ed25519/v1" signing_key Signature verification public bytes HKDF-SHA256keymaster_keyinfo"confidex/vault/ed25519/v1" signing_key Signature verification
Een aparte signeersleutel, afgeleid van dezelfde hoofdsleutel.

Wat is een vergrendelingsbestand en wat doet het?

Open de deuren van de capsuleruimte, HAL.
Het spijt me, Dave. Ik ben bang dat ik dat niet kan.

Een ruimte-odyssee

Het slot slaat een gecodeerde master_key op. Om het te openen zijn het hoofdwachtwoord en secret_key vereist. De openbare header bevat de formaatversie, de kluis-ID, het openbare deel van signing_key, een salt en de parameters voor wachtwoordverwerking. De interne formaatversie is gecodeerd samen met master_key.

Bij het maken van de vergrendeling beschikt de applicatie al over master_key, dus berekent deze secret_key vóór de versleuteling. Bij het openen levert de gebruiker secret_key afzonderlijk in: deze ontbreekt in de openbare kopie van het slot.

Na decodering berekent de applicatie het openbare deel van master_key met behulp van X25519 en vereist dat dit overeenkomt met secret_key. Vervolgens herstelt het signing_key en vergelijkt het het openbare deel met de sleutel in de header. Op deze manier controleert het slot zichzelf.

Om het raden van het hoofdwachtwoord te vertragen, moet elke poging tijd en geheugen kosten. Confidex gebruikt Argon2id met 64 MiB geheugen en drie passages. Wachtwoordverwerking maakt ook gebruik van een willekeurige salt. Het zout is niet geheim: het zorgt ervoor dat hetzelfde wachtwoord verschillende resultaten oplevert in verschillende sloten.

Als secret_key al bekend is, verandert de wachtwoordlengte de omvang van een aanval dramatisch. Er zijn ongeveer 12 miljoen combinaties van 5 willekeurige kleine Latijnse letters, ongeveer 8 miljard van 7 en ongeveer 95 biljard van 12. Als u van 7 naar 12 letters gaat, vergroot u de zoekruimte bijna 12 miljoen keer. Als het zoeken naar zeven letters gemiddeld één dag zou duren op een bepaalde machine, zouden vijf letters ongeveer twee minuten duren, en twaalf ongeveer 33.000 jaar.

Om één sleutel te verkrijgen uit het resultaat Argon2id en secret_key, gebruiken we de eerder geïntroduceerde functie HKDF-SHA256, met het label confidex/lock/wrap/v1. Het resultaat is wrapping_key, de sleutel die het slot codeert.

We moeten master_key verbergen en beschermen tegen manipulatie. XChaCha20-Poly1305 codeert het met wrapping_key en voegt een authenticatietag toe. De tag bindt de cijfertekst aan de publieke header: een verkeerd wachtwoord of een wijziging in de beveiligde bytes zorgt ervoor dat het openen mislukt. De tag en een willekeurige waarde voor eenmalig gebruik, de nonce, worden samen met de cijfertekst opgeslagen.

Elke keer dat het slot opnieuw wordt aangemaakt of gecodeerd, krijgt de applicatie een nieuwe salt en nonce. De gecodeerde inhoud omvat één versiebyte en de 32 bytes van master_key; ernaast worden een 24-byte nonce en een 16-byte tag opgeslagen. De documentatie van het algoritme beschrijft de constructie.

Een mismatch tussen de herstelde sleutels kan het gevolg zijn van een opzettelijk verkeerd ingedeeld slot of een bug in het programma dat het slot heeft gemaakt. Willekeurige bytecorruptie wordt meestal eerder gedetecteerd, wanneer de tag wordt gecontroleerd.

De server beschikt niet over de geheimen die nodig zijn om deze tag te controleren. Om het slot over te dragen, voegt de applicatie daarom een ​​handtekening toe die is gemaakt met signing_key. Het omvat het gehele voltooide bestand, inclusief de openbare header en de gecodeerde inhoud. De server controleert de handtekening en zet bij de eerste upload het volledige openbare deel van signing_key en de interne kluis-ID vast. Het controleert daaropvolgende wijzigingen aan de hand van deze sleutel. Het slot en de gecodeerde gegevens zijn ondertekend met dezelfde signing_key, hersteld van master_key.

De kluis

We kunnen het slot nu publiceren. Alleen iemand die secret_key en de master password kent, kan master_key lezen, terwijl iedereen de openbare ondertekeningssleutel uit het vergrendelingsbestand kan nemen en ondertekende gegevens kan controleren zonder de inhoud van het slot of de gegevens te zien.

Overweeg een wachtwoord, SuperPa55worD. Voorlopig willen we het alleen opslaan: na het sluiten van de applicatie zouden we het vergrendelingsbestand en het gecodeerde wachtwoord overal naartoe moeten kunnen sturen zonder dat iemand SuperPa55worD ziet, terwijl ze toch ons auteurschap kunnen verifiëren. De codering moet omkeerbaar zijn als we secret_key en de master password hebben. Een symmetrisch cijfer kan met dezelfde sleutel coderen en decoderen. Omdat we die sleutel niet bewaren, moeten we hem via het slot kunnen terugvinden. We noemen het message_key.

Voor elke nieuwe versleuteling ontvangen we een aparte message_key. We moeten het herstel nog regelen: als je de sleutel open laat liggen naast de cijfertekst, kan iedereen met een kopie alles lezen.

Van nieuwe willekeurige bytes maakt de applicatie een tijdelijk X25519-paar speciaal voor deze codering: record_key. Vervolgens worden het privégedeelte van record_key en de permanente openbare sleutel van de kluis, secret_key, gebruikt en wordt een gedeeld geheim berekend. Een aparte functie leidt message_key af van dat geheim; deze sleutel codeert ons wachtwoord.

Bij het lezen neemt de applicatie een andere route. Het gebruikt master_key en het openbare deel van record_key. Deze twee sleutels produceren hetzelfde gedeelde geheim. Wiskundig gezien vermenigvuldigen beide routes hetzelfde beginpunt op de curve met twee privégetallen, in een andere volgorde. De volgorde verandert het resultaat niet. We kunnen daarom de decoderingssleutel herstellen zonder het privégedeelte van record_key te behouden.

Het verkrijgen van dit geheim uit de twee publieke sleutels is praktisch onmogelijk: beide routes hebben de bijbehorende private sleutel nodig. Dankzij de openbare punten kunnen we niet efficiënt de privénummers herstellen die ze hebben geproduceerd. Deze wiskundige truc ondersteunt de beveiliging van de kluis. Maar het weerhoudt iemand er niet van om ons een andere kluis te geven met ons slot eraan.

We kunnen nu de cijfertekst opslaan naast het openbare deel van record_key. Het privégedeelte van record_key en het gebruikte message_key zijn niet langer nodig voor toekomstig lezen. Later herstelt de applicatie de decoderingssleutel van master_key en het opgeslagen openbare deel van record_key. Eén permanent kluisgeheim kan dus vele afzonderlijk gecodeerde waarden ontsleutelen, elk met zijn eigen message_key die indien nodig wordt hersteld.

Het verbergen van de tekst is niet voldoende: we moeten ook manipulatie detecteren tijdens het lezen ervan. Encryptie voegt daarom een ​​authenticatietag toe, een korte waarde die samen met de gecodeerde gegevens wordt gecontroleerd. Als de controle mislukt, wijst de applicatie het resultaat af. Het mag geen willekeurige of gewijzigde tekst weergeven als een succesvol gedecodeerd wachtwoord.

public bytes Fresh random bytes record_key secret_key Shared secret Derive message key message_key Encrypted passwordpacket public bytes Fresh random bytes record_key secret_key Shared secret Derive message key message_key Encrypted passwordpacket
Voor elke versleuteling een nieuw sleutelpaar.

We hebben een constructie beschreven waarin een asymmetrisch paar helpt een gedeeld geheim vast te stellen, terwijl message_key de inhoud codeert. Dit heet Hybrid Public Key Encryption, of HPKE, en wordt gespecificeerd in RFC 9180. Confidex gebruikt X25519 om overeenstemming te bereiken over het geheim, HKDF-SHA256 om sleutels af te leiden en ChaCha20-Poly1305 voor versleuteling met integriteitscontrole. Hier neemt HKDF het gedeelde geheim dat is overeengekomen met behulp van record_key en het master_key-paar; dit is een andere taak dan het afleiden van de Ed25519 seed. RFC 5869 beschrijft HKDF.

Voor de volgende versleuteling maakt de applicatie een nieuwe record_key en een nieuwe HPKE context. Hergebruik van een combinatie van message_key en nonce verbreekt de beveiliging van het cijfer. In Confidex worden de sleutel en nonce afgeleid binnen HPKE, gebruikt voor één bericht, en vervolgens wordt de context weggegooid.

ChaCha20 zelf gebruikt een 256-bit message_key. Om een ​​idee te krijgen van een directe aanval, stel je een fictieve supercomputer voor die een miljard miljard kandidaten per seconde test. Het doorzoeken van de 2²⁵⁶ mogelijke sleutels zou ongeveer 1,8 × 10⁵¹ jaar duren. RFC 8439 beschrijft het algoritme en de analyse ervan.

Een record

Wij blij?

Pulp-fictie

Het wachtwoord hebben we opgeslagen, maar voor het inloggen op een website zijn meestal ook een gebruikersnaam en een adres nodig. Soms hebben we er een briefje bij nodig. Als we deze waarden afzonderlijk opslaan, moet de applicatie op de een of andere manier onthouden dat ze bij elkaar horen. Laten we deze ‘papieren’ in een envelop stoppen. We combineren de velden in één verzameling en noemen deze een record.

Een record kan elke combinatie van velden bevatten. De applicatie voegt ze samen tot één pakket, comprimeert het en codeert het geheel met behulp van de constructie die we zojuist hebben beschreven. Veldwaarden, namen, typen en tags zijn allemaal beschermd. Als u het websiteadres wijzigt, zelfs als u hetzelfde wachtwoord behoudt, wordt er een nieuwe versie van de volledige record gemaakt, gecodeerd met een nieuwe record_key.

Hash-keten

MOET UW PROJECT EEN BLOCKCHAIN GEBRUIKEN? → NEE

XKCD 2267

Iemand die secret_key kent, kan een gecodeerde record voor onze kluis voorbereiden. Om te voorkomen dat deze als de onze wordt geaccepteerd, ondertekent de applicatie elke recordversie met signing_key. Zonder deze sleutel kan een buitenstaander geen versie maken met een handtekening die de server en andere apparaten zullen accepteren.

De gecodeerde versie heeft wat begeleidende informatie nodig: tot welke kluis en record deze behoort, wanneer deze is gewijzigd en welke versies eraan voorafgaan. Dit vormt de openbare header. Het bevat niet het wachtwoord, maar heeft nog steeds bescherming nodig: anders zou iemand het eigendom en de relaties van de objecten kunnen veranderen zonder de cijfertekst aan te raken. De applicatie ondertekent de exacte headerbytes samen met de cijfertekst. Als u de ondertekende gegevens wijzigt, wordt de handtekening ongeldig.

De server lijkt op een magazijn met verzegelde pakketten. Het ziet de header, de gecodeerde inhoud en de handtekening, maar niet het wachtwoord erin, en weet dat de ontvanger in de stad bestaat. Het controleert de handtekening aan de hand van het vastgezette openbare deel van signing_key en beslist of de nieuwe versie wordt geaccepteerd. Het heeft noch het hoofdwachtwoord, noch secret_key, noch enige privésleutel nodig. Bij het lezen controleert de client dezelfde handtekening en controleert vervolgens tijdens de decodering opnieuw de binding tussen de inhoud en de header, met behulp van de authenticatietag HPKE.

De handtekening beschermt ook de koppelingen tussen versies. Een SHA-256 hash van de exacte header en cijfertekstbytes wordt de versie-ID. De header van de volgende versie bevat de ID van de vorige versie. Wanneer we het wachtwoord van een website wijzigen, maakt en ondertekent de applicatie een nieuw object met deze link. Het oude object en zijn signatuur blijven ongewijzigd. Het vervangen van de voorganger met behoud van de handtekening van de nieuwe versie zal daarom mislukken. Als twee apparaten onafhankelijke wijzigingen aanbrengen, overleven beide takken; een samengevoegde versie kan naar beide verwijzen.

Weet je nog blockchain? Daar is het :-).

Met de openbare header kunnen we links controleren zonder te decoderen, maar sommige informatie blijft zichtbaar. De server kent de identificatiegegevens, versielinks, de door de client geleverde wijzigingstijd, het aantal velden en de gecodeerde grootte van elke veldwaarde. Encryptie verbergt de inhoud van het record, niet deze structurele informatie.

Header andparent IDs Encrypted recordpacket Sign revision signing_key Signed revision Header andparent IDs Encrypted recordpacket Sign revision signing_key Signed revision
De header en de cijfertekst zijn samen ondertekend.

Synchronisatie

"Geen datalek, een gratis gedistribueerde back-up."

Internet-meme.

Om op meerdere apparaten te kunnen werken, moeten we de vergrendeling en de geschiedenis tussen de apparaten overbrengen. Het objectformaat verandert niet: we dragen dezelfde gecodeerde bytes en handtekeningen over die lokaal zijn opgeslagen.

Voor handmatige overdracht maakt de applicatie een ZIP-archief met daarin de vergrendeling, alle lokaal opgeslagen gecodeerde recordversies en een ondertekende bestandslijst. Noch het wachtwoord, noch secret_key zijn inbegrepen. Op een ander apparaat opent de applicatie eerst het slot, controleert vervolgens de handtekeningen, decodeert de records en herstelt een lokale kluiskopie. Importeren is bedoeld voor een apparaat waarop deze kluis nog niet bestaat; het voegt de geschiedenis niet samen met een bestaande kopie.

Een synchronisatieserver wisselt wijzigingen uit tussen werkkopieën. Bij de eerste upload ontvangt het het ondertekende slot, controleert het de handtekening en berekent het adres van de openbare kluis uit het openbare deel van signing_key. Vervolgens onthoudt het deze sleutel en de kluis-ID uit de header: dit registreert de kluis op de server. Daaropvolgende wijzigingen moeten overeenkomen met deze identificatie en een handtekening van dezelfde sleutel dragen.

Een nieuw apparaat heeft het kluisadres en secret_key nodig, dus combineert de applicatie deze in een overdrachtscode met een controlesom. De gebruiker draagt ​​deze code over naar het apparaat en voert het hoofdwachtwoord afzonderlijk in; alleen het openbare adres wordt naar de server verzonden.

Na het openen van het gedownloade slot vergelijkt de applicatie de openbare delen van de herstelde sleutels met secret_key en de header, en vergelijkt het berekende adres met de overdrachtscode. Vervolgens wordt het geverifieerde openbare deel van signing_key opgeslagen om de records te controleren.

Tijdens de synchronisatie haalt de applicatie ontbrekende versies op en uploadt de wijzigingen. De server controleert handtekeningen en links naar voorgaande versies, slaat geaccepteerde objecten op en geeft deze door aan andere apparaten. Het controleren van ontvangen gegevens, het decoderen ervan en het oplossen van conflicten gebeurt allemaal op de apparaten van de gebruiker.

Bij registratie is geen naam, e-mailadres of apart wachtwoord vereist. De server ziet het openbare adres, de handtekeningverificatiesleutel, openbare headers, objectgroottes en versielinks. Het ziet ook IP-adressen en verzoektijden. Het hoofdwachtwoord, secret_key, privésleutels en platte tekstrecordinhoud blijven op de apparaten van de gebruiker staan. Iedereen die het publieke adres kent, kan de gecodeerde objecten downloaden, maar om ze te kunnen lezen zijn de geheimen van de gebruiker nodig.

Deze controles detecteren vervalste objecten, maar bewijzen niet dat de server de volledige geschiedenis en de laatste staat ervan heeft getoond. Het kan een branch verbergen die de klant nog nooit heeft gezien of een consistente oudere dataset retourneren. Een handtekening bevestigt de authenticiteit van een individueel object, niet de volledigheid van het antwoord van de server. De gebruiker merkt mogelijk ontbrekende wijzigingen op een ander apparaat, maar een consistente oude geschiedenis doorstaat de verificatie van hash-chain.

Het hoofdwachtwoord wijzigen

Dus, zou Uwe Heiligheid haar wachtwoord willen veranderen?

Hackers (1995)

Als u het hoofdwachtwoord wijzigt, wordt alleen het slot opnieuw gecodeerd; master_key binnenkant blijft hetzelfde. Records, hun handtekeningen, secret_key en het kluisadres blijven daarom ongewijzigd. Het nieuwe slot is ondertekend met hetzelfde signing_key.

Een oude vergrendelingskopie wordt nog steeds geopend met het oude wachtwoord en dezelfde secret_key; Als u het wachtwoord wijzigt, worden sleutels die al zijn vrijgegeven niet ingetrokken.

Het volledige beeld

Het slot beschermt master_key, message_key codeert records en signing_key maakt handtekeningen. ZIP-archieven en de server ontvangen gecodeerde objecten met handtekeningen.

public bytes public bytes public bytes master_key Fresh randombytes secret_key record_key Master password wrapping_key signing_key message_key lock Encrypted records Signed lockand records Signatureverification ZIP archive /sync server public bytes public bytes public bytes master_key Fresh randombytes secret_key record_key Master password wrapping_key signing_key message_key lock Encrypted records Signed lockand records Signatureverification ZIP archive /sync server
De sleutels, wat ze beschermen en wat kan worden gedeeld.