Hashing and MAC
hash turns data into a fixed-size fingerprint. hmac does the same with a secret mixed in, so only a holder of that secret can make one or check one.
Note
ELI5. A hash is a fingerprint of some data. Run the same bytes through it and you get the same short string every time; change one byte and the string changes completely. It is a one-way trip, so you cannot get the data back out of the fingerprint. A MAC is a fingerprint made with a shared secret mixed in, which means only someone holding that secret can produce a valid one or tell a valid one from a forgery. Use a hash to notice that data changed. Use a MAC to prove a message came from who it claims. Use neither one for passwords: they are fast on purpose, which is exactly wrong for guessing defence. That job belongs to Argon2.
Two functions and their helpers:
hash()
SHA-1, SHA-256, SHA-384 and SHA-512 digests via crypto.subtle.digest.
hmac() / hmacVerify()
Signing and constant-time verification, plus importHmacKey().
#Which one do I want
| The question you are answering | The function |
|---|---|
| Are these two blobs the same? | hash |
| Has this file changed since I last saw it? | hash |
| Can I store this API token without storing the token itself? | hash |
| Did this webhook really come from the provider? | hmacVerify |
| Can I hand a client a value it cannot tamper with? | hmac |
| Can I store this user password? | No. Use argon2Hash. |
| Can I turn a shared secret into several keys? | No. Use hkdf. |
#Shared behaviour
Both functions take string | BytesSource and share two options.
algorithm is one of "SHA-1", "SHA-256" (the default), "SHA-384" or "SHA-512". Names are matched case-insensitively, so "sha-256" works. Any other name throws UNSUPPORTED before Web Crypto is reached, and the message lists the four.
returnAs decides the shape of the result: "hex", "base64" (alias "b64"), "base64url" (alias "b64url"), or "uint8array" (alias "bytes"). Omit it and the output mirrors the input: a string in gives hex out, bytes in give bytes out.
import { hash } from "unsecure/hash";
await hash("hello world"); // string in -> hex string out
await hash(new TextEncoder().encode("hello world")); // bytes in -> Uint8Array out
await hash(bytes, { returnAs: "hex" }); // explicit wins#Errors
| What went wrong | Code |
|---|---|
| A value that is neither text nor bytes | INVALID_TYPE |
An algorithm or returnAs name the library does not know | UNSUPPORTED |
An empty secret (HMAC only) | OUT_OF_RANGE |
| Web Crypto refused the operation after the checks passed | PLATFORM, with the runtime's error as cause |
All of them are an UnsecureError.