Generar un hash de cadena en Javascript

589

Necesito convertir cadenas a alguna forma de hash. ¿Es esto posible en JavaScript?

No estoy utilizando un lenguaje del lado del servidor, así que no puedo hacerlo de esa manera.

Freesnöw
fuente
77
MD5 no es seguro, así que no busques ese.
henrikstroem
166
@henrikstroem Depende de lo que estés haciendo hashing; No hay nada de malo en usar md5 para hacer un hash para fines no relacionados con la seguridad.
Brad Koch
77
@BradKoch Depende de lo que estés haciendo; No hay nada de malo en usar md5 por motivos de seguridad. Ciertamente hay mejores métodos para contraseñas hash, pero md5 está bien para hacer cosas como firmar una URL.
Paul Ferrett
81
Me parece curioso que si bien MD5 es criticado en los comentarios aquí, casi todas las respuestas recomiendan algoritmos hash mucho peores y obtienen muchas votaciones positivas.
Domen
38
El uso de MD5 para verificar que una descarga se realizó intacta no le enviará mágicamente sus contraseñas a todos sus compañeros de trabajo.
James M. Lay

Respuestas:

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;
  }
});

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

esmiralha
fuente
22
Este es el mismo que se usa en Java. El hash << 5 - hashes el mismo hash * 31 + charpero MUCHO más rápido. Es bueno porque es muy rápido, y 31 es un primo pequeño. Gana gana allí.
corsiKa
41
Hice algunas pruebas en jsperf ( jsperf.com/hashing-strings ) y la función bit a bit es en realidad más lenta que la función basada en números.
skerit
17
@PeterAronZentai ¿Por qué es "inutilizable"? La salida producida por el código basado en números (hash * 31) + chares idéntica a la salida producida por el código basado en turnos ((hash<<5)-hash)+char, incluso para cadenas muy largas (lo he probado con cadenas que contienen más de un millón de caracteres), por lo que no es "inutilizable" en términos de precisión La complejidad es O (n) para las versiones basadas en números y en turnos, por lo que no es "inutilizable" en términos de complejidad.
TachyonVortex
13
¿Alguien puede comentar sobre la unicidad (o no) de la salida? Específicamente, si solo uso este hash para cadenas con una longitud menor que n, ¿cuál es el más grande npara el cual no puedo tener una colisión?
Don McCurdy
34
¿Hay alguna razón para que esto necesite (o deba) estar en el prototipo de String? ¿Sería menos efectivo / eficiente tener solo, por ejemplo; var hashCode = function hashCode (str) {etc...}? Y luego usar como hashCode("mystring")?
Rattray
146

EDITAR

basado en mis pruebas jsperf, la respuesta aceptada es en realidad más rápida: http://jsperf.com/hashcodelordvlad

ORIGINAL

Si alguien está interesado, aquí hay una versión mejorada (más rápida), que fallará en los navegadores más antiguos que carecen de la reducefunción de matriz.

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

versión de función de flecha de una línea:

hashCode = s => s.split('').reduce((a,b)=>{a=((a<<5)-a)+b.charCodeAt(0);return a&a},0)
lordvlad
fuente
3
¿hay alguna manera de obtener hash que solo sea un número positivo?
Prosto Trader
46
extraño. Acabo de probarlo y resultó ser mucho más lento que la respuesta aceptada. jsperf.com/hashcodelordvlad
lordvlad
113
Buen chico @ Lordvlad, en realidad probando su propia respuesta, y luego informando cuando fue más lento.
mikemaccana
99
Me acabo de dar cuenta: Tiene mucho sentido que la respuesta aceptada es más rápido, porque mi versión tiene que convertir la cadena en un arreglo en primer lugar, la asignación de memoria nueva y copiar todos los personajes ...
lordvlad
55
[] .reduce.call (str, (p, c, i, a) => (p << 5) - p + a.charCodeAt (i), 0);
Dizzy
108

Nota: Incluso con la mejor hash de 32 bits, las colisiones se producirá tarde o temprano.

La probabilidad de colisión de hash se puede calcular como 1 - e ^ (-k (k-1) / 2N, aproximadamente como k ^ 2 / 2N ( ver aquí ). Esto puede ser más alto de lo que sugiere la intuición:
suponiendo un hash de 32 bits yk = 10,000 elementos, se producirá una colisión con una probabilidad de 1.2%. ¡Para 77,163 muestras la probabilidad se convierte en 50%! ( calculadora )
Sugiero una solución en la parte inferior.

En una respuesta a esta pregunta, ¿qué algoritmo de hash es mejor para la unicidad y la velocidad? Ian Boyd publicó un buen análisis en profundidad . En resumen (según lo interpreto), llega a la conclusión de que Murmur es el mejor, seguido de FNV-1a.
El algoritmo String.hashCode () de Java que esmiralha propuso parece ser una variante de DJB2.

  • FNV-1a tiene una mejor distribución que DJB2, pero es más lento
  • DJB2 es más rápido que FNV-1a, pero tiende a producir más colisiones
  • MurmurHash3 es mejor y más rápido que DJB2 y FNV-1a (pero la implementación optimizada requiere más líneas de código que FNV y DJB2)

Algunos puntos de referencia con cadenas de entrada grandes aquí: http://jsperf.com/32-bit-hash
Cuando se cortan cadenas de entrada cortas , el rendimiento del murmullo disminuye, en relación con DJ2B y FNV-1a: http://jsperf.com/32- bit-hash / 3

Entonces, en general, recomendaría murmur3.
Vea aquí para una implementación de JavaScript: https://github.com/garycourt/murmurhash-js

Si las cadenas de entrada son cortas y el rendimiento es más importante que la calidad de distribución, use DJB2 (como lo propone la respuesta aceptada por esmiralha).

Si la calidad y el tamaño de código pequeño son más importantes que la velocidad, utilizo esta implementación de FNV-1a (basada en este código ).

/**
 * 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;
}

Mejora la probabilidad de colisión

Como se explica aquí , podemos extender el tamaño de bit hash usando este truco:

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)
}

Úselo con cuidado y no espere demasiado.

mar10
fuente
¿Por qué haces ("0000000" + (hval >>> 0).toString(16)).substr(-8);? ¿No es lo mismo que (hval >>> 0).toString(16)?
Manuel Meurer
3
esto agrega '0' iniciales para que el hash resultante siempre tenga 8 caracteres de longitud. Más fácil de leer y reconocer en los resultados, pero esa es mi opinión personal
marzo del
Ah ok, lo entiendo. Para pequeño hval, (hval >>> 0).toString(16)puede tener menos de 8 caracteres, por lo que debe rellenarlo con ceros. Estaba confundido porque (hval >>> 0).toString(16)siempre resultó en una cadena de 8 caracteres exactamente para mí.
Manuel Meurer
3
Me encanta esta respuesta porque produce un hash distribuido mucho mejor: otras funciones propuestas aquí crearán valores hash consecuentes. Por ejemplo, `hash (" ejemplo1 ") - hash (" ejemplo2 ") == 1", mientras que este es mucho más impredecible.
GavinoGrifoni
1
En respuesta a "FNV-1a tiene una distribución mejor que DJB2, pero es más lenta". Creo que debería decirse que FNV1a puede ser extremadamente rápido cuando se implementa utilizando la Math.imulfunción ES6 . Eso solo lo convierte en los principales puntos de referencia y, en última instancia, una mejor opción que DJB2 a largo plazo.
bryc
64

Basado en la respuesta aceptada en ES6. Más pequeño, mantenible y funciona en navegadores modernos.

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

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

EDITAR (2019-11-04) :

versión de función de flecha de una línea:

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

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

deekshith
fuente
1
Gracias por compartir que agregué str += ""antes del hash para evitar que se str.split is not a function
produzcan
44
Pero mucho, mucho más lento que cualquiera de estos: https://jsperf.com/hashing-strings
AndyO
También me di cuenta de que la solución "retro" más rápida también es más pequeña si quita los avances de línea de modo que solo tenga 3 líneas de largo.
AndyO
2
¿Alguna forma de que esto produzca resultados positivos pero únicos?
Hace
3
@deekshith La respuesta aceptada se utiliza hash |= 0para convertir a un int de 32 bits. Esta implementación no lo hace. ¿Es esto un error?
Sukima
48

Casi la mitad de las respuestas son implementaciones de Java String.hashCode, que no son de alta calidad ni súper rápidas. No es nada especial, solo se multiplica por 31 para cada personaje. Se puede implementar de manera simple y eficiente en una línea, y es mucho más rápido 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}

Con eso fuera del camino, aquí hay algo mejor: cyrb53 , un hash de 53 bits simple pero de alta calidad. Es bastante rápido, proporciona una muy buena distribución de hash y tiene tasas de colisión significativamente más bajas en comparación con cualquier hash de 32 bits.

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);
};

Similar a los conocidos algoritmos MurmurHash / xxHash, utiliza una combinación de multiplicación y Xorshift para generar el hash, pero no tan exhaustivo. Como resultado, es más rápido que en JavaScript y significativamente más simple de implementar.

Alcanza una avalancha (no estricta), lo que básicamente significa que pequeños cambios en la entrada tienen grandes cambios en la salida, haciendo que el hash resultante parezca aleatorio:

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

También puede suministrar una semilla para secuencias alternativas de la misma entrada:

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

Técnicamente es un hash de 64 bits (dos hashes de 32 bits no correlacionados en paralelo), pero JavaScript está limitado a enteros de 53 bits. Si es necesario, la salida completa de 64 bits todavía se puede usar alterando la línea de retorno para una cadena o matriz hexadecimal.

Tenga en cuenta que la construcción de cadenas hexadecimales puede ralentizar drásticamente el procesamiento por lotes en situaciones críticas de rendimiento.

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

Y solo por diversión, aquí hay un hash mínimo de 32 bits en 89 caracteres con mayor calidad que incluso 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}
bryc
fuente
44
Wow, esto es mucho mejor que el habitual * 31 para entradas cortas (o similares). :)
lapo
2
¿Dónde se chinicializa?
hellowill89
3
@ hellowill89 woops, olvidé declararlo y estaba sangrando en alcance global. arreglado ahora, gracias: ')
bryc
Falló para IE 11: el objeto no admite propiedad o método 'imul'.
BachT
2
@BachT Puede usar un polyfill o una cuña ES6 completa . Pero IE11 está trágicamente congelado en 2009 sin actualizaciones.
bryc
28

Si ayuda a alguien, combiné las dos respuestas principales en una versión más antigua tolerante al navegador, que usa la versión rápida si reduceestá disponible y recurre a la solución de esmiralha si no es así.

/**
 * @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;
}

El uso es como:

var hash = "some string to be hashed".hashCode();
Kyle Falconer
fuente
cómo optimizar este código para que se ejecute más rápido en cada navegador. 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

Esta es una variante refinada y de mejor rendimiento:

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;
};

Esto coincide con la implementación de Java del estándar object.hashCode()

Aquí también hay uno que devuelve solo códigos hash positivos:

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

Y aquí hay una coincidencia para Java que solo devuelve códigos hash positivos:

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

¡Disfrutar!

mmm
fuente
2
gran respuesta, pero ¿cuál es el propósito de << 0?
koolaang
8
@koolaang es el operador de mierda izquierdo, developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
mmm
29
@momomo ¿Querías decir desplazamiento a la izquierda ?
wdh
2
@momomo Creo que estaba preguntando por qué era un desplazamiento a la izquierda de cero bits.
jpfx1342
3
@Maykonn (2 ^
32-1
24

Estoy un poco sorprendido de que nadie haya hablado aún sobre la nueva API SubtleCrypto .

Para obtener un hash de una cadena, puede usar el subtle.digestmétodo:

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);
  });

Kaiido
fuente
44
Estoy de acuerdo. La conversión a hexadecimal podría hacerse un poco diferente ...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 función hash criptográfica para cadenas es un poco exagerada ... cryptono es exactamente eficiente.
bryc
Calidad confiable al azar sin tener que depender de personas que realizan pruebas, integrado (no se necesita una implementación personalizada), visible, y solo necesitaba unos cientos de números para generar un mapa del juego, esto parecía perfecto. Pero resulta que no hay absolutamente ninguna manera de hacerlo sincrónicamente. Tener que proporcionar algo de devolución de llamada asíncrona cada vez que llama a su motor aleatorio sembrado hace que el código sea súper ilegible y parezca ridículo. No entiendo a quién se le ocurrió esta horrible interfaz crypto.subtle, así que al final tuve que ir con xmur3 + sfc32 de esta respuesta: stackoverflow.com/a/47593316/1201863
Luc
7

Gracias al ejemplo de mar10, encontré una manera de obtener los mismos resultados en C # Y Javascript para un FNV-1a. Si hay caracteres unicode, la porción superior se descarta por el bien del rendimiento. No sé por qué sería útil mantenerlos cuando se realiza el hashing, ya que solo estoy haciendo hashing en las rutas de URL por ahora.

Versión 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());
}

Versión 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);
}
djabraham
fuente
@mathiasrw Es posible que los caracteres Unicode superen los 8 bits en la memoria, por lo que supongo que 0xFF simplemente enmascara cualquier cosa fuera de ese rango. Ver más sobre charCodeAt () aquí: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
djabraham
Si ES6 está disponible (todos los motores modernos lo admiten), Math.imulpuede usarse para el paso de multiplicación, lo que mejora enormemente el rendimiento . El único problema es que no funcionará en IE11 sin una cuña .
bryc
6

Una rápida y concisa que fue adaptada desde aquí :

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

Necesitaba una función similar (pero diferente) para generar una identificación única basada en el nombre de usuario y la hora actual. Entonces:

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 

editar junio de 2015: para el nuevo código uso shortid: https://www.npmjs.com/package/shortid

jcollum
fuente
2
@ t0r0X bueno ahora uso un módulo llamado shortid: npmjs.com/package/shortid
jcollum
1
¿Cómo estás usando el nombre de usuario con shortid? Sólo parece generar los identificadores pero no veo cómo se está utilizando para generar un hash de una cadena
cyberwombat
1
Esta respuesta tiene 3 votos a favor. Por mi vida no puedo imaginar por qué. Nadie ha dicho nada ...: - /
jcollum
1
@jcollum es por eso que casi nunca respondo preguntas obsoletas ... golpear y correr pasa desapercibido. incluso después de arreglar la respuesta, nadie viene a equilibrarla.
bryc
5

Mi revestimiento rápido (muy largo) basado en el Multiply+Xormétodo de FNV :

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

SubtleCrypto.digest

No estoy utilizando un lenguaje del lado del servidor, así que no puedo hacerlo de esa manera.

¿Estás seguro de que no puedes hacerlo de esa manera ?

¿Olvidaste que estás usando Javascript, el lenguaje en constante evolución?

Tratar SubtleCrypto. Es compatible con las funciones hash SHA-1, SHA-128, SHA-256 y 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.")));
})();

Константин Ван
fuente
¿Cómo es esto diferente de la respuesta de Kaiido dos años antes que la tuya ?
Luc
@Luc No lo es, al parecer.
Константин Ван
3

Llego un poco tarde a la fiesta, pero puedes usar este módulo: crypto :

const crypto = require('crypto');

const SALT = '$ome$alt';

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

El resultado de esta función es siempre una 64cadena de caracteres; algo como esto:"aa54e7563b1964037849528e7ba068eb7767b1fab74a8d80fe300828b996714a"

Ariel Jiménez
fuente
2

He combinado las dos soluciones (usuarios esmiralha y lordvlad) para obtener una función que debería ser más rápida para los navegadores que admiten la función js reduce () y aún compatible con los navegadores antiguos:

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;
    }
};

Ejemplo:

my_string = 'xyz';
my_string.hashCode();
Franco
fuente
2

Si desea evitar colisiones, puede usar un hash seguro como SHA-256 . Hay varias implementaciones de JavaScript SHA-256.

Escribí pruebas para comparar varias implementaciones de hash, consulte https://github.com/brillout/test-javascript-hash-implementations .

O vaya a http://brillout.github.io/test-javascript-hash-implementations/ , para ejecutar las pruebas.

brillante
fuente
1
Usar un hash criptográfico seguro puede ser extremadamente lento. Evitar colisiones es un producto del ancho de bits, no de seguridad. El hash no criptográfico de 128 bits o incluso 64 bits debería ser más que suficiente para la mayoría de los propósitos. MurmurHash3_x86_128 es bastante rápido y tiene muy pocas posibilidades de colisiones.
bryc
2

Esto debería ser un hash un poco más seguro que algunas otras respuestas, pero en una función, sin ninguna fuente precargada

Básicamente, creé una versión simplificada simplificada de sha1.
Toma los bytes de la cadena y los agrupa por "palabras" de 4 a 32 bits.
Luego, ampliamos cada 8 palabras a 40 palabras (para un mayor impacto en el resultado).
Esto va a la función hash (la última reducción) donde hacemos algunos cálculos con el estado actual y la entrada. Siempre sacamos 4 palabras.
Esta es casi una versión de un comando / una línea usando map, reduce ... en lugar de bucles, pero sigue siendo bastante rápido

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("");
};

También convertimos la salida a hexadecimal para obtener una cadena en lugar de una matriz de palabras.
El uso es simple. para expandir "a string".hash()regresará"88a09e8f9cc6f8c71c4497fbb36f84cd"

Franartur Čech
fuente
1

Fui por una simple concatenación de códigos char convertidos en cadenas hexadecimales. Esto tiene un propósito relativamente limitado, es decir, solo necesita una representación hash de una cadena CORTA (por ejemplo, títulos, etiquetas) para intercambiar con un lado del servidor que por razones no relevantes no puede implementar fácilmente el puerto Java hashCode aceptado. Obviamente no hay aplicación de seguridad aquí.

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('');
}

Esto se puede hacer más conciso y tolerante al navegador con Underscore. Ejemplo:

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

Supongo que si quisieras cortar cadenas más grandes de manera similar, podrías reducir los códigos de caracteres y hexadecimal la suma resultante en lugar de concatenar los caracteres individuales:

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, existe un mayor riesgo de colisión con este método, aunque podría jugar con la aritmética en la reducción, sin embargo, quería diversificar y alargar el hash.

ausente
fuente
1

Versión ligeramente simplificada de la respuesta de @esmiralha.

No anulo String en esta versión, ya que eso podría provocar un comportamiento no deseado.

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

Agregando esto porque nadie lo hizo todavía, y parece que se ha pedido e implementado mucho con hashes, pero siempre se hizo muy mal ...

Esto toma una entrada de cadena y un número máximo que desea que iguale el hash, y produce un número único basado en la entrada de cadena.

Puede usar esto para producir un índice único en una matriz de imágenes (si desea devolver un avatar específico para un usuario, elegido al azar, pero también elegido en función de su nombre, por lo que siempre se asignará a alguien con ese nombre )

También puede usar esto, por supuesto, para devolver un índice en una matriz de colores, como para generar colores de fondo de avatar únicos basados ​​en el nombre de alguien.

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);
}
Nick Steele
fuente
-1

No veo ninguna razón para usar este código criptográfico demasiado complicado en lugar de soluciones listas para usar, como la biblioteca de hash de objetos, etc. Confiar en el proveedor es más productivo, ahorra tiempo y reduce los costos de mantenimiento.

Simplemente use https://github.com/puleos/object-hash

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

hash({foo: 'bar'}) // => '67b69634f9880a282c14a0f0cb7ba20cf5d677e9'
hash([1, 2, 2.718, 3.14159]) // => '136b9b88375971dff9f1af09d7356e3e04281951'
Oleg Abrazhaev
fuente
El código fuente de esa biblioteca ni siquiera es legible ... solo 50k de código minificado.
bryc
1
@bryc así es como se supone que debe verse el código del proveedor :) y para ver las fuentes, puede consultar github.com/puleos/object-hash/blob/master/index.js
Oleg Abrazhaev
¿El código minimizado es de 35.4 KB mientras que la fuente completa es de 14.2 KB? Eso no tiene sentido.
bryc
2
@bryc, ¿has considerado esta línea? var crypto = require('crypto');. Creo que agrega este código de dependencia del proveedor en la versión minimizada durante una compilación.
Oleg Abrazhaev
Si realmente necesita hash Objetos, escribí any-serialize para serializar CUALQUIER objeto con claves de clasificación, luego cyrb53 para generar hash base36.
Polv