Stories

Detail Return Return

Chapter 18.自制可逆等位字符串加密解密(編碼解碼) - Stories Detail

歡迎來到「我是真的狗雜談世界」,關注不迷路

背景

最近做的項目多次遇到了分享邀請的需求點,即需要在接受邀請時能識別到邀請者的信息,又需要考慮信息敏感性,沒找到成熟的三方實現,於是自己思考實現了兩套。

思路方案

不能直接將邀請信息用於傳遞,需要對信息(一般是字符串,不是字符串也可以轉換為字符串)進行加密處理,或者説編碼處理。但同時需要滿足一下要求:

要求

  • 可逆:加密(編碼)後的密文應當能通過解密(解碼)獲得原文,否則就無法獲得邀請者信息了;
  • 長度:加密(編碼)後的密文應該儘可能與原文長度相當,可以略多(如果能更少也好,不過那需要涉及壓縮了,不是今天的重點);
  • 內容:加密(編碼)後的密文應當是可預知的字符集合,如果可設置更好;
  • 算法可公開(意味着存在額外依賴):不單單依靠一個固定算法,即便算法公開仍舊能保證安全性,否則算法一旦被破解也就沒什麼了;
  • 複雜度適當:太複雜的一般對計算要求很高,對開發(本人)成本也高,能滿足目前的項目需求即可(我絕對不會説我懶);

常見算法

常見的一些加密解密、編碼解碼算法:

  • 單向:md系列、sha1;
  • 對稱:des、aes;
  • 非對稱:rsa、dsa;
  • 簡單編碼:base64;

移位法

本質

  • 維護全量字符可能的順序,對給定字符串的每個字符按照其位置進行移位轉換,得到結果;
  • 位置到字符移位偏移量通過一套外部輸入的規則來指定;
  • 上述兩步相當於將字符枚舉、順序、偏移量規則三套作為算法變量交由用户控制,在不能完全知道三個信息的情況下,即便知道算法,也能保障密文安全性;

防篡改

  • 雖然用移位法可以保障用户無法通過密文還原原文,但用户可以用大量無序密文來攻擊,造成大量垃圾數據或暴力猜測到一些信息,因此還需要支持能校驗密文是否合法的功能,也就是防篡改。
  • 通用的思路是額外加一點校驗數據,由原文構造而成加入到密文中,解密(解碼)時校驗一遍作為驗證即可,比如網絡IP和TCP層的累加和就是這種思路。

實現

    public function encrypt(string $value): string
    {
        if (0 == $this->sortCount) {
            throw new EncrypterException('未設置字符排佈陣列');
        }
        if (0 == $this->shiftingCount) {
            throw new EncrypterException('未設置編碼密鑰');
        }
        $encrypted = '';
        for ($i = 0; $i < strlen($value); $i++) {
            if (!key_exists($value[$i], $this->sortList)) {
                throw new EncrypterException('發現不期待的字符');
            }
            $index = $this->sortList[$value[$i]];
            $index += $this->shiftingList[$i % $this->shiftingCount];
            $index %= $this->sortCount;
            $encrypted .= $this->sort[$index];
        }

        // 添加校驗位
        if ($this->checkSum) {
            $sum = 0;
            for ($i = 0; $i < strlen($value); $i++) {
                $sum += ord($value[$i]);
            }
            $sum %= $this->sortCount;
            $start = $this->sort[$sum];
            $end = $this->sort[$this->sortCount - $sum - 1];
            $encrypted = $start . $encrypted . $end;
        }

        return $encrypted;
    }
    public function decrypt(string $value): string
    {
        if (0 == $this->sortCount) {
            throw new EncrypterException('未設置字符排佈陣列');
        }
        if (0 == $this->shiftingCount) {
            throw new EncrypterException('未設置編碼密鑰');
        }

        // 拿出校驗位
        if ($this->checkSum) {
            if (strlen($value) < 2) {
                throw new EncrypterException('解密校驗失敗');
            }
            $start = $value[0];
            $end = $value[strlen($value) - 1];
            $value = substr($value, 1, -1);
        }

        $decrypted = '';
        for ($i = 0; $i < strlen($value); $i++) {
            if (!key_exists($value[$i], $this->sortList)) {
                throw new EncrypterException('發現不期待的字符');
            }
            $index = $this->sortList[$value[$i]];
            $index -= $this->shiftingList[$i % $this->shiftingCount];
            while ($index < 0) {
                $index += $this->sortCount;
            }
            $decrypted .= $this->sort[$index];
        }

        // 校驗校驗位
        if ($this->checkSum) {
            $sum = 0;
            for ($i = 0; $i < strlen($decrypted); $i++) {
                $sum += ord($decrypted[$i]);
            }
            $sum %= $this->sortCount;
            if ($start != $this->sort[$sum] || $end != $this->sort[$this->sortCount - $sum - 1]) {
                throw new EncrypterException('解密校驗失敗');
            }
        }

        return $decrypted;
    }

特點分析

  • 需要知道原文全部字符構成可能;
  • 原文和密文的字符構成是同一個集合;
  • 密文與原文長度相等,即使加入校驗位也僅多2位;
  • 適用於前端、用户可見可感知的傳遞場景;

異或法

本質

  • 利用a^b^b=a,也就是兩次異或復位的特性(字符可轉換成一個數值也就是一個多位的二進制,單位異或的特性在多位場景下同樣成立)
  • 同樣與原文、密文每個字符進行異或操作的字符應該與其位置規則有關,同移位法相關規則

可讀性

由於異或後的值可能超過輸入原值,字符轉換時可能轉換為非常用字符影響閲讀,因此可以選擇嵌套一層base64加解密方便閲讀。

實現

    public function encrypt(string $value): string
    {
        $value = $this->handle($value);
        if ($this->base64) {
            $value = base64_encode($value);
        }
        return $value;
    }
    public function decrypt(string $value): string
    {
        if ($this->base64) {
            $value = base64_decode($value);
        }
        return $this->handle($value);
    }
    protected function handle(string $string): string
    {
        $result = '';
        for ($i = 0; $i < strlen($string); $i++) {
            $t = $this->secret[$i % $this->secretCount];
            $result .= chr(ord($string[$i]) ^ ord($t));
        }
        return $result;
    }

分析

  • 即便不知道原文字符全部可能也可以使用這套算法;
  • 但原文和密文的字符構成不是同一個集合;
  • base64後密文一般較原文長,具體見base64編碼算法規則;
  • 適用於接口間、服務間數據傳輸場景;

Add a new Comments

Some HTML is okay.