Skip to content

Crypto ​

Cryptographic helpers for AES symmetric encryption, RSA asymmetric encryption, ECDH ephemeral key exchange, and Hash digests/HMACs, each wrapped in a scoped BaseHelper class.

In one example ​

The smallest real use: encrypt and decrypt a string with AES-256-GCM.

typescript
import { AES } from '@venizia/ignis-helpers';

const aes = AES.withAlgorithm('aes-256-gcm');
const secret = 'my-application-secret-key';

const encrypted = aes.encrypt({ message: 'This is a secret message.', secret });
// => base64 envelope: [version][idLen][id][iv][authTag][ciphertext]

const decrypted = aes.decrypt({ message: encrypted, secret });
// => 'This is a secret message.'

RSA and ECDH follow the same withAlgorithm() factory + encrypt/decrypt shape. Only the secret type and the speed/message-size trade-offs differ.

How it works ​

  • One factory pattern. Every algorithm class exposes a static withAlgorithm() that returns an instance. The constructors are public too, so new AES({ algorithm: 'aes-256-gcm' }) works. Prefer the factory; it is what the rest of the framework calls.
  • ECDH extends the neutral AbstractCryptoAlgorithm directly. It uses CryptoKey objects from the Web Crypto API (crypto.subtle), not string secrets. It skips the string-normalization helpers entirely.
  • Options objects, throw-by-default. Every encrypt/decrypt takes { message, secret, opts? }. On internal error, each throws by default. Pass opts.doThrow: false to get the original input back unchanged instead.

AES and RSA share BaseCryptoAlgorithm, which adds two helpers:

MethodDoes
normalizeSecretKey()Derives a key from your secret with PBKDF2-SHA256, 100,000 iterations. It never pads and never truncates
getAlgorithmKeySize()Parses the bit size out of the algorithm name and returns it in bytes - 32 from aes-256-gcm, not 256

normalizeSecretKey() takes { secret, length, kdfSalt?, kdfIterations? } and returns a Buffer of length bytes.

OptionTypeDefaultMeaning
kdfSaltstring'ignis-kdf-salt-v1'Per-deployment PBKDF2 salt. Must be at least 16 bytes, or the call throws
kdfIterationsnumber100000PBKDF2 iteration count

WARNING

kdfSalt and kdfIterations are part of the key. Whatever you pass on encrypt you must pass again on decrypt, or the derived key differs and the ciphertext will not open. Omit both for the shipped defaults.

The shipped salt is public and identical in every deployment, so it defeats a bare dictionary attack but not a table precomputed against that value. Pass your own kdfSalt for per-deployment isolation. Derived keys are memoised per secret, salt, iteration count and length, so repeated calls do not repeat the 100,000 rounds.

The pad-or-truncate behaviour that predates this lives on LegacyAES.normalizeSecretKeyLegacy and nowhere else. It exists so data already written in the old envelope stays readable.

Class comparison

ClassBase classSecret typeAsyncBest for
AESBaseCryptoAlgorithmTAESSecret - a string or an IAESKeyringEntry[]NoEncrypting data at rest, fast bulk encryption
RSABaseCryptoAlgorithmstring (base64 DER key)NoPublic-key encryption, small payloads
ECDHAbstractCryptoAlgorithmCryptoKeyYesSession key exchange with forward secrecy
HashBaseHelperstring (HMAC only, no secret for a plain digest)NoDigests and HMACs - no decrypt, a digest cannot be reversed

AES supports two modes selected at construction: aes-256-cbc (plain block cipher) and aes-256-gcm (authenticated - detects tampering). Everything on this page uses the default options.

See the Full reference for every option, the ECDH key-exchange flow, IECDHEncryptedPayload, and Hash's digest/HMAC methods.

Common tasks ​

Choose an AES mode ​

aes-256-gcm is authenticated encryption - tampering makes decryption throw instead of returning corrupted data. Prefer it for new code.

typescript
const aesGcm = AES.withAlgorithm('aes-256-gcm'); // recommended
const aesCbc = AES.withAlgorithm('aes-256-cbc'); // no tamper detection

Encrypt a file ​

encryptFile/decryptFile read the file synchronously, treat its contents as UTF-8, then run the same encrypt/decrypt as strings. They take { absolutePath, secret } and nothing else, so they always use the default salt and iteration count. Read the file yourself and call encrypt if you need a custom kdfSalt.

typescript
const encrypted = aes.encryptFile({ absolutePath: '/path/to/config.json', secret });
const decrypted = aes.decryptFile({ absolutePath: '/path/to/config.json.enc', secret });

Generate an RSA key pair and encrypt with it ​

Keys are DER-encoded (SPKI public, PKCS8 private). Base64-encode them to pass as the secret string.

typescript
import { RSA } from '@venizia/ignis-helpers';

const rsa = RSA.withAlgorithm();
const { publicKey, privateKey } = rsa.generateDERKeyPair(); // default 2048-bit modulus

const encrypted = rsa.encrypt({ message: 'secret data', secret: publicKey.toString('base64') });
const decrypted = rsa.decrypt({ message: encrypted, secret: privateKey.toString('base64') });
// => 'secret data'

Fail soft instead of throwing ​

Pass opts: { doThrow: false } to get the original message back on error instead of an exception. Use this when a decryption failure should be a fallback path, not a crash.

typescript
const result = rsa.encrypt({ message: 'test', secret: 'invalid-key', opts: { doThrow: false } });
// result === 'test' (original message, no throw)

Hash or HMAC a message ​

Hash covers one-way digests and keyed HMACs. digest takes no secret; hmac requires one and throws if it is empty.

typescript
import { Hash, HashAlgorithms } from '@venizia/ignis-helpers';

const checksum = Hash.withAlgorithm(HashAlgorithms.SHA256).digest({ message: 'payload' });
const signature = Hash.withAlgorithm(HashAlgorithms.SHA256).hmac({ message: 'payload', secret: webhookSecret });

Derive a shared session key with ECDH ​

ECDH is async (Web Crypto) and needs a deriveAESKey() step before either side can encrypt. See the Full reference for the complete key-exchange flow, salt handling, and additional authenticated data (AAD).

typescript
import { ECDH } from '@venizia/ignis-helpers';

const ecdh = ECDH.withAlgorithm();
const alice = await ecdh.generateKeyPair();
const bob = await ecdh.generateKeyPair();

See also ​

Files: