Funzione hash con garanzie di unicità e entropia controllabile

3

Esiste una classe di funzioni di hash che soddisfano le seguenti specifiche:

  • È possibile specificare il limite superiore e inferiore
  • L'univocità è garantita finché l'input è compreso tra i limiti superiore e inferiore
  • La quantità di entropia è controllabile, o almeno alta e uniformemente distribuita

Un esempio di funzione di hash a bassa entropia che produce risultati unici e consente di specificare il limite superiore

int hash(int x,int upperBound) {
    return x - (upperBound * (x \ upperBound));
}

Questo produrrebbe un numero compreso tra [0, UpperBound), ripristinando 0 quando il numero può essere diviso per upperBound.

Quindi diciamo che il nostro limite superiore è 20 ^ 3, che ci dà 46656 numeri che credo. L'alimentazione di un numero compreso tra 0 e 46655 dovrebbe produrre un risultato unico. Qualsiasi numero sopra produrrà una collisione. Fornire lo stesso numero dovrebbe sempre dare lo stesso risultato. Essere in grado di controllare l'entropia sarebbe un vantaggio, ma se è uniformemente distribuito e alto allora funzionerà anche bene.

L'obiettivo finale è trasformare il numero in una rappresentazione alfanumerica che può essere rapidamente visualizzata per determinare se è stata modificata dall'ultima volta che è stato richiesto un numero. Non dovrei ricevere lo stesso numero finché non sono stati utilizzati tutti i numeri.

    
posta Justin 22.12.2014 - 21:06
fonte

1 risposta

2

OK, quindi sento di aver fatto abbastanza ricerche per iniziare almeno a spostare le cose in una direzione utile.

Il termine per ciò che stai cercando generalmente è una " funzione di hash perfetta ", con l'aggiunta che desideri un certo grado di casualità potenzialmente gestibile.

In generale, lo stato dell'arte è che si utilizza un metodo algoritmico che genera una mappatura e quindi si salva la mappatura finale. Ci sono molti modi interessanti per andare su questo ( e altro ), ma il problema deriva dal fatto che la probabilità di una collisione con qualsiasi metodo casuale è probabile che vada a garantire una collisione prima di esaurire il tuo spazio di input. Fornirò un esempio di questo ora, nel codice C # (copia / incolla il codice, credo):

  System.Text.StringBuilder Sb = new System.Text.StringBuilder();

  System.Collections.Generic.HashSet<string> results = new System.Collections.Generic.HashSet<string>();

  using (System.Security.Cryptography.SHA512 hash = System.Security.Cryptography.SHA512Managed.Create() ) {
    System.Text.Encoding enc = System.Text.Encoding.UTF8;

    for (int input = 0; input < 10000; input++) {
      Byte[] result = hash.ComputeHash(enc.GetBytes(input.ToString()));

      foreach (Byte b in result) {
        Sb.Append(b.ToString("x2"));
      }

      results.Add(Sb.ToString().Substring(Sb.Length - 3));
    }

  }

Quello che sto facendo è fornire un valore di input compreso tra 0 e 9999, convertirlo in un hash SHA512 e quindi prendere solo le ultime 3 cifre alfanumeriche. Puoi quindi confrontare la dimensione di HashSet con gli input per determinare quanti duplicati hai.

Il risultato: con 10000 ingressi ottieni solo 3735 risultati unici. Ahi: ci sono molte collisioni! Se cambi la tua richiesta di mappatura a 4 cifre (cambia la riga di codice finale sopra a Sb.Length - 4), ottieni 9303 - non male! Se si concedono 5 cifre di output si ottiene 9955 - ancora con le collisioni e abbiamo esteso in modo massivo il nostro output consentito e solo 10000 input!

Quindi, se hai usato un metodo del genere, devi limitare notevolmente i tuoi input massimi e probabilmente aumentare anche la dimensione dell'output.

Se non ti interessa molto della casualità, puoi usare (x + 18) % 46656 , come in sostituzione dell'ultima riga con questo:

results.Add(((input + 18) % 46656).ToString());

Il risultato è zero collisioni e anche molto più veloce. L'output non è del tutto casuale, ovviamente, specialmente se si sale in sequenza.

Ora, con un po 'di sintonizzazione manuale sono riuscito a creare questo piccolo stupido:

results.Add(((input * (40001) + 11) % 46656).ToString());

Quindi prendi l'input, moltiplicalo per 40001, aggiungi 11, quindi "avvolgilo" al tuo input massimo. Su 0-46656 come input non genera collisioni, eppure salta dappertutto - f (1) - > 400012, mentre f (2) - > 33357. Se si salta usando i primi pochi input (0-2) la funzione che genera questi numeri è ancora più opaca e, poiché non è lineare, non è facile trovare la funzione che genera questo set. Moltiplicare per un numero dispari e puoi usare questo come una sorta di "seme" per la tua funzione: i numeri primi potrebbero essere una scelta ancora migliore! I piccoli numeri si traducono in minore nervosità, mentre moltiplicando per lo stesso numero si sta prendendo il mod di è ... beh, sempre 0. Sospetto che da qualche parte nel medio / alto sia l'ideale.

Quindi analizza l'output intero nel formato di stringa desiderato, e tadah, Bob è tuo zio!

Ora, se vuoi qualcosa di più casuale con entropia misurabile e che impiega molto tempo a decodificare ... beh, sarà molto più difficile organizzare di molto.

Spero che questo sia almeno utile per avvicinarti a dove vuoi andare.

    
risposta data 23.12.2014 - 18:46
fonte

Leggi altre domande sui tag