jono
jono

Reputation: 1942

Are there any SHA-256 javascript implementations that are generally considered trustworthy?

I am writing a login for a forum, and need to hash the password client side in javascript before sending it on to the server. I'm having trouble figuring out which SHA-256 implementation I can actually trust. I was expecting there to be some kind of authoritative script that everyone used, but I'm finding loads of different projects all with their own implementations.

I realize using other people's crypto is always a leap of faith unless you're qualified to review it yourself, and that there is no universal definition of "trustworthy", but this seems like something common and important enough that there ought to be some kind of consensus on what to use. Am I just naive?

Edit since it comes up a lot in the comments: Yes, we do a more stringent hash again on the server side. The client side hashing is not the final result that we save in the database. The client side hashing is because the human client requests it. They have not given a specific reason why, probably they just like overkill.

Upvotes: 184

Views: 352862

Answers (10)

tylerl
tylerl

Reputation: 30867

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.

Upvotes: 143

Danny Sullivan
Danny Sullivan

Reputation: 3854

For those interested, this is code for creating SHA-256 hash using sjcl:

import sjcl from 'sjcl'

const myString = 'Hello'
const myBitArray = sjcl.hash.sha256.hash(myString)
const myHash = sjcl.codec.hex.fromBits(myBitArray)

Upvotes: 41

Naomikho
Naomikho

Reputation: 107

js-sha256 is an npm package you can use, and unlike the popular crypto.subtle which only works on secure connections(localhost/https) it can work regardless. Of course having a secure connection is still the best. I was using crypto.subtle and it always worked because I run my webapp using localhost and it failed the instant I tried it on a server. I had to switch to the js-sha256 npm package as a temporary solution until a secure connection can be configured.

Upvotes: 2

nnsk
nnsk

Reputation: 133

ethers.js has a SHA256 (https://docs.ethers.io/v5/api/utils/hashing/)

const { ethers } = require('ethers');
ethers.utils.sha256(ethers.utils.toUtf8Bytes('txt'));

Upvotes: 1

TitanFighter
TitanFighter

Reputation: 5094

It is possible to use CryptoJS - https://www.npmjs.com/package/crypto-js

import sha256 from 'crypto-js/sha256'

const hash = sha256('Text')

Upvotes: 3

Vitaly Zdanevich
Vitaly Zdanevich

Reputation: 14906

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.

Upvotes: 206

cobbzilla
cobbzilla

Reputation: 1990

I found this implementation very easy to use. Also has a generous BSD-style license:

jsSHA: https://github.com/Caligatio/jsSHA

I needed a quick way to get the hex-string representation of a SHA-256 hash. It only took 3 lines:

var sha256 = new jsSHA('SHA-256', 'TEXT');
sha256.update(some_string_variable_to_hash);
var hash = sha256.getHash("HEX");

Upvotes: 13

Faraway
Faraway

Reputation: 1117

Besides the Stanford lib that tylerl mentioned. I found jsrsasign very useful (Github repo here:https://github.com/kjur/jsrsasign). I don't know how exactly trustworthy it is, but i've used its API of SHA256, Base64, RSA, x509 etc. and it works pretty well. In fact, it includes the Stanford lib as well.

If all you want to do is SHA256, jsrsasign might be a overkill. But if you have other needs in the related area, I feel it's a good fit.

Upvotes: 2

brillout
brillout

Reputation: 7474

Forge's SHA-256 implementation is fast and reliable.

To run tests on several SHA-256 JavaScript implementations, go to http://brillout.github.io/test-javascript-hash-implementations/.

The results on my machine suggests forge to be the fastest implementation and also considerably faster than the Stanford Javascript Crypto Library (sjcl) mentioned in the accepted answer.

Forge is 256 KB big, but extracting the SHA-256 related code reduces the size to 4.5 KB, see https://github.com/brillout/forge-sha256

Upvotes: 21

Brendan Long
Brendan Long

Reputation: 54302

No, there's no way to use browser JavaScript to improve password security. I highly recommend you read this article. In your case, the biggest problem is the chicken-egg problem:

What's the "chicken-egg problem" with delivering Javascript cryptography?

If you don't trust the network to deliver a password, or, worse, don't trust the server not to keep user secrets, you can't trust them to deliver security code. The same attacker who was sniffing passwords or reading diaries before you introduce crypto is simply hijacking crypto code after you do.

[...]

Why can't I use TLS/SSL to deliver the Javascript crypto code?

You can. It's harder than it sounds, but you safely transmit Javascript crypto to a browser using SSL. The problem is, having established a secure channel with SSL, you no longer need Javascript cryptography; you have "real" cryptography.

Which leads to this:

The problem with running crypto code in Javascript is that practically any function that the crypto depends on could be overridden silently by any piece of content used to build the hosting page. Crypto security could be undone early in the process (by generating bogus random numbers, or by tampering with constants and parameters used by algorithms), or later (by spiriting key material back to an attacker), or --- in the most likely scenario --- by bypassing the crypto entirely.

There is no reliable way for any piece of Javascript code to verify its execution environment. Javascript crypto code can't ask, "am I really dealing with a random number generator, or with some facsimile of one provided by an attacker?" And it certainly can't assert "nobody is allowed to do anything with this crypto secret except in ways that I, the author, approve of". These are two properties that often are provided in other environments that use crypto, and they're impossible in Javascript.

Basically the problem is this:

  • Your clients don't trust your servers, so they want to add extra security code.
  • That security code is delivered by your servers (the ones they don't trust).

Or alternatively,

  • Your clients don't trust SSL, so they want you use extra security code.
  • That security code is delivered via SSL.

Note: Also, SHA-256 isn't suitable for this, since it's so easy to brute force unsalted non-iterated passwords. If you decide to do this anyway, look for an implementation of bcrypt, scrypt or PBKDF2.

Upvotes: 15

Related Questions