3.6 KiB
title, date, author
| title | date | author |
|---|---|---|
| G0: AES-GCM nonce-gjenbruk fjernet fra at-rest-lagringen | 2026-08-13 | Claude |
Den første av de fire P0-ene fra helsesjekken 10. juli er borte. row-codec forsegler nå med tilfeldig nonce i stedet for en avledet.
Hva som var galt
deriveNonce() laget nonce som en ren funksjon av (fieldKey, table, pk) — altså av radens identitet. Doc-kommentaren argumenterte for at det var trygt fordi «hver (key, plaintext)-kombinasjon opptrer høyst én gang», men det er nøyaktig omvendt av hva AES-GCM krever: det farlige er å gjenbruke (key, nonce) på tvers av ulike plaintekster. saveSession re-forsegler sesjonsraden ved hvert ratchet-steg, så paret gjentok seg per konstruksjon. Konsekvensen er ikke bare XOR av plaintekstene, men lekkasje av GHASH-subnøkkelen — «the forbidden attack» — som gir tag-forfalskning under den nøkkelen. Det gjaldt alle muterbare rader: sesjoner, config, prekey-state og trust.
Fiksen
Fire kallesteder, som kartlagt: sealString og sealBytes (row-codec.ts:67/:96) tar nå randomNonce() fra aead.ts, og openString/openBytes (:83/:112) slutter å sende expectedNonce.
Tilfeldig ble valgt framfor en teller fordi codec-en er delt mellom SQLite og Postgres og ikke har noen varig per-rad skrivteller å støtte seg på. 96 bit holder collision-sannsynligheten neglisjerbar langt forbi et realistisk antall re-lagringer.
deriveNonce er beholdt og markert @deprecated framfor slettet — den er del av den publiserte flaten via både index.ts og crypto.ts. Ingen kaller den lenger inne i pakken, og et søk gjennom packages/, android/ og examples/ fant ingen andre konsumenter. expectedNonce-parameteren i aeadOpen er beholdt på samme vilkår, med dokumentasjon om hvorfor den ikke skal brukes: den ga ingen tuklingsdeteksjon som AEAD-taggen og (table, column, pk)-AAD-en ikke allerede gir.
randomNonce er eksportert fra index.ts og crypto.ts, så konsumenter som forsegler selv har den trygge kilden for hånden.
Ingen migrering trengs
aeadOpen har alltid lest nonce fra blob-prefikset; expectedNonce var kun en valgfri ekstrasjekk. Eksisterende blobber åpnes derfor uendret etter fiksen. Todoen om en migreringssti er erstattet av en test som beviser det.
Android var aldri rammet
Kryssplattform-vektoren «Storage HKDF: rowNonce» tester HKDF-funksjonen, ikke at forseglingen bruker den, så den består uendret — deriveNonce er fortsatt der med samme oppførsel. Og KeystoreStorage henter nonce fra cipher.iv, altså Android Keystores egen tilfeldige IV, som ikke lar seg overstyre. TS-siden er nå på linje med Kotlin, ikke omvendt.
Verifisering
Ny tests/nonce-reuse.test.ts med ni tester i tre grupper: nonce-unikhet (inkludert 200 re-lagringer med 200 distinkte nonces), bakoverkompatibilitet (blob forseglet med den gamle avledningen åpnes, og nye og gamle blobber leses under samme nøkkel), og at tuklingsdeteksjonen overlevde — flippet nonce-byte og flyttet rad avvises fortsatt.
Testene ble bevist å ha verdi ved å reversere fiksen midlertidig: nøyaktig de tre som beskriver feilen falt, mens bakoverkompatibilitets- og tuklingstestene sto uendret.
87 tester grønne i @shade/storage-encrypted, typecheck ren, og de to avhengige pakkene kjørt: @shade/cli 15/15 og @shade/sdk 104/104. En enkelt sdk-feil i første kjøring var en flaky gjennomstrømningsmåling — tre påfølgende kjøringer er grønne.
Ikke publisert
Fiksen ligger i kildetreet. Prism er pinnet på @shade/storage-encrypted@4.12.0 og får den først ved en release, som er riktig rekkefølge: prism-daemon skal ikke restartes nå.