AES API

AES-128 and AES-256 symmetric block-cipher encryption and decryption in CBC or ECB mode, with padded one-shot helpers and a streaming instance API.

Overview

The global identifier AES is a namespace of static helpers; its instance form is built from AES.create(). The convenience helpers encrypt and decrypt whole buffers with automatic PKCS#7 padding, while the instance API feeds block-aligned data through the cipher one buffer at a time. For asymmetric (RSA) operations, certificate handling, HMAC, or random bytes, see Crypto. For non-keyed digests, see Hash. For random keys and IVs, use Crypto.generateRandomBytes(n).

AES-CBC is unauthenticated: a single flipped ciphertext bit produces garbage plaintext rather than a clean error. There is no built-in integrity check. For confidentiality and tamper detection, pair it with Crypto.hmacDigest("sha256", macKey, ciphertext) (use a separate key from the encryption key) and verify the MAC before decrypting.

Conventions

Keys, IVs, plaintext and ciphertext are all Buffer instances.

Modes

Every mode argument is one of four strings, matched case-insensitively.

ModeDescription
"cbc-encrypt" / "cbc-decrypt" Cipher Block Chaining. The recommended choice. Requires a 16-byte iv.
"ecb-encrypt" / "ecb-decrypt" Electronic Codebook. Identical 16-byte plaintext blocks produce identical ciphertext blocks, which leaks structure; only safe for single-block key wrapping or other rare cases. Do not use for general data.

Keys

Keys must be exactly 16 or 32 bytes long, selecting AES-128 or AES-256 respectively. AES-192 is not supported by the underlying cipher implementation. The key is a Buffer of bytes, not a password. See Keys Are Not Passwords below.

IVs

CBC modes require a 16-byte initialisation vector. Each encryption with the same key must use a fresh, unpredictable IV; reuse leaks information, and predictable IVs enable replay-style attacks. Generate one with Crypto.generateRandomBytes(16) and prepend or store it alongside the ciphertext. The IV is not secret.

Padding

The convenience helpers (AES.encryptCbc / AES.decryptCbc) apply PKCS#7 padding automatically. The instance API (update) does not. Input must be a multiple of 16 bytes, and the caller is responsible for padding.

Errors

Invalid key or IV sizes, unknown mode strings, calling update before start, and feeding non-aligned buffers to update raise a Zym runtime error of the form AES.method(args) .... AES.decryptCbc returns null rather than raising a runtime error on bad PKCS#7 padding, ciphertext that is not a positive multiple of 16, or an empty input; these are the normal wrong-key and corrupt-data cases. See CLI conventions for the return-shape vocabulary.

Static Functions

AES.create()

Creates a fresh AES instance for the instance API described below.

Returns: A new AES instance.

AES.encryptCbc(key, iv, plaintextBuf)

Encrypts a buffer with AES-CBC, applying PKCS#7 padding automatically. The key length selects the cipher: 16 bytes for AES-128, 32 bytes for AES-256.

Returns: A Buffer of PKCS#7-padded ciphertext.

AES.decryptCbc(key, iv, ciphertextBuf)

Decrypts AES-CBC ciphertext produced with the same key and IV, stripping the PKCS#7 padding. Returns null rather than raising a runtime error on bad padding, on ciphertext that is not a positive multiple of 16 bytes, or on an empty input; these are the normal wrong-key and corrupt-data cases.

Returns: A Buffer of plaintext, or null.

round trip
var key = Crypto.generateRandomBytes(32)   // AES-256
var iv  = Crypto.generateRandomBytes(16)
var msg = Buffer.fromString("hello world")

var ct = AES.encryptCbc(key, iv, msg)
var pt = AES.decryptCbc(key, iv, ct)
print("%s\n", pt.toUtf8())                  // "hello world"

Instance Methods

An instance is created with AES.create() and driven through start, update, and finish. start(...) may be called repeatedly on the same instance with new modes, keys, or IVs to reuse the underlying context.

c.start(mode, key, iv?)

Initialises the cipher for the given mode. ECB modes take the two-argument form with no IV. CBC modes require the three-argument form with a 16-byte iv. Returns "ok".

c.update(buf)

Feeds a buffer through the cipher and returns the transformed output. The input must be a multiple of 16 bytes; no padding is applied, and the caller is responsible for padding the final block.

Returns: A Buffer of transformed output.

c.ivState()

CBC only. Returns the current chaining state as a 16-byte Buffer. Reading the state after each update lets a CBC stream be checkpointed and resumed across calls.

c.finish()

Clears the cipher state and returns "ok". The instance can be start()'d again afterwards.

instance reuse
var c = AES.create()
c.start("cbc-encrypt", key, iv)
var ct = c.update(block16)                 // One 16-byte block
c.finish()

c.start("cbc-decrypt", key, iv)            // Same instance, new direction
var pt = c.update(ct)
c.finish()

Keys Are Not Passwords

A user-supplied password is not an AES key. Passwords are typically short, low-entropy, and the wrong shape for direct use. Pass 16 or 32 bytes of high-entropy material, either random or derived through a proper key derivation function (PBKDF2, scrypt, Argon2).

There is no built-in KDF in this native yet. As a stop-gap, hashing a password with Hash.digest("sha256", passwordBuf) produces a 32-byte buffer that can be used as an AES-256 key, but it is not as good as a real KDF. It offers no work factor, no salt by default, and is trivially brute-forced if the password is weak. Use a real KDF when available.

What's Not Included

Examples

Encrypt-then-MAC

CBC alone is unauthenticated; pair it with HMAC for tamper detection.

var encKey = Crypto.generateRandomBytes(32)
var macKey = Crypto.generateRandomBytes(32) // Separate from encKey
var iv     = Crypto.generateRandomBytes(16)

var ct  = AES.encryptCbc(encKey, iv, Buffer.fromString("secret"))
var mac = Crypto.hmacDigest("sha256", macKey, ct)

// Wire format: iv || ct || mac
// On receive: verify mac, then decrypt.

Streaming with the Instance API

For data larger than memory, feed the cipher in 16-byte multiples and apply your own padding on the final block. c.ivState() after each update returns the current chaining state, so CBC streams can be checkpointed and resumed across calls.

var c = AES.create()
c.start("cbc-encrypt", key, iv)

// Feed N full 16-byte blocks. (Caller is responsible for padding the tail.)
var part1 = c.update(blockBuf1)            // Returns 16 bytes of ciphertext
var part2 = c.update(blockBuf2)
// ...
c.finish()

NIST Test Vector (AES-128-CBC)

The instance API does no padding, so feeding the 64-byte aligned plaintext of NIST SP 800-38A test vector F.2.1 produces the 64-byte canonical NIST ciphertext.

var key = Buffer.fromHex("2b7e151628aed2a6abf7158809cf4f3c")
var iv  = Buffer.fromHex("000102030405060708090a0b0c0d0e0f")
var pt  = Buffer.fromHex("6bc1bee22e409f96e93d7e117393172a"
                       + "ae2d8a571e03ac9c9eb76fac45af8e51"
                       + "30c81c46a35ce411e5fbc1191a0a52ef"
                       + "f69f2445df4f9b17ad2b417be66c3710")

var c = AES.create()
c.start("cbc-encrypt", key, iv)
var ct = c.update(pt)                       // Exact NIST output
c.finish()