@noviuz/kyc-crypto
v2.0.0
Published
Web Crypto helpers to encrypt the identity payload in-browser (defense-in-depth on top of HTTPS) for the Noviuz Hosted KYC Flow.
Readme
@noviuz/kyc-crypto
Web Crypto helpers to encrypt the identity payload in the browser before it is sent
straight to the Noviuz backend — defense-in-depth on top of HTTPS. Used by the hosted page
(@noviuz/web) only — never imported by partner JS or @noviuz/kyc-js; partners never see
this package's API.
Scheme (envelope v2 — AAD-bound)
Hybrid RSA-OAEP-256 + AES-256-GCM, with the ciphertext cryptographically bound to its context:
- generate a fresh AES-256-GCM key per call,
- encrypt the JSON identity/media payload with it, authenticating an AES-GCM
additionalDatabuilt from{ sessionId, keyId, payloadKind }(noviuz-kyc:v2\n<sessionId>\n<keyId>\n<payloadKind>), - wrap the AES key with the backend's RSA-OAEP public key.
The AAD binding means a ciphertext produced for one session/key/payload kind cannot be decrypted in a slot that expects a different one — even a MITM or a bug that splices ciphertext across requests fails closed on the backend's context check, not just on transport security.
import { encryptIdentityPayload } from '@noviuz/kyc-crypto';
const envelope = await encryptIdentityPayload(
{ taxNumber, fullName },
{ publicKeySpkiBase64, keyId, sessionId }, // sessionId REQUIRED — bound into the AAD
'identity', // PayloadKind: 'identity' | 'document' | 'selfie' | 'consent' | 'device'
);
// envelope = { v: 2, alg, keyId?, encryptedKey, iv, ciphertext } — opaque, no plaintext.API
encryptIdentityPayload(data, config, payloadKind, crypto?)→EncryptedIdentityPayloadencryptMediaPayload({ data, mime }, config, payloadKind, crypto?)— same envelope for document/selfie images (after client-side resize), with a larger 8MB capdecryptIdentityPayload(envelope, privateKey, { sessionId, payloadKind }, crypto?)→ original data (backend/tests). Only accepts v2 (context-bound) envelopes; refuses a legacy v1 envelope.buildEnvelopeAad({ sessionId, keyId, payloadKind })→ the exact byte sequence authenticated as AAD — must match the backend's reconstruction byte-for-byte.importRsaPublicKey(spkiBase64, crypto?)generateRsaKeyPair(crypto?)— dev/test/provisioning onlybytesToBase64/base64ToBytes,KycCryptoError
config: CryptoSessionConfig is { publicKeySpkiBase64, sessionId, keyId? } — sessionId is
required (an omitted/wrong value encrypts fine client-side but fails the backend's AAD check,
fail-closed by design).
Requires a secure context (crypto.subtle). Identity plaintext is capped at 16KB.
