Tokenization工作机制:生成、存储与映射

Tokenization 说白了就三件事:生成令牌、存好令牌、记清谁是谁。我做了这么多年数据安全,见过不少团队在第一步就翻车——令牌乱生成,映射表一团糟,最后连自己都解不回去。嗯,咱们今天就掰开揉碎讲清楚。

1. 生成过程:从原始值到令牌

生成不是一拍脑袋扔个随机数就完事。我习惯把它拆成四个步骤:

  1. 分割(Split) – 把原始数据按规则拆成有意义的最小单元。比如信用卡号 “4111-1111-1111-1111”,拆成 BIN + 中间段 + 校验位。
  2. 标准化(Normalize) – 去掉空格、连字符、大小写统一。你想想看,如果 “abc” 和 “ABC” 生成两个不同令牌,那映射表得膨胀成什么样。
  3. 映射核心(Mapping) – 原始值通过映射函数产生一个唯一令牌。这个函数可以是查随机表、加密算法、或者 FPE 算法。
  4. 输出(Output) – 返回令牌,同时把映射关系写进存储层。

我在项目里遇到过最典型的坑:标准化没做好。有一次客户反馈同一个身份证号在不同系统里生成了不同令牌。后来一查,一个系统存了 “12345678”,另一个存了 “1234 5678”。嗯,从此我把标准化放在了生成流程的第一步,谁也别想跳过。

我的习惯:生成时加一个 version 字段。这样以后升级映射算法,老令牌还能反向查回去,不用全量重生成。

2. 存储设计:令牌与映射表

生成完令牌,存什么?我见过有些设计只存令牌,原始值直接丢进 DB 里加密。这不是 Tokenization,这叫加密备份。Tokenization 的核心是映射表,它决定了你能不能找回原始值。

一张标准的映射表大概长这样:

字段类型说明
_idVARCHAR(64)令牌唯一标识
raw_valueBLOB / TEXT原始值(加密存储)
hash_valueCHAR(64)原始值的 SHA-256 哈希,用于去重和查找
namespaceVARCHAR(32)业务域,避免不同模块令牌冲突
created_atDATETIME生成时间
versionINT映射算法版本号

为什么要有 hash_value?你想想看,查找的时候如果每次都拿原始值去比对,性能极差。用哈希做索引,先找哈希,再验原始值是否真匹配,快得多。我建议哈希和令牌做成联合唯一索引,这样同一个原始值在不同 namespace 下可以有不同的令牌,不会串。

注意:原始值在映射表里必须加密存储。别直接明文躺数据库,否则等保审计会让你哭。我见过一家公司直接把身份证号明文放在映射表里,理由是“令牌化了别人也看不懂”。嗯,第二天就被合规约谈了。

3. 映射关系:双向查找

映射关系本质是两个方向的函数:

  • 正向映射:原始值 → 令牌(生成时用)
  • 反向映射:令牌 → 原始值(脱敏查询或恢复时用)

实现方式我最常用两种:

// 方式一:查表法(推荐生产环境)
class LookupTableMapping {
    insert(raw, ) {
        // 插入映射关系,重复原始值返回已有令牌
    }
    lookup() {
        // 根据令牌查找原始值
    }
}

// 方式二:可逆算法(如 FPE,但这章不展开)
class AlgorithmicMapping {
    tokenize(raw) {
        // 用密钥加密原始值,直接得到令牌
    }
    detokenize() {
        // 用密钥解密令牌,还原原始值
    }
}

我做支付系统时用的是查表法,因为要支持令牌长度定制——比如业务要求令牌必须能填进原格式的卡号字段(16位数字),查表法可以灵活控制令牌的生成策略。而 FPE 算法虽然不依赖存储,但长度和字符集受限,适合空间敏感的场景。

生产级提示:无论用哪种方法,反向查找一定要加熔断和限流。曾经有个同事把脱敏接口暴露在外网,结果流量把映射表打挂了,所有令牌都查不回来。你猜后来怎么着?业务停了半小时。

4. 避坑指南

  • 令牌不能包含敏感信息 – 我见过有人直接用原始值的哈希做令牌,这等于把明文特征暴露给别人。哈希后的值可被彩虹表反向猜测。
  • 映射表要备份 – 映射表丢了,令牌就变成一堆垃圾。我建议每天全量备份,实时 binlog 同步。
  • 令牌唯一性要强约束 – 数据库层面加唯一索引,别靠代码去重。两个线程同时插入同样的原始值,有几率生成两个不同令牌。
  • 小心令牌重用 – 如果原始值被删除,映射表里的令牌要不要回收?我偏向标记为过期而不是物理删除,防止历史数据引用断裂。
小技巧:我给每个令牌生成时都会打一个随机校验位(比如 Luhn 算法),这样接口层就能快速判断令牌格式是否合法,不用每次都查库。前端也能简单校验,用户体验好很多。
Tokenization 工作机制流程图 原始数据 分割 + 标准化 映射函数 (查表/算法) 生成令牌 映射表 (加密存储) ↔ raw_value 反向查找 反向查询路径 (虚线) 关键说明 ① 正向过程:原始数据 → 分割/标准化 → 映射函数 → 令牌 ② 存储依赖:映射表必须加密存储,哈希字段用于快速去重检索 ③ 反向过程:通过令牌查找映射表 -> 解密原始值 -> 返回

图里标得很清楚:正向生成走上半部分,反向查询绕下面一圈。我在第一版设计时就把这个图贴在会议室墙上,每次评审都指着它说“映射表是心脏,谁动它谁负责”。嗯,后来真没出过事。

总结一句话:生成靠函数,存储靠加密表,映射靠双向索引。做好这三环,Tokenization 的核心就稳了。