Generar un Hash a partir de una cadena en Javascript


Necesito convertir cadenas a algún tipo de hash. Es esto posible en JavaScript?

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

Author: John, 2011-10-01

18 answers

String.prototype.hashCode = function() {
  var hash = 0, i, chr;
  if (this.length === 0) return hash;
  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/

 600
Author: esmiralha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-11 18:23:21

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 navegadores antiguos que carecen de la función de matriz reduce.

hashCode = function(s){
  return s.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);              
}
 102
Author: lordvlad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-14 08:58:15

Nota: Incluso con el mejor hash de 32 bits, tendrá que lidiar con el hecho que las colisiones ocurrirán tarde o temprano. Es decir, dos cadenas de entrada diferentes devolverá el mismo valor hash con una probabilidad de al menos 1 : 2^32.

En una respuesta a esta pregunta ¿Qué algoritmo de hash es el mejor para la singularidad y la velocidad?, Ian Boyd publicó un buen análisis en profundidad. En resumen (como yo lo interpreto), llega a la conclusión de que Murmur es mejor, seguido de FNV-1a.
Cadena de Java.El algoritmo hashCode() propuesto por esmiralha 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 las cadenas de entrada cortas son hash, el rendimiento de murmur disminuye, en relación con DJ2B y FNV-1a: http://jsperf.com/32-bit-hash/3

Así que 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 la distribución, utilice DJB2 (como propone la respuesta de esmiralha).

Si la calidad y el tamaño pequeño del código 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;
}
 68
Author: mar10,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-12 07:31:24

Basado en 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!'));
 35
Author: deekshith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-01 04:33:10

Si ayuda a alguien, combiné las dos respuestas principales en una versión más antigua tolerante al navegador, que utiliza la versión rápida si reduce está disponible y vuelve a la solución de esmiralha si no lo está.

/**
 * @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 = new String("some string to be hashed").hashCode();
 24
Author: Kyle Falconer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-15 02:17:57

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 hashcodes positivos:

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

Y aquí hay una coincidencia para Java que solo devuelve hashcodes positivos:

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

Disfrute!

 17
Author: momo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-05 08:25:51

Estoy un poco sorprendido de que nadie haya hablado de la nueva API de SubtleCrypto todavía.

Para obtener un hash de una cadena, puede utilizar el subtle.digest mé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);
  });
 11
Author: Kaiido,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-16 08:42:41

Gracias al ejemplo de mar10, encontré una manera de obtener los mismos resultados en C# Y Javascript para un FNV-1a. Si los caracteres unicode están presentes, la parte superior se descarta por el bien del rendimiento. No sé por qué sería útil mantenerlas al hashear, ya que solo estoy hasheando rutas de url por ahora.

Versión en 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 de 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);
}
 6
Author: djabraham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-04 18:58:37

Necesitaba una función similar (pero diferente) para generar un ID único basado 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 Jun 2015: Para el nuevo código uso shortid: https://www.npmjs.com/package/shortid

 4
Author: jcollum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-23 17:32:08

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

String.prototype.hashCode = function() {
  var hash = 5381, i = this.length
  while(i)
    hash = (hash * 33) ^ this.charCodeAt(--i)
  return hash >>> 0;
}
 3
Author: soulmachine,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-08 19:25:33

Mi rápido (muy largo) un trazador de líneas basado en FNV Multiply+Xor método:

my_string.split('').map(v=>v.charCodeAt(0)).reduce((a,v)=>a+((a<<7)+(a<<3))^v).toString(16);
 3
Author: John Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-03 10:02:47

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 hash, ver https://github.com/brillout/test-javascript-hash-implementations .

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

 2
Author: brillout,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-22 19:26:43

He combinado las dos soluciones (los usuarios esmiralha y lordvlad) para obtener una función que debería ser más rápida para los navegadores que soportan la función js reduce () y aún compatible con 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();
 1
Author: Frank,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-29 11:07:24

Opté por una simple concatenación de códigos char convertidos a cadenas hexadecimales. Esto sirve para un propósito relativamente limitado, es decir, solo necesita una representación hash de una cadena CORTA (por ejemplo, títulos, etiquetas) para intercambiarse 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 el guion bajo. Ejemplo:

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

Supongo que si quisieras para hash cadenas más grandes de manera similar, simplemente podría reducir los códigos char y hexificar la suma resultante en lugar de concatenar los caracteres individuales juntos:

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

 1
Author: swornabsent,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-06 14:16:02

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

No sobreescribo String en esta versión, ya que podría resultar en algún 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;
}
 1
Author: crazy2be,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-17 23:01:00

Aquí hay un hash simple y bien distribuido de 53 bits. Es bastante rápido y tiene bajas tasas de colisión.

var cyrb53 = function(str) {
    var p1 = 2654435761, p2 = 1597334677, h1 = 0xdeadbeef | 0, h2 = 0x41c6ce57 | 0;
    for (var i = 0; i < str.length; i++)
        ch = str.charCodeAt(i), h1 = Math.imul(h1 + ch, p1), h2 = Math.imul(h2 + ch, p2);
    h1 = Math.imul(h1 ^ h1 >>> 16, p2), h2 = Math.imul(h2 ^ h2 >>> 15, p1);
    return (h2 & 2097151) * 4294967296 + h1;
};

Logra avalancha (no estricta), por lo que los cambios pequeños tienen grandes cambios en la salida, por lo que parece aleatorio:

0xe00c44e568f86 = "a"
0x893099dbedf04 = "b"
0x98f3f59367810 = "revenge"
0x45f880d099bbf = "revenue"

53 bits es el límite de enteros JS, y tiene significativamente menos probabilidad de colisión que los hashes de 32 bits. Pero si 53 bits no es suficiente para usted, todavía puede utilizar todos los 64 bits mediante la construcción de una cadena hexadecimal o matriz:

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

La captura es, la construcción de la la cadena hexadecimal se convierte en el cuello de botella en el rendimiento, y la matriz necesita dos operadores de comparación en lugar de uno, lo cual no es tan conveniente.

 1
Author: bryc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-10-03 23:12:09

Con esta solución podemos especificar el conjunto de caracteres para evitar algunos problemas cuando los valores se almacenan o envían entre capas de aplicaciones, por ejemplo: Cuando la cadena de resultado (hash) produce codificación porcentual y esa cadena se envía al controlador utilizando el método GET de la capa de vista.

function doHashCode() {
    String.prototype.hashCode = function () {
        var text = "";
        var possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

        for (var i = 0; i < 15; i++)
            text += possible.charAt(Math.floor(Math.random() * possible.length));
        return text;
    }

    var hash = new String().hashCode();
    $('#input-text-hash').val(hash); // your html input text

}
 0
Author: Cassio Seffrin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-17 18:03:45

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

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 siempre es 64 cadena de caracteres; algo así: "aa54e7563b1964037849528e7ba068eb7767b1fab74a8d80fe300828b996714a"

 0
Author: Frismaury,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-23 16:50:26