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.
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.
Respostas:
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/
hash << 5 - hash
é o mesmo, hash * 31 + char
mas muito mais rápido. É legal porque é muito rápido e o 31 é um primo pequeno. Ganhar ganhar lá.
(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.
n
, qual é o maior n
para o qual não posso ter uma colisão?
var hashCode = function hashCode (str) {etc...}
? E então usar como hashCode("mystring")
?
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 reduce
funçã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)
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 , aproximada como ( 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.
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.
("0000000" + (hval >>> 0).toString(16)).substr(-8);
? Não é o mesmo que (hval >>> 0).toString(16)
?
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.
Math.imul
funçã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.
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!'))
str += ""
antes de hashing a exceção evitar str.split is not a function
lançada quando não cordas foram passados como parâmetros
hash |= 0
para converter para um int de 32 bits. Esta implementação não. Isso é um inseto?
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 comMath.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}
ch
inicializado?
'imul'
.
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 reduce
disponí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();
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; }
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!
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.digest
mé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);
});
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('')); });
crypto
Não é exatamente de alto desempenho.
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);
}
Math.imul
poderá 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 .
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;
}
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
Meu liner rápido (muito longo) baseado no Multiply+Xor
método da FNV :
my_string.split('').map(v=>v.charCodeAt(0)).reduce((a,v)=>a+((a<<7)+(a<<3))^v).toString(16);
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.")));
})();
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 64
sequência de caracteres; algo assim:"aa54e7563b1964037849528e7ba068eb7767b1fab74a8d80fe300828b996714a"
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();
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.
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"
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.
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;
}
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);
}
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'
var crypto = require('crypto');
. Eu acho que adiciona esse código de dependência do fornecedor na versão minificada durante uma compilação.