用 Hashids 把数据库 ID 藏起来

最近在做一个对外 API,设计的时候有个问题挺头疼的——数据库用自增 ID,但直接把 /users/1/users/2 这种 URL 暴露出去总感觉不太好。用户一看就知道你有多少用户,还能遍历爬数据。

一开始想过用 UUID,但那玩意儿太长了,36 个字符,URL 看着巨丑。后来无意中刷到一个叫 Hashids 的库,专门干这事的。

这玩意儿是干嘛的

简单说就是把数字 ID 转成类似 YouTube 视频链接那种短字符串。

比如 jN5k8xQ 这种,看着就像随机生成的,但实际上可以还原回原来的数字。这样用户看到的 URL 就是 /users/jN5,根本猜不出来有多少用户,比自增 ID 隐蔽多了。

安装一把梭

Composer 安装就完事了:

1
composer require hashids/hashids

注意需要装 bcmathgmp 扩展,不然会报错。

基本用法

用起来贼简单:

1
2
3
4
5
6
7
8
9
use Hashids\Hashids;

$hashids = new Hashids('my salt');

// 编码
$hash = $hashids->encode(123); // 比如 "x3kQ"

// 解码
$numbers = $hashids->decode('x3kQ'); // 返回 [123]

有个小细节要注意,decode() 返回的永远是数组,即使你只编码了一个数字。所以取值要这样写:

1
$id = $hashids->decode($hash)[0] ?? null;

几个配置参数

构造函数可以传三个参数:

1
2
3
4
5
$hashids = new Hashids(
'my-salt', // 盐值,不同项目用不同的盐
8, // 最小长度,短了会填充
'abcdefghij123456' // 自定义字符集
);

盐值很重要,不同项目的盐值不一样,生成的哈希就不一样,可以防止被破解。

最小长度这个参数也挺有用,比如你想要所有 ID 都至少 8 位,短的会自动填充,看着更统一。

一个小坑

刚开始用的时候发现,传无效的哈希字符串去解码,不会报错,而是返回空数组。所以要判断解码是否成功:

1
2
3
4
$decoded = $hashids->decode($hash);
if (empty($decoded)) {
// 无效的哈希
}

还有不支持负数,传负数进去会返回空字符串,这点文档里有写但容易忘。

不是加密,不是加密,不是加密

重要的事情说三遍。Hashids 的作者在文档里反复强调,这不是加密库,不要用来加密敏感数据。

它能做到的只是”混淆”,让数字看起来不像数字。真要保护数据还是得靠权限控制和加密传输。我理解它更像是”给 ID 穿了件马甲”,防君子不防小人。

顺便提一句

原作者后来又出了个新版本叫 Sqids,本质上是一样的东西,据说算法有些改进。新项目可以考虑用 Sqids,不过 Hashids 也在维护,用着没啥问题。

最后

这个库在我们项目里用了大半年,效果挺好的。API 的 URL 看着干净,也避免了一些简单的遍历攻击。唯一的缺点就是调试的时候要看数据库记录得先解码一下,不过写个小脚本就能搞定的事,不算什么大问题。

如果你也在为暴露自增 ID 这事头疼,可以试试 Hashids,轻量好用,一个 Composer 包就搞定。