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.
Algorithms
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: AlgorithmhmacSha256
unstableHMAC with SHA-256, with a 32-byte tag.
Signature
declare const hmacSha256: AlgorithmhmacSha384
unstableHMAC with SHA-384, with a 48-byte tag.
Signature
declare const hmacSha384: AlgorithmhmacSha512
unstableHMAC with SHA-512, with a 64-byte tag.
Signature
declare const hmacSha512: AlgorithmConstructors
fromCryptoKey
unstableBuilds 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>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
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
Layers
layerWebCrypto
unstableLayer 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>layerWebCryptoWith
unstableBuilds 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
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";}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
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 // => trueUtils
timingSafeEqual
unstableCompares 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): booleanExample 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