从Javascript中的字符串生成哈希

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

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

949587 次浏览

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;}
const str = 'revenue'console.log(str, str.hashCode())

来源

编辑

根据我的jspef测试,接受的答案实际上更快: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)

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

window.newId = -># create a number based on the usernameunless window.userNumber?window.userNumber = 0for c,i in window.MyNamespace.userNamechar = window.MyNamespace.userName.charCodeAt(i)window.MyNamespace.userNumber+=char((window.MyNamespace.userNumber + Math.floor(Math.random() * 1e15) + new Date().getMilliseconds()).toString(36)).toUpperCase()

产品:

2DVFXJGEKL6IZPAKFQFLORGOENVMG... etc

2022年7月编辑:正如@canRau指出的那样,短篇小说的作者现在更喜欢纳米管https://github.com/ai/nanoid/

如果它对任何人都有帮助,我将前两个答案组合成一个较旧的浏览器容忍版本,如果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();

备注:即使使用最好的32位哈希,冲突迟早会发生。

哈希碰撞概率可以计算为1-e^(-k(k-1)/2N,近似为k^2 / 2N在这里看到)。这可能比直觉建议的要高:
假设一个32位哈希,k=10,000个项目,碰撞将以1.2%的概率发生。对于77,163个样本,概率变为50%!(计算器).
我建议在底部的解决方案。

在回答这个问题时哪种哈希算法最适合唯一性和速度?,伊恩·博伊德发布了一个很好的深入分析。简而言之(正如我所解释的那样),他得出的结论是MurmuHash是最好的,其次是FNV-1a。esmiralha提出的JavaString.hashCode()算法似乎是DJB2的变体。

  • FNV-1a的发行版比DJB2更好,但速度更慢
  • DJB2比FNV-1a快,但往往会产生更多的碰撞
  • MurMurHash3比DJB2和FNV-1a更好更快(但优化后的实现需要比FNV和DJB2更多的代码行)

这里有一些大输入字符串的基准测试:http://jsperf.com/32-bit-hash
输入字符串被散列时,Murmur的性能相对于DJ2B和FNV-1a下降:http://jsperf.com/32-bit-hash/3

总的来说,我会推荐Murura。
在这里查看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 stringreturn ("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)}

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

我结合了两个解决方案(用户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();

多亏了mar10的例子,我找到了一种在C#AND Javascript中为FNV-1a获得相同结果的方法。如果存在Unicode字符,出于性能考虑,上部将被丢弃。不知道为什么在散列时维护这些字符会有所帮助,因为我现在只散列URL路径。

C#版本

private static readonly UInt32 FNV_OFFSET_32 = 0x811c9dc5;   // 2166136261private static readonly UInt32 FNV_PRIME_32 = 0x1000193;     // 16777619
// Unsigned 32bit integer FNV-1apublic static UInt32 HashFnv32u(this string s){// byte[] arr = Encoding.UTF8.GetBytes(s);      // 8 bit expanded unicode arraychar[] 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 usedhash = hash ^ unchecked((byte)(arr[i] & 0xFF));hash = hash * FNV_PRIME_32;}return hash;}
// Signed hash for storing in SQL Serverpublic 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 usedfor (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);}

我选择了将转换为十六进制字符串的char码进行简单的连接。这有一个相对狭窄的目的,即只需要与服务器端交换的短字符串(例如标题、标签)的哈希表示,服务器端由于不相关的原因无法轻松实现接受的hashCodeJava端口。显然这里没有安全应用程序。

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

这可以通过Undercore变得更加简洁和浏览器容忍度。例子:

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

我想如果你想以类似的方式散列较大的字符串,你可以减少char代码并hexify结果总和,而不是将单个字符连接在一起:

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中摆弄算术,但您想要多样化并延长哈希。

如果您想避免碰撞,您可能希望使用安全哈希,例如SHA-256。有几个JavaScript SHA-256实现。

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

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

这是一个改进的、性能更好的变体,匹配Java实现了CharSequence的标准object.hashCode()

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

这里还有一个只返回积极哈希码的:

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

这里有一个匹配的Java,它只返回正哈希码:

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

对于那些不想将其附加到String的人来说,没有原型:

function hashCode(str) {var hash = 0, i = 0, len = str.length;while ( i < len ) {hash  = ((hash << 5) - hash + str.charCodeAt(i++)) << 0;}return hash;}
function hashcode(str) {hashCode(str) + 2147483647 + 1;}

好好享受!

基于ES6中的接受的答案。更小,可维护,适用于现代浏览器。

function hashCode(str) {return str.split('').reduce((prevHash, currVal) =>(((prevHash << 5) - prevHash) + currVal.charCodeAt(0))|0, 0);}
// Testconsole.log("hashCode(\"Hello!\"): ", hashCode('Hello!'));

编辑(2019-11-04)

单行箭头函数版本:

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

略微简化了@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;}

一个快速而简洁的改编自这里

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

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

要从字符串中获取哈希,您可以使用subtle.digest方法:

function getHash(str, algo = "SHA-256") {let strBuf = new TextEncoder().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 versionlet 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);});

我基于FNV的Multiply+Xor方法的快速(非常长)一行:

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

我有点迟到了,但你可以使用这个模块:加密货币

const crypto = require('crypto');
const SALT = '$ome$alt';
function generateHash(pass) {return crypto.createHmac('sha256', SALT).update(pass).digest('hex');}

此函数的结果始终是64字符串;类似于这样:"aa54e7563b1964037849528e7ba068eb7767b1fab74a8d80fe300828b996714a"

这里的许多答案都是来自Java的String.hashCode哈希函数。它可以追溯到1981年来自Gosling Emacs,非常弱,在现代JavaScript中的性能方面毫无意义。事实上,使用ES6Math.imul实现可以显着更快,但没有人注意到。我们可以在基本相同的性能下做得更好。

这是我做的一个-cyrb53,一个简单但高质量的53位哈希。它非常快,提供了非常好的*哈希分布,并且因为它输出53位,与任何 32位哈希相比,碰撞率显着降低。此外,您可以忽略SA的CC许可证,因为它是我的GitHub上的公共领域

const cyrb53 = (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);};
console.log(`cyrb53('a') -> ${cyrb53('a')}`)console.log(`cyrb53('b') -> ${cyrb53('b')}`)console.log(`cyrb53('revenge') -> ${cyrb53('revenge')}`)console.log(`cyrb53('revenue') -> ${cyrb53('revenue')}`)console.log(`cyrb53('revenue', 1) -> ${cyrb53('revenue', 1)}`)console.log(`cyrb53('revenue', 2) -> ${cyrb53('revenue', 2)}`)console.log(`cyrb53('revenue', 3) -> ${cyrb53('revenue', 3)}`)

*它与著名的MurMurHash/xxHash算法大致相似。它使用乘法和XorShift的组合来生成哈希,但不是那么彻底。因此它比JavaScript中的任何一个都快,实现起来也简单得多,但可能无法通过SMHasher中的所有测试。这不是一个加密哈希函数,所以不要将其用于安全目的。

像任何正确的哈希一样,它有雪崩效应,这基本上意味着输入的小变化会导致输出的大变化,从而使生成的哈希看起来更“随机”:

"501c2ba782c97901" = cyrb53("a")"459eda5bc254d2bf" = cyrb53("b")"fbce64cc3b748385" = cyrb53("revenge")"fb1d85148d13f93a" = cyrb53("revenue")

您可以选择为相同输入的备用流提供种子(无符号整数,最大32位):

"76fee5e6598ccd5c" = cyrb53("revenue", 1)"1f672e2831253862" = cyrb53("revenue", 2)"2b10de31708e6ab7" = cyrb53("revenue", 3)

从技术上讲,它是一个64位哈希,即并行计算的两个不相关的32位哈希,但JavaScript仅限于53位整数。如果方便,可以通过使用十六进制字符串或数组更改返回语句来使用完整的64位输出。

return [h2>>>0, h1>>>0];// orreturn (h2>>>0).toString(16).padStart(8,0)+(h1>>>0).toString(16).padStart(8,0);// orreturn 4294967296n * BigInt(h2) + BigInt(h1);

请注意,构造十六进制字符串会大大减慢批次处理作业。数组的效率要高得多,但显然需要两次检查而不是一次检查。我还包含了BigInt,它应该比String略快,但仍然比ArrayNumber慢得多。


只是为了好玩,这是TinySimpleHash,我能想到的最小的哈希仍然不错。它是89个字符中的32位哈希,随机性甚至比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}

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

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

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

添加这个是因为还没有人这样做,这似乎被要求并用哈希实现了很多,但它总是做得很差…

这需要一个字符串输入和您希望哈希相等的最大数字,并根据字符串输入生成一个唯一的数字。

您可以使用它在图像数组中生成唯一索引(如果您想为用户返回一个特定的头像,随机选择,但也根据他们的名字选择,因此它将始终分配给具有该名称的人)。

当然,您也可以使用它来返回颜色数组的索引,例如根据某人的姓名生成唯一的头像背景颜色。

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

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

这将基于传入的任意数量的参数生成一致的哈希:

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

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

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

UUIDv3和UUIDv5实际上是给定输入字符串的哈希。

  • UUIDv3基于MD5,
  • UUIDv5基于SHA-1。

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

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

npm install uuid

要实际生成UUIDv5,您需要一个唯一的命名空间。这个命名空间就像一个种子,应该是一个常量,以确保对于给定的输入,输出总是相同的。具有讽刺意味的是,您应该生成一个UUIDv4作为命名空间。最简单的方法是使用一些在线工具

一旦你有了一个命名空间,你就一切就绪了。

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

Jenkins一个在一个时间哈希相当不错:

//Credits (modified code): Bob Jenkins (http://www.burtleburtle.net/bob/hash/doobs.html)//See also: https://en.wikipedia.org/wiki/Jenkins_hash_function//Takes a string of any size and returns an avalanching hash string of 8 hex characters.function jenkinsOneAtATimeHash(keyString){let hash = 0;for (charIndex = 0; charIndex < keyString.length; ++charIndex){hash += keyString.charCodeAt(charIndex);hash += hash << 10;hash ^= hash >> 6;}hash += hash << 3;hash ^= hash >> 11;//4,294,967,295 is FFFFFFFF, the maximum 32 bit unsigned integer value, used here as a mask.return (((hash + (hash << 15)) & 4294967295) >>> 0).toString(16)};

示例:

jenkinsOneAtATimeHash('test')"31c25ec1"jenkinsOneAtATimeHash('a')"ca2e9442"jenkinsOneAtATimeHash('0')"6e3c5c6b"

您还可以删除末尾的.toString(16)部分以生成数字:

jenkinsOneAtATimeHash2('test')834821825jenkinsOneAtATimeHash2('a')3392050242jenkinsOneAtATimeHash2('0')1849449579

请注意,如果您不需要专门散列字符串关键,但只需要凭空生成的散列,您可以使用

window.crypto.getRandomValues(new Uint32Array(1))[0].toString(16)

示例:

window.crypto.getRandomValues(new Uint32Array(1))[0].toString(16)"6ba9ea7"window.crypto.getRandomValues(new Uint32Array(1))[0].toString(16)"13fe7edf"window.crypto.getRandomValues(new Uint32Array(1))[0].toString(16)"971ffed4"

与上述相同,您可以删除末尾的'. toString(16)部分以生成数字:

window.crypto.getRandomValues(new Uint32Array(1))[0]1154752776window.crypto.getRandomValues(new Uint32Array(1))[0]3420298692window.crypto.getRandomValues(new Uint32Array(1))[0]1781389127

备注:您还可以使用此方法一次生成多个值,例如:

window.crypto.getRandomValues(new Uint32Array(3))Uint32Array(3) [ 2050530949, 3280127172, 3001752815 ]

function hashCode(str) {return str.split('').reduce((prevHash, currVal) =>(((prevHash << 5) - prevHash) + currVal.charCodeAt(0))|0, 0);}
// Testconsole.log("hashCode(\"Hello!\"): ", hashCode('Hello!'));