Skip to content
Effect 4.0 is here. One ecosystem. Zero dependencies.

Mac unstable

Message authentication codes: HMAC with SHA-1, SHA-256, SHA-384, or SHA-512.

The Mac service generates keys, signs data, and verifies tags. The top-level key, fromCryptoKey, and toBytes functions build and export keys without a service, and timingSafeEqual compares byte arrays in constant time.

Keys are opaque. They hold either key bytes or a CryptoKey, display only their algorithm, and compare by reference.

14 exports Since v4.0.0 Source

Algorithms

hmacSha1

unstable
Since v4.0.0

HMAC with SHA-1, with a 20-byte tag.

Gotchas

HMAC-SHA-1 is still secure as a MAC, but use it only for interoperability with existing protocols.

Signature

declare const hmacSha1: Algorithm

hmacSha256

unstable
Since v4.0.0

HMAC with SHA-256, with a 32-byte tag.

Signature

declare const hmacSha256: Algorithm

hmacSha384

unstable
Since v4.0.0

HMAC with SHA-384, with a 48-byte tag.

Signature

declare const hmacSha384: Algorithm

hmacSha512

unstable
Since v4.0.0

HMAC with SHA-512, with a 64-byte tag.

Signature

declare const hmacSha512: Algorithm

Constructors

Since v4.0.0

Builds a key from an existing CryptoKey, such as a non-extractable one.

Details

The CryptoKey must be a secret HMAC key with the algorithm's hash and both the sign and verify usages, since a key reaches both operations. Otherwise the constructor fails with an InvalidKey reason. It must come from the Crypto object of the WebCrypto reference.

Signature

declare function fromCryptoKey<A extends Algorithm>(algorithm: A, cryptoKey: CryptoKey): Effect<Key<A>, CryptoError>

key

unstable
Since v4.0.0

Builds a key from raw key bytes.

Details

The bytes are copied into a new Uint8Array, so later changes to the input, including a Node.js Buffer, do not reach the key. Any non-empty length is accepted; RFC 2104 recommends at least the hash's output length. An empty key fails with an InvalidKey reason.

Signature

declare function key<A extends Algorithm>(algorithm: A, bytes: Uint8Array): Effect<Key<A>, CryptoError>

Converting

toBytes

unstable
Since v4.0.0

Exports a key's bytes.

Details

Returns a fresh Uint8Array that shares no memory with the key. A CryptoKey-backed key is exported on the WebCrypto reference, and fails with a NotExtractable reason when the CryptoKey is not extractable.

Signature

declare function toBytes(key: Key): Effect<Uint8Array<ArrayBufferLike>, CryptoError>

Guards

isKey

unstable
Since v4.0.0

Checks whether a value is a Key.

Signature

declare function isKey(u: unknown): u is Key<Algorithm>

Layers

Since v4.0.0

Layer that provides Mac on the WebCrypto reference.

Details

Signing and verification run on crypto.subtle. The layer imports a byte-backed key on first use and caches the CryptoKey. Generated keys come from SecureRandom, so a seeded SecureRandom gives reproducible keys.

Signature

declare const layerWebCrypto: Layer.Layer<Mac, never, SecureRandom.SecureRandom>
Since v4.0.0

Builds a Mac layer on the WebCrypto reference that can sign and verify byte-backed keys with a synchronous HMAC primitive.

When to use

Use to run HMAC on a runtime's native code, as NodeCrypto.layerMac does with createHmac from node:crypto.

Details

hmac receives the hash name, the key bytes, and the data, and returns the tag. It runs only for byte-backed keys. CryptoKey-backed keys run on crypto.subtle, and generated keys come from SecureRandom, as in layerWebCrypto. verify computes the tag with hmac and compares it with timingSafeEqual. Without hmac, the layer is layerWebCrypto.

Gotchas

hmac receives the key's own bytes, not a copy, so it must not modify or keep them.

Signature

declare function layerWebCryptoWith(options: {
readonly hmac?: (hash: "MD5" | "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512", key: Uint8Array, data: Uint8Array) => Uint8Array;
}): Layer<Mac, never, SecureRandom>

Example Signing with a native HMAC

import { Layer } from "effect"
import { Mac, SecureRandom } from "effect/crypto"
declare const nativeHmac: (hash: string, key: Uint8Array, data: Uint8Array) => Uint8Array
const layer = Mac.layerWebCryptoWith({ hmac: nativeHmac }).pipe(
Layer.provide(SecureRandom.layerWebCrypto)
)

Models

Algorithm

interface unstable
Since v4.0.0

An HMAC algorithm, such as hmacSha256.

Details

hash is the digest the HMAC is built on.

Signature

interface Algorithm {
readonly hash: Algorithm;
readonly name: "hmacSha1" | "hmacSha256" | "hmacSha384" | "hmacSha512";
}

Key

interface unstable
Since v4.0.0

An opaque HMAC key for the algorithm A.

Details

The key holds either key bytes or a CryptoKey from fromCryptoKey. toString, toJSON, and inspect show only the algorithm. Keys compare by reference and do not implement Equal, because comparing key bytes is not constant time.

Signature

interface Key<out A extends Algorithm = Algorithm> extends Inspectable {
readonly "~effect/crypto/Mac/Key": "~effect/crypto/Mac/Key";
readonly algorithm: A;
}

Services

Mac

unstable
Since v4.0.0

Service that generates HMAC keys, signs data, and verifies tags.

Signature

declare class Mac extends Shape<"effect/crypto/Mac", {
readonly generateKey: <A extends Algorithm>(algorithm: A) => Effect<Key<A>, CryptoError>;
readonly sign: (key: Key, data: Uint8Array) => Effect<Uint8Array<ArrayBufferLike>, CryptoError>;
readonly verify: (key: Key, tag: Uint8Array, data: Uint8Array) => Effect<boolean, CryptoError>;
}, this> {
constructor(_: never);
}

Example Signing and verifying a message

import { Effect, Layer } from "effect"
import { Mac, SecureRandom } from "effect/crypto"
const program = Effect.gen(function*() {
const mac = yield* Mac.Mac
const key = yield* mac.generateKey(Mac.hmacSha256)
const data = new TextEncoder().encode("message")
const tag = yield* mac.sign(key, data)
return yield* mac.verify(key, tag, data)
})
const layer = Mac.layerWebCrypto.pipe(Layer.provide(SecureRandom.layerWebCrypto))
const valid = await Effect.runPromise(program.pipe(Effect.provide(layer)))
valid // => true

Utils

Since v4.0.0

Compares two byte arrays in constant time.

When to use

Use to compare secrets of a fixed, known length, such as tags computed outside Mac. To check an HMAC tag, use Mac.verify.

Gotchas

Arrays of different lengths return false immediately, revealing their lengths. Equal-length arrays are compared with an XOR-accumulate loop; JavaScript runtimes do not guarantee constant-time execution.

Signature

declare function timingSafeEqual(a: Uint8Array, b: Uint8Array): boolean

Example Comparing fixed-length secrets

import { Mac } from "effect/crypto"
const expected = new Uint8Array([1, 2, 3])
const received = new Uint8Array([1, 2, 3])
Mac.timingSafeEqual(expected, received) // => true