Cipher unstable
Unauthenticated encryption with no integrity: compose with Mac.
AES-CTR and AES-CBC with PKCS#7 padding, for protocols that pair them with a
MAC, such as SSH, SRTP, JWE, CMS, and Matrix. Anyone can change the
ciphertext without being detected, so authenticate it with Mac
(encrypt-then-MAC), or use Aead instead.
The Cipher service generates keys, encrypts, and decrypts. The
top-level key, fromCryptoKey, and toBytes functions
build and export keys without a service. Both modes run on crypto.subtle.
Keys are opaque. They hold either key bytes or a CryptoKey, display only
their algorithm, and compare by reference.
Gotchas
- Never reuse an IV with the same key. For AES-CTR a repeated counter block reveals the XOR of two plaintexts. For AES-CBC the IV must also be unpredictable.
- AES-CTR decryption never fails, whatever the ciphertext. AES-CBC
decryption fails with one
DecryptionFailedreason for both bad padding and a bad length, so callers cannot tell them apart. Verify a MAC before decrypting, or the error itself becomes a padding oracle.
Algorithms
AES-128-CBC with PKCS#7 padding, with a 16-byte key.
Signature
declare const aes128Cbc: AlgorithmAES-128-CTR, with a 16-byte key.
Details
The 16-byte iv is the initial counter block, and the whole 128-bit block
is the counter, as in OpenSSL, Node.js, Go's cipher.NewCTR, and noble.
Signature
declare const aes128Ctr: AlgorithmAES-256-CBC with PKCS#7 padding, with a 32-byte key.
Signature
declare const aes256Cbc: AlgorithmAES-256-CTR, with a 32-byte key.
Details
The 16-byte iv is the initial counter block, and the whole 128-bit block
is the counter, as in OpenSSL, Node.js, Go's cipher.NewCTR, and noble.
Signature
declare const aes256Ctr: AlgorithmConstructors
fromCryptoKey
unstableBuilds a key from an existing CryptoKey, such as a non-extractable one.
Details
The CryptoKey must be a secret key for the algorithm's mode (AES-CTR or
AES-CBC) and length, with both the encrypt and decrypt 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
Copies the bytes, including Node.js Buffer inputs. A length other than
the algorithm's key length fails with an InvalidKey reason. AES-192 is
not supported.
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 Cipher on the WebCrypto reference.
Details
Encryption and decryption run on crypto.subtle. The layer imports a
byte-backed key on first use and caches the CryptoKey. AES-CTR uses the
whole 128-bit block as the counter (length: 128), which every runtime
supports. Generated keys come from SecureRandom, so a seeded
SecureRandom gives reproducible keys.
Signature
declare const layerWebCrypto: Layer.Layer<Cipher, never, SecureRandom.SecureRandom>Models
An unauthenticated cipher algorithm, such as aes256Cbc.
Details
mode is the WebCrypto algorithm name and keyLength is in bytes.
Signature
interface Algorithm { readonly keyLength: 16 | 32; readonly mode: "AES-CTR" | "AES-CBC"; readonly name: "aes128Ctr" | "aes256Ctr" | "aes128Cbc" | "aes256Cbc";}An opaque unauthenticated cipher 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/Cipher/Key": "~effect/crypto/Cipher/Key"; readonly algorithm: A;}Services
Service that generates keys, encrypts, and decrypts without authentication.
Signature
declare class Cipher extends Shape<"effect/crypto/Cipher", { readonly decrypt: (key: Key, ciphertext: Uint8Array, options: { readonly iv: Uint8Array; }) => Effect<Uint8Array<ArrayBufferLike>, CryptoError>; readonly encrypt: (key: Key, plaintext: Uint8Array, options: { readonly iv: Uint8Array; }) => Effect<Uint8Array<ArrayBufferLike>, CryptoError>; readonly generateKey: <A extends Algorithm>(algorithm: A) => Effect<Key<A>, CryptoError>;}, this> { constructor(_: never);}Example Encrypting with AES-CBC
import { Effect, Layer } from "effect"import { Cipher, SecureRandom } from "effect/crypto"
const program = Effect.gen(function*() { const cipher = yield* Cipher.Cipher const random = yield* SecureRandom.SecureRandom const key = yield* cipher.generateKey(Cipher.aes256Cbc) const iv = yield* random.randomBytes(16) const ciphertext = yield* cipher.encrypt(key, new TextEncoder().encode("secret"), { iv }) const plaintext = yield* cipher.decrypt(key, ciphertext, { iv }) return [ciphertext.length, new TextDecoder().decode(plaintext)]})
const layer = Cipher.layerWebCrypto.pipe(Layer.provideMerge(SecureRandom.layerWebCrypto))await Effect.runPromise(program.pipe(Effect.provide(layer))) // => [16, "secret"]