@tunghus193/pii-crypto
v0.2.1
Published
Deterministic PII encryption for Rino services
Readme
@tunghus193/pii-crypto
Deterministic PII encryption for Rino services (Node >=10.13.0).
This package is designed to work with the KMS v2 key provider (vh_account) which returns two 32-byte keys:
aes_key(pii-aes-key): AES-256-GCM encrypt/decrypt payloadsearch_key(pii-search-key): derive deterministic IV viaHMAC-SHA256(search_key, plaintext)[0:12]
Install
npm install @tunghus193/pii-cryptoNo GitLab token or custom registry — published on npmjs.com.
Publish (maintainers)
Requires npm account — publishes under your personal scope @tunghus193.
npm login # token lưu vào ~/.npmrc — không tạo .npmrc trong repo
npm whoami # phải in đúng username (tunghus193)
npm test
npm publish --access publicCI: set NPM_TOKEN in GitLab variables (CI tự tạo .npmrc trong job).
Environment variables
KMS_URL: base URL ofvh_account(e.g.https://account-api.example.com)KMS_CLIENT_ID/KMS_CLIENT_SECRET: credentials stored inkms_service_clientsPII_WRITE_ENABLED:1to write encrypted values, otherwise keep plaintext (dual-read always enabled)
Quickstart (Core only)
const { createPiiCrypto, FIELD_TYPES } = require('@tunghus193/pii-crypto');
async function main() {
const pii = createPiiCrypto({
kms: {
baseUrl: process.env.KMS_URL,
clientId: process.env.KMS_CLIENT_ID,
clientSecret: process.env.KMS_CLIENT_SECRET,
},
});
await pii.warmUp(); // GET /kms/v1/keys/pii (once at startup)
const enc = pii.encrypt('0912345678', { fieldType: FIELD_TYPES.PHONE });
const plain = pii.decrypt(enc);
console.log({ enc, plain });
}
main().catch((e) => {
console.error(e);
process.exit(1);
});Express + Sequelize (recommended for vh_api)
const { createPiiCrypto } = require('@tunghus193/pii-crypto');
const { attachSequelize } = require('@tunghus193/pii-crypto/sequelize');
async function initPii(models) {
const pii = createPiiCrypto({
kms: {
baseUrl: process.env.KMS_URL,
clientId: process.env.KMS_CLIENT_ID,
clientSecret: process.env.KMS_CLIENT_SECRET,
pollIntervalMs: 5 * 60 * 1000,
},
flags: {
getWriteEnabled: async () => process.env.PII_WRITE_ENABLED === '1',
},
});
await pii.warmUp();
attachSequelize(pii, models, {
User: ['mobile', 'email'],
Contact: ['mobile'],
});
return pii;
}What you get:
- Write path:
beforeSaveencrypts configured fields whenPII_WRITE_ENABLED=1 - Read path:
afterFinddecryptsenc.1.…values (and legacyenc$1$…); plaintext passes through - Envelope format:
enc.1.<iv>.<payload>(dot-separated). The legacy$separator broke Sequelize bound queries (Named bind parameter ... has no value) because Sequelize scans the whole SQL for$wordtokens. Legacy envelopes remain readable and are lazy-upgraded on save; searches dual-match both formats. - Search:
beforeFindrewriteswheretoIN (raw, detEncrypt(raw))so queries keep working during migration
Sequelize search behavior
Exact-match predicates on mapped PII fields support scalar values, Op.eq,
array shorthand, and Op.in. The transformer follows Sequelize symbol
containers such as Op.and and Op.or, but rewrites only mapped PII fields.
Non-PII values and non-plain objects are preserved by reference, including
Date, Buffer, RegExp, Sequelize.literal, Fn, Col, and custom class
instances. Operators that cannot perform deterministic exact matching, such
as Op.like, ranges, and regexp, are left unchanged.
Express middleware (non-Sequelize)
const express = require('express');
const { expressPiiMiddleware } = require('@tunghus193/pii-crypto/express');
async function main() {
const app = express();
app.use(await expressPiiMiddleware({
kms: {
baseUrl: process.env.KMS_URL,
clientId: process.env.KMS_CLIENT_ID,
clientSecret: process.env.KMS_CLIENT_SECRET,
},
}));
app.get('/demo', (req, res) => {
res.json({ enc: req.pii.encrypt('0912345678') });
});
app.listen(3000);
}Next.js (server-only helper)
Create a singleton getter (only import this from server code):
// lib/pii.js
const { createNextPiiGetter } = require('@tunghus193/pii-crypto/next');
module.exports = createNextPiiGetter({
kms: {
baseUrl: process.env.KMS_URL,
clientId: process.env.KMS_CLIENT_ID,
clientSecret: process.env.KMS_CLIENT_SECRET,
},
});Use it in route handlers:
const getPii = require('./lib/pii');
async function handler(req, res) {
const pii = await getPii();
const enc = pii.encrypt(req.query.mobile);
res.json({ enc });
}KMS endpoints used
GET /kms/v1/keys/pii(startup fetch, returns key material)GET /kms/v1/keys/config(polling versions, optional)
Development
npm test
npm run build