首页 文章

在Javascript中从字符串生成哈希

提问于
浏览
426

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

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

18 回答

  • 74

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

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

    如果你想避免碰撞,你可能想要使用secure hash,如SHA-256 . 有几种JavaScript SHA-256实现 .

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

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

  • 104
    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/

  • 6

    一个快速简洁的改编自here

    String.prototype.hashCode = function() {
      var hash = 5381, i = this.length
      while(i)
        hash = (hash * 33) ^ this.charCodeAt(--i)
      return hash >>> 0;
    }
    
  • 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;
    }
    
  • 3

    EDIT

    基于我的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);              
    }
    
  • 3

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

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

    使用此解决方案,我们可以指定字符集,以避免在应用程序层之间存储或发送值时出现一些问题,例如:当结果字符串(哈希)产生百分比编码并且该字符串使用视图中的GET方法发送到控制器时层 .

    function doHashCode() {
        String.prototype.hashCode = function () {
            var text = "";
            var possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    
            for (var i = 0; i < 15; i++)
                text += possible.charAt(Math.floor(Math.random() * possible.length));
            return text;
        }
    
        var hash = new String().hashCode();
        $('#input-text-hash').val(hash); // your html input text
    
    }
    
  • 2

    我有点惊讶没人谈到新的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);
      });
    
  • 4

    多亏了mar10的例子,我找到了一种方法,可以在C#和Javascript中为FNV-1a获得相同的结果 . 如果存在unicode字符,则为了性能而丢弃上部 . 不知道为什么在散列时维护它们会有所帮助,因为现在只有哈希url路径 .

    C# Version

    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 Version

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

    我需要一个类似的功能(但不同)来根据用户名和当前时间生成唯一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

  • 14

    Note: 即使使用最好的32位散列,您也必须处理迟早会发生冲突的事实 . 即两个不同的输入字符串将返回相同的哈希值,概率至少为1:2 ^ 32 .

    在回答这个问题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

    So in general I would recommend 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;
    }
    
  • 1

    我去了一个简单的串联转换为十六进制字符串的字符串 . 这用于相对狭窄的目的,即仅需要与服务器端交换的SHORT字符串的散列表示(例如 Headers ,标签),出于不相关的原因,该服务器端不能容易地实现所接受的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中使用算法,但是你想要多样化并延长散列 .

  • 0

    我有点迟到了,但你可以使用这个模块:crypto

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

    这个函数的结果总是 64 个字符串;像这样的东西: "aa54e7563b1964037849528e7ba068eb7767b1fab74a8d80fe300828b996714a"

  • 625

    如果它可以帮助任何人,我将前两个答案组合成一个较旧的浏览器容忍版本,如果 reduce 可用则使用快速版本并且不会回到esmiralha 's solution if it' .

    /**
     * @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();
    
  • 3

    这是一个精致且性能更好的变体:

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

    请享用!

  • 24

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

    这是一个简单,分布均匀的53位哈希 . 它速度非常快,碰撞率低 .

    var cyrb53 = function(str) {
        var p1 = 2654435761, p2 = 1597334677, h1 = 0xdeadbeef | 0, h2 = 0x41c6ce57 | 0;
        for (var i = 0; i < str.length; i++)
            ch = str.charCodeAt(i), h1 = Math.imul(h1 + ch, p1), h2 = Math.imul(h2 + ch, p2);
        h1 = Math.imul(h1 ^ h1 >>> 16, p2), h2 = Math.imul(h2 ^ h2 >>> 15, p1);
        return (h2 & 2097151) * 4294967296 + h1;
    };
    

    它使用类似于xxHash / MurmurHash的技术,但不是那么彻底 . 它实现了雪崩(非严格),因此输入中的微小变化会使输出发生很大变化,使其看起来是随机的:

    0xe00c44e568f86 = "a"
    0x893099dbedf04 = "b"
    0x98f3f59367810 = "revenge"
    0x45f880d099bbf = "revenue"
    

    53位是JS整数的限制,并且具有比32位哈希小得多的碰撞机会 . 但是如果53位对你来说还不够,你仍然可以通过构造十六进制字符串或数组来使用所有64位:

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

    问题是,构造十六进制字符串成为性能瓶颈,并且数组需要两个比较运算符而不是一个,这不方便 .

相关问题