Offuscare un ID

Sto cercando un modo per crittografare / offuscare un ID intero in un altro intero. Più precisamente, ho bisogno di una funzione int F(int x) , così

  • x F (x) è la corrispondenza uno-a-uno (se x! = y, F (x)! = F (y))
  • dato F (x), è facile scoprire x – quindi F non è una funzione hash
  • dato xe F (x) è difficile / imansible scoprire F (y), qualcosa come x ^ 0x1234 non funzionerà

Per chiarezza, non sto cercando una soluzione di crittografia forte, è solo offuscamento. Immagina un’applicazione web con URL come example.com/profile/1 , example.com/profile/2 ecc. I profili stessi non sono segreti, ma vorrei impedire ai casual voyeur di visualizzare / recuperare tutti i profili uno dopo l’altro, quindi preferirei nasconderli dietro qualcosa come example.com/profile/23423 , example.com/profile/80980234 ecc. Sebbene i token archiviati nel database possano svolgere il lavoro abbastanza facilmente, sono curioso di sapere se c’è qualche semplice matematica disponibile per Questo.

Un requisito importante di cui non ero chiaro è che i risultati dovrebbero apparire “casuali”, ovvero dati una sequenza x,x+1,...,x+n , F(x),F(x+1)...F(x+n) non dovrebbe formare una progressione di alcun tipo.

Offusca il problema con una combinazione di 2 o 3 metodi semplici:

  • XOR
  • mischia singoli bit
  • convertire in rappresentazione modulare (D.Knuth, Vol. 2, Capitolo 4.3.2)
  • scegliere 32 (o 64) sottoinsiemi di bit sovrapposti e bit XOR in ogni sottoinsieme (bit di parità di sottoinsiemi)
  • lo rappresenta in un sistema numerico a lunghezza variabile e in cifre shuffle
  • scegli una coppia di interi dispari x e y che sono inversioni moltiplicative l’una dell’altra (modulo 2 32 ), quindi moltiplica per x per offuscare e moltiplicare per y per ripristinare, tutte le moltiplicazioni sono modulo 2 32 (fonte: “Un uso pratico del moltiplicativo inversa “di Eric Lippert )

Il metodo di sistema numerico a lunghezza variabile non obbedisce ai requisiti di “progressione” da solo. Produce sempre brevi progressioni aritmetiche. Ma quando combinato con qualche altro metodo, dà buoni risultati.

Lo stesso vale per il metodo di rappresentazione modulare.

Ecco un esempio di codice C ++ per 3 di questi metodi. L’esempio di Shuffle bits può utilizzare alcune maschere e distanze diverse per essere più imprevedibili. Altri 2 esempi sono buoni per numeri piccoli (solo per dare l’idea). Dovrebbero essere estesi per offuscare correttamente tutti i valori interi.

 // *** Numberic system base: (4, 3, 5) -> (5, 3, 4) // In real life all the bases multiplied should be near 2^32 unsigned y = x/15 + ((x/5)%3)*4 + (x%5)*12; // obfuscate unsigned z = y/12 + ((y/4)%3)*5 + (y%4)*15; // restore // *** Shuffle bits (method used here is described in D.Knuth's vol.4a chapter 7.1.3) const unsigned mask1 = 0x00550055; const unsigned d1 = 7; const unsigned mask2 = 0x0000cccc; const unsigned d2 = 14; // Obfuscate unsigned t = (x ^ (x >> d1)) & mask1; unsigned u = x ^ t ^ (t << d1); t = (u ^ (u >> d2)) & mask2; y = u ^ t ^ (t << d2); // Restore t = (y ^ (y >> d2)) & mask2; u = y ^ t ^ (t << d2); t = (u ^ (u >> d1)) & mask1; z = u ^ t ^ (t << d1); // *** Subset parity t = (x ^ (x >> 1)) & 0x44444444; u = (x ^ (x << 2)) & 0xcccccccc; y = ((x & 0x88888888) >> 3) | (t >> 1) | u; // obfuscate t = ((y & 0x11111111) << 3) | (((y & 0x11111111) << 2) ^ ((y & 0x22222222) << 1)); z = t | ((t >> 2) ^ ((y >> 2) & 0x33333333)); // restore 

Vuoi che la trasformazione sia reversibile e non ovvia. Sembra una crittografia che prende un numero in un determinato intervallo e produce un numero diverso nello stesso intervallo. Se il tuo intervallo è a 64 bit, usa DES. Se il tuo intervallo è a 128 bit, utilizza AES. Se si desidera un intervallo diverso, la soluzione migliore è probabilmente il cifrario Hasty Pudding , progettato per far fronte a blocchi di dimensioni diverse e con intervalli numerici che non si adattano perfettamente a un blocco, ad esempio da 100.000 a 999.999.

L’offuscamento non è veramente sufficiente in termini di sicurezza.

Tuttavia, se stai cercando di contrastare lo spettatore casuale, ti consiglio una combinazione di due metodi:

  • Una chiave privata che si combina con l’ID li riunisce insieme
  • Ruotando i bit di una certa quantità sia prima che dopo che il tasto è stato applicato

Ecco un esempio (usando lo pseudo codice):

  def F(x) x = x XOR 31415927 # XOR x with a secret key x = rotl(x, 5) # rotate the bits left 5 times x = x XOR 31415927 # XOR x with a secret key again x = rotr(x, 5) # rotate the bits right 5 times x = x XOR 31415927 # XOR x with a secret key again return x # return the value end 

Non l’ho provato, ma penso che questo sia reversibile, dovrebbe essere veloce e non troppo facile per scoprire il metodo.

Ho trovato molto utile questa particolare parte di codice Python / PHP:

https://github.com/marekweb/opaque-id

Ho scritto un articolo sulle permutazioni sicure con cifrari a blocchi , che dovrebbe soddisfare le vostre esigenze come dichiarato.

Suggerirei, tuttavia, che se vuoi indovinare gli identificatori, dovresti semplicemente usarli in primo luogo: generare UUID e usarli come chiave primaria per i tuoi record in primo luogo – non è necessario essere in grado per convertire in e da un ID ‘reale’.

Fai qualcosa con i bit dell’ID che non li distruggerà. Per esempio:

  • ruotare il valore
  • utilizzare la ricerca per sostituire alcune parti del valore
  • xor con qualche valore
  • scambia i bit
  • scambia byte
  • rispecchiare l’intero valore
  • rispecchiare una parte del valore
  • … Usa la tua immaginazione

Per la decodifica, fai tutto ciò in ordine inverso.

Crea un programma che “crittografa” alcuni valori interessanti per te e li metta in una tabella che puoi esaminare. Avere lo stesso programma PROVA la tua routine di crittografia / decrittografia CON tutto il set di valori che vuoi avere nel tuo sistema.

Aggiungi roba all’elenco sopra alle routine fino a quando i tuoi numeri non verranno visualizzati correttamente.

Per qualsiasi altra cosa, prendi una copia di The Book .

Non sei sicuro di quanto “difficile” tu abbia bisogno di essere, di quanto veloce, o di quanta poca memoria usi. Se non si dispone di vincoli di memoria, è ansible creare un elenco di tutti gli interi, mischiarli e utilizzare tale elenco come mapping. Tuttavia, anche per un intero di 4 byte, avresti bisogno di molta memoria.

Tuttavia, questo potrebbe essere reso più piccolo, quindi, invece di mappare tutti gli interi, dovresti mappare solo il byte 2 (o il caso peggiore 1) e applicarlo a ciascun gruppo dell’intero. Quindi, usando 2 byte un intero sarebbe (gruppo1) (gruppo2) mapperesti ciascun gruppo attraverso la mappa casuale. Ma questo significa che se cambi solo group2, la mapping per group1 rimarrebbe la stessa. Questo potrebbe essere “corretto” mappando diversi bit per ogni gruppo.

Quindi, * (gruppo2) potrebbe essere (bit 14,12,10,8,6,4,2,0) quindi, l’aggiunta di 1 cambierebbe sia il gruppo1 sia il gruppo2 .

Tuttavia, questa è solo sicurezza per oscurità, chiunque possa alimentare numeri nella funzione (anche se si mantiene la funzione segreta) potrebbe facilmente capirlo.

Genera una chiave simmetrica privata da utilizzare nella tua applicazione e crittografa il tuo intero con esso. Questo soddisferà tutti e tre i requisiti, incluso il più difficile # 3: uno dovrebbe indovinare la tua chiave per rompere il tuo schema.

Quello che stai descrivendo qui sembra essere l’opposto di una funzione a senso unico: è facile da invertire ma molto difficile da applicare. Un’opzione consisterebbe nell’utilizzare un algoritmo di crittografia a chiave pubblica standard, disponibile in commercio, in cui si fissa una chiave pubblica (segreta, scelta a caso) che si mantiene una chiave segreta e privata condivisa con il mondo. In questo modo, la tua funzione F (x) sarebbe la crittografia di x utilizzando la chiave pubblica. È quindi ansible decifrare facilmente F (x) indietro in x utilizzando la chiave di decrittazione privata. Si noti che i ruoli della chiave pubblica e privata sono invertiti qui – si distribuisce la chiave privata a tutti in modo che possano decifrare la funzione, ma mantenere la chiave pubblica segreta sul server. Quel modo:

  1. La funzione è una biiezione, quindi è invertibile.
  2. Dato F (x), x è calcolabile in modo efficiente.
  3. Dato x e F (x), è estremamente difficile calcolare F (y) da y, poiché senza la chiave pubblica (supponendo che si usi uno schema crittografico fortemente crittografato) non esiste un modo fattibile per crittografare i dati, anche se il privato la chiave di decodifica è nota.

Questo ha molti vantaggi. Innanzitutto, puoi essere certo che il sistema crittografico è sicuro, poiché se utilizzi un algoritmo ben definito come RSA, non devi preoccuparti di un’insicurezza accidentale. In secondo luogo, ci sono già delle librerie là fuori per fare questo, quindi non è necessario programmare molto e può essere immune agli attacchi dei canali laterali. Infine, puoi rendere ansible a chiunque di andare e invertire F (x) senza che nessuno sia in grado di calcolare F (x).

Un dettaglio: non dovresti usare semplicemente il tipo int standard qui. Anche con gli interi a 64 bit, ci sono così poche combinazioni possibili che un utente malintenzionato potrebbe semplicemente forzare la forza bruta a provare a capovolgere tutto finché non troveranno la crittografia F (y) per alcuni y anche se non hanno la chiave. Suggerirei di usare qualcosa come un valore di 512 bit, poiché anche un attacco di fantascienza non sarebbe in grado di forzare la forza.

Spero che questo ti aiuti!

Se xor è accettabile per tutto, ma inferendo F(y) dato x e F(x) allora penso che tu possa farlo con un sale . Per prima cosa scegli una funzione unidirezionale segreta. Ad esempio S(s) = MD5(secret ^ s) . Quindi F(x) = (s, S(s) ^ x) dove s viene scelto casualmente. L’ho scritto come una tupla ma puoi combinare le due parti in un numero intero, ad esempio F(x) = 10000 * s + S(s) ^ x . La decifrazione estrae di nuovo il sale s e usa F'(F(x)) = S(extract s) ^ (extract S(s)^x) . Dato x e F(x) puoi vedere s (anche se è leggermente offuscato) e puoi dedurre S(s) ma per qualche altro utente y con un sale casuale differente l’utente sapendo che F(x) non può trovare S(t) .