Skip to content

CryptoUtility

Overview

The CryptoUtility is a singleton that provides application-level cryptographic operations. It wraps the IGNIS Framework's AES helper (AES-256-GCM mode) and hash function to offer two capabilities:

  1. Symmetric encryption/decryption -- using APP_ENV_APPLICATION_SECRET as the key, for storing sensitive data like payment credentials in the database.
  2. HMAC-SHA256 signing -- for webhook payload integrity verification.

Source: packages/core/src/utilities/crypto.utility.ts (53 lines)

Class Definition

typescript
import { AES, applicationEnvironment, EnvironmentKeys, hash } from '@venizia/ignis';

export interface ISignOptions {
  timestamp: number;
  eventType: string;
  parts: Array<string>;
  secret: string;
}

export class CryptoUtility {
  private static _instance: CryptoUtility;
  private readonly _aes: AES;
  private readonly _encryptionKey: string;

  private constructor() {
    this._aes = AES.withAlgorithm('aes-256-gcm');
    this._encryptionKey = applicationEnvironment.get<string>(
      EnvironmentKeys.APP_ENV_APPLICATION_SECRET,
    );
  }

  static getInstance(): CryptoUtility;
  encrypt(text: string): string;
  decrypt(encryptedText: string): string;
  sign(opts: ISignOptions): string;
}

Configuration

VariableRequiredDescription
APP_ENV_APPLICATION_SECRETYesThe secret key used for AES-256-GCM encryption. Must be set before CryptoUtility.getInstance() is called.
bash
# .env.development
APP_ENV_APPLICATION_SECRET=my-application-secret-key-at-least-32-chars

WARNING

The APP_ENV_APPLICATION_SECRET is used as the AES-256-GCM key. If you change it, all previously encrypted data becomes unrecoverable. Treat this value as critical infrastructure.

Import

typescript
import { CryptoUtility } from '@nx/core';
// or
import { CryptoUtility } from '@nx/core/utilities';

API

getInstance()

Returns the singleton instance. On first call, initializes the AES cipher with APP_ENV_APPLICATION_SECRET.

typescript
const crypto = CryptoUtility.getInstance();

encrypt(text)

Encrypts a plaintext string using AES-256-GCM with the application secret. Returns a base64-encoded ciphertext string containing the IV, ciphertext, and GCM authentication tag.

typescript
const crypto = CryptoUtility.getInstance();

const encrypted = crypto.encrypt('my-api-key-12345');
// "base64-encoded-iv+ciphertext+authtag"

decrypt(encryptedText)

Decrypts a previously encrypted string back to plaintext. Throws if the ciphertext was tampered with (GCM authentication failure) or if the wrong key is used.

typescript
const crypto = CryptoUtility.getInstance();

const original = crypto.decrypt(encrypted);
// "my-api-key-12345"

sign(opts)

Creates an HMAC-SHA256 signature for webhook payload verification. Joins all parts with | (pipe) and hashes with the provided secret.

Signature format: timestamp|eventType|part1|part2|... hashed with SHA256, output as base64.

typescript
const crypto = CryptoUtility.getInstance();

const signature = crypto.sign({
  timestamp: 1705708800000,
  eventType: 'payment.success',
  parts: ['txn_abc123', '50000', 'VND'],
  secret: 'webhook-shared-secret',
});
// Base64-encoded HMAC-SHA256 of "1705708800000|payment.success|txn_abc123|50000|VND"

ISignOptions

PropertyTypeDescription
timestampnumberUnix timestamp (milliseconds) for replay protection
eventTypestringEvent type identifier (e.g., payment.success)
partsstring[]Additional payload parts to include in the signature
secretstringHMAC secret key (typically the webhook's shared secret)

API Summary

MethodSignatureDescription
getInstance(): CryptoUtilityGet singleton instance
encrypt(text: string): stringAES-256-GCM encrypt with application secret
decrypt(encryptedText: string): stringAES-256-GCM decrypt with application secret
sign(opts: ISignOptions): stringHMAC-SHA256 sign a structured payload

Usage Examples

Encrypt Payment Credentials

The @nx/payment package uses CryptoUtility to encrypt payment provider credentials before storing them in the database, and decrypt them when loading configurations:

typescript
import { CryptoUtility } from '@nx/core';

// Encrypt credentials before storing
const crypto = CryptoUtility.getInstance();
const encryptedApiKey = crypto.encrypt(vnpayApiKey);
const encryptedSecretKey = crypto.encrypt(vnpaySecretKey);

await configurationRepository.create({
  data: {
    key: 'VNPAY_QR_MMS',
    value: JSON.stringify({
      apiKey: encryptedApiKey,
      secretKey: encryptedSecretKey,
    }),
  },
});
typescript
// Decrypt credentials when loading
const config = await configurationRepository.findByKey('VNPAY_QR_MMS');
const parsed = JSON.parse(config.value);

const apiKey = crypto.decrypt(parsed.apiKey);
const secretKey = crypto.decrypt(parsed.secretKey);

Sign Webhook Payloads

The @nx/payment webhook dispatcher uses sign() to generate signatures that webhook consumers can verify:

typescript
import { CryptoUtility } from '@nx/core';

const crypto = CryptoUtility.getInstance();

// Generate signature for outgoing webhook
const timestamp = Date.now();
const signature = crypto.sign({
  timestamp,
  eventType: 'mq-pay:attempt.success',
  parts: [transactionId, attemptId, amount.toString()],
  secret: webhookConfig.secret,
});

// Include in webhook headers
const headers = {
  'X-Webhook-Signature': signature,
  'X-Webhook-Timestamp': timestamp.toString(),
};

Verify Incoming Webhook Signatures

On the receiving end, recompute the signature and compare:

typescript
import { CryptoUtility } from '@nx/core';

function verifyWebhookSignature(req: Request, secret: string): boolean {
  const crypto = CryptoUtility.getInstance();
  const receivedSignature = req.headers.get('X-Webhook-Signature');
  const timestamp = Number(req.headers.get('X-Webhook-Timestamp'));
  const body = req.body;

  const expectedSignature = crypto.sign({
    timestamp,
    eventType: body.eventType,
    parts: [body.transactionId, body.attemptId, body.amount.toString()],
    secret,
  });

  return receivedSignature === expectedSignature;
}

Architecture

IGNIS Framework Reference

CryptoUtility uses two primitives from the IGNIS Framework:

  • AES class -- Provides AES-256-GCM authenticated encryption. See IGNIS Crypto Helper for the full AES API, including file encryption, custom IV, and encoding options.
  • hash() function -- Provides HMAC-SHA256 and MD5 hashing. See IGNIS Crypto Utility for usage details.

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.