
# hash()

> One async function over the four SHA digests Web Crypto exposes, with the output shape you ask for.

```ts
import { hash } from "unsecure/hash";

await hash("hello world");
// "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
```

## Signature

```ts
function hash(
  data: string | BytesSource,
  options?: {
    algorithm?: "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512";
    returnAs?: "hex" | "base64" | "b64" | "base64url" | "b64url" | "uint8array" | "bytes";
  },
): Promise<string | Uint8Array>;
```

## Options

| Option      | Type              | Default                | What it does                                                               |
| ----------- | ----------------- | ---------------------- | -------------------------------------------------------------------------- |
| `algorithm` | `DigestAlgorithm` | `"SHA-256"`            | Which digest to compute. Matched case-insensitively, so `"sha-256"` works. |
| `returnAs`  | `DigestReturnAs`  | mirrors the input type | The shape of the result.                                                   |

`SHA-1` is there for interoperability with systems that still require it, such as the default OTP algorithm. Do not pick it for anything new.

## Return shape

| You passed                | You get back                  |
| ------------------------- | ----------------------------- |
| a `string`                | a hex `string`                |
| a `BytesSource`           | a `Uint8Array`                |
| anything, plus `returnAs` | exactly what `returnAs` names |

```ts
await hash("hello world", { returnAs: "base64" });
// "uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="

await hash("hello world", { returnAs: "base64url" });
// "uU0nuZNNPgilLlLX2n2r-sSE7-N6U4DukIj3rOLvzek"

await hash("hello world", { algorithm: "SHA-512" });
// "309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff…"

await hash(new TextEncoder().encode("hello world"));
// Uint8Array(32) [185, 77, 39, 185, …]

await hash(new TextEncoder().encode("hello world"), { returnAs: "hex" });
// "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
```

Strings are encoded as UTF-8 before hashing.

## Errors

| Cause                                                 | `code`         |
| ----------------------------------------------------- | -------------- |
| `data` is neither text nor bytes                      | `INVALID_TYPE` |
| `algorithm` is not one of the four                    | `UNSUPPORTED`  |
| `returnAs` is not a name the library knows            | `UNSUPPORTED`  |
| Web Crypto refused the digest after the checks passed | `PLATFORM`     |

Both `UNSUPPORTED` checks run before Web Crypto is reached, so a typo costs nothing. See [Errors](/safety/errors).

## Recipe: store an API token without storing the token

You have to be able to check a token a client sends. You do not have to be able to read it back. Store the digest.

```ts
import { hash } from "unsecure/hash";
import { secureGenerate } from "unsecure/generate";
import { secureCompare } from "unsecure/compare";

// Issue: show `token` to the user once, keep only the digest
const token = secureGenerate({ length: 48, specials: false });
const tokenHash = await hash(token);
await db.tokens.insert({ userId, tokenHash });

// Check, on each request
async function findToken(received: string) {
  const receivedHash = await hash(received);
  const row = await db.tokens.findByHash(receivedHash);
  if (!row) return null;
  return secureCompare(row.tokenHash, receivedHash) ? row : null;
}
```

A plain SHA-256 is the right tool here and the wrong tool for a password, because a token has full random entropy and a password does not. See [Password hashing](/crypto/password-hashing) for the other case.

## Recipe: notice that content changed

```ts
import { hash } from "unsecure/hash";

const etag = await hash(JSON.stringify(document), { returnAs: "base64url" });
if (etag === request.headers.get("if-none-match")) return new Response(null, { status: 304 });
```

## Pitfall: large files and streams

`hash()` calls `crypto.subtle.digest`, which needs the whole input in memory. Web Crypto has no incremental digest, so there is nothing the library can do about it.

```ts
// Loads the entire file into memory
const fileHash = await hash(hugeFileBuffer);
```

For streams, reach for the runtime's own API: `crypto.createHash()` in Node, `crypto.subtle.digestStream()` where it exists.

## Pitfall: comparing digests with `===`

A digest is not secret, but the value you compare it against often is. Use [`secureCompare`](/generate/secrets/compare) whenever the comparison decides whether a request is authorized.
