How can I hash a string with SHA256?
Asked Answered
B

9

37

I'm looking to get a SHA256 hash in JavaScript, on Google Chrome 79.0.3945.130 (64-bit). I've been looking around thinking there would be some sort of official library or function, but all I found were loads of different projects, each with different scripts, and I'm not so sure scripts to trust (as I'm not an expert and definitely not qualified to evaluate them) or how to implement them.

I need the output in text, not hex.

This is what I've tried so far.

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

The console outputs the following error.

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

Brion answered 16/1, 2020 at 20:54 Comment(3)
You're right - re window.crypto - I've added that as an answer (before I noticed you'd written the same thing in your update). If you still use StackOverflow, can you update the answer?Gouge
Be aware that crypto.subtle.digest is not suitable if you're trying to hash a large file (like 1GB+). Currently, you have to resort to 3rd-party libs.Prostitution
This worked for me, but I had problems doing anything with the returned value of hashHex, such as printing it to the screen. My solution was to retrieve the hash value hashhex from the console log ONLY: for local use, this is perfectly adequate.Thermogenesis
G
57

2021 update - SHA256 is now included in current browsers

As you mention in your question, you don't need custom Crypto implementations to do this.

WebCrypto is supported in all current browsers. Use window.crypto.subtle.digest to make a SHA 256 hash.

'digest' by the way is a slightly dated way to refer to a hash. People used to refer to hashing functions as 'message digests' - some people still do.

Based on MDN example, I've published this code on npm:

npm i boring-webcrypto-sha256

Then

import { getSHA256Hash } from "boring-webcrypto-sha256";

Or if you want to maintain your own version:

const getSHA256Hash = async (input) => {
  const textAsBuffer = new TextEncoder().encode(input);
  const hashBuffer = await window.crypto.subtle.digest("SHA-256", textAsBuffer);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  const hash = hashArray
    .map((item) => item.toString(16).padStart(2, "0"))
    .join("");
  return hash;
};

Then just use await getSHA256Hash('someInput')

You can confirm this is correct using the Linux command line:

echo -n 'someInput' | sha256sum

Which will give you the same hash.

Gouge answered 19/5, 2021 at 9:18 Comment(1)
Be careful, in Chrome 60, they added a feature that disables crypto.subtle for non-TLS connections. See https://mcmap.net/q/25292/-how-to-enable-crypto-subtle-for-unsecure-origins-in-chromeDorser
E
61

Hellow there :D it's quite a function. If you are a scholar, you would like to check this article: https://www.movable-type.co.uk/scripts/sha256.html

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

Source: https://geraintluff.github.io/sha256/

Ethnic answered 16/1, 2020 at 21:3 Comment(9)
I can't figure out why people are you downvoting you? Your function works and with no dependanciesCharinile
I didn't downvote, but I believe he was down voted because in the world of cryptography, it's highly advised to not "roll your own" crypto as it isn't vetted and could have security issues. It's usually best to go with a well-known library as it gives you the best security and, usually, constant updates.Dagney
I think this is great for educational purposes, thanks a lot.Timecard
Because copy pasting code from Stack Overflow is a dependency. The copy pasted code is just an unmanaged dependency, where there's little bug reporting and no centralised update mechanism. The code is definitely good for education purposes but I'm concerned that copy pasting code is being presented as an alternative to using a library.Gouge
I didn't use this answer, but it lead me in the right directions. Here's some other information I found thanks to it: Web Cryptography API: developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digestSardius
@Mr_Antivius: some people don't understand that hashes can be used for things that are not security related.Niobe
@mikemaccana: which well maintained library for sha256 that works in the browser and outside of https would you recommend? Using any library that someone pushed to npm and never looked at again is as much of an unmanaged dependecy as copying the code from SO.Niobe
I can not use the Web Cryptography API because in my situation I can not be asynchronous. I also can not use a third party dependency because for me size is critical. Security is not a concern for me. As engineers, let's all kindly keep in mind that one developer's constraints are not equal to that of another. Thanks much to the author for this! <3Dropforge
Just found this code in a production system! It won't be staying there...Marna
G
57

2021 update - SHA256 is now included in current browsers

As you mention in your question, you don't need custom Crypto implementations to do this.

WebCrypto is supported in all current browsers. Use window.crypto.subtle.digest to make a SHA 256 hash.

'digest' by the way is a slightly dated way to refer to a hash. People used to refer to hashing functions as 'message digests' - some people still do.

Based on MDN example, I've published this code on npm:

npm i boring-webcrypto-sha256

Then

import { getSHA256Hash } from "boring-webcrypto-sha256";

Or if you want to maintain your own version:

const getSHA256Hash = async (input) => {
  const textAsBuffer = new TextEncoder().encode(input);
  const hashBuffer = await window.crypto.subtle.digest("SHA-256", textAsBuffer);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  const hash = hashArray
    .map((item) => item.toString(16).padStart(2, "0"))
    .join("");
  return hash;
};

Then just use await getSHA256Hash('someInput')

You can confirm this is correct using the Linux command line:

echo -n 'someInput' | sha256sum

Which will give you the same hash.

Gouge answered 19/5, 2021 at 9:18 Comment(1)
Be careful, in Chrome 60, they added a feature that disables crypto.subtle for non-TLS connections. See https://mcmap.net/q/25292/-how-to-enable-crypto-subtle-for-unsecure-origins-in-chromeDorser
H
7

Checkout this: https://github.com/brix/crypto-js

You can use the following:

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

or without require:

<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>
Heidi answered 16/1, 2020 at 21:2 Comment(2)
Thanks for the answer (it works), but I need the output to be in text format, not hex format, sorry if I didn't make that clear in the description of the problem.Brion
I think it is a text format (it just looks like hex).Heidi
T
4

Pure javascript, no dependencies needed

You can use SubtleCrypto.digest() to help you.

It needs a Uint8Array

If your data is a Blob

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

If data is string, use TextEncoder.encode() convert to Uint8Array

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

Below is a runnable example for your reference.

<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>
Triform answered 27/7, 2021 at 13:11 Comment(4)
This is a copy of my answer from two months prior.Gouge
I don't think there is a suspicion of plagiarism, the primary method is the same, but I try to make people understand the meaning of the parameters and provide a runnable example. My score did not drop because of this, so I think you are still very polite, as you only expressed your personal opinion. My native language is not English, and I understand your thinking that the documentation provides enough information, but the fastest way for me to understand is to have a practical example, which is why I posted my answer.Triform
@mikemaccana: so it's perfectly what SO wants us to do: copy information from the original source because we want to pile up information here (which makes it an unmanaged dependency, but yolo)Niobe
@Triform sorry I should have been more precise with my language. I don’t believe you plagiarised anything, I’m saying your answer is a duplicate of an existing answer.Gouge
M
2

Cannot read property 'digest' of undefined when calling crypto.subtle.digest implies that subtle is not available within crypto; therefore, digest can't possibly exist, as its containing module does not exist.
Logically then, crypto.subtle must not be available in this scope, and in fact, that holds true in the browser 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.

In a secure context, your code works perfectly 😄


1: Secure contexts - Web security | MDN
2: When is a context considered secure? - Secure contexts - Web security | MDN
3: Window.postMessage() - Web APIs | MDN
4: Window functionality features - Window.open() - Web APIs | MDN

Mcalister answered 24/5, 2020 at 8:49 Comment(0)
K
1

The code is fine. The last line should look like this:

var vDigest = await sha256(passwordInput);
Kingkingbird answered 23/11, 2020 at 20:14 Comment(0)
T
1

please also refer to How to calculate SHA hash of a string in browser JavaScript

You may use CDN library.

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

Please quote the following script tag into your 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>

For how to use the methods from the library , please refer to the website below :

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

The following page shows an example where my suggestion comes from :

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

Trafficator answered 6/4, 2022 at 13:17 Comment(1)
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From ReviewTesstessa
S
1

Quick answer:

const sha256 = async (data) => {
    const textAsBuffer = new TextEncoder().encode(data);
    const hashBuffer = await window.crypto.subtle.digest('SHA-256', textAsBuffer);
    const hashArray = Array.from(new Uint8Array(hashBuffer))
    const digest = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    return digest;
}
const textToHash = "myData";
const hash = await sha256(textToHash);
console.log(hash);
Subordinary answered 29/10, 2022 at 7:4 Comment(0)
E
0

The answer above from ofundefined has a bunch of missing semicolons and mistakes in it. I have cleaned up the code so you can call it as a function. I am not sure if this will work with unicode characters. Might have to convert unicode into normal ascii to work. BUT, as the linked article says... this shouldn't be used in a production environment. Also, it does seem the ORIGINAL version of this code does support unicode so maybe it's better to use that than this function.

The article from above (which seems to support unicode) shows this function which is different than the function posted below...

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

HOW TO USE

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

OUTPUT

89bae4aeb761e42cb71ba6b62305e0980154cf21992c9ab2ab6fc40966ab5bf3

TESTED WITH PHP HASH - OUTPUT

89bae4aeb761e42cb71ba6b62305e0980154cf21992c9ab2ab6fc40966ab5bf3

This isn't the FULL function as shown on the other linked page above. It does work "as is" with non unicode characters as you can see from my example above.

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;
};
Engross answered 10/12, 2020 at 15:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.