如何在 JS 中使用 SHA256 散列字符串?

How can I hash a string with SHA256 in JS?

描述

我希望在 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

检查这个: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>

你好 :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/

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

When is a context considered secure? developer.mozilla.org

A context will be considered secure when it's delivered securely (or locally), and when it cannot be used to provide access to secure APIs to a context that is not secure. In practice, this means that for a page to have a secure context, it and all the pages along its parent and opener chain must have been delivered securely.

For example, a page delivered securely over TLS is not considered a secure context if it has a parent or ancestor document that was not delivered securely; otherwise, the page would then be able to expose sensitive APIs to the non-securely delivered ancestor via postMessage messages. Similarly, if a TLS-delivered document is opened in a new window by an insecure context without noopener being specified, then the opened window is not considered to be a secure context (since the opener and the opened window could communicate via postMessage).

Locally delivered files such as http:// localhost* and file:// paths are considered to have been delivered securely.

¦¦¦¦¦ Contexts that are not local must be served over https:// or wss:// and where the protocols used should not be considered deprecated.

在安全环境下,您的代码可以完美运行


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

代码没问题。 最后一行应如下所示:

var vDigest = await sha256(passwordInput);

上面来自 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;
};

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

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

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

纯 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>

另请参阅

您可以使用 CDN 库。

https://cdnjs.com/libraries/crypto-js

请将以下脚本标记引用到您的 html :

<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js" integrity="sha512-E8QSvWZ0eCLGk4km3hxSsNmGWbLtSCSUcewDQPQWZF6pEU8GlT8a5fF32wOl1i8ftdMhssTrF/OhyGWwonTcXA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

关于如何使用库中的方法,请参考以下网站:

https://cryptojs.gitbook.io/docs/

以下页面显示了我的建议来自的示例:

https://codepen.io/omararcus/pen/QWwBdmo