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.
| Formato | Radix | Alphabet | Caso de uso |
|---|---|---|---|
ff1-decimal | 10 | 0-9 | PAN (cartão), CPF, telefone |
ff1-hex | 16 | 0-9 a-f | Hashes, IDs hex |
ff1-alpha | 26 | A-Z | Códigos alfa, RG sem dígito |
ff1-alnum | 62 | A-Za-z0-9 | Tokens 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 char | Output 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.ciphertextseparado - 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 emdetokenize UNIQUEconstraint emblind_indexprevine 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-nnmanté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 dado —
cpf,rg,pan,email. Não misture. - Use
aadquando 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)