Backend kryptering
Innan 6 nov
Obs! Detta är spekulationer och planering. Målet är att maximera specifikationen så mycket som möjligt och för att se hur långt jag kan komma med det.
- [ ] Situation med lösenord, ska inte skickas till servern. Motverkar resten av upplägget. Hash lösenord på klientsidan, hash igen på server? Analysera problem och lösning.
I största möjliga mån kryptera all data på servern. Krypteras hos klienten innan det skickas till server. Det enda som inte kommer kunna krypteras är användarnamnet och en tids-ram på ca 2 år som är gemensam för alla användare och enbart talar om att en användaren har loggat in någon gång under de 2 åren. Det sistnämnda behövs för att radera användare och data från användare som aldrig mer återvänder. Servern ska inte kunna öppna och läsa användarens innehåll.
Inloggning
Användare ber servern om sin krypterade nyckel och dekrypterar den lokalt.
En användare kan ge sitt användarnamn och erhålla den krypterade nyckeln. Enbart användaren kan dekryptera nyckeln. Det spelar ingen roll om användare A utger sig för att vara B, för användare A kan inte dekryptera nyckeln som är associerad med användare B. Alltså A kan inte agera som B eftersom nyckeln krävs för att både läsa och uppdatera data.
Auth servern (en sådan) skyddar användarens data genom att kräva identifiering för tillkomst. Authentifikation här har dock inte samma kritiska roll att skydda all användardata som den traditionellt gör. Detta eftersom all data redan skyddas av kryptering. Vi kan dock lägga på ett lager av authentifikation för att ändå ha det skyddet.
Men nej. Märk väl att servern inte vet vilken data som tillhör vem, detta för att allt avsiktligt är krypterat och anonymiserst. Därför kan vi inte lägga ett filter som säger att bara användare A kan tillgå data X. Eftersom datan är krypterad är den redan bättre skyddad än via Authentifikation. (se över post-quantum aspekten) (använder vi uuidv7 som id för data så är det också svart att gissa data-fragment, och än mindre att sätta ihop dem till block)
Hur som helst kan auth-servern användas för att skydda den lilla mängd data som ändå associeras med användaren, vilket är den krypterade hemliga nyckeln. Det hindrar angripare från att dumpa databasen genom att gissa användarnamn. Men även om angriparen skulle få tag på den krypterade nyckeln, så är det fruktlöst eftersom nyckeln är krypterad, men vi vill ändå minimera all tillgänglig data.
Auth-servern Jag har redan programmerat auth-servern och sessions-hantering (och fått ordning på all komplexitet med cookies), så det är bara att använda det.
- Steg 1 en angivet.
- Steg 2 ej angivet.
- Klienten frågar mk-servern efter dennes krypterade nyckeln (KEK). Nyckeln (N) som döljs bakom krypteringen (KEK) används för att kryptera data som skickas till servern.
- Klienten använder samma lösenord för password-based key derivation function (PBKDF2). Dirivativet används för att dekryptera KEK för att erhålla den heliga nyckeln (N).
- Klienten krypterar data med nyckeln (N) och skickar till servern. Klienten dekrpyterar data från servern med nyckeln (N) när data ska läsas.
Problem
Säkerheten på servern är stark. Servern kan inte se din data. Men klientens säkerhet är en utmaning.
Här stöter vi på problem. Vi vill lagra den hemliga nyckeln på klientens enhet (dator/mobil) men det är inte bra att spara den permanent i webläsarens lagrings-utrymme. Någon kan dumpa telefonens minne och se nyckeln.
Låt oss resonera kring syftet med krypteringen. Syftet är att hindra servern från att läsa din data. Alltså, att hindra mig (system-administratören, och eventuellt andra parter, ex vid data-läcka) från att tillgå din data. Syftet är inte att skydda din data från den lokala restaurang-kocken som råkar ha fysisk tillgång till din telefon och vill läsa dina hemliga familje-recept. Men, ha i åtanke att du redan ändå har tillräckligt skydd i telefonen i form av pin-kod och krypterad hårddisk (det är standard idag på telefoner, möjligtvis att du måste ställa in det).
Därför kan vi lagra den hemliga nyckeln i webläsarens permanenta lagringsutrymme. På detta vis beböver inte användaren ange lösenordet varje gång programmet ska användas.
- Klienten frågar först efter den egna index-filen som innehåller en lista med id-nunmer på alla krypterade delar som tillhör användaren. (Förändring: kryptera nyckeln tillsammans med referensen till index filen, så vi bara har en entitet som är kopplad till användaren).
- Klienten dekrypterar index-filen med nyckeln (N) och erhåller en lista på referenser till alla krypterade delar som tillhör användaren. Indexfilen innehåller alltså metadata.
- Klienten laddar ner de önskade krypterade delarna och dekrypterar dem med nyckeln (N).
- Klienten skapar lokal database med okrypterad data. När data ändras krypteras den och skickas till servern.
Notis: om data "aaaaaa" delas up i 3 delar och sedan ändras till "aaBaaa", då antagligen kan vi räkna ut vilka delar som ändrades och enbart kryptera dem och uppdatera genom att skicka till servern. Detta har teoretiskt en fördel då inte all data behöver uppdateras. Eftersom vi jobbar med krypterad data skanar vi precision i databasen, men detta är ett sätt att öka precisionen. I praktiken kommer blocken dock vara större. Data kommer också förskjutas vilket kräver att all efterkommande data uppdateras, ex "aaBBBBaaa". Där måste "aaa" på slutet uppdateras även om de ej ändrats.
Lagringsprocess
Klienten vill spara en text som är X karaktärer lång.
- Klienten talar om till servern att reservera 10 block i databasen och skicka dess ID-nummer. Ett block är en rad i databasen som kan hålla Y antal tecken. Varje block har ett id (row-id) i form av en UUIDv4. Reservation av ID görs för att undvika placeholder-koplexitet där klienten behöver ett ID som ännu ej existerar (därför att klienten har skapat data som vid tidpunkten inte existerar på servern).
- Klienten delar upp data i mindre delar som krypteras på klientens enhet enligt standard.
- Klienten räknar alla data-delar och bestämmer hur många ID-nummer som behövs. Klienten reserverar mer id-nummer om det behövs. Klienten associerar varje data-del med ett ID-nummer.
- Klienten skickar alla krypterade data-delar till servern. Någon sorts mekanism för att säkerställa att alla delar kommer fram till servern.
- Klienten uppdaterar en lokal index-fil som associerar alla ID-nummer med vad de nu är associerade med (en matlista, en journal, etc).
- Klienten krypterar index-filen och skickar den till servern.
Vad servern lagrar
- En krypterad index-fil som innehåller en lista på alla krypterade delar som tillhör användaren.
- En krypterad nyckel (K) som används för att kryptera data som skickas till servern. Enbart användaren kan dekryptera nyckeln (N).
- En roling window-hash som används för att radera data-delar som inte längre är aktuell. Servern vet inte exakt när en data-del tillkom.
- Krypterade data-delar. Servern vet inte vilken användare som äger vilken data-del.
Vad servern vet
Servern vet följannde:
- Att användarnamnet "Kalle" är associerat med en krypterad index-fil med row-id [row-id]. Index-filen innehåller referenser till alla krypterade delar som tillhör Kalle.
- Att "Kalle" har en krypterad nyckel (K). Enbart Kalle kan dekryptera nyckeln (N).
- Att "Kalle" har loggat in någon gång det senaste två åren men inte exakt när. (2 år är ett exempel på tidsfönster, ej bestämt än)
- Att Kalle har en krypterad nyckel (K). Enbart Kalle kan dekryptera nyckeln (N).
- Servern vet att en krypterad data-del
Vad servern inte vet
- Servern vet inte vad nyckeln (N) är som Kalle använder för att kryptera data som skickas till serven. Den nyckeln har Kalle krypterat med ett lösenord och den krypterade versionen (K) lagras på servern.
- Servern vet inte vad för data Kalle lagrar. Kalle använder den hemliga nyckeln (N) för att kryptera all data innan det skickas till servern. Servern kan därför inte dekryptera och läsa Kalles data.
- Servern vet inte exakt tid som Kalle har loggat in (enbart någon gång de senaste två åren).
- Servern vet inte vilka krypterade data-delar som tillhör kalle. Servern vet inte vilka användare som äger vilka krypterade delar.
- Servern vet inte längden på den data som kalle krypterar (kan ej se att ett data-block är exmpelvis 3 mb och kan därmed inte gissa att det är en mp3-fil).
- Servern vet inte hur många data-delar som Kalle har lagrat om ens några.
- Servern vet inte sambandet mellan data-delar, alltså att data-del X hänger ihop med data-del Y.
- Servern vet inte när en data-del-sekvens börjar och slutar.
- Servern vet inte hur många data-delar som en sekvens innefattar.
- Servern vet inte hur mycket data Kalles index-fil innehåller (Att-göra: säkerställ detta).
- Servern vet inte exakt när en data-del skapades (enbart att den skapades någon gång de senaste två åren, som exempel).
- Servern lagrar inte data om när data uppdateras.
Index-filen
Klienten har en index-fil som klienten krypterar. Servern lagrar den. Index-filen innehåller en lista på row-id för alla alla krypterade delar som tillhör användaren, samt vad dessa delar är associerade med (en matlista, en journal, etc).
Notis
Index-filens stolek måste döljas/obfuskeras.
Problem och lösningar
Problem 1: Hur radera data från en användare som inte längre använder systemet? Vi måste veta vilka krypterade delar som tillhör användaren, men vi vill samtidigt att servern inte ens ska veta vilka krypterade delar som tillhör användaren.
Lösning 1: sätt timestamp på varje krypterad del. Om användaren inte loggar in på en viss tid, raderas data. Användaren uppdaterar timestampen regelbundet (automatiskt) för att förhindra radering.
Problem 2: Vi vill inte ha möjlighet att korellera användarens login-tid med timestamp på krypterad data. Om vi kan det, då kan vi gissa vilka krypterade delar som tillhör vilken användare, genom att titta på timestamps. Vi kan inte använda UUIDv7 för tids-baserad UUID etersom exakt timestamp kan återskapas från UUIDv7.
Lösning 2: Hashing with a rolling window or timed hashes. Hash-Based Time Buckets (HBTB). Vi skapar en hash som representerar en viss tidsperiod. Om nuvarande tid är 12345678, då gör vi en hash på 12345xxx, och när tiden är 12346xxx, då vet vi att 100 enheter har passerat. Vi kan då radera eller arkivera alla krypterade delar som är äldre än 100 enheter. Kravet är då att användaren loggar in regelbundet. Vid varje inloggning uppdaterar användaren timestampen på alla sina krypterade delar (men ENBART om delarna är äldre än 100 enheter. Om delarna är yngre än 100 enheter, då uppdateras inte timestampen, eftersom en uppdatering enbart skulle sätta samma tidsfönster som redan är satt).
Mer om längden på tids-fönstret: Låt säga tidsfönstret är på 1 år. Då lägger vi till ytterligare 1 år. Vi har 2 år inom vilket användaren måste logga in. Om användaren loggar in på sista dagen i ett tids-fönster, då har användaren fortfarande 1 år kvar att vara inaktiv på innan datan raderas. Vi behöver 2 x Tidsram eftersom en användare kan logga in i början, mitten eller slutet av en tidsram, och alla användare ska ha minst 1 år säker tid oavsett när de loggade in. Detta för att vi aldrig sparar exakta datum då användaren loggar in.
1, 2 år är bara exempel, bestäm siffror senare.
Problem 3: Servern kan nu radera gammla data utan att veta vilka delar som tillhör vilken användare om vi bara tittar på databasen, men om trafiken avlyssnas på servern i realtid (vilket är osannolikt informationen knappas har sådant intresse, men ändå), då kan servern se att en användare uppdaterar x rader i databasen, och vet då att dessa x rader tillhör användaren.
Behöver veta vart en angripare skulle observera trafiken.
Om tidigare åtgärder är att gå långt så är följande en extrem åtgärd.
För att eliminera databasen som attack-vektor, kan vi skapa en pool i Redis, och sedan batch-spara till databasen. Då kan vi inte se vilka uppdateringar som kommer från vilken användare om det är databasens trafik som avlyssnas. Dock kan antagligen Redis angripas men det hela försvårar. Batch-uppdateringar kan ge effektivare uppdateringar och spara på databas-anrop. Redis poolen fungerar då också som en cache, vilket kan ge snabbare svar till användaren.
Lösning 3: Finns flera lösningar: proxy, blanda med fejk-data för att skapa "brus i signalen", gruppera flera användares uppdateringar för att göra det svårare att urskilja.
Problem x1: Förhindra användare från att skriva över andra användares data. Om servern inte vet vems data som är vems, hur kan vi då förhindra att användare skriver över varandras data? Vi kan inte lita på att klienten skickar rätt data.
Lösning: x1: Nonce-based encryption. Klienten associerar en nonce med varje del-data. För enkelhetens skull, använd row-id som nonce. Det är alltså row-id för den krypterade data-delen i serverns databas. Nonce behöver inte hemlighållas.
En nonche skapas via användar-id (som bara användaren vet, inte serven) plus rad-id för data blocket. Användare_id_1+848385884=J8J5K9UFZV57. Den sista delen skickas till servern som har samma del registrerad i samband med att data locket skapades. Servern vet då att användaren äger just det blocket, men vet inte vilken användare eftersom servern bara ser J8J5K9UFZV57.
(Förslag helt från Github copilot AI ;) Problem x2: Om en användare skickar samma data flera gånger, då kommer den krypterade datan att se likadan ut. Om en användare skickar samma data flera gånger, då kan en angripare se att samma data skickas flera gånger och därmed gissa att det är samma data.
Lösning x2: Lägg till en karaktär eller två med skräpdata till varje krypterad del. Denna skräpdata tas bort när data dekrypteras. Det ger unikhet till varje krypterad del.
- [ ] Aspekt 1: Användare kan inte skriva över annans data, men användare kan efterfråga vilken krypterad del som helst genom att gissa row-id. Spelar antagligen ingen roll då det är krypterat ändå, men kan vara värt se över mekanism för att förhindra detta.
Redis poolen
En pool i Redis som samlar databas-uppdateringar i en pool under X minuter, och sedan gör en batch-skrivning till databasen. Fungerar också som cache (beskrevs också ovan).
Auth-servern lagrar:
- Användarnamn och hash av lösenordet.
- Sessions-id.
- En roling window-hash som används för att radera användare som inte längre är aktuell. Servern vet inte exakt när en användare loggade in senast.
MK-servern lagrar också någon sorts cookie vad jag mins just nu.
Hur när uuid raderas och används igen....
Plan
CREATE TABLE users (
user_id Unsigned int PRIMARY KEY, -- Unique identifier for each user
username VARCHAR(255) NOT NULL UNIQUE, -- Unique username
encrypted_meta TEXT NOT NULL -- Encrypted meta field, contains user's group ID, index UUID, and encryption key
);
CREATE TABLE data_entries (
uuid unsigned int PRIMARY KEY, -- Unique identifier for each data entry
group_id unsigned int NOT NULL, -- Group ID unique to each user, but not linked with users table
encrypted_data TEXT NOT NULL, -- Encrypted data content
nonce_hash CHAR(64) NOT NULL -- Hash of the nonce for update and retrieval verification
);
CREATE TABLE group_time_buckets (
group_id unsigned int PRIMARY KEY, -- Primary key for each group, unique to each user
-- Not char 64
rolling_window_hash CHAR(64) NOT NULL -- Hash representing the current rolling time window for the group’s data
);