Programming

Javascript의 문자열에서 해시 생성

procodes 2020. 2. 11. 22:41
반응형

Javascript의 문자열에서 해시 생성


문자열을 해시 형태로 변환해야합니다. 이것이 JavaScript에서 가능합니까?

서버 측 언어를 사용하지 않아서 그렇게 할 수 없습니다.


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/


편집하다

내 jsperf 테스트를 기반으로 허용되는 답변이 실제로 더 빠릅니다. 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);              
}

참고 : 최상의 32 비트 해시라도 조만간 충돌 발생합니다.

해시 충돌 probablility은 다음과 같이 계산 될 수 1-e ^ (-k (k-1) / 2N로 aproximated, k ^ 2 / 2N( 여기 참조 ). 이는 직감이 제안한 것보다 높을 수 있습니다
. 32 비트 해시와 k = 10,000 항목을 가정하면 확률 1.2 %로 충돌이 발생합니다. 77,163 개의 샘플에서 확률은 50 %가됩니다! ( 계산기 ).
하단에 해결 방법을 제안합니다.

이 질문에 대한 답변으로 고유성과 속도에 가장 적합한 해싱 알고리즘은 무엇입니까? Ian Boyd는 심층 분석 결과를 올렸다 . 한마디로 (내가 해석 한대로), 그는 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- 비트 해시 / 3

따라서 일반적으로 murmur3을 권장합니다.
JavaScript 구현에 대해서는 여기를 참조하십시오 : https://github.com/garycourt/murmurhash-js

입력 문자열이 짧고 분배 품질보다 성능이 더 중요한 경우, esmiralha의 승인 된 답변에서 제안한대로 DJB2를 사용하십시오.

품질과 작은 코드 크기가 속도보다 중요한 경우 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)
}

조심해서 사용하고 너무 많이 기대하지 마십시오.


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


누군가에게 도움이된다면 상위 2 가지 답변을 구식 브라우저 허용 버전으로 결합했습니다.이 버전 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();

답변의 거의 절반은 Java 구현으로 String.hashCode, 고품질도 빠르지도 않습니다. 너무 특별하지는 않습니다. 매번 31로 배수됩니다. 한 줄로 효율적으로 구현할 수 있으며 다음과 같이 훨씬 빠릅니다 Math.imul.

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

여기 간단하고 잘 분산 된 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 비트 해시 (2 개의 상관되지 않은 32 비트 해시 병렬)이지만 JavaScript는 53 비트 정수로 제한됩니다. 16 진 문자열 또는 배열의 리턴 라인을 변경하여 전체 64 비트를 계속 사용할 수 있습니다.

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

캐치는 16 진수 문자열을 구성하면 성능의 병목 현상이되고 배열에는 하나 대신 두 개의 비교 연산자가 필요하므로 편리하지 않습니다. 따라서 고성능 응용 프로그램에 사용할 때 명심하십시오.


FNV / DJB2 / SMDB를 여전히 능가하는 89 개의 문자로 된 최소 32 비트 해시가 있습니다.

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

이것은 세련되고 성능이 뛰어난 변형입니다.

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

즐겨!


아직 새로운 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);
  });


mar10의 예제 덕분에 FNV-1a에 대해 C # AND Javascript에서 동일한 결과를 얻는 방법을 찾았습니다. 유니 코드 문자가 있으면 성능 향상을 위해 상단 부분이 삭제됩니다. 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());
}

자바 스크립트 버전

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

사용자 이름과 현재 시간을 기반으로 고유 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


여기 에서 적응 한 빠르고 간결한 것 :

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

FNV의 Multiply+Xor방법을 기반으로 한 빠른 (매우 긴) 라이너 :

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

나는 두 가지 솔루션 (사용자 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();

충돌을 피하려면 SHA-256 과 같은 보안 해시 를 사용하십시오 . 몇 가지 JavaScript SHA-256 구현이 있습니다.

여러 해시 구현을 비교하는 테스트를 작성했습니다 ( https://github.com/brillout/test-javascript-hash-implementations 참조) .

또는 http://brillout.github.io/test-javascript-hash-implementations/ 로 이동 하여 테스트를 실행하십시오.


나는 파티에 늦었지만이 모듈을 사용할 수 있습니다 : crypto :

const crypto = require('crypto');

const SALT = '$ome$alt';

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

이 함수의 결과는 항상 64문자열입니다. 이 같은:"aa54e7563b1964037849528e7ba068eb7767b1fab74a8d80fe300828b996714a"


16 진수 문자열로 변환 된 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('');
}

Underscore를 사용하면 더 간결하고 브라우저에 잘 견딜 수 있습니다. 예:

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

비슷한 방식으로 더 큰 문자열을 해시하려는 경우 개별 문자를 함께 연결하는 대신 문자 코드를 줄이고 결과 합계를 16 진수로 만들 수 있다고 가정합니다.

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"

자연스럽게이 방법과 충돌 할 위험이 있지만 감소의 산술을 피할 수는 있지만 해시를 다양 화하고 길게하고 싶었습니다.


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

아직 아무도 없기 때문에 이것을 추가하면 해시로 많은 것을 요청하고 구현하는 것처럼 보이지만 항상 매우 좋지 않습니다.

이것은 문자열 입력과 해시와 같을 최대 수를 취하고 문자열 입력에 따라 고유 한 숫자를 생성합니다.

이를 사용하여 이미지 배열로 고유 한 인덱스를 생성 할 수 있습니다 (사용자에 대해 특정 아바타를 반환하고 임의로 선택했지만 이름을 기준으로 선택한 아바타는 항상 해당 이름을 가진 사람에게 할당됩니다) ).

물론이 이름을 사용하여 다른 사람의 이름을 기반으로 고유 한 아바타 배경색을 생성하는 것과 같이 색인을 여러 색상으로 반환 할 수도 있습니다.

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


객체 해시 라이브러리 등과 같은 즉시 사용 가능한 솔루션 대신이 복잡하게 암호화 된 암호화 코드를 사용해야하는 이유는 없습니다. 공급 업체에 의존하는 것이보다 생산적이고 시간을 절약하며 유지 보수 비용을 절감합니다.

https://github.com/puleos/object-hash를 사용 하십시오.

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

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

참고 URL : https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript



반응형