How can I hash a string with SHA256 in JS?

38,644

Solution 1

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.

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

If you want something asynchronous, sha.js for example has 12.8 million downloads a month, and is actively maintained.

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

Solution 2

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/

Solution 3

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>

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

Solution 5

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

Share:
38,644
Federico Fusco
Author by

Federico Fusco

I'm a student, still learning the ways of the computer world. I hope to expand that knowledge on this website and help others improve their abilities as well! I'm learning JS as well as PHP and a few other languages.

Updated on July 05, 2022

Comments

  • Federico Fusco
    Federico Fusco almost 2 years

    Description

    I'm looking to hash a string locally with SHA256 in Javascript. 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. EDIT: I need the output in text, not hexes, sorry if I didn't explain that when posting the original question.

    Code

    Here's 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); 
    

    Console Output:

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

    I'm new to javascript and I'm open to all suggestions, so yeah.

    Update

    Although most of your suggestions work, for those of you who are looking to use the Web Crypto API, the answer was on line #5. I needed to change crypto.subtle.digest to window.crypto.subtle.digest

    • Admin
      Admin over 4 years
      What browser (including version) are you using?
    • Federico Fusco
      Federico Fusco over 4 years
      @Amy I'm using Google Chrome Version 79.0.3945.130 (64-bit), hope this helps
    • mikemaccana
      mikemaccana about 3 years
      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?
    • jordanfb
      jordanfb over 2 years
      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.
    • geralds
      geralds over 2 years
      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.
  • Federico Fusco
    Federico Fusco over 4 years
    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.
  • Gen4ik
    Gen4ik over 4 years
    I think it is a text format (it just looks like hex).
  • Albert Renshaw
    Albert Renshaw about 4 years
    I can't figure out why people are you downvoting you? Your function works and with no dependancies
  • Mr_Antivius
    Mr_Antivius about 4 years
    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.
  • David ROUMANET
    David ROUMANET almost 4 years
    I think this is great for educational purposes, thanks a lot.
  • mikemaccana
    mikemaccana about 3 years
    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.
  • Ser
    Ser almost 3 years
    Be careful, in Chrome 60, they added a feature that disables crypto.subtle for non-TLS connections. See stackoverflow.com/a/46671627/2294168
  • carloswm85
    carloswm85 over 2 years
    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/digest
  • jordanfb
    jordanfb over 2 years
    Native is async too await crypto.subtle.digest(). Returns a Promise (see developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/…)
  • Nick Vu
    Nick Vu about 2 years
    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 Review
  • djdance
    djdance about 2 years
    attention, data is not a string, and returned data is not a string too (it's a promise), to do convert and async use example in the Mozilla's link above
  • mikemaccana
    mikemaccana about 2 years
    @djdance if you mean you have a Promise and want to get the value inside the Promise, you’ll need to use await.