สร้างแฮชจากสตริงใน Javascript


585

ฉันต้องการแปลงสตริงเป็นแฮชบางรูปแบบ เป็นไปได้ใน JavaScript?

ฉันไม่ได้ใช้ภาษาฝั่งเซิร์ฟเวอร์ดังนั้นฉันจึงไม่สามารถทำเช่นนั้นได้


7
MD5 ไม่ปลอดภัยดังนั้นอย่ามองหาอันนั้น
henrikstroem

166
@henrikstroem ขึ้นอยู่กับสิ่งที่คุณกำลังคร่ำครวญ; ไม่มีอะไรผิดปกติกับการใช้ md5 เพื่อแฮชเพื่อจุดประสงค์ด้านความปลอดภัย
แบรดโคช์

7
@BradKoch ขึ้นอยู่กับสิ่งที่คุณทำ; ไม่มีอะไรผิดปกติในการใช้ md5 เพื่อความปลอดภัย มีวิธีที่ดีกว่าสำหรับการแฮ็ชรหัสผ่าน แต่ md5 นั้นใช้ได้สำหรับการทำสิ่งต่าง ๆ เช่นการเซ็นชื่อ URL
Paul Ferrett

81
ฉันคิดว่ามันตลกว่าในขณะที่ MD5 ถูกวิพากษ์วิจารณ์ในความคิดเห็นที่นี่คำตอบเกือบทั้งหมดแนะนำอัลกอริธึมการแฮ็กที่แย่กว่านั้นและรับ upvotes มากมาย
domen

38
การใช้ MD5 เพื่อตรวจสอบว่าการดาวน์โหลดนั้นไม่เกิดขึ้นจริงจะไม่ส่งอีเมลรหัสผ่านของคุณไปให้เพื่อนร่วมงานของคุณทั้งหมด
James M. Lay

คำตอบ:


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

ที่มา: http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/


22
นี่เป็นสิ่งเดียวกับที่ใช้ใน Java hash << 5 - hashเป็นเช่นเดียวhash * 31 + charแต่เร็วมาก มันดีเพราะมันเร็วมากและ 31 ก็เป็นไพรม์เล็ก ๆ ชนะที่นั่น
corsiKa

41
ฉันทำการทดสอบสองสามครั้งกับ jsperf ( jsperf.com/hashing-strings ) และฟังก์ชั่น bitwise นั้นช้ากว่าฟังก์ชั่นพื้นฐานที่ใช้จำนวนมาก
skerit

17
@PeterAronZentai ทำไม "ไม่ใช้งาน" ผลลัพธ์ที่ผลิตโดยรหัสตามตัวเลข(hash * 31) + charจะเหมือนกับผลลัพธ์ที่ผลิตโดยรหัส shift-based ((hash<<5)-hash)+charแม้สำหรับสายยาวมาก (ฉันได้ทดสอบกับสตริงที่มีมากกว่าล้านตัวอักษร) ดังนั้นจึงไม่ใช่ "ใช้ไม่ได้" ในแง่ ของความถูกต้อง ความซับซ้อนคือ O (n) สำหรับทั้งเวอร์ชันที่อิงตัวเลขและ shift-based ดังนั้นจึงไม่ใช่ "ใช้ไม่ได้" ในแง่ของความซับซ้อน
TachyonVortex

13
ทุกคนสามารถแสดงความคิดเห็นเกี่ยวกับเอกลักษณ์ (หรือไม่) ของการส่งออก? โดยเฉพาะถ้าฉันใช้แฮชนี้เฉพาะกับสตริงที่มีความยาวน้อยกว่าnสิ่งที่ใหญ่ที่สุดnที่ฉันไม่สามารถชนได้คืออะไร
Don McCurdy

34
มีเหตุผลใดที่ความต้องการ (หรือควร) นี้อยู่ในต้นแบบของ String หรือไม่? มันจะมีประสิทธิภาพ / มีประสิทธิภาพน้อยกว่าที่จะมีเช่น; var hashCode = function hashCode (str) {etc...}? แล้วใช้เป็นhashCode("mystring")?
rattray

146

แก้ไข

จากการทดสอบ 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);              
}

ฟังก์ชั่นลูกศรแบบหนึ่งซับ:

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

3
มีวิธีการรับแฮชซึ่งเป็นจำนวนบวกเท่านั้นหรือไม่
ผู้ซื้อขาย Prosto

46
แปลก. ฉันเพิ่งทดสอบและกลายเป็น waaay ช้ากว่าคำตอบที่ยอมรับ jsperf.com/hashcodelordvlad
lordvlad

113
คนดี @lordvlad ทดสอบคำตอบของเขาเองแล้วรายงานเมื่อมันช้าลง
mikemaccana

9
ฉันเพิ่งตระหนัก: มันทำให้รู้สึกดีว่าคำตอบที่ได้รับการยอมรับเป็นเร็วขึ้นเพราะรุ่นของฉันมีการเปิดสตริงเป็นอาร์เรย์แรกจัดสรรหน่วยความจำใหม่และคัดลอกตัวละครทุกตัว ...
lordvlad

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

108

หมายเหตุ:แม้จะมีแฮชขนาด 32 บิตที่ดีที่สุดการชนก็จะเกิดขึ้นไม่ช้าก็เร็ว

ความน่าจะเป็นของการชนกันของแฮชสามารถคำนวณได้ว่าเป็น 1 - e ^ (-k (k-1) / 2Nประมาณ k ^ 2 / 2N ( ดูที่นี่ ) สิ่งนี้อาจสูงกว่าสัญชาตญาณแนะนำ:
สมมติว่ามีแฮชแบบ 32 บิตและ k = 10,000 รายการการชนจะเกิดขึ้นโดยมีความน่าจะเป็น 1.2% สำหรับตัวอย่าง 77,163 ความน่าจะเป็น 50%! ( เครื่องคิดเลข )
ฉันแนะนำวิธีแก้ปัญหาที่ด้านล่าง

ในคำตอบสำหรับคำถามนี้ อัลกอริทึมการแฮชแบบใดดีที่สุดสำหรับความเป็นเอกลักษณ์และความเร็ว Ian Boyd โพสต์การวิเคราะห์เชิงลึกที่ดี ในระยะสั้น (ในขณะที่ฉันตีความมัน) เขาสรุปว่า Murmur นั้นดีที่สุดตามด้วย FNV-1a
String.hashCode () อัลกอริทึมของ Java ที่ esmiralha เสนอนั้นน่าจะเป็นตัวแปรของ 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- บิตกัญชา / 3

ดังนั้นโดยทั่วไปฉันจะแนะนำ murmur3
ดูที่นี่สำหรับการใช้งาน 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 string
        return ("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)
}

ใช้ด้วยความระมัดระวังและอย่าคาดหวังมากเกินไป


ทำไมคุณจะทำอย่างไร("0000000" + (hval >>> 0).toString(16)).substr(-8);? ไม่เหมือนกัน(hval >>> 0).toString(16)ใช่ไหม
Manuel Meurer

3
การเพิ่มนี้นำหน้า '0' เพื่อให้แฮชที่ได้นั้นมีความยาว 8 ตัวอักษรเสมอ ง่ายต่อการอ่านและรับรู้ในผลลัพธ์ แต่นั่นเป็นความคิดเห็นส่วนตัวของฉัน
mar10

อ่าฉันเข้าใจแล้ว สำหรับธุรกิจขนาดเล็กhval, (hval >>> 0).toString(16)อาจจะน้อยกว่า 8 ตัวอักษรเพื่อให้คุณแผ่นกับศูนย์ ฉันสับสนเพราะ(hval >>> 0).toString(16)ส่งผลให้เกิดสตริงอักขระ 8 ตัวสำหรับฉันเสมอ
Manuel Meurer

3
ฉันชอบคำตอบนี้เพราะทำให้แฮชที่กระจายตัวดีกว่ามาก: ฟังก์ชั่นอื่น ๆ ที่เสนอที่นี่จะทำให้ค่าแฮชตามมา เช่น `hash (" example1 ") - hash (" example2 ") == 1" ในขณะที่อันนี้คาดเดาไม่ได้มากขึ้น
GavinoGrifoni

1
ในการตอบสนองต่อ "FNV-1a มีการกระจายที่ดีกว่า DJB2 แต่ช้ากว่า" - ฉันคิดว่าน่าจะกล่าวได้ว่า FNV1a นั้นเร็วมากเมื่อใช้งานด้วยMath.imulฟังก์ชันES6 เพียงอย่างเดียวที่ทำให้มันเป็นมาตรฐานชั้นนำและในที่สุดก็เป็นทางเลือกที่ดีกว่า DJB2 ในระยะยาว
bryc

64

อ้างอิงจากคำตอบที่ยอมรับใน ES6 ขนาดเล็กบำรุงรักษาและใช้งานได้ในเบราว์เซอร์ที่ทันสมัย

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

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

แก้ไข (2019-11-04) :

ฟังก์ชั่นลูกศรแบบหนึ่งซับ:

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

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


1
ขอบคุณที่แบ่งปันฉันเพิ่มstr += ""ก่อนที่ hashing เพื่อหลีกเลี่ยงข้อstr.split is not a functionผิดพลาดเกิดขึ้นเมื่อไม่มีการส่งสตริงเป็นพารามิเตอร์
BeetleJuice

4
แต่มากช้ากว่าสิ่งเหล่านี้มาก: https://jsperf.com/hashing-strings
AndyO

ฉันเพิ่งสังเกตเห็นว่าวิธีการแก้ปัญหา "ย้อนยุค" ที่เร็วที่สุดนั้นเล็กลงเช่นกันถ้าคุณลบตัวดึงข้อมูลบรรทัดเพื่อให้มีความยาวเพียง 3 บรรทัด
AndyO

2
มีวิธีใดที่จะให้ผลลัพธ์นี้เป็นผลในเชิงบวก แต่ยังคงไม่เหมือนใคร
Dids

3
@deekshith คำตอบที่ยอมรับใช้hash |= 0ในการแปลงเป็น 32 บิต การใช้งานนี้ไม่ได้ นี่เป็นข้อบกพร่องหรือไม่?
Sukima

48

เกือบครึ่งคำตอบคือการใช้งานของ Java String.hashCodeซึ่งไม่ได้คุณภาพสูงหรือเร็วสุด มันไม่มีอะไรพิเศษเกินไปมันแค่คูณ 31 ด้วยตัวละครแต่ละตัว สามารถใช้งานได้ง่ายและมีประสิทธิภาพในหนึ่งบรรทัดและเร็วขึ้นด้วยMath.imul:

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

เมื่อพ้นทางแล้วนี่คือสิ่งที่ดีกว่า - cyrb53แฮช 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);
};

คล้ายกับอัลกอริธึม MurmurHash / xxHash ที่รู้จักกันดีโดยใช้การรวมกันของการคูณและXorshiftเพื่อสร้างแฮช แต่ไม่ได้ละเอียด ผลที่ตามมาก็คือเร็วกว่าทั้งใน JavaScript และง่ายต่อการใช้

มันบรรลุผลถล่ม (ไม่เข้มงวด) ซึ่งโดยทั่วไปหมายถึงการเปลี่ยนแปลงเล็กน้อยในอินพุตมีการเปลี่ยนแปลงครั้งใหญ่ในเอาต์พุตทำให้แฮชผลลัพธ์ปรากฏขึ้นแบบสุ่ม:

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 บิต (แฮชแบบ 32 บิตที่ไม่ได้เชื่อมโยงกันสองตัวในแนวขนาน) แต่จาวาสคริปต์นั้นถูก จำกัด ไว้ที่จำนวนเต็ม 53 บิต หากจำเป็นต้องใช้เอาต์พุต 64- บิตเต็มรูปแบบที่ยังคงสามารถใช้งานได้โดยการเปลี่ยนบรรทัดส่งคืนสำหรับสตริง hex หรืออาร์เรย์

โปรดทราบว่าการสร้างสตริง hex สามารถชะลอการประมวลผลแบทช์อย่างมากในสถานการณ์ที่มีความสำคัญต่อประสิทธิภาพ

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

และเพื่อความสนุกนี่คือแฮชแบบ 32 บิตขั้นต่ำใน89 ตัวอักษรที่มีคุณภาพสูงกว่าแม้แต่ 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}

4
ว้าวนี่ดีกว่าปกติ * 31 หนึ่งสำหรับอินพุตสั้น (หรือคล้ายกัน) :)
lapo

2
จะchเริ่มต้นที่ไหน
hellowill89

3
@ hellowill89 woops ฉันลืมที่จะประกาศและมีเลือดออกในขอบเขตทั่วโลก ได้รับการแก้ไขแล้วขอบคุณ: ')
bryc

ล้มเหลวสำหรับ IE 11: 'imul'วัตถุไม่สนับสนุนคุณสมบัติหรือวิธีการ
BachT

2
@BachT คุณสามารถใช้polyfillหรือเต็มชิม ES6 แต่ IE11 นั้นหยุดยั้งอนาถในปี 2009 โดยไม่มีการอัพเดท
bryc

28

ถ้ามันช่วยทุกคนฉันรวมคำตอบสองข้อแรกเข้ากับเวอร์ชันที่ทนต่อเบราว์เซอร์ที่เก่ากว่าซึ่งใช้เวอร์ชันที่รวดเร็วถ้า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();

วิธีปรับรหัสนี้ให้ทำงานเร็วขึ้นในทุกเบราว์เซอร์ 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

นี่คือตัวแปรที่ได้รับการปรับปรุงและมีประสิทธิภาพดีกว่า:

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

สนุก!


2
คำตอบที่ดี แต่วัตถุประสงค์ของ << 0 คืออะไร
koolaang

8
@koolaang เป็นผู้ดำเนินการอึที่เหลือdeveloper.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
mmm

29
@momomo คุณหมายถึงกะซ้ายหรือเปล่า
wdh

2
@ โมโมโมะฉันคิดว่าเขาถามว่าทำไมมันถึงเปลี่ยนเป็นศูนย์ซ้าย
jpfx1342

3
@Maykonn (2 ^ 32 - 1)
Nijraj Gelani

24

ฉันประหลาดใจเล็กน้อยที่ไม่มีใครพูดถึง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
ฉันเห็นด้วย. การแปลงเป็นฐานสิบหกสามารถทำได้แตกต่างกันเล็กน้อย ...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
ฟังก์ชั่นแฮชการเข้ารหัสสำหรับสตริงนั้นเกินความcryptoเป็นไปได้เล็กน้อย ..
bryc

คุณภาพที่เชื่อถือได้แบบสุ่มโดยไม่ต้องพึ่งพาคนที่ใช้การทดสอบในตัว (ไม่จำเป็นต้องใช้งานแบบกำหนดเอง) seedable และฉันต้องการตัวเลขสองสามร้อยเท่านั้นในการสร้างแผนที่เกมดูเหมือนจะสมบูรณ์แบบ แต่มันกลับกลายเป็นว่าไม่มีทางที่จะทำแบบซิงโครนัส ต้องให้การเรียกกลับแบบอะซิงค์บางครั้งทุกครั้งที่คุณเรียกเอ็นจินการสุ่มของคุณทำให้โค้ดไม่สามารถอ่านได้และดูไร้สาระ ฉันไม่เข้าใจว่าใครมากับอินเทอร์เฟซ crypto.subtle เส็งเคร็งดังนั้นในที่สุดฉันต้องไปกับ xmur3 + sfc32 จากคำตอบนี้: stackoverflow.com/a/47593316/1201863
Luc

7

ขอบคุณตัวอย่างจาก mar10 ฉันพบวิธีที่จะได้ผลลัพธ์เดียวกันใน C # AND Javascript สำหรับ FNV-1a หากมีตัวอักษร unicode อยู่ส่วนบนจะถูกยกเลิกเพื่อประสิทธิภาพ ไม่ทราบว่าทำไมจึงเป็นประโยชน์ในการรักษาผู้ที่เมื่อ hashing เป็นเพียง hashing เส้นทาง 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);
}

@mathiasrw เป็นไปได้ที่อักขระ Unicode จะเกิน 8 บิตในหน่วยความจำดังนั้นฉันจึงถือว่า 0xFF เพียงแค่ปิดบังอะไรก็ตามที่อยู่นอกช่วงนั้น ดูเพิ่มเติมเกี่ยวกับ charCodeAt () ที่นี่: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
djabraham

หาก ES6 ใช้ได้ (ทุกเครื่องมือที่ทันสมัยรองรับ) Math.imulสามารถใช้สำหรับขั้นตอนการคูณซึ่งช่วยเพิ่มประสิทธิภาพการทำงาน ปัญหาเดียวคือมันจะไม่ทำงานใน IE11 โดยไม่ต้องชิม
bryc

6

รวดเร็วและรัดกุมซึ่งดัดแปลงมาจากที่นี่ :

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

5

ฉันต้องการฟังก์ชั่นที่คล้ายกัน (แต่แตกต่างกัน) เพื่อสร้าง 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: สำหรับรหัสใหม่ฉันใช้ shortid: https://www.npmjs.com/package/shortid


2
@ t0r0X ดีตอนนี้ผมใช้โมดูลที่เรียกว่า shortid: npmjs.com/package/shortid
jcollum

1
คุณใช้ชื่อผู้ใช้กับชอร์ตอย่างไร? มันก็ดูเหมือนว่าจะสร้างรหัส แต่ผมไม่เห็นว่าคุณกำลังใช้ในการสร้างกัญชาจากสตริง
cyberwombat

1
คำตอบนี้มี 3 โหวต สำหรับชีวิตของฉันฉันไม่สามารถจินตนาการได้ว่าทำไม ไม่มีใครพูดอะไรเลย ... : - /
jcollum

1
@ jcollum นั่นเป็นเหตุผลที่ฉันแทบจะไม่เคยตอบคำถามค้างเลย .. ไม่มีใครสังเกตเห็นเลย แม้หลังจากที่คุณแก้ไขคำตอบแล้วก็ไม่มีใครเข้ามาหาสมดุล
bryc

5

หนึ่งซับ (ยาวมาก) ของฉันเร็วโดยใช้Multiply+Xorวิธีการของ FNV :

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

5

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


สิ่งนี้แตกต่างจากคำตอบของไคโด้สองปีก่อนหน้าคุณไหม
Luc

@Luc มันไม่ชัด
КонстантинВан

3

ฉันค่อนข้างช้าไปงานปาร์ตี้ แต่คุณสามารถใช้โมดูลนี้: crypto :

const crypto = require('crypto');

const SALT = '$ome$alt';

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

ผลลัพธ์ของฟังก์ชันนี้คือ64สตริงอักขระเสมอ บางสิ่งเช่นนี้"aa54e7563b1964037849528e7ba068eb7767b1fab74a8d80fe300828b996714a"


2

ฉันได้รวมทั้งสองโซลูชัน (ผู้ใช้ 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();

2

หากคุณต้องการหลีกเลี่ยงการชนคุณอาจต้องการใช้แฮชที่ปลอดภัยเช่นSHA-256 SHA-256มีการปรับใช้ JavaScript SHA-256 หลายอย่าง

ฉันเขียนการทดสอบเพื่อเปรียบเทียบการปรับใช้แฮชหลายตัวให้ดูที่https://github.com/brillout/test-javascript-hash-implementations https://github.com/brillout/test-javascript-hash-implementations

หรือไปที่ http://brillout.github.io/test-javascript-hash-implementations/เพื่อทำการทดสอบ


1
การใช้แฮชการเข้ารหัสที่ปลอดภัยอาจช้ามาก การหลีกเลี่ยงการชนเป็นผลิตภัณฑ์ที่มีความกว้างบิตไม่ใช่ความปลอดภัย แฮชที่ไม่ใช่การเข้ารหัสลับ 128 บิตหรือแม้แต่ 64 บิตควรมากกว่าเพียงพอสำหรับวัตถุประสงค์ส่วนใหญ่ MurmurHash3_x86_128ค่อนข้างเร็วและมีโอกาสชนน้อยมาก
bryc

2

นี่ควรเป็นแฮชที่มีความปลอดภัยมากกว่าคำตอบอื่น ๆ แต่ในฟังก์ชั่นโดยไม่มีแหล่งที่โหลดไว้ล่วงหน้า

ฉันสร้างsha1 เวอร์ชันย่อแบบย่อ
คุณใช้จำนวนไบต์ของสตริงและจัดกลุ่มตาม "คำ" 4 ถึง 32 บิต
จากนั้นเราจะขยายทุก 8 คำเป็น 40 คำ (เพื่อให้ได้ผลลัพธ์ที่ดีขึ้น)
สิ่งนี้จะไปที่ฟังก์ชั่นการแฮช (การลดครั้งสุดท้าย) ซึ่งเราทำการคำนวณทางคณิตศาสตร์กับสถานะปัจจุบันและอินพุต เรามักจะได้ 4 คำออกมา
นี่เป็นเวอร์ชันเกือบหนึ่งคำสั่ง / หนึ่งบรรทัดโดยใช้แผนที่ลด ... แทนที่จะเป็นลูป แต่ก็ยังค่อนข้างเร็ว

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"


1

ฉันไปเพื่อการเรียงต่อกันแบบง่ายของรหัส char ที่แปลงเป็นสตริงฐานสิบหก สิ่งนี้ทำหน้าที่ในวัตถุประสงค์ที่ค่อนข้างแคบนั่นคือเพียงแค่ต้องการการแทนค่าแฮชของสตริง 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('');
}

สิ่งนี้สามารถทำเพิ่มเติมสั้นและทนต่อเบราว์เซอร์ด้วยขีดล่าง ตัวอย่าง:

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

ฉันคิดว่าถ้าคุณต้องการแฮชสตริงขนาดใหญ่ในแบบเดียวกันคุณก็สามารถลดรหัส char และทำให้ผลรวมที่เกิดขึ้นไม่ใช่การรวมตัวอักขระแต่ละตัวเข้าด้วยกัน:

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"

มีความเสี่ยงต่อการชนกันมากขึ้นด้วยวิธีนี้แม้ว่าคุณจะเล่นซอกับเลขคณิตในการลดลง แต่คุณต้องการกระจายและยืดแฮชให้ยาวขึ้น


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

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

-1

ฉันไม่เห็นเหตุผลใด ๆ ที่จะใช้รหัส crypto ที่ซับซ้อนแทนการใช้โซลูชันที่พร้อมใช้งานเช่นไลบรา Object-hash หรืออื่น ๆ การพึ่งพาผู้ขายนั้นมีประสิทธิผลมากกว่าประหยัดเวลาและลดค่าบำรุงรักษา

เพียงใช้https://github.com/puleos/object-hash

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

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

ซอร์สโค้ดของ lib นั้นไม่สามารถอ่านได้ .. เพียงแค่ 50k ของโค้ดย่อ
bryc

1
@bryc เป็นวิธีที่รหัสผู้ขายควรมีลักษณะ :) และสำหรับแหล่งที่มาคุณสามารถตรวจสอบgithub.com/puleos/object-hash/blob/master/index.js
Oleg Abrazhaev

รหัสย่อคือ 35.4 KB ในขณะที่แหล่งที่มาเต็มรูปแบบคือ 14.2 KB? มันไม่สมเหตุสมผลเลย
bryc

2
@bryc คุณพิจารณาบรรทัดนี้แล้วหรือยัง var crypto = require('crypto');. ฉันคิดว่ามันเพิ่มรหัสอ้างอิงนี้จากผู้จำหน่ายในเวอร์ชันย่อระหว่างการสร้าง
Oleg Abrazhaev

หากคุณต้องการแฮช Object จริงๆฉันเขียนany-serializeเพื่อซีเรียลไลซ์ ANY Object ด้วยคีย์เรียงจากนั้นcyrb53เพื่อสร้างแฮช base36
Polv
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.