混淆id的一種方法

衆所周知,在web應用的API中,老是會出現數據庫item的id。好比GET /posts/1表示獲取id爲1的文章內容。這樣作十分簡潔,但存在被人爬數據的風險。好比我能夠大體猜想或者試一下id的範圍,1,2,3...10000這樣迭代地爬數據。若是服務器不作訪問限制,很輕易就能把全部數據就能爬下來。並且,這樣的數字id也會暴露一些信息,好比id小的通常是更早建立的。python

因此要對id進行混淆,混淆有這麼幾個特色:web

  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

但這比較容易被人猜出是異或,須要再加上別的操做服務器

我看到的一個比較好的方法也是我目前在用的是:app

  1. 對id求個hash,取前16字節,做爲segment1
  2. 對segment1求hash,取前8字節,做爲segment2
  3. 將segment2轉換爲整數,加上id,再變回byte array
  4. 將segment1和segment2鏈接起來再求個hash,取前8字節,做爲segment3(用於恢復時的驗證)
  5. 鏈接segment一、二、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的整數能夠無限大,代碼是這樣的post

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)
相關文章
相關標籤/搜索