【问题标题】:How can I hash a string with SHA256 in JS?如何在 JS 中使用 SHA256 散列字符串?
【发布时间】:2020-05-03 18:28:45
【问题描述】:

说明

我希望在 Javascript 中使用 SHA256 在本地散列一个字符串。 我一直在考虑是否会有某种官方库或功能, 但我发现的只是大量不同的项目,每个项目都有不同的脚本,我不太确定要信任的脚本(因为我不是专家,绝对没有资格评估它们)或如何实现它们。 编辑:我需要文本输出,而不是十六进制,抱歉,如果我在发布原始问题时没有解释这一点。

代码

这是我迄今为止尝试过的:

async function sha256(message) {
  // encode as UTF-8
  const msgBuffer = new TextEncoder('utf-8').encode(message);

  // hash the message
  const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);

  // convert ArrayBuffer to Array
  const hashArray = Array.from(new Uint8Array(hashBuffer));

  // convert bytes to hex string
  const hashHex = hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('');
  console.log(hashHex);
  return hashHex;
}
sha256(passwordInput); 

控制台输出:

Uncaught (in promise) TypeError: Cannot read property 'digest' of undefined

我是 javascript 新手,我愿意接受所有建议,所以是的。

更新

尽管您的大部分建议都有效,但对于那些希望使用 Web Crypto API 的人来说,答案在第 5 行。我需要将crypto.subtle.digest 更改为window.crypto.subtle.digest

【问题讨论】:

  • 您使用的是什么浏览器(包括版本)?
  • @Amy 我使用的是 Google Chrome 版本 79.0.3945.130(64 位),希望对您有所帮助
  • 你是对的 - re window.crypto - 我已经添加了这个作为答案(在我注意到你在更新中写了同样的东西之前)。如果你还在用 StackOverflow,能不能更新一下答案?
  • 请注意,crypto.subtle.digest 不适合您尝试散列大文件(如 1GB+)。目前,您必须求助于 3rd-party 库。

标签: javascript hash sha256


【解决方案1】:

你好 :D 这是一个很好的功能。 如果您是学者,请查看这篇文章:https://www.movable-type.co.uk/scripts/sha256.html

纯javascript,无需依赖:

var sha256 = function sha256(ascii) {
    function rightRotate(value, amount) {
        return (value>>>amount) | (value<<(32 - amount));
    };
    
    var mathPow = Math.pow;
    var maxWord = mathPow(2, 32);
    var lengthProperty = 'length'
    var i, j; // Used as a counter across the whole file
    var result = ''

    var words = [];
    var asciiBitLength = ascii[lengthProperty]*8;
    
    //* caching results is optional - remove/add slash from front of this line to toggle
    // Initial hash value: first 32 bits of the fractional parts of the square roots of the first 8 primes
    // (we actually calculate the first 64, but extra values are just ignored)
    var hash = sha256.h = sha256.h || [];
    // Round constants: first 32 bits of the fractional parts of the cube roots of the first 64 primes
    var k = sha256.k = sha256.k || [];
    var primeCounter = k[lengthProperty];
    /*/
    var hash = [], k = [];
    var primeCounter = 0;
    //*/

    var isComposite = {};
    for (var candidate = 2; primeCounter < 64; candidate++) {
        if (!isComposite[candidate]) {
            for (i = 0; i < 313; i += candidate) {
                isComposite[i] = candidate;
            }
            hash[primeCounter] = (mathPow(candidate, .5)*maxWord)|0;
            k[primeCounter++] = (mathPow(candidate, 1/3)*maxWord)|0;
        }
    }
    
    ascii += '\x80' // Append Ƈ' bit (plus zero padding)
    while (ascii[lengthProperty]%64 - 56) ascii += '\x00' // More zero padding
    for (i = 0; i < ascii[lengthProperty]; i++) {
        j = ascii.charCodeAt(i);
        if (j>>8) return; // ASCII check: only accept characters in range 0-255
        words[i>>2] |= j << ((3 - i)%4)*8;
    }
    words[words[lengthProperty]] = ((asciiBitLength/maxWord)|0);
    words[words[lengthProperty]] = (asciiBitLength)
    
    // process each chunk
    for (j = 0; j < words[lengthProperty];) {
        var w = words.slice(j, j += 16); // The message is expanded into 64 words as part of the iteration
        var oldHash = hash;
        // This is now the undefinedworking hash", often labelled as variables a...g
        // (we have to truncate as well, otherwise extra entries at the end accumulate
        hash = hash.slice(0, 8);
        
        for (i = 0; i < 64; i++) {
            var i2 = i + j;
            // Expand the message into 64 words
            // Used below if 
            var w15 = w[i - 15], w2 = w[i - 2];

            // Iterate
            var a = hash[0], e = hash[4];
            var temp1 = hash[7]
                + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) // S1
                + ((e&hash[5])^((~e)&hash[6])) // ch
                + k[i]
                // Expand the message schedule if needed
                + (w[i] = (i < 16) ? w[i] : (
                        w[i - 16]
                        + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15>>>3)) // s0
                        + w[i - 7]
                        + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2>>>10)) // s1
                    )|0
                );
            // This is only used once, so *could* be moved below, but it only saves 4 bytes and makes things unreadble
            var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) // S0
                + ((a&hash[1])^(a&hash[2])^(hash[1]&hash[2])); // maj
            
            hash = [(temp1 + temp2)|0].concat(hash); // We don't bother trimming off the extra ones, they're harmless as long as we're truncating when we do the slice()
            hash[4] = (hash[4] + temp1)|0;
        }
        
        for (i = 0; i < 8; i++) {
            hash[i] = (hash[i] + oldHash[i])|0;
        }
    }
    
    for (i = 0; i < 8; i++) {
        for (j = 3; j + 1; j--) {
            var b = (hash[i]>>(j*8))&255;
            result += ((b < 16) ? 0 : '') + b.toString(16);
        }
    }
    return result;
};

来源: https://geraintluff.github.io/sha256/

【讨论】:

  • 我不明白为什么人们对你投反对票?您的功能有效且没有依赖关系
  • 我没有投反对票,但我相信他被投反对票是因为在密码学世界中,强烈建议不要“推出自己的”加密,因为它未经审查并且可能存在安全问题.通常最好使用知名库,因为它可以为您提供最好的安全性,并且通常会不断更新。
  • 我认为这非常适合教育目的,非常感谢。
  • 因为 从 Stack Overflow 复制粘贴代码是一个依赖项。复制粘贴的代码只是一个非托管依赖项,几乎没有错误报告,也没有集中更新机制。该代码绝对适合用于教育目的,但我担心复制粘贴代码被用作使用库的替代方法。
  • 我没有使用这个答案,但它引导我朝着正确的方向前进。由于它,我发现了一些其他信息:Web Cryptography API:developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
【解决方案2】:

2021 年更新 - SHA256 现已包含在当前浏览器中

正如您在问题中提到的,您不需要自定义 Crypto 实现来执行此操作。

WebCrypto is supported in all current browsers。使用 window.crypto.subtle.digest 进行 SHA 256 哈希。

const digest = await window.crypto.subtle.digest('SHA-256', data);

如果您想要异步的东西,例如 sha.js 每月有 1280 万次下载,并且正在积极维护。

const digest = shajs('sha256').update(data).digest('hex')

【讨论】:

【解决方案3】:

查看:https://github.com/brix/crypto-js

您可以使用以下内容:

require(["crypto-js/aes", "crypto-js/sha256"], function (AES, SHA256)
{
    console.log(SHA256("Message")); 
});

或者不需要:

<script type="text/javascript" src="path-to/bower_components/crypto-js/crypto-js.js"></script>
<script type="text/javascript">
    var encrypted = CryptoJS.AES(...);
    var encrypted = CryptoJS.SHA256(...);
</script>

【讨论】:

  • 感谢您的回答(它有效),但我需要输出为文本格式,而不是十六进制格式,抱歉,如果我没有在问题描述中明确说明。跨度>
  • 我认为它是一种文本格式(它看起来像十六进制)。
【解决方案4】:

纯javascript,无需依赖

您可以使用SubtleCrypto.digest() 来帮助您。

它需要一个Uint8Array

如果您的数据是 Blob

const blob = new Blob([file])
const arrayBuffer = await blob.arrayBuffer()
const uint8Array = new Uint8Array(arrayBuffer)
SubtleCrypto.digest("SHA-256", uint8Array)

如果数据是字符串,使用TextEncoder.encode()转换为Uint8Array

const uint8Array = new TextEncoder().encode(data)
SubtleCrypto.digest("SHA-256", uint8Array)

以下是一个可运行的示例供您参考。

<input type="file" multiple/>
<input placeholder="Press `Enter` when done."/>
<script>

  /**
   * @param {"SHA-1"|"SHA-256"|"SHA-384"|"SHA-512"} algorithm https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
   * @param {string|Blob} data
   */
  async function getHash(algorithm, data) {

    const main = async (msgUint8) => { // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#converting_a_digest_to_a_hex_string
      const hashBuffer = await crypto.subtle.digest(algorithm, msgUint8)
      const hashArray = Array.from(new Uint8Array(hashBuffer))
      return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
    }

    if (data instanceof Blob) {
      const arrayBuffer = await data.arrayBuffer()
      const msgUint8 = new Uint8Array(arrayBuffer)
      return await main(msgUint8)
    }
    const encoder = new TextEncoder()
    const msgUint8 = encoder.encode(data)
    return await main(msgUint8)
  }

  const inputFile = document.querySelector(`input[type="file"]`)
  const inputText = document.querySelector(`input[placeholder^="Press"]`)
  inputFile.onchange = async (event) => {
    for (const file of event.target.files) {
      console.log(file.name, file.type, file.size + "bytes")
      const hashHex = await getHash("SHA-256", new Blob([file]))
      console.log(hashHex)
    }
  }

  inputText.onkeyup = async (keyboardEvent) => {
    if (keyboardEvent.key === "Enter") {
      const hashHex = await getHash("SHA-256", keyboardEvent.target.value)
      console.log(hashHex)
    }
  }
</script>

【讨论】:

    【解决方案5】:

    Cannot read property 'digest' of undefined 在调用crypto.subtle.digest 时暗示subtlecrypto 中不可用;因此,digest 不可能存在,因为它的包含模块不存在。
    从逻辑上讲,crypto.subtle 一定不能在这个范围内使用,事实上,在浏览器 anywhere outside of a secure context 中也是如此。

    什么时候上下文被认为是安全的? developer.mozilla.org

    当上下文以安全方式(或本地)交付时,并且当它不能用于提供对不安全上下文的安全 API 的访问时,上下文将被视为安全。在实践中,这意味着要让页面具有安全上下文,它以及其父链和开启链上的所有页面都必须已安全交付。

    例如,如果通过 TLS 安全交付的页面具有未安全交付的父文档或祖先文档,则该页面不被视为安全上下文;否则,该页面将能够通过postMessage 消息向非安全传递的祖先公开敏感API。类似地,如果一个 TLS 传递的文档在一个新窗口中被一个不安全的上下文打开而没有指定noopener,那么打开的窗口不被认为是一个安全的上下文(因为打开器和打开的窗口可以通过 postMessage 进行通信) .

    本地交付的文件,例如 http://localhost* 和 file:// 路径被认为已安全交付。

    ¦¦¦¦¦非本地上下文必须通过https://wss:// 并且所使用的协议不应被视为已弃用。

    在安全的上下文中,您的代码可以完美运行 ?


    1: 安全上下文 - Web security | MDN
    2: 什么时候认为上下文是安全的? - Secure contexts - Web security | MDN
    3: Window.postMessage() - Web APIs | MDN
    4: 窗口功能特性 - Window.open() - Web APIs | MDN

    【讨论】:

      【解决方案6】:

      代码很好。 最后一行应该是这样的:

      var vDigest = await sha256(passwordInput);
      

      【讨论】:

        【解决方案7】:

        上面来自 ofundefined 的答案有一堆丢失的分号和错误。我已经清理了代码,因此您可以将其作为函数调用。我不确定这是否适用于 unicode 字符。可能必须将 unicode 转换为正常的 ascii 才能工作。但是,正如链接文章所说......这不应该在生产环境中使用。此外,这段代码的原始版本似乎确实支持 unicode,所以使用它可能比使用这个函数更好。

        上面的文章(似乎支持unicode)显示了这个函数,它与下面发布的函数不同......

        https://www.movable-type.co.uk/scripts/sha256.html

        如何使用

        console.log(sha256('Perry Computer Services'));
        

        输出

        89bae4aeb761e42cb71ba6b62305e0980154cf21992c9ab2ab6fc40966ab5bf3
        

        使用 PHP 哈希测试 - 输出

        89bae4aeb761e42cb71ba6b62305e0980154cf21992c9ab2ab6fc40966ab5bf3
        

        这不是上面其他链接页面上显示的完整功能。从我上面的示例中可以看出,它确实可以“按原样”使用非 unicode 字符。

        function sha256(ascii) {
            function rightRotate(value, amount) {
                return (value >>> amount) | (value << (32 - amount));
            }
            ;
        
            var mathPow = Math.pow;
            var maxWord = mathPow(2, 32);
            var lengthProperty = 'length';
            var i, j; // Used as a counter across the whole file
            var result = '';
        
            var words = [];
            var asciiBitLength = ascii[lengthProperty] * 8;
        
            //* caching results is optional - remove/add slash from front of this line to toggle
            // Initial hash value: first 32 bits of the fractional parts of the square roots of the first 8 primes
            // (we actually calculate the first 64, but extra values are just ignored)
            var hash = sha256.h = sha256.h || [];
            // Round constants: first 32 bits of the fractional parts of the cube roots of the first 64 primes
            var k = sha256.k = sha256.k || [];
            var primeCounter = k[lengthProperty];
            /*/
             var hash = [], k = [];
             var primeCounter = 0;
             //*/
        
            var isComposite = {};
            for (var candidate = 2; primeCounter < 64; candidate++) {
                if (!isComposite[candidate]) {
                    for (i = 0; i < 313; i += candidate) {
                        isComposite[i] = candidate;
                    }
                    hash[primeCounter] = (mathPow(candidate, .5) * maxWord) | 0;
                    k[primeCounter++] = (mathPow(candidate, 1 / 3) * maxWord) | 0;
                }
            }
        
            ascii += '\x80'; // Append Ƈ' bit (plus zero padding)
            while (ascii[lengthProperty] % 64 - 56)
                ascii += '\x00'; // More zero padding
        
            for (i = 0; i < ascii[lengthProperty]; i++) {
                j = ascii.charCodeAt(i);
                if (j >> 8)
                    return; // ASCII check: only accept characters in range 0-255
                words[i >> 2] |= j << ((3 - i) % 4) * 8;
            }
            words[words[lengthProperty]] = ((asciiBitLength / maxWord) | 0);
            words[words[lengthProperty]] = (asciiBitLength);
        
            // process each chunk
            for (j = 0; j < words[lengthProperty]; ) {
                var w = words.slice(j, j += 16); // The message is expanded into 64 words as part of the iteration
                var oldHash = hash;
                // This is now the undefinedworking hash", often labelled as variables a...g
                // (we have to truncate as well, otherwise extra entries at the end accumulate
                hash = hash.slice(0, 8);
        
                for (i = 0; i < 64; i++) {
                    var i2 = i + j;
                    // Expand the message into 64 words
                    // Used below if 
                    var w15 = w[i - 15], w2 = w[i - 2];
        
                    // Iterate
                    var a = hash[0], e = hash[4];
                    var temp1 = hash[7]
                            + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) // S1
                            + ((e & hash[5]) ^ ((~e) & hash[6])) // ch
                            + k[i]
                            // Expand the message schedule if needed
                            + (w[i] = (i < 16) ? w[i] : (
                                    w[i - 16]
                                    + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3)) // s0
                                    + w[i - 7]
                                    + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10)) // s1
                                    ) | 0
                                    );
                    // This is only used once, so *could* be moved below, but it only saves 4 bytes and makes things unreadble
                    var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) // S0
                            + ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2])); // maj
        
                    hash = [(temp1 + temp2) | 0].concat(hash); // We don't bother trimming off the extra ones, they're harmless as long as we're truncating when we do the slice()
                    hash[4] = (hash[4] + temp1) | 0;
                }
        
                for (i = 0; i < 8; i++) {
                    hash[i] = (hash[i] + oldHash[i]) | 0;
                }
            }
        
            for (i = 0; i < 8; i++) {
                for (j = 3; j + 1; j--) {
                    var b = (hash[i] >> (j * 8)) & 255;
                    result += ((b < 16) ? 0 : '') + b.toString(16);
                }
            }
            return result;
        };
        

        【讨论】:

          猜你喜欢
          • 2011-07-28
          • 1970-01-01
          • 2020-11-21
          • 2018-06-12
          • 2018-06-29
          • 2019-10-04
          • 1970-01-01
          • 2018-12-30
          • 2012-09-16
          相关资源
          最近更新 更多