Genera un hash dalla stringa in Javascript


589

Ho bisogno di convertire le stringhe in qualche forma di hash. Questo è possibile in JavaScript?

Non sto usando una lingua lato server, quindi non posso farlo in questo modo.


7
MD5 non è sicuro, quindi non cercarlo.
henrikstroem,

166
@henrikstroem Dipende da cosa stai facendo l'hashing; non c'è niente di sbagliato nell'usare md5 per creare un hash per scopi non di sicurezza.
Brad Koch,

7
@BradKoch Dipende da cosa stai facendo; non c'è niente di sbagliato nell'usare md5 per motivi di sicurezza. Esistono sicuramente metodi migliori per l'hashing delle password, ma md5 va bene per fare cose come firmare un URL.
Paul Ferrett,

81
Trovo divertente che mentre MD5 sia criticato nei commenti qui, quasi tutte le risposte raccomandano algoritmi hash molto peggiori e ottengono molti voti positivi.
domen,

38
L'utilizzo di MD5 per verificare che un download sia rimasto intatto non magicamente invierà le tue password a tutti i tuoi colleghi.
James M. Lay

Risposte:


790
Object.defineProperty(String.prototype, 'hashCode', {
  value: function() {
    var hash = 0, i, chr;
    for (i = 0; i < this.length; i++) {
      chr   = this.charCodeAt(i);
      hash  = ((hash << 5) - hash) + chr;
      hash |= 0; // Convert to 32bit integer
    }
    return hash;
  }
});

Fonte: http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/


22
Questo è lo stesso usato in Java. Lo hash << 5 - hashè la stessa hash * 31 + char, ma molto più veloce. È bello perché è così veloce e 31 è un piccolo numero primo. Vinci, vinci lì.
corsiKa

41
Ho fatto alcuni test su jsperf ( jsperf.com/hashing-strings ) e la funzione bit a bit è in realtà più lenta della funzione basata sui numeri.
skerit,

17
@PeterAronZentai Perché è "inutilizzabile"? L'output prodotto dal codice basato su numeri (hash * 31) + charè identico all'output prodotto dal codice basato su shift ((hash<<5)-hash)+char, anche per stringhe molto lunghe (l'ho testato con stringhe contenenti oltre un milione di caratteri), quindi non è "inutilizzabile" in termini di precisione. La complessità è O (n) sia per le versioni basate sul numero che per quelle basate sul turno, quindi non è "inutilizzabile" in termini di complessità.
TachyonVortex,

13
Qualcuno può commentare l'unicità (o meno) dell'output? In particolare, se uso questo hash solo per stringhe con lunghezza inferiore a n, qual è il più grande nper il quale non posso avere una collisione?
Don McCurdy,

34
C'è qualche motivo per cui questo (o dovrebbe) essere sul prototipo String? Sarebbe meno efficace / efficiente avere ad es. var hashCode = function hashCode (str) {etc...}? E poi usa come hashCode("mystring")?
rastrellare il

146

MODIFICARE

sulla base dei miei test jsperf, la risposta accettata è in realtà più veloce: http://jsperf.com/hashcodelordvlad

ORIGINALE

se qualcuno è interessato, ecco una versione migliorata (più veloce), che fallirà sui browser più vecchi che non dispongono della reducefunzione array.

hashCode = function(s){
  return s.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);              
}

versione con funzione freccia one-liner:

hashCode = s => s.split('').reduce((a,b)=>{a=((a<<5)-a)+b.charCodeAt(0);return a&a},0)

3
c'è un modo per ottenere l'hash che è solo un numero positivo?
Prosto Trader,

46
strano. l'ho appena testato e si è rivelato essere più lento della risposta accettata. jsperf.com/hashcodelordvlad
lordvlad il

113
Bravo ragazzo @lordvlad, in realtà testando la propria risposta, e poi riferendo quando è stato più lento.
mikemaccana,

9
Ho appena capito: ha perfettamente senso che la risposta accettata sia più veloce, perché la mia versione deve prima trasformare la stringa in un array, allocando nuova memoria e copiando ogni personaggio ...
lordvlad

5
[] .reduce.call (str, (p, c, i, a) => (p << 5) - p + a.charCodeAt (i), 0);
Dizzy,

108

Nota: anche con il miglior hash a 32 bit, prima o poi si verificheranno delle collisioni .

La probabilità di collisione dell'hash può essere calcolata come 1 - e ^ (-k (k-1) / 2N, approssimata come k ^ 2 / 2N ( vedi qui ). Questo può essere superiore a quanto l'intuizione suggerisce:
supponendo un hash a 32 bit e k = 10.000 elementi, si verificherà una collisione con una probabilità dell'1,2%. Per 77.163 campioni la probabilità diventa 50%! ( calcolatrice ).
Suggerisco una soluzione alternativa in fondo.

In una risposta a questa domanda Quale algoritmo di hashing è il migliore per unicità e velocità? , Ian Boyd ha pubblicato una buona analisi approfondita . In breve (come lo interpreto), giunge alla conclusione che Murmur è il migliore, seguito da FNV-1a.
L'algoritmo String.hashCode () di Java proposto da esmiralha sembra essere una variante di DJB2.

  • FNV-1a ha una distribuzione migliore rispetto a DJB2, ma è più lento
  • DJB2 è più veloce di FNV-1a, ma tende a produrre più collisioni
  • MurmurHash3 è migliore e più veloce di DJB2 e FNV-1a (ma l'implementazione ottimizzata richiede più righe di codice rispetto a FNV e DJB2)

Alcuni benchmark con stringhe di input di grandi dimensioni qui: http://jsperf.com/32-bit-hash
Quando si eseguono l'hash delle stringhe di input brevi , le prestazioni del mormorio diminuiscono rispetto a DJ2B e FNV-1a: http://jsperf.com/32- bit-hash / 3

Quindi, in generale, consiglierei murmur3.
Vedi qui per un'implementazione JavaScript: https://github.com/garycourt/murmurhash-js

Se le stringhe di input sono brevi e le prestazioni sono più importanti della qualità di distribuzione, utilizzare DJB2 (come proposto dalla risposta accettata da esmiralha).

Se la qualità e le dimensioni ridotte del codice sono più importanti della velocità, utilizzo questa implementazione di FNV-1a (basata su questo codice ).

/**
 * Calculate a 32 bit FNV-1a hash
 * Found here: https://gist.github.com/vaiorabbit/5657561
 * Ref.: http://isthe.com/chongo/tech/comp/fnv/
 *
 * @param {string} str the input value
 * @param {boolean} [asString=false] set to true to return the hash value as 
 *     8-digit hex string instead of an integer
 * @param {integer} [seed] optionally pass the hash of the previous chunk
 * @returns {integer | string}
 */
function hashFnv32a(str, asString, seed) {
    /*jshint bitwise:false */
    var i, l,
        hval = (seed === undefined) ? 0x811c9dc5 : seed;

    for (i = 0, l = str.length; i < l; i++) {
        hval ^= str.charCodeAt(i);
        hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
    }
    if( asString ){
        // Convert to 8 digit hex string
        return ("0000000" + (hval >>> 0).toString(16)).substr(-8);
    }
    return hval >>> 0;
}

Migliora la probabilità di collisione

Come spiegato qui , possiamo estendere la dimensione del bit di hash usando questo trucco:

function hash64(str) {
    var h1 = hash32(str);  // returns 32 bit (as 8 byte hex string)
    return h1 + hash32(h1 + str);  // 64 bit (as 16 byte hex string)
}

Usalo con cura e non aspettarti troppo però.


Perché lo fai ("0000000" + (hval >>> 0).toString(16)).substr(-8);? Non è lo stesso di (hval >>> 0).toString(16)?
Manuel Meurer,

3
questo aggiunge i primi "0" in modo che l'hash risultante sia sempre lungo 8 caratteri. Più facile da leggere e riconoscere nelle uscite, ma questa è la mia opinione personale
mar10

Ah ok, ho capito. Per i piccoli hval, (hval >>> 0).toString(16)potrebbe contenere meno di 8 caratteri, quindi puoi riempirlo di zeri. Ero solo confuso perché mi è (hval >>> 0).toString(16)sempre risultato esattamente una stringa di 8 caratteri.
Manuel Meurer,

3
Adoro questa risposta perché produce un hash distribuito molto meglio: altre funzioni qui proposte daranno conseguenti valori di hash. Ad esempio `hash (" esempio1 ") - hash (" esempio2 ") == 1", mentre questo è molto più imprevedibile.
GavinoGrifoni,

1
In risposta a "FNV-1a ha una distribuzione migliore rispetto a DJB2, ma è più lento" - Penso che si dovrebbe dire che FNV1a può essere estremamente veloce quando implementato usando la Math.imulfunzione ES6 . Questo da solo lo rende il migliore punto di riferimento, e alla fine una scelta migliore rispetto a DJB2 a lungo termine.
bryc,

64

Basato sulla risposta accettata in ES6. Più piccolo, gestibile e funziona nei browser moderni.

function hashCode(str) {
  return str.split('').reduce((prevHash, currVal) =>
    (((prevHash << 5) - prevHash) + currVal.charCodeAt(0))|0, 0);
}

// Test
console.log("hashCode(\"Hello!\"): ", hashCode('Hello!'));

EDIT (2019-11-04) :

versione con funzione freccia one-liner:

const hashCode = s => s.split('').reduce((a,b) => (((a << 5) - a) + b.charCodeAt(0))|0, 0)

// test
console.log(hashCode('Hello!'))


1
Grazie per la condivisione che ho aggiunto str += ""prima dell'hash per evitare l'eccezione str.split is not a functiongenerata quando le non stringhe venivano passate come parametri
BeetleJuice

4
Ma molto, molto più lentamente di tutti questi: https://jsperf.com/hashing-strings
AndyO

Ho anche notato che la soluzione "retro" più veloce è in realtà più piccola se si rimuovono gli avanzamenti di riga in modo che siano lunghi solo 3 righe.
AndyO,

2
Un modo per ottenere questo produce solo risultati positivi ma ancora unici?
Dids

3
@deekshith La risposta accettata utilizza hash |= 0per convertire in un int a 32 bit. Questa implementazione no. è un insetto?
Sukima,

48

Quasi la metà delle risposte sono implementazioni di Java String.hashCode, che non è né di alta qualità né super veloce. Non è niente di speciale, si moltiplica solo per 31 per ogni personaggio. Può essere implementato in modo semplice ed efficiente in una riga ed è molto più veloce con Math.imul:

hashCode=s=>{for(var i=0,h;i<s.length;i++)h=Math.imul(31,h)+s.charCodeAt(i)|0;return h}

Detto questo, ecco qualcosa di meglio : cyrb53 , un hash a 53 bit semplice ma di alta qualità. È abbastanza veloce, offre un'ottima distribuzione dell'hash e ha tassi di collisione significativamente più bassi rispetto a qualsiasi hash a 32 bit.

const cyrb53 = function(str, seed = 0) {
    let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
    for (let i = 0, ch; i < str.length; i++) {
        ch = str.charCodeAt(i);
        h1 = Math.imul(h1 ^ ch, 2654435761);
        h2 = Math.imul(h2 ^ ch, 1597334677);
    }
    h1 = Math.imul(h1 ^ h1>>>16, 2246822507) ^ Math.imul(h2 ^ h2>>>13, 3266489909);
    h2 = Math.imul(h2 ^ h2>>>16, 2246822507) ^ Math.imul(h1 ^ h1>>>13, 3266489909);
    return 4294967296 * (2097151 & h2) + (h1>>>0);
};

Simile ai noti algoritmi MurmurHash / xxHash, utilizza una combinazione di moltiplicazione e Xorshift per generare l'hash, ma non altrettanto approfondito. Di conseguenza è più veloce di entrambi in JavaScript e significativamente più semplice da implementare.

Raggiunge una valanga (non rigorosa), il che significa sostanzialmente che piccoli cambiamenti nell'input hanno grandi cambiamenti nell'output, rendendo l'hash risultante casuale:

0xc2ba782c97901 = cyrb53("a")
0xeda5bc254d2bf = cyrb53("b")
0xe64cc3b748385 = cyrb53("revenge")
0xd85148d13f93a = cyrb53("revenue")

Puoi anche fornire un seme per flussi alternativi dello stesso input:

0xee5e6598ccd5c = cyrb53("revenue", 1)
0x72e2831253862 = cyrb53("revenue", 2)
0x0de31708e6ab7 = cyrb53("revenue", 3)

Tecnicamente è un hash a 64 bit (due hash non correlati in parallelo a 32 bit), ma JavaScript è limitato a numeri interi a 53 bit. Se necessario, è ancora possibile utilizzare l'output completo a 64 bit modificando la riga di ritorno per una stringa esadecimale o un array.

Tenere presente che la costruzione di stringhe esadecimali può rallentare drasticamente l'elaborazione batch in situazioni critiche per le prestazioni.

return (h2>>>0).toString(16).padStart(8,0)+(h1>>>0).toString(16).padStart(8,0);
// or
return [h2>>>0, h1>>>0];

E solo per divertimento, ecco un hash minimo a 32 bit in 89 caratteri con qualità superiore rispetto a FNV o DJB2:

TSH=s=>{for(var i=0,h=9;i<s.length;)h=Math.imul(h^s.charCodeAt(i++),9**9);return h^h>>>9}

4
Caspita, è molto meglio del solito * 31 per input brevi (o simili). :)
lapo,

2
Dove viene chinizializzato?
hellowill89,

3
@ hellowill89 woops, ho dimenticato di dichiararlo e sanguinavo in ambito globale. risolto ora, grazie: ')
bryc

Non riuscito per IE 11: l'oggetto non supporta la proprietà o il metodo 'imul'.
BachT

2
@BachT È possibile utilizzare un polyfill o uno spessore ES6 completo . Ma IE11 è tragicamente congelato nel 2009 senza aggiornamenti.
bryc

28

Se aiuta qualcuno, ho combinato le prime due risposte in una versione più vecchia tollerante al browser, che utilizza la versione veloce se reduceè disponibile e ricade nella soluzione di esmiralha se non lo è.

/**
 * @see http://stackoverflow.com/q/7616461/940217
 * @return {number}
 */
String.prototype.hashCode = function(){
    if (Array.prototype.reduce){
        return this.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);              
    } 
    var hash = 0;
    if (this.length === 0) return hash;
    for (var i = 0; i < this.length; i++) {
        var character  = this.charCodeAt(i);
        hash  = ((hash<<5)-hash)+character;
        hash = hash & hash; // Convert to 32bit integer
    }
    return hash;
}

L'uso è come:

var hash = "some string to be hashed".hashCode();

come ottimizzare questo codice per funzionare più velocemente in ogni browser. String.prototype.hashCode = function(){ var hash = 5381; if (this.length === 0) return hash; for (var i = 0; i < this.length; i++) { var character = this.charCodeAt(i); hash = ((hash<<5)+hash)^character; // Convert to 32bit integer } return hash; }
Musakkhir Sayyed,

26

Questa è una variante raffinata e dalle prestazioni migliori:

String.prototype.hashCode = function() {
    var hash = 0, i = 0, len = this.length;
    while ( i < len ) {
        hash  = ((hash << 5) - hash + this.charCodeAt(i++)) << 0;
    }
    return hash;
};

Ciò corrisponde all'implementazione di Java dello standard object.hashCode()

Eccone anche uno che restituisce solo hashcode positivi:

String.prototype.hashcode = function() {
    return (this.hashCode() + 2147483647) + 1;
};

Ed eccone uno corrispondente per Java che restituisce solo hashcode positivi:

public static long hashcode(Object obj) {
    return ((long) obj.hashCode()) + Integer.MAX_VALUE + 1l;
}

Godere!


2
ottima risposta, ma qual è lo scopo di << 0?
Koolaang,

8
@koolaang è l'operatore di merda di sinistra, developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
mmm

29
@momomo Intendevi spostamento a sinistra ?
con il

2
@momomo Penso che stesse chiedendo perché fosse uno spostamento a sinistra di zero bit.
jpfx1342,

3
@Maykonn (2 ^ 32 - 1)
Nijraj Gelani,

24

Sono un po 'sorpreso che nessuno abbia ancora parlato della nuova API di SubtleCrypto .

Per ottenere un hash da una stringa, è possibile utilizzare il subtle.digestmetodo:

function getHash(str, algo = "SHA-256") {
  let strBuf = new TextEncoder('utf-8').encode(str);
  return crypto.subtle.digest(algo, strBuf)
    .then(hash => {
      window.hash = hash;
      // here hash is an arrayBuffer, 
      // so we'll connvert it to its hex version
      let result = '';
      const view = new DataView(hash);
      for (let i = 0; i < hash.byteLength; i += 4) {
        result += ('00000000' + view.getUint32(i).toString(16)).slice(-8);
      }
      return result;
    });
}

getHash('hello world')
  .then(hash => {
    console.log(hash);
  });


4
Sono d'accordo. La conversione in esadecimale potrebbe essere fatta in modo leggermente diverso ...var promise = crypto.subtle.digest({name: "SHA-256"}, Uint8Array.from(data)); promise.then(function(result){ console.log(Array.prototype.map.call(new Uint8Array(result), x => x.toString(16).padStart(2, '0')).join('')); });
Denis Giffeler,

3
Una funzione hash crittografica per le stringhe è un po 'eccessiva ... cryptonon è esattamente performante.
bryc,

Qualità affidabile casuale senza dover fare affidamento su persone che eseguono test, integrato (nessuna implementazione personalizzata necessaria), seminabile, e ho avuto bisogno solo di poche centinaia di numeri per generare una mappa di gioco, questo sembrava perfetto. Ma si scopre che non c'è assolutamente modo di farlo in modo sincrono. Dover fornire qualcosa di callback asincrono ogni volta che chiami il tuo motore casuale seminato rende il codice super illeggibile e sembra ridicolo. Non capisco chi ha avuto questa schifosa interfaccia crypto.subtle, quindi alla fine ho dovuto andare con xmur3 + sfc32 da questa risposta: stackoverflow.com/a/47593316/1201863
Luc

7

Grazie all'esempio di mar10, ho trovato il modo di ottenere gli stessi risultati in C # E Javascript per un FNV-1a. Se sono presenti caratteri unicode, la parte superiore viene scartata per motivi di prestazioni. Non so perché sarebbe utile mantenerli quando si esegue l'hashing, poiché per ora sono solo percorsi URL di hashing.

Versione C #

private static readonly UInt32 FNV_OFFSET_32 = 0x811c9dc5;   // 2166136261
private static readonly UInt32 FNV_PRIME_32 = 0x1000193;     // 16777619

// Unsigned 32bit integer FNV-1a
public static UInt32 HashFnv32u(this string s)
{
    // byte[] arr = Encoding.UTF8.GetBytes(s);      // 8 bit expanded unicode array
    char[] arr = s.ToCharArray();                   // 16 bit unicode is native .net 

    UInt32 hash = FNV_OFFSET_32;
    for (var i = 0; i < s.Length; i++)
    {
        // Strips unicode bits, only the lower 8 bits of the values are used
        hash = hash ^ unchecked((byte)(arr[i] & 0xFF));
        hash = hash * FNV_PRIME_32;
    }
    return hash;
}

// Signed hash for storing in SQL Server
public static Int32 HashFnv32s(this string s)
{
    return unchecked((int)s.HashFnv32u());
}

Versione JavaScript

var utils = utils || {};

utils.FNV_OFFSET_32 = 0x811c9dc5;

utils.hashFnv32a = function (input) {
    var hval = utils.FNV_OFFSET_32;

    // Strips unicode bits, only the lower 8 bits of the values are used
    for (var i = 0; i < input.length; i++) {
        hval = hval ^ (input.charCodeAt(i) & 0xFF);
        hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
    }

    return hval >>> 0;
}

utils.toHex = function (val) {
    return ("0000000" + (val >>> 0).toString(16)).substr(-8);
}

@mathiasrw È possibile che i caratteri Unicode superino gli 8 bit in memoria, quindi suppongo che 0xFF maschera semplicemente qualsiasi cosa al di fuori di tale intervallo. Scopri di più su charCodeAt () qui: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
djabraham il

Se ES6 è disponibile (tutti i motori moderni lo supportano), Math.imulpuò essere utilizzato per la fase di moltiplicazione, che migliora notevolmente le prestazioni . L'unico problema è che non funzionerà in IE11 senza uno spessore .
bryc

6

Uno veloce e conciso che è stato adattato da qui :

String.prototype.hashCode = function() {
  var hash = 5381, i = this.length
  while(i)
    hash = (hash * 33) ^ this.charCodeAt(--i)
  return hash >>> 0;
}

5

Avevo bisogno di una funzione simile (ma diversa) per generare un ID univoco in base al nome utente e all'ora corrente. Così:

window.newId = ->
  # create a number based on the username
  unless window.userNumber?
    window.userNumber = 0
  for c,i in window.MyNamespace.userName
    char = window.MyNamespace.userName.charCodeAt(i)
    window.MyNamespace.userNumber+=char
  ((window.MyNamespace.userNumber + Math.floor(Math.random() * 1e15) + new Date().getMilliseconds()).toString(36)).toUpperCase()

produce:

2DVFXJGEKL
6IZPAKFQFL
ORGOENVMG
... etc 

modifica giugno 2015: per il nuovo codice che uso shortid: https://www.npmjs.com/package/shortid


2
@ t0r0X bene ora uso un modulo chiamato shortid: npmjs.com/package/shortid
jcollum

1
Come stai usando il nome utente con shortid? Sembra solo generare id ma non vedo come stai usando per generare un hash da una stringa
cyberwombat

1
Questa risposta ha 3 voti negativi. Per la mia vita non riesco a immaginare il perché. Nessuno ha detto niente ...: - /
jcollum,

1
@jcollum è per questo che non rispondo quasi mai a domande scadute .. hit and run passa inosservato. anche dopo aver sistemato la risposta, nessuno arriva per bilanciarla.
bryc

5

La mia veloce (molto lunga) fodera basata sul Multiply+Xormetodo di FNV :

my_string.split('').map(v=>v.charCodeAt(0)).reduce((a,v)=>a+((a<<7)+(a<<3))^v).toString(16);

5

SubtleCrypto.digest

Non sto usando una lingua lato server, quindi non posso farlo in questo modo.

Sei sicuro di non poterlo fare in quel modo ?

Hai dimenticato di utilizzare Javascript, la lingua in continua evoluzione?

Prova SubtleCrypto. Supporta le funzioni hash SHA-1, SHA-128, SHA-256 e SHA-512.


async function hash(message/*: string */) {
	const text_encoder = new TextEncoder;
	const data = text_encoder.encode(message);
	const message_digest = await window.crypto.subtle.digest("SHA-512", data);
	return message_digest;
} // -> ArrayBuffer

function in_hex(data/*: ArrayBuffer */) {
	const octets = new Uint8Array(data);
	const hex = [].map.call(octets, octet => octet.toString(16).padStart(2, "0")).join("");
	return hex;
} // -> string

(async function demo() {
	console.log(in_hex(await hash("Thanks for the magic.")));
})();


In che modo differisce dalla risposta di Kaiido due anni prima della tua ?
Luc

@Luc Non lo è, a quanto pare.
Константин Ван

3

Sono in ritardo alla festa, ma puoi usare questo modulo: crypto :

const crypto = require('crypto');

const SALT = '$ome$alt';

function generateHash(pass) {
  return crypto.createHmac('sha256', SALT)
    .update(pass)
    .digest('hex');
}

Il risultato di questa funzione è sempre la 64stringa di caratteri; qualcosa come questo:"aa54e7563b1964037849528e7ba068eb7767b1fab74a8d80fe300828b996714a"


2

Ho combinato le due soluzioni (utenti esmiralha e lordvlad) per ottenere una funzione che dovrebbe essere più veloce per i browser che supportano la funzione js di riduzione () e comunque compatibile con i vecchi browser:

String.prototype.hashCode = function() {

    if (Array.prototype.reduce) {
        return this.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);   
    } else {

        var hash = 0, i, chr, len;
        if (this.length == 0) return hash;
        for (i = 0, len = this.length; i < len; i++) {
        chr   = this.charCodeAt(i);
        hash  = ((hash << 5) - hash) + chr;
        hash |= 0; // Convert to 32bit integer
        }
        return hash;
    }
};

Esempio:

my_string = 'xyz';
my_string.hashCode();

2

Se si desidera evitare le collisioni, è possibile utilizzare un hash sicuro come SHA-256 . Esistono diverse implementazioni JavaScript SHA-256.

Ho scritto dei test per confrontare diverse implementazioni di hash, vedi https://github.com/brillout/test-javascript-hash-implementations .

Oppure vai su http://brillout.github.io/test-javascript-hash-implementations/ , per eseguire i test.


1
L'uso di un hash crittografico sicuro può essere estremamente lento. Evitare le collisioni è un prodotto della larghezza dei bit, non della sicurezza. L'hash non crittografico a 128 bit o anche i 64 bit dovrebbero essere più che sufficienti per la maggior parte degli scopi. MurmurHash3_x86_128 è abbastanza veloce e ha una probabilità molto bassa di collisioni.
bryc,

2

Questo dovrebbe essere un hash un po 'più sicuro rispetto ad altre risposte, ma in una funzione, senza alcuna fonte precaricata

Ho creato fondamentalmente una versione semplificata minimizzata di sha1.
Prendi i byte della stringa e li raggruppa per "parole" da 4 a 32 bit.
Quindi estendiamo ogni 8 parole a 40 parole (per un impatto maggiore sul risultato).
Questo va alla funzione di hashing (l'ultima riduzione) dove facciamo alcuni calcoli con lo stato corrente e l'input. Otteniamo sempre 4 parole.
Questa è quasi una versione a un comando / una riga che usa map, riduci ... invece dei loop, ma è ancora piuttosto veloce

String.prototype.hash = function(){
    var rot = (word, shift) => word << shift | word >>> (32 - shift);
    return unescape(encodeURIComponent(this.valueOf())).split("").map(char =>
            char.charCodeAt(0)
        ).reduce((done, byte, idx, arr) =>
            idx % 4 == 0 ? [...done, arr.slice(idx, idx + 4)] : done
        , []).reduce((done, group) =>
            [...done, group[0] << 24 | group[1] << 16 | group[2] << 8 | group[3]]
        , []).reduce((done, word, idx, arr) =>
            idx % 8 == 0 ? [...done, arr.slice(idx, idx + 8)] : done
        , []).map(group => {
            while(group.length < 40)
                group.push(rot(group[group.length - 2] ^ group[group.length - 5] ^ group[group.length - 8], 3));
            return group;
        }).flat().reduce((state, word, idx, arr) => {
            var temp = ((state[0] + rot(state[1], 5) + word + idx + state[3]) & 0xffffffff) ^ state[idx % 2 == 0 ? 4 : 5](state[0], state[1], state[2]);
            state[0] = rot(state[1] ^ state[2], 11);
            state[1] = ~state[2] ^ rot(~state[3], 19);
            state[2] = rot(~state[3], 11);
            state[3] = temp;
            return state;
        }, [0xbd173622, 0x96d8975c, 0x3a6d1a23, 0xe5843775,
            (w1, w2, w3) => (w1 & rot(w2, 5)) | (~rot(w1, 11) & w3),
            (w1, w2, w3) => w1 ^ rot(w2, 5) ^ rot(w3, 11)]
        ).slice(0, 4).map(p =>
            p >>> 0
        ).map(word =>
            ("0000000" + word.toString(16)).slice(-8)
        ).join("");
};

convertiamo anche l'output in esadecimale per ottenere una stringa anziché una matrice di parole.
L'utilizzo è semplice. per l'esempio "a string".hash()tornerà"88a09e8f9cc6f8c71c4497fbb36f84cd"


1

Ho optato per una semplice concatenazione di codici char convertiti in stringhe esadecimali. Questo ha uno scopo relativamente ristretto, vale a dire solo la necessità di una rappresentazione hash di una stringa SHORT (ad esempio titoli, tag) da scambiare con un lato server che per motivi non rilevanti non può facilmente implementare la porta Java hashCode accettata. Ovviamente nessuna applicazione di sicurezza qui.

String.prototype.hash = function() {
  var self = this, range = Array(this.length);
  for(var i = 0; i < this.length; i++) {
    range[i] = i;
  }
  return Array.prototype.map.call(range, function(i) {
    return self.charCodeAt(i).toString(16);
  }).join('');
}

Questo può essere reso più conciso e tollerante con il browser con Underscore. Esempio:

"Lorem Ipsum".hash()
"4c6f72656d20497073756d"

Suppongo che se si desidera eseguire l'hashing di stringhe più grandi in modo simile, è possibile ridurre i codici char ed esadecimare la somma risultante anziché concatenare i singoli caratteri insieme:

String.prototype.hashLarge = function() {
  var self = this, range = Array(this.length);
  for(var i = 0; i < this.length; i++) {
    range[i] = i;
  }
  return Array.prototype.reduce.call(range, function(sum, i) {
    return sum + self.charCodeAt(i);
  }, 0).toString(16);
}

'One time, I hired a monkey to take notes for me in class. I would just sit back with my mind completely blank while the monkey scribbled on little pieces of paper. At the end of the week, the teacher said, "Class, I want you to write a paper using your notes." So I wrote a paper that said, "Hello! My name is Bingo! I like to climb on things! Can I have a banana? Eek, eek!" I got an F. When I told my mom about it, she said, "I told you, never trust a monkey!"'.hashLarge()
"9ce7"

Naturalmente più rischio di collisione con questo metodo, anche se si potrebbe giocherellare con l'aritmetica nella riduzione, tuttavia si voleva diversificare e allungare l'hash.


1

Versione leggermente semplificata della risposta di @ esmiralha.

Non sostituisco String in questa versione, poiché ciò potrebbe comportare comportamenti indesiderati.

function hashCode(str) {
    var hash = 0;
    for (var i = 0; i < str.length; i++) {
        hash = ~~(((hash << 5) - hash) + str.charCodeAt(i));
    }
    return hash;
}

1

Aggiungendo questo perché nessuno lo ha ancora fatto, e questo sembra essere richiesto e implementato molto con gli hash, ma è sempre fatto molto male ...

Ciò richiede un input di stringa e un numero massimo a cui si desidera che l'hash sia uguale e produce un numero univoco basato sull'input di stringa.

Puoi usarlo per produrre un indice univoco in una matrice di immagini (Se vuoi restituire un avatar specifico per un utente, scelto a caso, ma anche scelto in base al loro nome, quindi sarà sempre assegnato a qualcuno con quel nome ).

Puoi anche usare questo, ovviamente, per restituire un indice in una matrice di colori, come per generare colori di sfondo avatar unici basati sul nome di qualcuno.

function hashInt (str, max = 1000) {
    var hash = 0;
    for (var i = 0; i < str.length; i++) {
      hash = ((hash << 5) - hash) + str.charCodeAt(i);
      hash = hash & hash;
    }
    return Math.round(max * Math.abs(hash) / 2147483648);
}

-1

Non vedo alcun motivo per utilizzare questo codice crittografico complicato invece di soluzioni pronte all'uso, come la libreria di hash degli oggetti, ecc. Fare affidamento sul fornitore è più produttivo, consente di risparmiare tempo e ridurre i costi di manutenzione.

Basta usare https://github.com/puleos/object-hash

var hash = require('object-hash');

hash({foo: 'bar'}) // => '67b69634f9880a282c14a0f0cb7ba20cf5d677e9'
hash([1, 2, 2.718, 3.14159]) // => '136b9b88375971dff9f1af09d7356e3e04281951'

Il codice sorgente di quella lib non è nemmeno leggibile .. solo 50k di codice minimizzato.
bryc,

1
@bryc ecco come dovrebbe apparire il codice del fornitore :) e per le fonti puoi controllare github.com/puleos/object-hash/blob/master/index.js
Oleg Abrazhaev

Il codice minimizzato è 35,4 KB mentre l'origine completa è 14,2 KB? Non ha senso.
bryc

2
@bryc hai preso in considerazione questa linea? var crypto = require('crypto');. Penso che aggiunge questo codice di dipendenza dal fornitore nella versione ridotta durante una compilazione.
Oleg Abrazhaev,

Se hai davvero bisogno di hash Objects, ho scritto any-serialize per serializzare QUALSIASI Object con chiavi di ordinamento, quindi cyrb53 per generare hash base36.
Polv,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.