
# 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](/crypto/password-hashing).
::

Two functions and their helpers:

::card-group
::card{title="hash()" icon="i-lucide-hash" to="/crypto/hashing/hash"}
SHA-1, SHA-256, SHA-384 and SHA-512 digests via `crypto.subtle.digest`.
::
::card{title="hmac() / hmacVerify()" icon="i-lucide-signature" to="/crypto/hashing/hmac"}
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`](/crypto/hashing/hash)                    |
| Has this file changed since I last saw it?                   | [`hash`](/crypto/hashing/hash)                    |
| Can I store this API token without storing the token itself? | [`hash`](/crypto/hashing/hash)                    |
| Did this webhook really come from the provider?              | [`hmacVerify`](/crypto/hashing/hmac)              |
| Can I hand a client a value it cannot tamper with?           | [`hmac`](/crypto/hashing/hmac)                    |
| Can I store this user password?                              | No. Use [`argon2Hash`](/crypto/password-hashing). |
| Can I turn a shared secret into several keys?                | No. Use [`hkdf`](/crypto/key-derivation).         |

## 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.

```ts
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`](/safety/errors).
