混淆id的一种方法

35岁倒计时
• 阅读 9232

众所周知,在web应用的API中,总是会出现数据库item的id。比如GET /posts/1表示获取id为1的文章内容。这样做十分简洁,但存在被人爬数据的风险。比如我可以大致猜测或者试一下id的范围,1,2,3...10000这样迭代地爬数据。如果服务器不做访问限制,很轻易就能把所有数据就能爬下来。而且,这样的数字id也会暴露一些信息,比如id小的一般是更早创建的。

所以要对id进行混淆,混淆有这么几个特点:

  1. 它是一个无符号整数到字符串的一一对应的函数
  2. 双向的,混淆之后可以恢复,所以不能用hash
  3. 不表现出递增的特征
  4. 不用像加密那样强,也不用有密钥
  5. 没有整数范围的限制。这一条是我加的,google能搜到很多id混淆的方法但它们可能要求id在2^32-1之内,比如对2^32求一个multiplicative inverse,这是一个不错的方法但因为这个限制我没有采用它。

最简单的一个方法是找一个比较大的数字进行异或,比如1-10跟1093420374进行异或的结果是这样的:

1 : 1093420375
2 : 1093420372
3 : 1093420373
4 : 1093420370
5 : 1093420371
6 : 1093420368
7 : 1093420369
8 : 1093420382
9 : 1093420383
10: 1093420380

但这比较容易被人猜出是异或,需要再加上别的操作

我看到的一个比较好的方法也是我目前在用的是:

  1. 对id求个hash,取前16字节,作为segment1
  2. 对segment1求hash,取前8字节,作为segment2
  3. 将segment2转换为整数,加上id,再变回byte array
  4. 将segment1和segment2连接起来再求个hash,取前8字节,作为segment3(用于恢复时的验证)
  5. 连接segment1、2、3,做base64,得到混淆后的id

恢复的时候只用

  1. base64解码
  2. 取前16字节得到segment1,后8字节得到segment3,剩余字节是segment2
  3. 验证hash(segmemt1+segment2)是否等于segment3
  4. int(segment2)-int(hash(segment1))得到id

这用python实现比较方便,因为python的整数可以无限大,代码是这样的

pythonclass Obfuscator:
    _head_bytes = 16
    _mid_bytes = 8
    _tail_bytes = 8

    @staticmethod
    def bytearray_to_int(byte_arr):
        return int.from_bytes(byte_arr, byteorder='big')

    @staticmethod
    def int_to_bytearray(num):
        assert isinstance(num, int) and num >= 0
        if num == 0:
            return b'0'
        result = []
        while num > 0:
            d, m = divmod(num, 256)
            result.append(m)
            num = d
        return bytes(result[::-1])

    @classmethod
    def obfuscate(cls, uid):
        if not uid:
            return ''
        uid_bytes = cls.int_to_bytearray(uid)
        seg1 = hashlib.sha1(uid_bytes).digest()[:cls._head_bytes]

        seg2 = hashlib.sha1(seg1).digest()[:cls._mid_bytes]
        seg2 = cls.int_to_bytearray(uid + cls.bytearray_to_int(seg2))

        seg3 = hashlib.sha1(seg1 + seg2).digest()[:cls._tail_bytes]

        return base64.urlsafe_b64encode(seg1 + seg2 + seg3).decode()

    @classmethod
    def restore(cls, obscure_str):
        if not obscure_str:
            return -1
        seg_bytes = base64.urlsafe_b64decode(obscure_str)
        seg1 = seg_bytes[:cls._head_bytes]
        seg2 = seg_bytes[cls._head_bytes:-cls._tail_bytes]
        seg3 = seg_bytes[-cls._tail_bytes:]

        if hashlib.sha1(seg1 + seg2).digest()[:cls._tail_bytes] != seg3:
            return -1
        seg1 = hashlib.sha1(seg1).digest()[:cls._mid_bytes]
        return cls.bytearray_to_int(seg2) - cls.bytearray_to_int(seg1)
点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
List的Select 和Select().tolist()
List<PersondelpnewList<Person{newPerson{Id1,Name"小明1",Age11,Sign0},newPerson{Id2,Name"小明2",Age12,
Stella981 Stella981
3年前
Python+Selenium自动化篇
本篇文字主要学习selenium定位页面元素的集中方法,以百度首页为例子。0.元素定位方法主要有:id定位:find\_element\_by\_id('')name定位:find\_element\_by\_name('')class定位:find\_element\_by\_class\_name(''
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究