web-dev-qa-db-fra.com

Générer un hachage à partir d'une chaîne en Javascript

J'ai besoin de convertir des chaînes en une forme de hachage. Est-ce possible en JavaScript?

Je n'utilise pas de langage côté serveur, je ne peux donc pas le faire de cette façon.

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

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

658
esmiralha

MODIFIER

sur la base de mes tests jsperf, la réponse acceptée est en réalité plus rapide: http://jsperf.com/hashcodelordvlad

ORIGINAL

si quelqu'un est intéressé, voici une version améliorée (plus rapide) qui échouera sur les navigateurs plus anciens dépourvus de la fonction de tableau reduce.

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

Note: Même avec le meilleur hachage 32 bits, vous devrez faire face au fait Que les collisions vont se produisent tôt ou tard. C'est à dire. Deux chaînes d'entrée différentes renverront la même valeur de hachage avec une probabilité d'au moins 1: 2 ^ 32.

En réponse à cette question Quel algorithme de hachage est le meilleur pour l'unicité et la vitesse? , Ian Boyd a publié une bonne analyse en profondeur .. En bref (selon mon interprétation), il arrive à la conclusion que Murmur est le meilleur choix, suivi de FNV-1a.
L'algorithme String.hashCode () de Java proposé par esmiralha semble être une variante de DJB2.

  • FNV-1a a une meilleure distribution que DJB2, mais est plus lente 
  • DJB2 est plus rapide que FNV-1a, mais a tendance à donner plus de collisions
  • MurmurHash3 est meilleur et plus rapide que DJB2 et FNV-1a (mais l'implémentation optimisée nécessite plus de lignes de code que FNV et DJB2)

Quelques repères avec de grandes chaînes d’entrée ici: http://jsperf.com/32-bit-hash
Lorsque courtes chaînes d'entrée sont hachées, les performances du murmure baissent par rapport à DJ2B et FNV-1a: http://jsperf.com/32-bit-hash/3

Donc en général je recommanderais murmur3.
Voir ici pour une implémentation de JavaScript: https://github.com/garycourt/murmurhash-js

Si les chaînes d'entrée sont courtes et que les performances sont plus importantes que la qualité de la distribution, utilisez DJB2 (comme le propose la réponse acceptée par esmiralha).

Si la qualité et la petite taille du code sont plus importantes que la rapidité, j'utilise cette implémentation de FNV-1a (basé sur ce code ).

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

Basé sur réponse acceptée dans ES6. Plus petit, maintenable et fonctionne dans les navigateurs modernes.

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

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

42
deekshith

Si cela peut aider quelqu'un, j'ai combiné les deux réponses principales dans une version plus ancienne tolérante pour le navigateur, qui utilise la version rapide si reduce est disponible et utilise la solution esmiralha si ce n'est pas le cas.

/**
 * @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'utilisation est comme:

var hash = new String("some string to be hashed").hashCode();
24
Kyle Falconer

C'est une variante raffinée et plus performante: 

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

Ceci correspond à l'implémentation Java de la object.hashCode() standard

Voici également celui qui renvoie uniquement les codes de hachage positifs: 

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

Et voici une correspondance pour Java qui ne retourne que des hashcodes positifs: 

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

Prendre plaisir!

18
momomo

Je suis un peu surpris que personne n'ait encore parlé de la nouvelle - API SubtleCrypto .

Pour obtenir un hachage d'une chaîne, vous pouvez utiliser la méthode subtle.digest

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

14
Kaiido

Grâce à l'exemple de mar10, j'ai trouvé un moyen d'obtenir les mêmes résultats en C # ET Javascript pour un FNV-1a. Si des caractères unicode sont présents, la partie supérieure est supprimée pour des raisons de performance. Je ne sais pas pourquoi il serait utile de maintenir ceux-ci lors du hachage, car je ne hachais que les chemins des URL pour l'instant.

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

Version 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
djabraham

Voici un hachage 53 bits simple et bien distribué. Il est assez rapide et a un taux de collision nettement inférieur à celui d'un hachage 32 bits.

var cyrb53 = function(str, seed = 0) {
    var h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
    for (var 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);
};

Il utilise des techniques similaires à xxHash/MurmurHash3, mais pas aussi complètes. Il provoque une avalanche (non stricte), de sorte que de petits changements dans l'entrée ont de grands changements dans la sortie, ce qui le fait apparaître de manière aléatoire:

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

Vous pouvez également fournir une graine pour d'autres flux de la même entrée:

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

Techniquement, c'est un hachage 64 bits, mais JavaScript est limité aux entiers 53 bits. Les 64 bits complets peuvent toujours être utilisés en modifiant la ligne de retour pour une chaîne ou un tableau hexadécimal:

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

Le problème est que la construction de la chaîne hexadécimale devient le goulot d'étranglement des performances et que le tableau a besoin de deux opérateurs de comparaison au lieu d'un, ce qui n'est pas aussi pratique. Alors gardez cela à l'esprit lorsque vous l'utilisez pour des applications hautes performances.


Et juste pour le plaisir, voici un hachage minimal de 32 bits en 89 caractères qui bat encore FNV/DJB2/SMDB:

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

Mon rapide (très long) one liner basé sur la méthode Multiply+Xor de FNV:

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

Un rapide et concis qui a été adapté de ici :

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

J'avais besoin d'une fonction similaire (mais différente) pour générer un identifiant unique basé sur le nom d'utilisateur et l'heure actuelle. Alors: 

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

Produit: 

2DVFXJGEKL
6IZPAKFQFL
ORGOENVMG
... etc 

edit Jun 2015: Pour le nouveau code, j'utilise shortid: https://www.npmjs.com/package/shortid

4
jcollum

Si vous voulez éviter les collisions, vous pouvez utiliser un secure hash like SHA-256 . Il existe plusieurs implémentations de JavaScript SHA-256.

J'ai écrit des tests pour comparer plusieurs implémentations de hachage, voir https://github.com/brillout/test-javascript-hash-implementations .

Ou allez à http://brillout.github.io/test-javascript-hash-implementations/ , pour exécuter les tests.

2
brillout

J'ai combiné les deux solutions (esmiralha et lordvlad) pour obtenir une fonction plus rapide pour les navigateurs prenant en charge la fonction js reduction () et toujours compatible avec les anciens navigateurs:

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

Exemple: 

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

Version légèrement simplifiée de la réponse de @ esmiralha.

Je ne remplace pas String dans cette version, car cela pourrait entraîner un comportement indésirable.

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

SubtleCrypto.digest

Je n’utilise pas de langage côté serveur, je ne peux donc pas le faire de cette façon.

Etes-vous sûr de ne pas pouvoir le faire de cette façon ?

Avez-vous oublié que vous utilisez Javascript, la langue en constante évolution?

Essayez SubtleCrypto . Il prend en charge les fonctions de hachage SHA-1, SHA-128, SHA-256 et 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.")));
})();

Je suis allé pour une concaténation simple de codes de caractères convertis en chaînes hexadécimales. Cela sert un objectif relativement étroit, à savoir qu’il suffit d’avoir une représentation en hachage d’une chaîne SHORT (par exemple des titres, des balises) à échanger avec un côté serveur qui, pour des raisons non pertinentes, ne peut pas implémenter facilement le port Java hashCode accepté. Évidemment aucune application de sécurité ici.

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

Cela peut être rendu plus concis et tolérant le navigateur avec Underscore. Exemple:

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

Je suppose que si vous vouliez hacher des chaînes plus grandes de la même manière, vous pourriez simplement réduire les codes de caractères et hexifier la somme résultante plutôt que de concaténer les caractères individuels ensemble:

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"

Naturellement, plus de risques de collision avec cette méthode, bien que vous puissiez jouer avec l'arithmétique de la réduction, mais vous vouliez diversifier et allonger le hachage.

1
swornabsent

En ajoutant cela parce que personne ne l’a encore fait, et cela semble être demandé et mis en œuvre souvent avec des hachages, mais cela se fait toujours très mal….

Cela prend une entrée de chaîne et un nombre maximal que vous souhaitez que le hachage soit égal, et produit un nombre unique basé sur l'entrée de chaîne.

Vous pouvez l'utiliser pour créer un index unique dans un tableau d'images (si vous souhaitez renvoyer un avatar spécifique pour un utilisateur, choisi au hasard, mais également en fonction de son nom, il sera donc toujours attribué à une personne portant ce nom. ).

Vous pouvez également utiliser cela, bien sûr, pour retourner un index dans un tableau de couleurs, par exemple pour générer des couleurs d'arrière-plan uniques pour un avatar basées sur le nom d'une personne.

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
Nick Steele

Je suis un peu en retard à la fête, mais vous pouvez utiliser ce module: crypto

const crypto = require('crypto');

const SALT = '$ome$alt';

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

Le résultat de cette fonction est toujours 64 chaîne de caractères; quelque chose comme ceci: "aa54e7563b1964037849528e7ba068eb7767b1fab74a8d80fe300828b996714a" 

0
Frismaury