Pular para o conteúdo principal

Tokenization / Format-Preserving Encryption

A partir da v1.9, o CipherVault expõe tokenization determinística em 3 formatos custom. A v3.0 adicionou NIST FF1 oficial (SP 800-38G) em 4 radices, recomendado para PCI DSS e LGPD por reconhecimento auditor. Os formatos custom continuam funcionando para vaults existentes.

Útil para PCI DSS (mascarar PAN preservando formato), LGPD (substituir CPF/RG sem alterar schema do DB), e qualquer cenário onde você precisa lookup estável sem revelar valor real.

Determinístico = mesma input → mesmo token

"12345678900" → "98712345005" (preserving)
"12345678900" → "a1f2b8c9..." (alphanumeric)
"12345678900" → "550e8400-..." (uuid v4)

Como mesmo input gera mesmo token sempre, você pode:

  • Fazer JOIN entre tabelas em DB do app
  • Detectar duplicatas sem revelar o valor
  • Buscar/indexar pelo token

Sob o capô: blind index HMAC-SHA256(blind_key, value) → seed para xorshift32 PRNG → mapeamento char-by-char com regras do formato.

Formatos NIST FF1 (recomendado — v3.0+)

NIST SP 800-38G define Format-Preserving Encryption baseado em estrutura Feistel + AES-CBC-MAC. O token É o ciphertext — não há lookup em DB, detokenize é apenas decrypt. Auditor-friendly.

FormatoRadixAlphabetCaso de uso
ff1-decimal100-9PAN (cartão), CPF, telefone
ff1-hex160-9 a-fHashes, IDs hex
ff1-alpha26A-ZCódigos alfa, RG sem dígito
ff1-alnum62A-Za-z0-9Tokens genéricos
curl -X POST https://cv.acme.com.br/tokenization/vaults \
-d '{ "name": "pan-prd", "format": "ff1-decimal" }'
"4111111111111111" → "9823745638291052" (preserva 16 dígitos)
"12345678900" → "98712345005" (CPF, 11 dígitos)

Formatos custom (v1.9 — mantidos)

Para vaults criados antes da v3.0 ou casos específicos.

preserving (caso PCI clássico)

Preserva char-class:

Input charOutput char
dígito (0-9)dígito (0-9)
letra (a-z, A-Z)letra (mesma case)
símbolo literal (-, ., /, etc.)mesmo símbolo
"4111-1111-1111-1111" → "9823-7456-3829-1052"
"123.456.789-00" → "987.654.321-77"
"rafael@acme.com.br" → "tlpqrk@aupx.byw.gw"

uuid (v4 determinístico)

"qualquer-coisa" → "550e8400-e29b-41d4-a716-446655440000"

Útil quando schema do app exige UUID (ex.: trocar email user-facing por UUID pseudo-anônimo).

alphanumeric (length match)

N chars [A-Za-z0-9]:

"12345678900" (length=11) → "a3xR9pQz1Vk"

Setup

1. Criar vault de tokenization

curl -X POST https://cv.acme.com.br/tokenization/vaults \
-H "Authorization: Bearer $CV_TOKEN" \
-d '{
"name": "billing-pii",
"format": "preserving",
"description": "Tokenization de CPF/RG/PAN para tabela customers"
}'

Backend gera DEK (cifra com KEK) e blind_key (cifra com KEK) — ambos armazenados em tokenization_vaults.

2. Tokenizar

curl -X POST https://cv.acme.com.br/tokenization/vaults/billing-pii/tokenize \
-H "Authorization: Bearer $CV_TOKEN" \
-d '{ "values": ["12345678900", "98765432100"] }'

# Resposta:
# {
# "tokens": ["98712345005", "12340987763"]
# }

3. Detokenizar

curl -X POST https://cv.acme.com.br/tokenization/vaults/billing-pii/detokenize \
-H "Authorization: Bearer $CV_TOKEN" \
-d '{ "tokens": ["98712345005"] }'

# Resposta:
# {
# "values": ["12345678900"]
# }

Audit de detokenize tem severity warning — toda revelação de valor real é high-trust.

Caso de uso PCI DSS

-- DB do app armazena apenas tokens preserving format
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
cpf_token VARCHAR(11), -- valor tokenizado
pan_token VARCHAR(19), -- valor tokenizado
...
);

-- Indexes funcionam normalmente sobre tokens
CREATE INDEX idx_cpf ON customers(cpf_token);

-- JOIN entre tabelas:
SELECT c.*, p.amount
FROM customers c
JOIN payments p ON p.cpf_token = c.cpf_token
WHERE c.cpf_token = '98712345005';

-- App detokeniza apenas no momento de exibir / processar

Resultado: o DB nunca tem PAN/CPF real. Vazamento de dump SQL não vaza PII. O CipherVault é o único lugar com a chave para reverter.

SDK Python

from ciphervault.admin import AdminClient

cv = AdminClient(url="https://cv.acme.com.br", token=os.environ["CV_TOKEN"])

# Tokenizar lote
tokens = cv.tokenization.tokenize(
vault_name="billing-pii",
values=["12345678900", "98765432100"],
)

# Detokenizar
values = cv.tokenization.detokenize(
vault_name="billing-pii",
tokens=["98712345005"],
)

Implementação

NIST FF1 (v3.0+)

  • lib/ff1.js — Feistel (10 rounds) com AES-CBC-MAC como round function
  • Token É o ciphertext — não há tokenization_records.ciphertext separado
  • Sem blind_index necessário — determinismo vem da própria construção FF1 (mesma chave + mesma input + mesmo radix → mesmo token)
  • Reversível com a mesma DEK do vault

Custom (v1.9 — fallback)

  • Determinismo via blind index: HMAC-SHA256(blind_key, value) é o seed do PRNG. Mesma input → mesmo seed → mesma sequência → mesmo token
  • Ciphertext AES-256-GCM(DEK, value) armazenado em tokenization_records.ciphertext — usado em detokenize
  • UNIQUE constraint em blind_index previne duplicatas

Comum

  • Cross-tenant guard em todas as ops — vault de tenant A nunca acessível por tenant B
  • Plaintext nunca em logs — apenas blind_index e ciphertext
  • Delete vault: exige reason ≥ 10 chars. Apaga DEK + blind_key + records — tokens gerados ficam inutilizáveis (não há recovery)

Tabelas

tokenization_vaults (id, name, format, dek_ciphertext, blind_key_ciphertext, ...)
tokenization_records (vault_id, blind_index UNIQUE, token, ciphertext, ...)

Limitações

  • Format preserving: símbolos literais não são embaralhados — pode vazar estrutura (ex.: xxx.yyy.zzz-nn mantém os pontos e o traço)
  • Não suporta ranges/comparações (>, <) — só lookup de igualdade
  • Detokenize em lote limitado a 1000 itens por request

Boas práticas

  • Vaults separados por tipo de dadocpf, rg, pan, email. Não misture.
  • Use aad quando aplicável (ex.: tenant_id|customer_id) — embora ainda não exposto no endpoint atual, blind_key isola por vault
  • Audit alertas — picos em detokenize fora de horário comercial
  • Delete vault só com aprovação — em Enterprise, configure approval para tokenization_vault_delete (não está no enum default — adicionar customizado)
  • Rotacionar blind_key não é trivial — exige re-tokenization de TODOS os registros (planeje em janelas de manutenção)