Створити хеш із рядка в Javascript


585

Мені потрібно конвертувати рядки в якусь форму хешу. Чи можливо це в JavaScript?

Я не використовую серверну мову, тому не можу це зробити так.


7
MD5 не захищений, тому не шукайте цього.
henrikstroem

166
@henrikstroem Залежить від того, що ти хешиш; немає нічого поганого в тому, щоб використовувати md5 для створення хешу для цілей безпеки.
Бред Кох

7
@BradKoch Залежить від того, що ти робиш; немає нічого поганого у використанні md5 в цілях безпеки. Звичайно, є кращі методи хешування паролів, але md5 просто чудово виконує такі дії, як підписання URL-адреси.
Пол Ферретт

81
Мені здається смішним, що в той час, як тут у коментарях критикують MD5, майже всі відповіді рекомендують значно гірші алгоритми хешування та отримують багато оновлень.
domen

38
Використання MD5 для перевірки того, що завантаження було недоторканим, не магічним чином надсилатиме ваші паролі усім вашим колегам.
Джеймс М. Лежав

Відповіді:


787
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/


22
Це той самий, що використовується в Java. Це hash << 5 - hashте саме, що hash * 31 + charі ЛОТ швидше. Це приємно, бо це так швидко, а 31 - це невеликий прем'єр. Win виграти там.
corsiKa

41
Я зробив кілька тестів на jsperf ( jsperf.com/hashing-strings ), і побітова функція насправді повільніше, ніж функція на основі чисел.
skerit

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

EDIT

На основі моїх тестів на 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)

3
чи є спосіб отримати хеш, який є лише позитивним числом?
Prosto Trader

46
дивно. Я щойно перевірив це, і виявилося, що він частіше повільний, ніж прийнята відповідь. jsperf.com/hashcodelordvlad
lordvlad

113
Хороший хлопець @lordvlad, насправді тестував власну відповідь, а потім повідомляв, коли це повільніше.
mikemaccana

9
Я щойно зрозумів: Має сенс, що прийнята відповідь швидша, тому що моя версія повинна спочатку перетворити рядок у масив, виділивши нову пам'ять та скопіювавши кожен символ ...
lordvlad

5
[] .reduce.call (str, (p, c, i, a) => (p << 5) - p + a.charCodeAt (i), 0);
Запаморочення

108

Примітка. Навіть із найкращим 32-бітовим хешем зіткнення будуть рано чи пізно станеться.

Вірогідність зіткнення хешу може бути обчислена як 1 - e ^ (-k (k-1) / 2N, апроксимація як k ^ 2 / 2N ( див. Тут ). Це може бути вище, ніж пропонує інтуїція: якщо
припустити 32-бітний хеш і k = 10000 предметів, відбудеться зіткнення з ймовірністю 1,2%. Для 77 163 проби ймовірність стає 50%! ( калькулятор ).
Я пропоную вирішити внизу.

У відповідь на це питання Який алгоритм хешування найкращий для унікальності та швидкості? , Ян Бойд опублікував хороший глибокий аналіз . Якщо коротко (як я його тлумачу), він приходить до висновку, що Мурмур найкращий, за ним слідує FNV-1a.
Алгоритм Java 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- біт-хеш / 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)
}

Використовуйте його обережно, але не чекайте занадто багато.


Навіщо це робити ("0000000" + (hval >>> 0).toString(16)).substr(-8);? Хіба це не те саме (hval >>> 0).toString(16)?
Мануель Меурер

3
це додає провідні 0, так що отриманий хеш завжди має 8 символів. Легше читати та розпізнавати у результатах, але це моя особиста думка
mar10

Ну добре, я розумію. Для малих hval, (hval >>> 0).toString(16)може бути менше , ніж 8 символів, так що ви подушечка його нулями. Я просто розгубився, бо (hval >>> 0).toString(16)завжди в мене виходило рівно 8 символьних рядків.
Мануель Меурер

3
Мені подобається ця відповідь, оскільки вона створює набагато краще розподілений хеш: інші функції, запропоновані тут, будуть робити наступні хеш-значення. Наприклад, хеш ("example1") - хеш ("example2") == 1 ", тоді як цей набагато більш непередбачуваний.
GavinoGrifoni

1
У відповідь на "FNV-1a має кращий розподіл, ніж DJB2, але повільніше" - я думаю, що слід сказати, що FNV1a може бути надзвичайно швидким, коли реалізується за допомогою функції ES6 Math.imul. Це одне лише робить його провідними орієнтирами, і, зрештою, кращим вибором, ніж 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!'));

EDIT (2019-11-04) :

версія стрілки з одним вкладишем:

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

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


1
Дякую за поділ, який я додав str += ""до хешування, щоб уникнути виключення, str.split is not a functionколи не передаються рядки як параметри
BeetleJuice

4
Але набагато, набагато повільніше будь-якого з них: https://jsperf.com/hashing-strings
AndyO

Я також щойно помітив, що найшвидше "ретро" рішення насправді менше, якщо ви вилучаєте канали ліній, щоб вони були лише 3 рядки.
AndyO

2
Будь-який спосіб досягти цього лише позитивних, але все ж унікальних результатів?
Діди

3
@deekshith Прийнята відповідь використовує hash |= 0для перетворення в 32-бітний int. Ця реалізація не робить. Це помилка?
Сукіма

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 забуває, я забув заявити про це і почав кровоточити в глобальний масштаб. виправлено зараз, спасибі: ')
bryc

Помилка IE 11: Об'єкт не підтримує властивість чи метод 'imul'.
BachT

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/…
mmm

29
@momomo Ви мали на увазі зміну ліворуч ?
wdh

2
@momomo Я думаю, що він запитував, чому це ліва зміна нуля біт.
jpfx1342

3
@Maykonn (2 ^ 32 - 1)
Nijraj Gelani

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


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

Надійна якість випадкових випадків без того, щоб покладатися на людей, які виконують тести, вбудований (не потрібна спеціальна реалізація), зручно використовувати, і мені знадобилося лише кілька сотень цифр для створення карти гри, це здавалося ідеальним. Але виявляється, немає абсолютно ніякого способу зробити це синхронно. Необхідність надати деякий асинхронний зворотній виклик щоразу, коли ви викликаєте ваш посіяний випадковий двигун, робить код супер нечитабельним і виглядає смішним. Я не розумію, хто придумав цей хитрий інтерфейс crypto.subtle, тому мені, врешті-решт, довелося перейти з xmur3 + sfc32 з цієї відповіді: stackoverflow.com/a/47593316/1201863
Luc

7

Завдяки прикладу mar10, я знайшов спосіб отримати ті самі результати у C # AND Javascript для FNV-1a. Якщо є символи unicode, верхня частина відкидається заради виконання. Не знаю, чому було б корисно підтримувати їх під час хешування, оскільки наразі я лише хешую 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);
}

@mathiasrw Символи Unicode можуть перевищувати 8 біт у пам'яті, тому я припускаю, що 0xFF просто маскує все, що знаходиться поза цим діапазоном. Більше про charCodeAt () дивіться тут: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
djabraham

Якщо ES6 доступний (всі сучасні двигуни підтримують його), Math.imulйого можна використовувати для кроку множення, що значно покращує продуктивність . Тільки проблема в тому, що не працюватиме в IE11 без прокладки .
bryc

6

Швидкий і стислий, який був адаптований звідси :

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

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


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 скарати () і все ще сумісна зі старими браузерами:

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/ , щоб запустити тести.


1
Використання захищеного криптографічного хешу може бути надзвичайно повільним. Уникнення зіткнень - це продукт бітової ширини, а не безпеки. 128-бітний некриптографічний хеш або навіть 64 біт повинен бути більш ніж достатнім для більшості цілей. MurmurHash3_x86_128 досить швидкий і має дуже низький шанс зіткнення.
bryc

2

Це має бути трохи більш захищеним хешем, ніж деякі інші відповіді, але у функції, без попереднього завантаженого джерела

Я в основному створив спрощену спрощену версію sha1.
Ви берете байти рядка і згрупуєте їх на 4 - 32-бітні "слова".
Потім ми розширюємо кожні 8 слів до 40 слів (для більшого впливу на результат).
Це переходить до функції хешування (останнє зменшення), де ми виконуємо математику з поточним станом та входом. У нас завжди виходить 4 слова.
Це майже однокомандна / однорядкова версія, що використовує map, зменшити ... замість циклів, але це все ще досить швидко

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

ми також перетворюємо вихід у шістнадцятковий, щоб отримати рядок замість масиву слів.
Використання просте. для expample "a string".hash()повернеться"88a09e8f9cc6f8c71c4497fbb36f84cd"


1

Я пішов на просте з'єднання кодів char, перетворених у шістнадцяткові рядки. Це служить відносно вузькій цілі, а саме просто потрібне хеш-представлення рядка SHORT (наприклад, заголовки, теги) для обміну на стороні сервера, яка з нерелевантних причин не може легко реалізувати прийнятий порт hashCode Java. Очевидно, що тут немає жодної програми безпеки.

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

Це можна зробити більш короткою та стійкою до браузера за допомогою підкреслення. Приклад:

"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"

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


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

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

Це вимагає введення рядка і максимального числа, за яким ви хочете, щоб хеш дорівнював, і створює унікальне число на основі введення рядка.

Ви можете використовувати це для створення унікального індексу в масиві зображень (Якщо ви хочете повернути певний аватар для користувача, вибраний випадковим чином, але також обраний на основі їх імені, тому він завжди буде призначений комусь із цим ім'ям ).

Ви можете, звичайно, також використовувати це для повернення індексу в масив кольорів, як-от для створення унікальних кольорів тла аватара на основі чийогось імені.

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 тис. Мінімізованого коду.
bryc

1
@bryc ось так повинен виглядати код продавця :), а джерела ви можете перевірити github.com/puleos/object-hash/blob/master/index.js
Олег Абражаєв

Мінімізований код становить 35,4 КБ, тоді як повне джерело - 14,2 КБ? Це безглуздя.
bryc

2
@bryc Ви розглядали цей рядок? var crypto = require('crypto');. Я думаю, що він додає цей код залежності від постачальника у мінімізованій версії під час збирання.
Олег Абражаєв

Якщо вам справді потрібні хеш-об’єкти, я написав будь-яку серіалізацію, щоб серіалізувати БУДЬ-який об’єкт за допомогою клавіш сортування, а потім cyrb53 для створення base36 хешу.
Полв
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.