CryptoUtility
Tổng quan
CryptoUtility là một singleton cung cấp các thao tác mật mã ở cấp ứng dụng. Nó bọc helper AES của IGNIS Framework (chế độ AES-256-GCM) và hàm hash để cung cấp hai khả năng:
- Mã hoá/giải mã đối xứng -- dùng
APP_ENV_APPLICATION_SECRETlàm khoá, để lưu trữ dữ liệu nhạy cảm như thông tin xác thực thanh toán trong cơ sở dữ liệu. - Ký HMAC-SHA256 -- để xác minh tính toàn vẹn của payload trong webhook.
Nguồn: packages/core/src/utilities/crypto.utility.ts (53 dòng)
Định nghĩa lớp
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;
}Cấu hình
| Biến | Bắt buộc | Mô tả |
|---|---|---|
APP_ENV_APPLICATION_SECRET | Có | Khoá bí mật dùng cho mã hoá AES-256-GCM. Phải được thiết lập trước khi gọi CryptoUtility.getInstance(). |
# .env.development
APP_ENV_APPLICATION_SECRET=my-application-secret-key-at-least-32-charsWARNING
APP_ENV_APPLICATION_SECRET được dùng làm khoá AES-256-GCM. Nếu bạn thay đổi nó, toàn bộ dữ liệu đã mã hoá trước đó sẽ không thể khôi phục. Hãy coi giá trị này như hạ tầng trọng yếu.
Import
import { CryptoUtility } from '@nx/core';
// or
import { CryptoUtility } from '@nx/core/utilities';API
getInstance()
Trả về instance singleton. Ở lần gọi đầu tiên, nó khởi tạo cipher AES với APP_ENV_APPLICATION_SECRET.
const crypto = CryptoUtility.getInstance();encrypt(text)
Mã hoá một chuỗi văn bản thuần bằng AES-256-GCM với application secret. Trả về chuỗi ciphertext mã hoá base64 chứa IV, ciphertext và GCM authentication tag.
const crypto = CryptoUtility.getInstance();
const encrypted = crypto.encrypt('my-api-key-12345');
// "base64-encoded-iv+ciphertext+authtag"decrypt(encryptedText)
Giải mã một chuỗi đã mã hoá trước đó về văn bản thuần. Ném lỗi nếu ciphertext bị can thiệp (GCM authentication thất bại) hoặc nếu dùng sai khoá.
const crypto = CryptoUtility.getInstance();
const original = crypto.decrypt(encrypted);
// "my-api-key-12345"sign(opts)
Tạo chữ ký HMAC-SHA256 để xác minh payload của webhook. Nối tất cả các phần với | (dấu pipe) rồi băm với secret được cung cấp.
Định dạng chữ ký: timestamp|eventType|part1|part2|... được băm bằng SHA256, đầu ra ở dạng base64.
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
| Thuộc tính | Kiểu | Mô tả |
|---|---|---|
timestamp | number | Unix timestamp (mili giây) để chống tấn công phát lại |
eventType | string | Định danh loại sự kiện (ví dụ: payment.success) |
parts | string[] | Các phần payload bổ sung để đưa vào chữ ký |
secret | string | Khoá bí mật HMAC (thường là shared secret của webhook) |
Tóm tắt API
| Phương thức | Chữ ký | Mô tả |
|---|---|---|
getInstance | (): CryptoUtility | Lấy instance singleton |
encrypt | (text: string): string | Mã hoá AES-256-GCM với application secret |
decrypt | (encryptedText: string): string | Giải mã AES-256-GCM với application secret |
sign | (opts: ISignOptions): string | Ký HMAC-SHA256 cho một payload có cấu trúc |
Ví dụ sử dụng
Mã hoá thông tin xác thực thanh toán
Package @nx/payment dùng CryptoUtility để mã hoá thông tin xác thực của nhà cung cấp thanh toán trước khi lưu vào cơ sở dữ liệu, và giải mã chúng khi tải cấu hình:
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,
}),
},
});// 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);Ký payload của webhook
Bộ phát webhook của @nx/payment dùng sign() để tạo chữ ký mà bên tiêu thụ webhook có thể xác minh:
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(),
};Xác minh chữ ký webhook đến
Ở phía nhận, tính lại chữ ký rồi so sánh:
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;
}Kiến trúc
Tham chiếu IGNIS Framework
CryptoUtility dùng hai primitive từ IGNIS Framework:
- Lớp
AES-- Cung cấp mã hoá xác thực AES-256-GCM. Xem IGNIS Crypto Helper để biết API AES đầy đủ, bao gồm mã hoá file, IV tuỳ chỉnh và các tuỳ chọn encoding. - Hàm
hash()-- Cung cấp băm HMAC-SHA256 và MD5. Xem IGNIS Crypto Utility để biết chi tiết sử dụng.