在业务系统中,通常都存在着根据id查询详细信息的场景,比如GET /item/100,获取id为100的商品,这是最常规的做法,但不适用于对外服务,因为数字id泄露了内部信息,通过更改id可以访问其他数据,如果使用程序脚本还可以把所有数据爬下来,另外通常id是递增的,id较小通常代表创建时间早。
因而对外服务中我们需要对id做混淆,合格的算法混淆后的id通常需要达到以下几点:
-
随机数字或字符串
-
无特征,相邻id混淆后非递增、非相近数字或字符串
-
双向,可逆
-
原始id和混淆id为一对一关系
从算法实现角度来说,主要考虑:
-
最终混淆结果无法用常规的算法解密,比如迅雷的url明显就是base64,解码后就能找到规律
-
支持加盐,同一算法添加不同的随机数据后混淆的结果能有巨大差异,保证即使算法公开也无风险
-
支持long类型,不少数据库尤其是分布式系统的id很可能是超出int范围的
-
不论id长短一律输出固定长度,比如YouTube中的视频id都是11个字符
常用的混淆方式包括:
-
XOR
-
位、字节、字符换位
-
部分值替换
-
乘法逆
一般要达到理想的效果需要几种方式叠加使用。
分享一种实现方式供参考 an id obfuscation algorithm to generate a string of length 25 · GitHub
使用方式如下:
IDObfuscation ob = new IDObfuscation(0x1234);
String obfuscatedId = ob.obfuscate(1);
ob.restore(obfuscatedId);
构造函数内的key为随机数,不同参数生成的同一id的结果都会不同。
obfuscate是混淆方法,restore是恢复方法。
以下介绍一下大致的实现方式:
在混淆时,首先通过swap方法对id的高低位的各32个字节进行替换,恢复时同样使用swap方法,这是从数学上通过XOR确保2次调用swap后返回结果一致。
opaque方法对数字进行混淆,即使相邻数字的结果也非雷同,采用了XOR加法乘法和移位,构造函数内的key就在这里用到,为了避免巧合正好XOR后为0,添加一个数字,方法内的几个数字并非magic number,纯属随意填写,实际该方法只要数字返回前& 0xFFFFFFFFL都能确保有效的,直接返回参数也可以,以上做法只是为了增强混淆的效果以及引入key造成算法使用上的差异性。
执行opaque方法后返回的是数字长度不定的long,并且因为java没有无符号数字,返回的数字也有可能是负数,接下来的toStr方法就是为了将long转成定长的包含a-zA-Z0-9的字符串。
一个long用字符串形式表示最多是20个字符,1个减号加上19个数字,为了定长就会涉及到padding,又需要将有效信息和padding区分,此处采用了将对应数字转成ASCII码的方式,0-9分别对应0x30-0x39,代表负数的减号对应0x2D,ASCII码内只要非这10个值都可以作为padding,这里采用0x29开始递减的19个值,19是理论上会用到的padding个数,实际swap方法返回的long值不会需要那么多padding。
目标结果的字符串的字符一共有62种可能,也就是62进制的计算,为了避免使用BigInteger,将每4个字符的ASCII码叠加后的数字做62进制运算,数字范围在0x2D313030和0x39393939之间,而62进制的4次方是0xE17810,5次方是0x369B13E0,为了统一长度避免此处再做padding,统一将每个字符减去3,确保在62的5次方的涵盖范围内,也就是说每4个数字转成5个字符,20个数字依次分5组转换完成后的字符串reverse后返回25个字符做为最终的混淆id。
恢复时就是反向操作,没什么特殊的地方。
整个实现尽可能的采用数字移位,因为这是对于计算机来说最高效的执行逻辑。
最后看一下执行效果:
原始id | key=0x1233 混淆后id | key=0x1234 混淆后id |
---|---|---|
1 | 5Ap1f8YNc65AYqW7PQoL7O9pS | 8X6JV6JEoY8XpCG1lebEv4ZfX |
2 | 32Rlv7PQoN49B8O9fw0G7QXCm | 31LRX6HaWO2rqvf6Igqq8Yvi5 |
3 | 49BGY48u5H6H8Dj9h8zd1kpec | 30Eyy30Euq7O91p0bslfv4ZXI |
4 | 8XojG3zxrr5AYmK7O9lI7QW0O | 9gloV2tUo47QFpEzTlIT6HaOB |
5 | 1kYO12r7yh8ZCNJ6IPGd30nHW | 9hsPU31LRb2tVDo8XYX7v4IYS |
6 | 49BW58ZkPK0amVS8XYLi2rqzm | 1kHcV2rqSn8YNUm1lNoi2r7uX |
7 | 32Rp97Q4pz9h9F87QW8lv5OcL | 3zxvr7QFkzzSNu52rZLc2rqe6 |
8 | 48Min30m9E48dhc1kpidv4Iot | 1kpSDzTDOp6GkuQ30WY8v3kWa |
9 | 5AY2l5AZJO9hJ611j0I70ciFY | 7RdGZ0bst09fwbL9h8rT32RVa |
10 | 6IxpX1kHcb8Z1zQ31K65v4IYN | 5AH3132RNG9hbEG30WY9v3km3 |
参考
123