Gere um Hash a partir de uma string em Javascript


586

Eu preciso converter seqüências de caracteres para algum tipo de hash. Isso é possível em JavaScript?

Não estou utilizando uma linguagem do lado do servidor, portanto não posso fazer dessa maneira.


7
O MD5 não é seguro, portanto, não procure por esse.
Henrikstroem

166
@henrikstroem Depende do que você está usando; não há nada errado em usar o md5 para criar um hash para fins não relacionados à segurança.
Brad Koch

7
@BradKoch Depende do que você está fazendo; não há nada errado em usar o md5 para fins de segurança. Certamente existem métodos melhores para o hash de senhas, mas o md5 é adequado para fazer coisas como assinar um URL.
Paul Ferrett

81
Acho engraçado que, embora o MD5 seja criticado nos comentários aqui, quase todas as respostas recomendam algoritmos de hash muito piores e recebem muitos votos positivos.
domen

38
Usar o MD5 para verificar se um download ficou intacto não vai magicamente enviar suas senhas a todos os seus colegas de trabalho.
James M. Lay

Respostas:


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

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


22
Este é o mesmo usado em Java. O hash << 5 - hashé o mesmo, hash * 31 + charmas muito mais rápido. É legal porque é muito rápido e o 31 é um primo pequeno. Ganhar ganhar lá.
precisa saber é o seguinte

41
Fiz alguns testes no jsperf ( jsperf.com/hashing-strings ) e a função bit a bit é realmente mais lenta que a função baseada em número.
Skerit

17
@PeterAronZentai Por que é "inutilizável"? A saída produzida pelo código baseado em número (hash * 31) + charé idêntica à saída produzida pelo código baseado em turnos ((hash<<5)-hash)+char, mesmo para strings muito longas (eu testei com strings contendo mais de um milhão de caracteres), portanto, não é "inutilizável" em termos de precisão. A complexidade é O (n) para as versões baseada em número e baseada em turnos, portanto, não é "inutilizável" em termos de complexidade.
TachyonVortex

13
Alguém pode comentar sobre a singularidade (ou não) da saída? Especificamente, se eu usar esse hash apenas para strings com comprimento menor que n, qual é o maior npara o qual não posso ter uma colisão?
Don McCurdy

34
Existe algum motivo para isso precisar (ou deveria) estar no protótipo String? Seria menos eficaz / eficiente apenas ter, por exemplo; var hashCode = function hashCode (str) {etc...}? E então usar como hashCode("mystring")?
Rattray 4/08

146

EDITAR

com base nos meus testes jsperf, a resposta aceita é realmente mais rápida: http://jsperf.com/hashcodelordvlad

ORIGINAL

se alguém estiver interessado, aqui está uma versão melhorada (mais rápida), que falhará em navegadores mais antigos que não possuem a reducefunção de matriz.

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

versão da função seta de uma linha:

hashCode = s => s.split('').reduce((a,b)=>{a=((a<<5)-a)+b.charCodeAt(0);return a&a},0)

3
existe uma maneira de obter hash que é apenas o número positivo?
Prosto Trader

46
esquisito. Acabei de o testar e ficou mais lento do que a resposta aceita. jsperf.com/hashcodelordvlad
lordvlad

113
Bom rapaz @lordvlad, testando sua própria resposta e relatando quando era mais lento.
Mikemaccana

9
Eu só percebi: Faz todo o sentido que a resposta aceita é mais rápido, porque a minha versão tem que girar a corda em uma matriz primeiro, alocar nova memória e copiar todos os personagens ...
lordvlad

5
[] .reduce.call (str, (p, c, i, a) => (p << 5) - p + a.charCodeAt (i), 0);
313 Dizzy

108

Nota: Mesmo com o melhor de hash de 32 bits, as colisões vão ocorrer mais cedo ou mais tarde.

A probabilidade de colisão de hash pode ser calculada como 1 - e ^ (-k (k-1) / 2N, aproximada como k ^ 2 / 2N ( veja aqui ). Isso pode ser maior do que a intuição sugere:
Assumindo um hash de 32 bits e k = 10.000 itens, ocorrerá uma colisão com uma probabilidade de 1,2%. Para 77.163 amostras, a probabilidade se torna 50%! ( calculadora ).
Sugiro uma solução alternativa na parte inferior.

Em resposta a esta pergunta Qual algoritmo de hash é melhor para exclusividade e velocidade? , Ian Boyd publicou uma análise aprofundada . Em resumo (como eu o interpreto), ele chega à conclusão de que Murmur é o melhor, seguido por FNV-1a.
O algoritmo String.hashCode () de Java que a esmiralha propôs parece ser uma variante do DJB2.

  • O FNV-1a tem uma distribuição melhor que o DJB2, mas é mais lento
  • DJB2 é mais rápido que FNV-1a, mas tende a produzir mais colisões
  • MurmurHash3 é melhor e mais rápido que DJB2 e FNV-1a (mas a implementação otimizada requer mais linhas de código que FNV e DJB2)

Alguns benchmarks com grandes seqüências de caracteres de entrada aqui: http://jsperf.com/32-bit-hash
Quando sequências curtas de entrada são divididas, o desempenho do sopro cai em relação ao DJ2B e FNV-1a: http://jsperf.com/32- bit-hash / 3

Então, em geral, eu recomendaria murmur3.
Veja aqui uma implementação de JavaScript: https://github.com/garycourt/murmurhash-js

Se as seqüências de entrada forem curtas e o desempenho for mais importante que a qualidade da distribuição, use o DJB2 (conforme proposto pela resposta aceita por esmiralha).

Se a qualidade e o tamanho pequeno do código forem mais importantes que a velocidade, eu uso esta implementação do FNV-1a (com base nesse código ).

/**
 * Calculate a 32 bit FNV-1a hash
 * Found here: https://gist.github.com/vaiorabbit/5657561
 * Ref.: http://isthe.com/chongo/tech/comp/fnv/
 *
 * @param {string} str the input value
 * @param {boolean} [asString=false] set to true to return the hash value as 
 *     8-digit hex string instead of an integer
 * @param {integer} [seed] optionally pass the hash of the previous chunk
 * @returns {integer | string}
 */
function hashFnv32a(str, asString, seed) {
    /*jshint bitwise:false */
    var i, l,
        hval = (seed === undefined) ? 0x811c9dc5 : seed;

    for (i = 0, l = str.length; i < l; i++) {
        hval ^= str.charCodeAt(i);
        hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
    }
    if( asString ){
        // Convert to 8 digit hex string
        return ("0000000" + (hval >>> 0).toString(16)).substr(-8);
    }
    return hval >>> 0;
}

Melhore a probabilidade de colisão

Conforme explicado aqui , podemos estender o tamanho do bit de hash usando este truque:

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

Use-o com cuidado e não espere muito.


Por que você faz ("0000000" + (hval >>> 0).toString(16)).substr(-8);? Não é o mesmo que (hval >>> 0).toString(16)?
Manuel Meurer 22/10

3
isso adiciona '0s iniciais para que o hash resultante tenha sempre 8 caracteres. Mais fácil de ler e reconhecer em resultados, mas essa é a minha opinião pessoal
Mar10

Ah, entendi. Em tamanho pequeno hval, (hval >>> 0).toString(16)pode ter menos de 8 caracteres, por isso é preenchido com zeros. Fiquei confuso porque (hval >>> 0).toString(16)sempre resultou em uma string de exatamente 8 caracteres para mim.
Manuel Meurer 22/10

3
Adoro essa resposta porque ela produz um hash muito melhor distribuído: outras funções propostas aqui farão valores de hash consequentes. Por exemplo, `hash (" exemplo1 ") - hash (" exemplo2 ") == 1", enquanto este é muito mais imprevisível.
GavinoGrifoni

1
Em resposta a "O FNV-1a tem uma melhor distribuição que o DJB2, mas é mais lento" - acho que se deve dizer que o FNV1a pode ser extremamente rápido quando implementado usando a Math.imulfunção ES6 . Isso por si só faz com que seja um marco de referência e, finalmente, uma escolha melhor que o DJB2 a longo prazo.
bryc

64

Com base na resposta aceita no ES6. Menor, sustentável e funciona em navegadores modernos.

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

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

EDIT (2019-11-04) :

versão da função seta de uma linha:

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

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


1
Obrigado por compartilhar acrescentei str += ""antes de hashing a exceção evitar str.split is not a functionlançada quando não cordas foram passados como parâmetros
BeetleJuice

4
Mas muito, muito mais lento do que qualquer um destes: https://jsperf.com/hashing-strings
AndyO

Também notei que a solução "retro" mais rápida também é menor se você remover os feeds de linha, de modo que ela tenha apenas 3 linhas.
AndyO

2
Alguma maneira de fazer isso produzir apenas resultados positivos, mas ainda únicos?
DIDs

3
@deekshith A resposta aceita usa hash |= 0para converter para um int de 32 bits. Esta implementação não. Isso é um inseto?
Sukima 29/03

48

Quase metade das respostas são implementações do Java String.hashCode, que não são de alta qualidade nem são super rápidas. Não é nada muito especial, apenas multiplica por 31 para cada personagem. Ele pode ser implementado de maneira simples e eficiente em uma linha e é muito mais rápido com Math.imul:

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

Com isso fora do caminho, aqui está algo melhor - cyrb53 , um hash de 53 bits simples, mas de alta qualidade. É bastante rápido, fornece uma distribuição de hash muito boa e possui taxas de colisão significativamente mais baixas em comparação com qualquer hash de 32 bits.

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

Semelhante aos conhecidos algoritmos MurmurHash / xxHash, ele usa uma combinação de multiplicação e Xorshift para gerar o hash, mas não tão completo. Como resultado, é mais rápido que no JavaScript e significativamente mais simples de implementar.

Atinge avalanche (não estrita), o que basicamente significa que pequenas mudanças na entrada têm grandes mudanças na saída, fazendo com que o hash resultante pareça aleatório:

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

Você também pode fornecer uma semente para fluxos alternativos da mesma entrada:

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

Tecnicamente, é um hash de 64 bits (dois hashes não correlacionados de 32 bits em paralelo), mas o JavaScript é limitado a números inteiros de 53 bits. Se necessário, a saída completa de 64 bits ainda pode ser usada alterando a linha de retorno para uma sequência ou matriz hexadecimal.

Esteja ciente de que a construção de cadeias hexadecimais pode diminuir drasticamente o processamento em lote em situações críticas de desempenho.

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

E apenas por diversão, aqui está um hash mínimo de 32 bits em 89 caracteres com qualidade superior ao FNV ou 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
Uau, isso é muito melhor do que o habitual * 31 para entradas curtas (ou similares). :)
lapo 03/10

2
Onde é chinicializado?
precisa saber é o seguinte

3
@ hellowill89 woops, esqueci de declarar e estava sangrando no escopo global. corrigido agora, obrigado: ')
bryc 10/01/19

Falha no IE 11: o objeto não suporta propriedade ou método 'imul'.
Bächt

2
@BachT Você pode usar um polyfill ou um calço ES6 completo . Mas o IE11 é tragicamente congelado em 2009, sem atualizações.
21919 bryc #

28

Se isso ajuda alguém, combinei as duas principais respostas em uma versão mais tolerante ao navegador, que usa a versão rápida, se reducedisponível, e volta para a solução da esmiralha, se não estiver.

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

O uso é como:

var hash = "some string to be hashed".hashCode();

como otimizar esse código para executar mais rapidamente em todos os navegadores. 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; }
Musakkhir Sayyed

26

Esta é uma variante refinada e com melhor desempenho:

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

Isso corresponde à implementação do padrão do Java object.hashCode()

Aqui também está um que retorna apenas códigos de hash positivos:

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

E aqui está um correspondente para Java que retorna apenas códigos de hash positivos:

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

Aproveitar!


2
ótima resposta, mas qual é o objetivo de << 0?
koolaang

8
@koolaang é o operador de merda à esquerda, developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…
mmm

29
@momomo Você quis dizer turno à esquerda ?
Wdh

2
@ Momomo Eu acho que ele estava perguntando por que era um turno esquerdo de zero bits.
Jpfx1342

3
@Maykonn (2 ^
32-1

24

Estou um pouco surpreso que ninguém tenha falado sobre a nova API SubtleCrypto ainda.

Para obter um hash de uma string, você pode usar o subtle.digestmétodo:

function getHash(str, algo = "SHA-256") {
  let strBuf = new TextEncoder('utf-8').encode(str);
  return crypto.subtle.digest(algo, strBuf)
    .then(hash => {
      window.hash = hash;
      // here hash is an arrayBuffer, 
      // so we'll connvert it to its hex version
      let result = '';
      const view = new DataView(hash);
      for (let i = 0; i < hash.byteLength; i += 4) {
        result += ('00000000' + view.getUint32(i).toString(16)).slice(-8);
      }
      return result;
    });
}

getHash('hello world')
  .then(hash => {
    console.log(hash);
  });


4
Concordo. A conversão para hex poderia ser feito um pouco diferente ...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('')); });
Denis Giffeler

3
Uma função hash criptográfica para strings é um pouco exagerada. cryptoNão é exatamente de alto desempenho.
bryc

Qualidade confiável e aleatória sem ter que confiar nas pessoas que executam testes, embutido (nenhuma implementação personalizada é necessária), inicializável e eu só precisava de algumas centenas de números para gerar um mapa do jogo, isso parecia perfeito. Mas acontece que não há absolutamente nenhuma maneira de fazer isso de forma síncrona. Ter que fornecer alguma coisa de retorno de chamada assíncrona toda vez que você chamar seu mecanismo aleatório semeado torna o código super ilegível e parece ridículo. Eu não entendi quem veio com essa interface ruim de criptografia.subtle, então no final eu tive que usar o xmur3 + sfc32 com esta resposta: stackoverflow.com/a/47593316/1201863
Luc

7

Graças ao exemplo de mar10, encontrei uma maneira de obter os mesmos resultados em C # AND Javascript para um FNV-1a. Se houver caracteres unicode, a parte superior será descartada por uma questão de desempenho. Não sei por que seria útil mantê-las durante o hash, já que estou apenas fazendo o hash de caminhos de URL por enquanto.

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

Versão 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 É possível que os caracteres Unicode excedam 8 bits na memória, então suponho que o 0xFF simplesmente oculte qualquer coisa fora desse intervalo. Veja mais sobre charCodeAt () aqui: developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…
djabraham

Se o ES6 estiver disponível (todos os mecanismos modernos o suportam), Math.imulpoderá ser usado para a etapa de multiplicação, o que melhora significativamente o desempenho . O único problema é que ele não funcionará no IE11 sem um calço .
bryc

6

Um rápido e conciso que foi adaptado daqui :

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

5

Eu precisava de uma função semelhante (mas diferente) para gerar um ID exclusivo com base no nome de usuário e no horário atual. Assim:

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

Produz:

2DVFXJGEKL
6IZPAKFQFL
ORGOENVMG
... etc 

editar Jun 2015: Para o novo código, uso shortid: https://www.npmjs.com/package/shortid


2
@ t0r0X bem, agora eu uso um módulo chamado ShortID: npmjs.com/package/shortid
jcollum

1
Como você está usando o nome de usuário com shortid? Parece apenas para gerar ids, mas eu não vejo como você está usando para gerar um hash de uma string
cyberwombat

1
Esta resposta tem 3 votos negativos. Pela minha vida, não consigo imaginar o porquê. Ninguém disse nada ...: - /
jcollum 16/02

1
@ jcollum é por isso que quase nunca respondo perguntas obsoletas .. batidas e corridas passam despercebidas. mesmo depois de acertar a resposta, ninguém aparece para equilibrá-la.
Br9c

5

Meu liner rápido (muito longo) baseado no Multiply+Xormétodo da FNV :

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

5

SubtleCrypto.digest

Não estou utilizando uma linguagem do lado do servidor, portanto não posso fazer dessa maneira.

Tem certeza de que não pode fazer dessa maneira ?

Você esqueceu que está usando Javascript, a linguagem em constante evolução?

Tente SubtleCrypto. Ele suporta funções de hash SHA-1, SHA-128, SHA-256 e 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.")));
})();


Como isso difere da resposta de Kaiido dois anos antes da sua ?
Luc

@ Luc Não é, aparentemente.
Константин Ван

3

Estou meio atrasado para a festa, mas você pode usar este módulo: crypto :

const crypto = require('crypto');

const SALT = '$ome$alt';

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

O resultado dessa função é sempre é a 64sequência de caracteres; algo assim:"aa54e7563b1964037849528e7ba068eb7767b1fab74a8d80fe300828b996714a"


2

Combinei as duas soluções (usuários esmiralha e lordvlad) para obter uma função que deveria ser mais rápida para navegadores que suportam a função js reduzir () e ainda compatível com navegadores antigos:

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

Exemplo:

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

2

Se você deseja evitar colisões, pode usar um hash seguro como o SHA-256 . Existem várias implementações JavaScript SHA-256.

Eu escrevi testes para comparar várias implementações de hash, consulte https://github.com/brillout/test-javascript-hash-implementations .

Ou vá para http://brillout.github.io/test-javascript-hash-implementations/ para executar os testes.


1
O uso de um hash criptográfico seguro pode ser extremamente lento. Evitar colisões é um produto da largura de bits, não de segurança. O hash não criptográfico de 128 bits ou mesmo 64 bits deve ser mais do que suficiente para a maioria dos propósitos. MurmurHash3_x86_128 é bastante rápido e tem uma chance muito baixa de colisões.
Br9c

2

Esse hash deve ser um pouco mais seguro do que algumas outras respostas, mas em uma função, sem nenhuma fonte pré-carregada

Criei basicamente uma versão simplificada e simplificada do sha1.
Você pega os bytes da string e os agrupa em "palavras" de 4 a 32 bits.
Em seguida, estendemos a cada 8 palavras para 40 palavras (para maior impacto no resultado).
Isso vai para a função de hash (a última redução), onde fazemos algumas contas com o estado atual e a entrada. Nós sempre damos 4 palavras.
Esta é quase uma versão de um comando / linha usando map, reduzir ... em vez de loops, mas ainda é muito rápido

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

também convertemos a saída em hexadecimal para obter uma string em vez de uma matriz de palavras.
O uso é simples. por exemplo "a string".hash(), retornará"88a09e8f9cc6f8c71c4497fbb36f84cd"


1

Eu fui para uma simples concatenação de códigos de caracteres convertidos em seqüências de caracteres hexadecimais. Isso serve a um propósito relativamente restrito, ou seja, apenas a necessidade de uma representação hash de uma string SHORT (por exemplo, títulos, tags) ser trocada com um servidor que, por razões não relevantes, não possa implementar facilmente a porta Java hashCode aceita. Obviamente, nenhum aplicativo de segurança aqui.

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

Isso pode ser mais conciso e tolerante ao navegador com o Underscore. Exemplo:

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

Suponho que, se você quisesse hash de seqüências maiores de maneira semelhante, seria possível reduzir os códigos de caracteres e hexificar a soma resultante, em vez de concatenar os caracteres individuais:

String.prototype.hashLarge = function() {
  var self = this, range = Array(this.length);
  for(var i = 0; i < this.length; i++) {
    range[i] = i;
  }
  return Array.prototype.reduce.call(range, function(sum, i) {
    return sum + self.charCodeAt(i);
  }, 0).toString(16);
}

'One time, I hired a monkey to take notes for me in class. I would just sit back with my mind completely blank while the monkey scribbled on little pieces of paper. At the end of the week, the teacher said, "Class, I want you to write a paper using your notes." So I wrote a paper that said, "Hello! My name is Bingo! I like to climb on things! Can I have a banana? Eek, eek!" I got an F. When I told my mom about it, she said, "I told you, never trust a monkey!"'.hashLarge()
"9ce7"

Naturalmente, há mais risco de colisão com esse método, embora você possa mexer na aritmética da redução, no entanto, deseja diversificar e aumentar o hash.


1

Versão ligeiramente simplificada da resposta de @ esmiralha.

Não substituo String nesta versão, pois isso pode resultar em algum comportamento indesejado.

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

1

Adicionando isso porque ninguém o fez ainda, e isso parece ser solicitado e implementado muito com hashes, mas sempre é feito muito mal ...

Isso requer uma entrada de sequência e um número máximo que você deseja que o hash seja igual e produz um número exclusivo com base na entrada de sequência.

Você pode usar isso para produzir um índice exclusivo em uma matriz de imagens (se desejar retornar um avatar específico para um usuário, escolhido aleatoriamente, mas também com base em seu nome, ele será sempre atribuído a alguém com esse nome )

Você também pode usar isso, é claro, para retornar um índice para uma variedade de cores, como para gerar cores de fundo de avatar exclusivas com base no nome de alguém.

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

Não vejo nenhum motivo para usar esse código criptográfico complicado em vez de soluções prontas para o uso, como biblioteca de hash de objetos ou etc. confiar no fornecedor é mais produtivo, economiza tempo e reduz os custos de manutenção.

Basta usar https://github.com/puleos/object-hash

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

hash({foo: 'bar'}) // => '67b69634f9880a282c14a0f0cb7ba20cf5d677e9'
hash([1, 2, 2.718, 3.14159]) // => '136b9b88375971dff9f1af09d7356e3e04281951'

O código fonte dessa lib nem sequer é legível .. apenas 50k de código minificado.
bryc 17/02/19

1
@bryc é assim que o código do fornecedor deveria parecer :) e, para fontes, você pode conferir github.com/puleos/object-hash/blob/master/index.js
Oleg Abrazhaev

O código reduzido é 35.4 KB enquanto a fonte completa é 14.2 KB? Isso não faz sentido.
21919 bryc #

2
@bryc você considerou esta linha? var crypto = require('crypto');. Eu acho que adiciona esse código de dependência do fornecedor na versão minificada durante uma compilação.
Oleg Abrazhaev 20/03/19

Se você realmente precisa fazer hash de objetos, escrevi any-serialize para serializar QUALQUER objeto com chaves de classificação e, em seguida, cyrb53 para gerar o hash base36.
Polv 22/02
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.