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.

Answer from mikemaccana on Stack Overflow
Top answer
1 of 5
72

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.

2 of 5
69

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/

Top answer
1 of 10
209

On https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest I found this snippet that uses internal js module:

async function sha256(message) {
    // encode as UTF-8
    const msgBuffer = new TextEncoder().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 => b.toString(16).padStart(2, '0')).join('');
    return hashHex;
}

Note that crypto.subtle in only available on https or localhost - for example for your local development with python3 -m http.server you need to add this line to your /etc/hosts: 0.0.0.0 localhost

Reboot - and you can open localhost:8000 with working crypto.subtle.

2 of 10
143

Most modern browsers now support crypto operations natively. See https://developer.mozilla.org/en-US/docs/Web/API/Crypto.

That said, client-side crypto is usually not as good an idea as it might seem.

The fundamental principle is that you (at the server end) have to assume that everything happening on the client side may be malicious, and may not even be running your code at all. If your crypto operations are meant to protect the user's data from a third-party's discovery or interference, the appropriate crypto layer to use is TLS rather than rolling your own in the client.

Sending sensitive data over TLS does not require additional crypto in transit beyond that provided transparently by TLS for compliance with any laws or best practices. If you're sending sensitive data, send it over TLS. From your application's perspective this will look like plain-text, but that's entirely appropriate and merely a reflection of the level of abstraction of the encryption, not the level of security it provides. Just because a security mechanism feels simple to use doesn't mean the protections it provides are simplistic.

And don't say: "I'm already using TLS, but I'm making it BETTER by doing client-side as well." Complicating your application with more security-adjacent components doesn't add security. Instead, you're sacrificing true long-term security in order to feel like you're doing more.

The context specified for this question is exactly the wrong place to do client-side crypto. There's a good reason why major tech companies never do what this question suggests, and they're successfully fending off nation-state level attackers.

The case where client-side browser crypto is appropriate is when the client is considered stand-alone and the user's protection is not expected to be guarded by the server operator. This is an exceptionally rare situation, but shows up in things like offline web applications.

🌐
npm
npmjs.com › package › js-sha256
js-sha256 - npm
sha256('Message to hash'); sha224('Message to hash'); var hash = sha256.create(); hash.update('Message to hash'); hash.hex(); var hash2 = sha256.update('Message to hash'); hash2.update('Message2 to hash'); hash2.array(); // HMAC sha256.hmac('key', 'Message to hash'); sha224.hmac('key', 'Message to hash'); var hash = sha256.hmac.create('key'); hash.update('Message to hash'); hash.hex(); var hash2 = sha256.hmac.update('key', 'Message to hash'); hash2.update('Message2 to hash'); hash2.array();
      » npm install js-sha256
    
Published   May 24, 2025
Version   0.11.1
Author   Chen, Yi-Cyuan
🌐
Movable Type
movable-type.co.uk › scripts › sha256.html
SHA-256 Cryptographic Hash Algorithm implemented in JavaScript | Movable Type Scripts
In principle, this could be done by M[N-1][14] = ((msg.length-1)*8) >>> 32; M[N-1][15] = ((msg.length-1)*8) & 0xffffffff; However, JavaScript bit-ops convert their arguments to 32-bits, so n >>> 32 would give 0. Hence I use arithmetic operators instead: for the most-significant 32-bit number, I divide the (original) length by 2^32, and use floor() convert the result to an integer. Note that what is returned is the textual hexadecimal representation of the binary hash.
🌐
Geraintluff
geraintluff.github.io › sha256
JavaScript SHA256 demo
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&#39t bother trimming off the extra ones, they&#39re harmless as long as we&#39re 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) ?
🌐
30 Seconds of Code
30secondsofcode.org › home › javascript › browser › sha-256 hash
Calculate SHA-256 hash in JavaScript - 30 seconds of code
October 7, 2023 - As the promise resolves to an ArrayBuffer, you will need to read the data using DataView.prototype.getUint32(). Then, you need to convert the data to its hexadecimal representation using Number.prototype.toString(). Add the data to an array using Array.prototype.push(). Finally, use Array.prototype.join() to combine values in the array of hexes into a string. const hashValue = val => crypto.subtle .digest('SHA-256', new TextEncoder('utf-8').encode(val)) .then(h => { let hexes = [], view = new DataView(h); for (let i = 0; i < view.byteLength; i += 4) hexes.push(('00000000' + view.getUint32(i).toString(16)).slice(-8)); return hexes.join(''); }); hashValue( JSON.stringify({ a: 'a', b: [1, 2, 3, 4], foo: { c: 'bar' } }) ).then(console.log); // '04aa106279f5977f59f9067fa9712afc4aedc6f5862a8defc34552d8c7206393'
🌐
GitHub
gist.github.com › HaNdTriX › 239f45939ee8b9f012861bb22808ba42
Convert a string to an sha256 hash in JavaScript · GitHub
(async () => { { const assertion = 'Will return the same hash for the same value' const hash1 = await sha256('foobar') const hash2 = await sha256('foobar') console.assert(hash1 === hash2, assertion) } { const assertion = 'Will return the different hashes for the different values' const hash1 = await sha256('foobar2') const hash2 = await sha256('foobar') console.assert(hash1 !== hash2, assertion) } })()
Find elsewhere
🌐
MDN Web Docs
developer.mozilla.org › en-US › docs › Web › API › SubtleCrypto › digest
SubtleCrypto: digest() method - Web APIs | MDN
The inhabitants refer to it as the planet Earth."; async function digestMessage(message) { const encoder = new TextEncoder(); const data = encoder.encode(message); const hash = await window.crypto.subtle.digest("SHA-256", data); return hash; } digestMessage(text).then((digestBuffer) => console.log(digestBuffer.byteLength), ); The digest is returned as an ArrayBuffer, but for comparison and display digests are often represented as hex strings.
🌐
MojoAuth
mojoauth.com › hashing › sha-256-in-javascript-in-browser
SHA-256 in JavaScript in Browser | Hashing and Validation Across Programming Languages
In this code snippet, we utilize the TextEncoder to convert the input string into a byte array, then apply the crypto.subtle.digest method to generate the SHA-256 hash.
Top answer
1 of 2
8

Using 62 characters seems an odd choice:

const digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

Prefer base64url.

For one thing, the various divide and modulo base operations become simple bit shifts when length of digits is 2^6.

For another, it becomes easier to predict how encoded message length relates to original length.


        result = result * base + digits.indexOf(string.charAt(i));

The repeated indexOf O(N) linear scan seems tedious. Prefer to use a hash map which gives the answer in O(1) constant time.


    while (0 < int) {

Clearly this is identical to an int > 0 expression. Except that the Gentle Reader can pronounce the latter as "while int positive". As written it seems a little contorted to make sense of it.


        const a = ...
        const h = bits[i + 7];

I don't get it. Why introduce a bunch of temp vars? Just console.log eight expressions and be done with it. Or iterate through a loop eight times.

This sha_256 function seems to be doing at least two things, computing a hash and sending it to the console. The single responsibility principle suggests breaking this out into two functions.


This submission is crying out for a couple of simple unit tests. It's sooo easy. Just add one or two, so we can see what an encode / decode roundtrip looks like.


wanted to know whether an approach like this is secure

That depends on your use case, which you did not describe. The good news is you're starting with a cryptographic hash function so an attacker cannot induce collisions as easily as he might with a simple checksum.

Let's assume the input value is somewhat long and has > 256 bits of entropy.

Then any approach like the current one, which truncates the sha256 result, will be less secure against collisions compared with with an approach that preserves all 256 bits.

Many practical use cases would require fewer than 256 bits of entropy. Suppose our analysis of traffic volume suggests the risk of collision would be acceptable when using just 128 bits. Is using the first half of the sha256 hash a secure approach? Yes, it is. As is the use of any similar subset, such as the last half. This is all subject to the obvious caveat that we chose to reduce the security parameter. In general, if you want N < 256 bits of entropy, reporting the N-bit prefix of a sha256() result is secure.

The most efficient way to send the bits is as raw binary via an 8-bit clean channel. If that's not available, for example because we need to put them in an URL, then we must do some sort of channel encoding as seen in the present source code. Notice that this will necessarily expand the message, which is the opposite of "compression" that you mentioned. To send a 24-bit message as hex we could send six ASCII characters, or as Base64 we could send four characters. If the message is initial prefix of sha256() output, then we have "truncated" the hash, trading bandwidth for security.


This code achieves its design goals. We might wish to slightly adjust the design. A small amount of refactoring is indicated.

I would be happy to delegate or accept maintenance tasks on this codebase.

2 of 2
0

You are using a JavaScript library to do this, which will result in terrible performance.

You are much better off using a native library. For instance, in NodeJS you can do the following:

const { createHash } = require('crypto');

function sha256inb64(value) {
    const hash = createHash('sha256');
    hash.update(value);
    return hash.digest('base64');
}

I've deliberately kept the name short. This returns the digest directly as base 64 which is much faster to encode and doesn't limit the output size.

You can limit the number of output characters to 8 by using hash.digest('base64').substring(0, 8) in the code provided on top.

Note that this only provides (8 / 4) * 3 = 6 bytes or 48 bits of security, so it is not considered cryptographically secure anymore, rendering the SHA256 function next to useless.

If you don't need cryptographic security then there are a lot faster hashes available, see e.g. a comparison of hashes here.


function stringToInt(string) {

What does this even mean? What does it mean for a string to be put into an integer? Will it create an integer of any particular size? It doesn't constrain the input string for certain.

Otherwise the base conversion seems to be reasonable.


int = Math.floor(int / base);

This is definitely restricting the input size.

function intToString(int) {

Same problem with the naming, although it at least seems to be the reverse operation of stringToInt.


function sha_256(value) {

This name is a terrible name for this particular function. You're using a sha256 that - for some bad reason - always returns hexadecimals. Now by adding an underscore it suddenly returns the a value after "compression" in a different text encoding? It isn't explained why, why it is useful or that it is "compressed" as claimed.

let bits = hexToBytes(sha256(value) /* returns a hex string */ )

Wait, what? Does it return bits, bytes or hexadecimals? You are aware that those are different concepts, right? It returns bytes as indicated.

for (let i = 0; i < bits.length; i += 8) {
    const a = bits[i];
    const b = bits[i + 1];
    const c = bits[i + 2];
    const d = bits[i + 3];
    const e = bits[i + 4];
    const f = bits[i + 5];
    const g = bits[i + 6];
    const h = bits[i + 7];
    const chunk = intToString(a + b + c + d + e + f + g + h);
    str += chunk.padStart(2, '0');
}

Note I've used the correct but rolled back code of the question here, as the original code doesn't make any sense by itself. That edit should not have been rolled back.

If you're doing the counting instead of the computer then you're probably doing something wrong. You can add up to a total using an inner for loop.

Worse is that adding values is really dangerous when it comes to "compression"; it is of course clear that 1 + 2 will add up to the same value as 2 + 1. If you do that with a lot of values then it will allow an attacker to quickly find collisions. Just taking the first characters is likely more secure - and it cannot be less secure.

🌐
GitHub
github.com › codegasms › sha256
GitHub - codegasms/sha256: SHA-256 hash function implementation in Javascript
const hashString = require('@codegasms/sha256'); const message = 'Hello World'; const hashedMessageHex = hashString(message, 'hex'); const hashedMessageBinary = hashString(message, 'binary'); const hashedMessageBase64 = hashString(message, 'base64');
Author   codegasms
🌐
Delft Stack
delftstack.com › home › howto › javascript › sha256 javascript
How to Implement SHA-256 in JavaScript | Delft Stack
February 2, 2024 - Firstly, we import the module using require() and then create a forge object initialized for the SHA-256 via calling the create() function. The update() updates the data while the digest() function performs hashing and returns the hash value ...
🌐
GitHub
github.com › emn178 › js-sha256
GitHub - emn178/js-sha256: A simple SHA-256 / SHA-224 hash function for JavaScript supports UTF-8 encoding.
sha256('Message to hash'); sha224('Message to hash'); var hash = sha256.create(); hash.update('Message to hash'); hash.hex(); var hash2 = sha256.update('Message to hash'); hash2.update('Message2 to hash'); hash2.array(); // HMAC sha256.hmac('key', 'Message to hash'); sha224.hmac('key', 'Message to hash'); var hash = sha256.hmac.create('key'); hash.update('Message to hash'); hash.hex(); var hash2 = sha256.hmac.update('key', 'Message to hash'); hash2.update('Message2 to hash'); hash2.array();
Starred by 956 users
Forked by 261 users
Languages   JavaScript 97.5% | HTML 2.5%
🌐
CodePen
codepen.io › aminraisy › pen › OeOvxr
SHA256 Hash function
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* SHA-256 implementation in JavaScript | (c) Chris Veness 2002-2010 | www.movable-type.co.uk */ /* - see http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html */ /* http://csrc.nist.gov/groups/ST/toolkit/examples.html */ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ var Sha256 = {}; // Sha256 namespace /** * Generates SHA-256 hash of string * * @param {String} msg String to be hashed * @param {Boolean} [utf8encode=true] Encode msg as UTF-8 before generating hash * @returns {String} Hash of msg as hex character string */ Sha256.hash = function(msg, utf8encode) { utf8encode = (typeof utf8encode == 'undefined') ?
🌐
Movable Type
movable-type.co.uk › scripts › js › crypto › docs › sha256.js.html
JSDoc: Source: sha256.js
*/ static hash(msg, options) { const defaults = { msgFormat: 'string', outFormat: 'hex' }; const opt = Object.assign(defaults, options); // note use throughout this routine of 'n >>> 0' to coerce Number 'n' to unsigned 32-bit integer switch (opt.msgFormat) { default: // default is to convert string to UTF-8, as SHA only deals with byte-streams case 'string': msg = utf8Encode(msg); break; case 'hex-bytes':msg = hexBytesToString(msg); break; // mostly for running tests } // constants [§4.2.2] const K = [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5
🌐
Futurestud.io
futurestud.io › tutorials › node-js-calculate-a-sha256-hash
Node.js — Calculate a SHA256 Hash
import { createHash } from 'node:crypto' /** * Returns a SHA256 hash using SHA-2 for the given `content`. * * @see https://en.wikipedia.org/wiki/SHA-2 * * @param {String} content * * @returns {String} */ function sha256(content) { return createHash('sha256').update(content).digest('hex') }
🌐
Coursesweb
coursesweb.net › javascript › sha256-encrypt-hash_cs
SHA256 Encrypt hash in JavaScript
JavaScript introductory course, free lessons and tutorials. Learning JavaScript programming language
🌐
GitHub
gist.github.com › rumkin › fa728e5a6b28625f53db
sha-256.js · GitHub
GitHub Gist: instantly share code, notes, and snippets.
🌐
C# Corner
c-sharpcorner.com › article › generating-cryptographic-hashes-with-sha-256-in-javascript
Generating Cryptographic Hashes with SHA-256 in JavaScript
June 12, 2023 - The function converts the input data to a Uint8Array buffer, performs the hash computation using the digest method, and then converts the resulting hash to a hexadecimal string for improved readability.