在Javascript中从字符串生成哈希

时间:2011-09-30 21:52:05

标签: javascript hash

我需要将字符串转换为某种形式的哈希。这在JavaScript中是否可行?

我没有使用服务器端语言,所以我不能这样做。

24 个答案:

答案 0 :(得分:688)

String.prototype.hashCode = function() {
  var hash = 0, i, chr;
  if (this.length === 0) return hash;
  for (i = 0; i < this.length; i++) {
    chr   = this.charCodeAt(i);
    hash  = ((hash << 5) - hash) + chr;
    hash |= 0; // Convert to 32bit integer
  }
  return hash;
};

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

答案 1 :(得分:111)

修改

基于我的jsperf测试,接受的答案实际上更快:http://jsperf.com/hashcodelordvlad

<强> ORIGINAL

如果有人感兴趣,这是一个改进的(更快的)版本,在缺少reduce数组函数的旧浏览器上会失败。

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

答案 2 :(得分:83)

  

注意:即使使用最佳的32位哈希,也会迟早发生冲突

     

哈希冲突概率可以计算为   1 - e ^ (-k(k-1) / 2N,   近似为   k^2 / 2N   (see here)。   这可能高于直觉所暗示的:
  假设32位散列和k = 10,000项,将发生碰撞,概率为1.2%。   对于77,163个样本,概率变为50%!   (calculator)。
  我建议在底部找到解决方法。

回答这个问题 Which hashing algorithm is best for uniqueness and speed?, Ian Boyd发表了一篇好文章in depth analysis。 简而言之(正如我所解释的那样),他得出的结论是Murmur是最好的,其次是FNV-1a。 esmiralha提出的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-bit-hash/3

所以一般情况下我会推荐murmur3。
请参阅此处获取JavaScript实现: https://github.com/garycourt/murmurhash-js

如果输入字符串很短且性能比分发质量更重要,请使用DJB2(由esmiralha接受的答案提议)。

如果质量和小代码大小比速度更重要,我使用FNV-1a的这种实现(基于this code)。

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

改善碰撞概率

As explained here,我们可以使用这个技巧扩展哈希位大小:

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

小心使用它,但不要期望太多。

答案 3 :(得分:45)

基于ES6中的accepted answer。更小,可维护,适用于现代浏览器。

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

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

答案 4 :(得分:24)

如果它对任何人有帮助,我将前两个答案合并到一个较旧的浏览器容忍版本中,如果reduce可用,则使用快速版本,如果不是,则使用esmiralha的解决方案。

/**
 * @see http://stackoverflow.com/q/7616461/940217
 * @return {number}
 */
String.prototype.hashCode = function(){
    if (Array.prototype.reduce){
        return this.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);              
    } 
    var hash = 0;
    if (this.length === 0) return hash;
    for (var i = 0; i < this.length; i++) {
        var character  = this.charCodeAt(i);
        hash  = ((hash<<5)-hash)+character;
        hash = hash & hash; // Convert to 32bit integer
    }
    return hash;
}

用法如下:

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

答案 5 :(得分:19)

这是一种精致且性能更佳的变体:

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

享受!

答案 6 :(得分:14)

我有点惊讶没有人谈过新的SubtleCrypto API

要从字符串中获取哈希值,可以使用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);
  });

答案 7 :(得分:12)

这是一个简单的,分布良好的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);
};

它使用类似于xxHash / MurmurHash3的技术,但不够彻底。它实现了雪崩(非严格),因此输入中的细微变化会在输出中产生较大的变化,从而使其呈现随机性:

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

您还可以为相同输入的备用流提供种子:

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

从技术上讲,它是64位哈希,但是JavaScript限于53位整数。通过更改十六进制字符串或数组的返回行,仍可以使用完整的64位:

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

问题在于,构造十六进制字符串成为性能的瓶颈,并且数组需要两个比较运算符而不是一个,这并不方便。因此,在将其用于高性能应用程序时,请记住这一点。


只是为了好玩,这是89个字符中的最小32位哈希,仍然胜过FNV / DJB2 / SMDB:

TSH=s=>{for(var i=0,h=6;i<s.length;)h=Math.imul(h^s.charCodeAt(i++),9**9);return h^h>>>9}

答案 8 :(得分:6)

感谢mar10的例子,我找到了一种方法,可以在C#和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);
}

答案 9 :(得分:4)

快速简洁的改编自here

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

答案 10 :(得分:4)

基于FNV Multiply+Xor方法的快速(非常长)单线程:

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

答案 11 :(得分:4)

我需要一个类似的功能(但不同),根据用户名和当前时间生成唯一ID。所以:

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年6月:对于新代码,我使用shortid:https://www.npmjs.com/package/shortid

答案 12 :(得分:2)

如果您想避免碰撞,可以使用像secure hash这样的SHA-256。 有几种JavaScript SHA-256实现。

我编写了测试来比较几个哈希实现,请参阅https://github.com/brillout/test-javascript-hash-implementations

或者转到http://brillout.github.io/test-javascript-hash-implementations/,运行测试。

答案 13 :(得分:2)

我已经将这两个解决方案(用户esmiralha和lordvlad)结合起来,为支持js功能 reduce()且仍与旧浏览器兼容的浏览器提供更快的功能:

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

答案 14 :(得分:2)

我参加聚会有点晚,但是您可以使用以下模块:crypto

const crypto = require('crypto');

const SALT = '$ome$alt';

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

此函数的结果始终为64个字符串;像这样的东西:"aa54e7563b1964037849528e7ba068eb7767b1fab74a8d80fe300828b996714a"

答案 15 :(得分: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;
}

答案 16 :(得分:1)

我选择了转换为十六进制字符串的字符代码的简单连接。这用于相对狭窄的目的,即仅需要与服务器端交换的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('');
}

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

与此方法碰撞的风险自然更大,但你可以在reduce中使用算法,但是你想要多样化并延长散列。

答案 17 :(得分: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);
}

答案 18 :(得分:1)

SubtleCrypto.digest

  

我没有使用服务器端语言,所以我不能那样做。

确定要那样做 吗?

您是否忘了您正在使用Java语言(一种不断发展的语言)?

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

答案 19 :(得分:1)

这是一个紧凑的ES6友好可读代码段

const stringHashCode = str => {
  let hash = 0
  for (let i = 0; i < str.length; ++i)
    hash = (Math.imul(31, hash) + str.charCodeAt(i)) | 0

  return hash
}

答案 20 :(得分:1)

UUID v3 和 UUID v5 实际上是给定输入字符串的哈希值。

  • UUID v3 基于 MD5,
  • UUID v5 基于 SHA-1。

因此,最明显的选择是使用 UUID v5。

幸运的是,有一个流行的 npm 包,其中包含所有 UUID 算法。

npm install uuid

要实际生成 UUID v5,您需要一个唯一的命名空间。这个命名空间就像一个种子,应该是一个常量,以确保对于给定的输入,输出总是相同的。具有讽刺意味的是,您应该生成一个 UUID v4 作为命名空间。最简单的方法是using some online tool

获得命名空间后,一切就绪。

import { v5 as uuidv5 } from 'uuid';

const MY_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341';
const hash = uuidv5('input', MY_NAMESPACE);

如果你的输入字符串总是一个 URL,那么你可以使用一些默认的命名空间。

const hashForURL = uuidv5('https://www.w3.org/', uuidv5.URL);

答案 21 :(得分:0)

这应该比其他答案更安全,但是在函数中,没有任何预加载的源代码

我基本上创建了sha1的缩小版简化版本。
您获取字符串的字节并将其按4到32位的“单词”进行分组
然后,我们将每8个字扩展到40个字(以对结果产生更大的影响)。
这进入到散列函数(最后一个减少),其中我们对当前状态和输入进行一些数学运算。我们总是说出四个字。
这几乎是一个使用map,reduce ...而不是循环的单命令/单行版本,但是它仍然相当快

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

我们还将输出转换为十六进制以获取字符串而不是单词数组。
用法很简单。示例"a string".hash()将返回"88a09e8f9cc6f8c71c4497fbb36f84cd"

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("");
};
let str = "the string could even by empty";
console.log(str.hash())//9f9aeca899367572b875b51be0d566b5

答案 22 :(得分:0)

这会根据传入的任意数量的参数生成一致的哈希值:

/**
 * Generates a hash from params passed in
 * @returns {string} hash based on params
 */
function fastHashParams() {
    var args = Array.prototype.slice.call(arguments).join('|');
    var hash = 0;
    if (args.length == 0) {
        return hash;
    }
    for (var i = 0; i < args.length; i++) {
        var char = args.charCodeAt(i);
        hash = ((hash << 5) - hash) + char;
        hash = hash & hash; // Convert to 32bit integer
    }
    return String(hash);
}

fastHashParams('hello world')输出"990433808"

fastHashParams('this',1,'has','lots','of','params',true)输出"1465480334"

答案 23 :(得分:-2)

我看不出有任何理由使用这种过于复杂的密码而不是使用现成的解决方案(例如对象哈希库等)。依靠供应商可以提高生产率,节省时间并降低维护成本。

只需使用https://github.com/puleos/object-hash

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

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