2. Tokenization 的工作原理与实现方式

2.1 什么是 Tokenization?一句话讲透

Tokenization,说白了就是把敏感数据换成一串无意义的「替身号码」。这个替身叫 ,它本身不包含任何原始信息,但你能用它完成业务操作——比如用 付款、查订单、看病历。

我刚开始接触这个概念时,总以为是加密的变种。后来踩过一次坑才明白: 不是可逆算法,它是一对一的映射关系。你拿着 找不到原始值,除非去查一张专门的映射表( Vault)。

核心区别:加密是数学变换(可逆),Tokenization 是查表替换(单向依赖)。 Vault 那张表才是真正的敏感数据保险柜。

2.2 两步走:生成 与存储映射

2.2.1 生成方式

常见的生成方式有三种,我按生产环境碰到概率排个序:

  1. 随机生成 —— 完全随机字符串(UUID、随机数字)。安全性最高,碰撞概率可控。我自己的项目里90%场景都选这个。
  2. 格式保留(FPE) —— 生成的 和原数据格式一样。比如信用卡号 16 位, 也是 16 位数字。处理遗留系统时这个特别香,但性能比随机差一些。
  3. Hash 加盐 —— 严格来说不算 Tokenization,但很多团队拿它当轻量方案。注意:Hash 不可逆,但彩虹表风险需要加随机盐。
方式安全性性能格式保留我见过的最佳场景
随机生成高快否支付令牌、用户ID
FPE中高中是旧系统改造、数据库索引字段
Hash+盐中快否内部临时 ,不做长期存储

我个人习惯是:能用随机就别用 FPE。格式保留看着方便,但攻击者可以靠格式约束缩小暴力枚举空间——我在一次渗透测试里见识过,攻击者只用 2 小时就把 16 位数字 FPE 的 空间压缩到 10^8 量级。

2.2.2 Vault 映射表

生成 后,必须把 和原始值的映射关系存起来。这个表就叫 Vault。它的设计直接决定系统安全性。

我曾经帮一家金融科技公司重构过 Vault,他们的原始表长这样:

-- 反面教材:不能这么建
CREATE TABLE _vault (
  id INT PRIMARY KEY,
  raw_credit_card VARCHAR(19),
   VARCHAR(64),
  created_at TIMESTAMP
);

问题是:谁查到这个表,谁就拿到原始卡号, 形同虚设。正确的做法:

-- 正确做法:加密存储原始值
CREATE TABLE _vault (
  _hash VARCHAR(64) PRIMARY KEY,  -- 对  做 hash 做索引
  encrypted_raw BLOB,                  -- 原始数据用字段级加密
  _format VARCHAR(20),
  created_at TIMESTAMP,
  expires_at TIMESTAMP
);
-- 注意:没有单独的 id 字段,靠 _hash 查询
-- 加密密钥单独存储在 HSM 或 KMS 中

我踩过的坑:千万不要直接在 Vault 里明文存原始值。即使用户只能通过 API 查 ,数据库被拖走时照样裸奔。要么整表加密,要么至少对原始字段单独加密。

2.3 Tokenization 的完整工作流

画个图大家更容易理解。这是我在项目里经常贴墙上的流程:

Tokenization 工作流 Tokenization 典型工作流 应用系统 收集敏感数据 Tokenization API 生成 & 存储 Vault 加密存储映射 ( ⇄ 原始值) 返回 (红色箭头) 业务系统使用 进行后续操作(支付、查询、记录) 需要原始数据时:去令牌化 应用系统请求 用 换原始值 去令牌化 API 权限校验 & 解密 Vault 解密返回原始值 返回原始值 注意 去令牌化必须经过严格权限控制,一般只允许授权服务访问。 生产环境中建议配合审计日志,记录每次去令牌化请求。

整个过程其实就两步:

  • 令牌化(Tokenize): 应用把敏感数据发给 Tokenization API,API 生成 并把映射存进 Vault,然后只把 还给应用。此后数据就安全了。
  • 去令牌化(Detokenize): 当业务确实需要原始数据(比如退款、征信核查),应用拿 去换,同样走 API,Vault 解密后返回。

嗯,这里要注意:去令牌化操作必须比令牌化更严格。我见过有团队只对令牌化做了权限控制,去令牌化接口却没人管,结果内鬼直接批量拉数据——这相当于给贼留了后门。

2.4 三种主流实现架构

2.4.1 集中式 Vault

最常见的方案。所有 映射存在一个中心化存储,对外暴露标准 API。优点是管理简单、审计方便;缺点是单点瓶颈,Vault 挂了全系统瘫痪。

2.4.2 分布式 服务

把 Vault 拆成多个分片,或者用 Redis + 持久化组合。适合高并发场景。我曾在支付系统中用 Redis Cluster 做 缓存,TPS 扛到 5 万+。但注意: 映射不能丢,所以缓存必须配合数据库双写。

2.4.3 客户端内嵌 库

有些场景(比如移动端 App)不适合每次都请求 API,干脆在客户端 SDK 里做 Tokenization。原始数据不走网络,只在本地生成 。典型代表是 Apple Pay 的 Device Account Number。但这需要强安全环境(如 Secure Enclave),而且 Vault 的同步是一个难点。

一句话总结选型:合规敏感、需要严格审计的选集中式;高并发、低延迟的选分布式;设备绑定、离线场景的选客户端内嵌。

2.5 避坑指南

这部分都是我用真金白银换来的经验:

  • 长度不要拍脑袋 —— 太长浪费存储,太短容易碰撞。我一般建议随机 长度 ≥ 32 字节(256 位),FPE 的 长度跟原始数据相同。
  • Vault 一定要做数据生命周期管理 —— 过期 要定期清理,否则映射表越积越大,查询越来越慢。
  • 高可用别只靠一个节点 —— Vault 是典型的读多写少(写 只有一次,但后续业务查询、去令牌化会反复查),读写分离 + 缓存命中率要重点设计。
  • 我曾经遇到一个坑:开发阶段为了方便,把 Vault 和业务库放在同一个 RDS 实例里。结果上线后业务库大查询拖慢了 写入,导致并发 生成直接超时。教训是: Vault 必须独立部署,最好用独立的数据库集群。

2.6 性能指标参考

下表来自我参与的一个日活 500 万电商项目的实际压测数据:

操作平均延迟P99 延迟吞吐量(每秒)备注
Tokenize(随机 32 字节)1.8ms5.2ms8,200单节点 + Redis 缓存
Tokenize(FPE 16 位)12.4ms35.1ms1,200FPE 计算密集
Detokenize(命中缓存)0.6ms2.1ms15,000+缓存命中率 92%
Detokenize(未命中)6.3ms18.7ms3,500需要查库并解密

你看 FPE 的延迟比随机高了近 7 倍。所以除非遗系系统必须格式保留,我建议优先用随机 。别为了「看着顺眼」牺牲性能。

2.7 安全补充: Vault 的加密层级

很多新手以为 Tokenization 本身可以替代加密,这是误解。 Vault 里的原始数据必须额外加密。我推荐两层加密策略:

  1. 静态加密:Vault 表空间级加密(TDE),防止磁盘被盗泄露。
  2. 字段级加密:对原始数据列单独用 AES-256 加密,密钥托管在 HSM 或 KMS。这样即使 DBA 有权限查表,也看不到明文。

说实话,大部分数据泄露事件都不是从 Tokenization 接口突破的,而是直接拖库或拿到 Vault 的访问权限。所以保护 Vault 的访问控制比如何生成 更重要——这句话送给大家,共勉。