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

589

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

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

Freesnöw
источник
7
MD5 небезопасен, поэтому не ищите его.
Генрикстроем
166
@henrikstroem Зависит от того, что вы хэшируете; нет ничего плохого в использовании md5 для создания хэша в целях безопасности.
Брэд Кох
7
@BradKoch Зависит от того, что вы делаете; нет ничего плохого в использовании md5 в целях безопасности. Конечно, есть лучшие методы для хеширования паролей, но md5 отлично подходит для таких вещей, как подпись URL.
Пол Ферретт
81
Я нахожу забавным то, что в то время как MD5 подвергается критике в комментариях здесь, почти все ответы рекомендуют гораздо худшие алгоритмы хеширования и получают много голосов.
Домен
38
Использование MD5 для проверки того, что загрузка не была повреждена, не означает, что ваши пароли будут отправлены по электронной почте вашим паролям.
Джеймс М. Лей

Ответы:

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

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

esmiralha
источник
22
Это тот же, который используется в Java. То hash << 5 - hashже самое, hash * 31 + charно намного быстрее. Это приятно, потому что это так быстро, а 31 - это простое число. Выиграй, выиграй там.
CorsiKa
41
Я провел несколько тестов на jsperf ( jsperf.com/hashing-strings ), и побитовая функция на самом деле медленнее, чем числовая функция.
скерит
17
@PeterAronZentai Почему это «непригодно»? Вывод, полученный с помощью кода на основе чисел, (hash * 31) + charидентичен выводу, сгенерированному кодом на основе сдвига ((hash<<5)-hash)+char, даже для очень длинных строк (я тестировал его со строками, содержащими более миллиона символов), поэтому он не является «непригодным» в терминах точности. Сложность составляет O (n) для версий на основе числа и на основе сдвига, поэтому она не является «непригодной» с точки зрения сложности.
TachyonVortex
13
Кто-нибудь может прокомментировать уникальность (или нет) вывода? В частности, если я использую этот хэш только для строк с длиной меньше n, что является самым большим, nдля которого я не могу иметь коллизию?
Дон МакКарди
34
Есть ли какая-то причина, по которой это должно (или должно) быть в прототипе String? Будет ли какой-нибудь менее эффективным / действенным просто иметь, например; var hashCode = function hashCode (str) {etc...}? А потом использовать как hashCode("mystring")?
rattray
146

РЕДАКТИРОВАТЬ

основываясь на моих тестах 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);              
}

версия со стрелкой в ​​одну строку:

hashCode = s => s.split('').reduce((a,b)=>{a=((a<<5)-a)+b.charCodeAt(0);return a&a},0)
lordvlad
источник
3
Есть ли способ получить хеш, который является только положительным числом?
Просто Trader
46
странно. Я только что проверил это, и оказалось, что это будет медленнее, чем принятый ответ. jsperf.com/hashcodelordvlad
lordvlad
113
Хороший парень @lordvlad, на самом деле проверяет свой ответ, а затем сообщает, когда он был медленнее.
микемакана
9
Я только что понял: вполне понятно, что принятый ответ быстрее, потому что моя версия должна сначала превратить строку в массив, выделить новую память и скопировать каждый символ ...
lordvlad
5
[] .reduce.call (str, (p, c, i, a) => (p << 5) - p + a.charCodeAt (i), 0);
Диззи
108

Примечание: Даже с лучшим 32-битным кешем, столкновение будет рано или поздно произойдет.

Вероятность коллизии хеша может быть рассчитана как 1 - е ^ (-к (к-1) / 2Н, приблизительно, как к ^ 2 / 2Н ( см. Здесь ). Это может быть выше, чем предполагает интуиция:
если принять 32-битный хэш и k = 10000 элементов, произойдет столкновение с вероятностью 1,2%. Для 77 163 выборок вероятность становится 50%! ( калькулятор ).
Я предлагаю обходной путь внизу.

В ответ на этот вопрос Какой алгоритм хеширования лучше всего подходит для уникальности и скорости? Ян Бойд опубликовал хороший углубленный анализ . Короче говоря (насколько я понимаю), он приходит к выводу, что Murmur лучше, а затем FNV-1a.
Алгоритм Java String.hashCode (), предложенный Эсмиралхой, кажется вариантом 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- битовой хэш / 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;
}

Улучшить вероятность столкновения

Как объяснено здесь , мы можем расширить размер хеш-бита, используя этот трюк:

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

Используйте это с осторожностью и не ожидайте слишком многого все же.

Mar10
источник
Почему ты это делаешь ("0000000" + (hval >>> 0).toString(16)).substr(-8);? Разве это не то же самое, что и (hval >>> 0).toString(16)?
Мануэль Морер
3
это добавляет начальные 0, чтобы результирующий хеш всегда имел длину 8 символов. Легче читать и распознавать в выходных данных, но это мое личное мнение
марта
Ах, хорошо, я понял. Для маленьких hval, (hval >>> 0).toString(16)может быть менее 8 символов, поэтому вы дополняете его нулями. Я был просто сбит с толку, потому что (hval >>> 0).toString(16)всегда приводил к строке из 8 символов.
Мануэль Морер
3
Мне нравится этот ответ, потому что он создает намного лучше распределенный хеш: другие функции, предлагаемые здесь, будут иметь последующие значения хеша. Например, hash ("example1") - hash ("example2") == 1 ", тогда как этот гораздо более непредсказуем.
GavinoGrifoni
1
В ответ на «FNV-1a имеет лучшее распределение, чем DJB2, но медленнее» - я думаю, следует сказать, что FNV1a может быть очень быстрым при реализации с использованием Math.imulфункции ES6 . Это само по себе делает его высшим ориентиром и, в конечном счете, лучшим выбором, чем DJB2 в долгосрочной перспективе.
Bryc
64

На основании принятого ответа в 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!'));

РЕДАКТИРОВАТЬ (2019-11-04) :

версия со стрелкой в ​​одну строку:

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

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

deekshith
источник
1
Спасибо за совместное использование, которое я добавил str += ""перед хэшированием, чтобы избежать исключения, str.split is not a function
возникающего при передаче
4
Но намного, намного медленнее, чем любой из них: https://jsperf.com/hashing-strings
AndyO
Я также только что заметил, что самое быстрое «ретро» решение на самом деле меньше, если вы уберете переводы строки так, что оно будет всего 3 строки.
AndyO
2
Есть ли способ, чтобы это дало только положительные, но все же уникальные результаты?
ПВН
3
@deekshith Принятый ответ используется hash |= 0для преобразования в 32-битное целое число. Эта реализация не делает. Это ошибка?
Sukima
48

Почти половина ответов - реализация Java String.hashCode, которая не является ни качественной, ни супер быстрой. Ничего особенного, просто умножается на 31 для каждого персонажа. Он может быть реализован просто и эффективно в одну строку и намного быстрее с Math.imul:

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

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

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

Подобно хорошо известным алгоритмам MurmurHash / xxHash, он использует комбинацию умножения и Xorshift для генерации хэша, но не так тщательно. В результате это быстрее, чем в JavaScript, и значительно проще в реализации.

Достигается лавина (не строгая), что в основном означает, что небольшие изменения во входных данных имеют большие изменения в выходных данных, в результате чего результирующий хэш выглядит случайным:

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-битный хэш (два некоррелированных 32-битных хэша параллельно), но 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-битный хэш в 89 символов с более высоким качеством, чем даже FNV или 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
Вау, это намного лучше, чем обычный * 31 для коротких (или похожих) входов. :)
Lapo
2
Где chинициализируется?
hellowill89
3
@ hellowill89 woops, я забыл объявить это и истекал кровью в глобальном масштабе. исправлено сейчас, спасибо: ')
Bryc
Ошибка для IE 11: объект не поддерживает свойство или метод 'imul'.
БахТ
2
@BachT Вы можете использовать полифилл или полную прокладку ES6 . Но IE11 трагически замерз в 2009 году без обновлений.
bryc
28

Если это кому-нибудь поможет, я объединил два верхних ответа в более старую версию, устойчивую к браузеру, которая использует быструю версию, если 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 = "some string to be hashed".hashCode();
Кайл Фалконер
источник
Как оптимизировать этот код, чтобы он работал быстрее в каждом браузере. 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; }
Мусакхир Сайед
26

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

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

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

ммм
источник
2
отличный ответ, но какова цель << 0?
koolaang
8
@koolaang, это левый оператор дерьма, developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
ммм
29
@momomo Вы имели в виду левый сдвиг ?
WDH
2
@ momomo Я думаю, он спрашивал, почему это был нулевой сдвиг влево.
jpfx1342
3
@Maykonn (2 ^ 32 - 1)
Ниджрадж Гелани
24

Я немного удивлен, что никто не говорил о новом 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);
  });

Kaiido
источник
4
Согласен. Преобразование в гекс может быть сделано немного по-другому ...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('')); });
Денис Гиффелер
3
Криптографическая хеш-функция для строк немного излишня .. cryptoне совсем производительная.
bryc
Надежное качество произвольно, без необходимости полагаться на людей, выполняющих тесты, встроенных (не требует специальной реализации), seedable, и мне понадобилось всего несколько сотен чисел для создания игровой карты, это казалось идеальным. Но оказывается, что нет абсолютно никакого способа сделать это синхронно. Необходимость предоставления некоторого асинхронного обратного вызова каждый раз, когда вы вызываете свой отобранный случайный механизм, делает код супер нечитаемым и выглядит нелепо. Я не понимаю, кто придумал этот дерьмовый интерфейс crypto.subtle, поэтому мне в конце концов пришлось воспользоваться xmur3 + sfc32 из этого ответа: stackoverflow.com/a/47593316/1201863
Люк
7

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

Версия 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());
}

Версия 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
источник
@mathiasrw Возможно, символы Юникода превышают 8 бит в памяти, поэтому я предполагаю, что 0xFF просто маскирует все, что находится за пределами этого диапазона. Подробнее о charCodeAt () читайте здесь: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
djabraham,
Если ES6 доступен (все современные движки поддерживают его), Math.imulможно использовать для этапа умножения, что значительно повышает производительность . Единственная проблема, это не будет работать в IE11 без прокладки .
Брич
6

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

String.prototype.hashCode = function() {
  var hash = 5381, i = this.length
  while(i)
    hash = (hash * 33) ^ this.charCodeAt(--i)
  return hash >>> 0;
}
soulmachine
источник
5

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

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 

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

jcollum
источник
2
@ t0r0X хорошо, теперь я использую модуль под названием shorttid: npmjs.com/package/shortid
jcollum
1
Как вы используете имя пользователя с Shorttid? Кажется, он просто генерирует идентификаторы, но я не понимаю, как вы используете его для генерации хеша из строки
cyberwombat,
1
Этот ответ имеет 3 отрицательных голосов. Для жизни я не могу себе представить, почему. Никто ничего не сказал ...: - /
jcollum
1
@jcollum, поэтому я почти никогда не отвечаю на устаревшие вопросы. Хиты и пробежки остаются незамеченными. даже после того, как вы исправите ответ, никто не придет, чтобы уравновесить его.
Bryc
5

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

my_string.split('').map(v=>v.charCodeAt(0)).reduce((a,v)=>a+((a<<7)+(a<<3))^v).toString(16);
Джон Смит
источник
5

SubtleCrypto.digest

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

Вы уверены, что не можете сделать это таким образом ?

Вы забыли, что используете Javascript, язык, который постоянно развивается?

Попробуй SubtleCrypto. Он поддерживает хэш-функции SHA-1, SHA-128, SHA-256 и 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.")));
})();

Константин Ван
источник
Чем это отличается от ответа Кайидо за два года до твоего ?
Люк
@Luc Это не так, по-видимому.
Константин Ван
3

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

const crypto = require('crypto');

const SALT = '$ome$alt';

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

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

Ариэль Хименес
источник
2

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

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();
Фрэнк
источник
2

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

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

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

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

Это должно быть немного более безопасным хешем, чем некоторые другие ответы, но в функции, без какого-либо предварительно загруженного источника

Я создал в основном упрощенную версию sha1.
Вы берете байты строки и группируете их по 4–32 битным «словам».
Затем мы расширяем каждые 8 ​​слов до 40 слов (для большего влияния на результат).
Это относится к функции хеширования (последнее уменьшение), где мы выполняем некоторые математические операции с текущим состоянием и входными данными. Мы всегда получаем 4 слова.
Это почти версия для одной команды / одной строки, использующая карту, сокращение ... вместо циклов, но она все еще довольно быстрая

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

мы также конвертируем вывод в hex, чтобы получить строку вместо массива слов.
Использование простое. для образца "a string".hash()вернется"88a09e8f9cc6f8c71c4497fbb36f84cd"

Франартур Чех
источник
1

Я пошел на простую конкатенацию кодов символов, преобразованных в шестнадцатеричные строки. Это служит для относительно узкой цели, а именно для того, чтобы просто было необходимо обменять хеш-представление строки SHORT (например, заголовки, теги) со стороны сервера, которая по несущественным причинам не может легко реализовать принятый порт 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"

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

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"

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

swornabsent
источник
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;
}
crazy2be
источник
1

Добавление этого, потому что никто еще этого не сделал, и, кажется, об этом много говорят и реализуют с помощью хэшей, но это всегда делается очень плохо ...

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

Вы можете использовать это для создания уникального индекса в массиве изображений (если вы хотите вернуть определенный аватар для пользователя, выбранный случайным образом, но также выбранный на основе его имени, поэтому он всегда будет назначен кому-то с таким именем ).

Конечно, вы также можете использовать это для возврата индекса в массив цветов, например, для генерации уникальных фоновых цветов аватара на основе чьего-либо имени.

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

Я не вижу причин использовать этот слишком сложный криптографический код вместо готовых к использованию решений, таких как библиотека объектных хэшей и т. Д., Полагаясь на поставщика, более продуктивно, экономит время и снижает затраты на обслуживание.

Просто используйте https://github.com/puleos/object-hash

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

hash({foo: 'bar'}) // => '67b69634f9880a282c14a0f0cb7ba20cf5d677e9'
hash([1, 2, 2.718, 3.14159]) // => '136b9b88375971dff9f1af09d7356e3e04281951'
Олег Абражаев
источник
Исходный код этой библиотеки даже не читается ... всего лишь 50 Кб минимизированного кода.
БРИКа
1
@bryc - вот как должен выглядеть код вендора :), а источники можно посмотреть на github.com/puleos/object-hash/blob/master/index.js
Олег Абражаев
Сокращенный код составляет 35,4 КБ, а полный исходный код - 14,2 КБ. Это бессмысленно.
БРИКа
2
@ bryc ты рассматривал эту строчку? var crypto = require('crypto');, Я думаю, что он добавляет этот код зависимости от поставщика в минимизированной версии во время сборки.
Олег Абражаев
Если вам действительно нужно хешировать объекты, я написал any-serialize для сериализации ЛЮБОГО объекта с ключами сортировки, а затем cyrb53 для генерации base36-хеша.
Polv