Генерация хэша из строки в Javascript

433

Мне нужно преобразовать строки в какую-либо форму хэша. Возможно ли это в JavaScript?

Я не использую серверный язык, поэтому я не могу этого сделать.

  • 2
    Вы хотите посмотреть что-то вроде Javascript MD5 .
  • 5
    MD5 небезопасен, так что не ищите его.
Показать ещё 9 комментариев
Теги:
hash

18 ответов

668
Лучший ответ
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;
};

Источник: http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/

  • 19
    Это тот же, который используется в Java. hash << 5 - hash такой же, как hash * 31 + char но НАМНОГО быстрее. Это приятно, потому что это так быстро, а 31 - это простое число. Выиграй, выиграй там.
  • 1
    Очень легкий, чтобы добавить на странице тоже. Мне это нравится. Просто интересно, какое сопротивление это имеет к обращению хэшей? например, последовательности чисел иногда приводят к предсказуемым хэшам.
Показать ещё 35 комментариев
102

ИЗМЕНИТЬ

на основе моих тестов jsperf, принятый ответ на самом деле быстрее: http://jsperf.com/hashcodelordvlad

ОРИГИНАЛ

если кому-то интересно, вот улучшенная (более быстрая) версия, которая не будет работать в старых браузерах, у которых отсутствует функция массива reduce.

hashCode = function(s){
  return s.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);              
}
  • 0
    Есть ли способ получить хеш, который является только положительным числом?
  • 0
    @ lordvlad Могу ли я использовать это в проекте с открытым исходным кодом? Я думаю, что скопировал его раньше, так как он использует return a | = 0, а не return a & a.
Показать ещё 15 комментариев
78

Примечание: Даже с лучшим 32-битным хешем вам придется иметь дело с фактом что столкновения произойдут рано или поздно. То есть две разные строки ввода вернет одно и то же значение хэша с вероятностью не менее 1: 2 ^ 32.

В ответе на этот вопрос Какой алгоритм хеширования лучше всего подходит для уникальности и скорости?, Ян Бойд опубликовал хороший углубленный анализ. Короче говоря (как я это интерпретирую), он приходит к выводу, что Мурмур лучше, а затем FNV-1a.
Javas String.hashCode(), предложенный esmiralha, кажется, является вариантом DJB2.

  • FNV-1a имеет лучшее распределение, чем DJB2, но медленнее
  • DJB2 быстрее, чем FNV-1a, но имеет тенденцию давать больше столкновений
  • MurmurHash3 лучше и быстрее, чем DJB2 и FNV-1a (но оптимизированная реализация требует большего количества строк кода, чем FNV и DJB2)

Некоторые тесты с большими входными строками здесь: http://jsperf.com/32-bit-hash
Когда короткие входные строки хэшируются, производительность ропота падает по сравнению с DJ2B и FNV-1a: http://jsperf.com/32-bit-hash/3

Так что в общем я бы рекомендовал murmur3.
См. Здесь для реализации JavaScript: https://github.com/garycourt/murmurhash-js

Если строки ввода коротки, а производительность важнее качества распределения, используйте DJB2 (как предложено принятым ответом esmiralha).

Если качество и малый размер кода важнее скорости, я использую эту реализацию FNV-1a (на основе этого кода).

/**
 * 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;
}
  • 0
    Почему вы это делаете ("0000000" + (hval >>> 0).toString(16)).substr(-8); ? Разве это не то же самое, что (hval >>> 0).toString(16) ?
  • 1
    это добавляет начальные 0, чтобы результирующий хеш всегда имел длину 8 символов. Легче читать и распознавать в выводах, но это мое личное мнение
Показать ещё 5 комментариев
38

На основании принятого ответа в ES6. Меньше, поддерживается и работает в современных браузерах.

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

// Test
console.log("hashCode(\"Hello!\"): ", hashCode('Hello!'));
  • 0
    Спасибо, что поделились, я добавил str += "" перед хэшированием, чтобы избежать исключения. str.split is not a function когда в качестве параметров передаются не-строки
  • 2
    Но намного, намного медленнее, чем любой из них: https://jsperf.com/hashing-strings
Показать ещё 8 комментариев
25

Если это кому-то помогает, я объединил два верхних ответа в версии с более старым браузером, которая использует быструю версию, если reduce доступна и возвращается к решению esmiralha, если это не так.

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

Использование:

var hash = new String("some string to be hashed").hashCode();
  • 0
    Как оптимизировать этот код, чтобы он работал быстрее в каждом браузере. 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; }
17

Это усовершенствованный и более эффективный вариант:

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

Это соответствует реализации Java стандарта object.hashCode()

Вот и тот, который возвращает только положительные хэш-коды:

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

И вот подходящий для Java, который возвращает только положительные хэш-коды:

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

Наслаждайтесь!

  • 1
    отличный ответ, но какова цель << 0?
  • 6
    @koolaang, это левый оператор дерьма, developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Показать ещё 7 комментариев
14

Я немного удивлен, что никто не говорил о новом API SubtleCrypto.

Чтобы получить хэш из строки, вы можете использовать метод 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);
  });
  • 3
    Согласен. Преобразование в hex может быть сделано немного по-другому ... 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('')); });
  • 0
    Криптографическая хеш-функция для строк немного излишня .. crypto не совсем эффективна.
6

Благодаря примеру mar10, я нашел способ получить те же результаты в С# и Javascript для FNV-1a. Если присутствуют символы Unicode, верхняя часть отбрасывается ради производительности. Не знаю, почему было бы полезно поддерживать их при хешировании, так как теперь я только хэширую URL-адреса.

Версия С#

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

Версия 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);
}
  • 0
    Есть ли какая-то причина, чтобы добавить & 0xFF в версию JS?
  • 0
    @mathiasrw Возможно, символы Юникода превышают 8 бит в памяти, поэтому я предполагаю, что 0xFF просто маскирует все, что находится за пределами этого диапазона. Подробнее о charCodeAt () читайте здесь: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Показать ещё 1 комментарий
5

Здесь простой, хорошо распределенный 53-битный хеш. Он довольно быстрый и имеет значительно более низкую частоту столкновений по сравнению с 32-битным хешем.

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

Он использует методы, похожие на xxHash/MurmurHash, но не так тщательно. Достигается лавина (не строгая), поэтому небольшие изменения на входе имеют большие изменения на выходе, что делает его случайным:

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

Вы также можете предоставить начальное число для альтернативных потоков с одним и тем же входом:

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

Технически это 64-битный хеш, но JavaScript ограничен 53-битными целыми числами. Полные 64 бита все еще можно использовать, изменив строку возврата для шестнадцатеричной строки или массива:

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

Суть в том, что построение шестнадцатеричной строки становится узким местом в производительности, и массиву нужны два оператора сравнения вместо одного, что не так удобно.


И просто для удовольствия, вот 32-битный хеш в oneliner, который все еще превосходит FNV/DJB2/SMDB:

ash=s=>{for(var i=0,h=1;i<s.length;)h=Math.imul(h^s.charCodeAt(i++),951274213);return(h^h>>>9)>>>0}
  • 2
    Вау, это намного лучше, чем обычный * 31 для коротких (или аналогичных) входов. :)
  • 0
    Где ch инициализируется?
Показать ещё 4 комментария
4

Мне нужна была подобная функция (но другая) для создания уникального идентификатора на основе имени пользователя и текущего времени. Итак:

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

Производит:

2DVFXJGEKL
6IZPAKFQFL
ORGOENVMG
... etc 

edit Jun 2015: для нового кода я использую shortid: https://www.npmjs.com/package/shortid

  • 2
    @ t0r0X хорошо, теперь я использую модуль под названием shorttid: npmjs.com/package/shortid
  • 0
    Как вы используете имя пользователя с Shorttid? Кажется, он просто генерирует идентификаторы, но я не вижу, как вы используете его для генерации хеша из строки.
Показать ещё 2 комментария
3

Мой быстрый (очень длинный) один вкладыш на основе метода FNV Multiply+Xor:

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

Быстрая и краткая, которая была адаптирована отсюда:

String.prototype.hashCode = function() {
  var hash = 5381, i = this.length
  while(i)
    hash = (hash * 33) ^ this.charCodeAt(--i)
  return hash >>> 0;
}
  • 0
    Любое обновление по этому ответу на ваш вопрос здесь: superuser.com/questions/1217499/… ...
  • 0
    @ McDonald's еще не нашел хорошего решения
2

Если вы хотите избежать столкновений, вы можете использовать безопасный хэш, например SHA-256. Существует несколько реализаций JavaScript SHA-256.

Я написал тесты для сравнения нескольких реализаций хеширования, см. https://github.com/brillout/test-javascript-hash-implementations.

Или зайдите в http://brillout.github.io/test-javascript-hash-implementations/, чтобы запустить тесты.

  • 1
    Использование безопасного криптографического хэша может быть чрезвычайно медленным. Предотвращение столкновений является результатом ширины бита, а не безопасности. 128-битный некриптографический хэш или даже 64 бита должно быть более чем достаточно для большинства целей. MurmurHash3_x86_128 довольно быстрый и имеет очень низкий шанс столкновения.
2

Я объединил два решения (пользователи esmiralha и lordvlad), чтобы получить функцию, которая должна быть быстрее для браузеров, поддерживающих js-функцию уменьшить() и по-прежнему совместимую со старыми браузерами:

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

Пример:

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

С помощью этого решения мы можем указать набор символов, чтобы избежать некоторых проблем, когда значения сохраняются или передаются между уровнями приложений, например: Когда результирующая строка (хеш) производит процентное кодирование и эта строка отправляется контроллеру с использованием метода GET из уровня представления.

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

}
1

Немного упрощенная версия ответа @esmiralha.

Я не переопределяю String в этой версии, так как это может привести к некоторому нежелательному поведению.

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

Я пошел для простой конкатенации кодов char, преобразованных в шестнадцатеричные строки. Это относится к относительно узкой цели, а именно простому хэш-представлению короткой строки (например, заголовков, тегов), которая должна быть обменена с серверной стороной, которая по не соответствующим причинам не может легко реализовать принятый порт Java hashCode. Очевидно, что здесь нет приложения безопасности.

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

Это можно сделать более кратким и устойчивым к браузерам с помощью Underscore. Пример:

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

Я предполагаю, что если бы вы хотели использовать более строгие строки с таким же образом, вы могли бы просто уменьшить коды char и исправить итоговую сумму, а не объединить отдельные символы вместе:

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"

Естественно больше риск столкновения с этим методом, хотя вы могли бы играть с арифметикой в ​​сокращении, однако вы хотели разнообразить и удлинить хэш.

0

Я немного опаздываю на вечеринку, но вы можете использовать этот модуль: crypto:

const crypto = require('crypto');

const SALT = '$ome$alt';

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

Результат этой функции всегда составляет 64 символа; что-то вроде этого: "aa54e7563b1964037849528e7ba068eb7767b1fab74a8d80fe300828b996714a"

Ещё вопросы

Сообщество Overcoder
Наверх
Меню