歡迎來到「我是真的狗雜談世界」,關(guān)注不迷路
背景
最近做的項目多次遇到了分享邀請的需求點,即需要在接受邀請時能識別到邀請者的信息,又需要考慮信息敏感性,沒找到成熟的三方實現(xiàn),于是自己思考實現(xiàn)了兩套。
思路方案
不能直接將邀請信息用于傳遞,需要對信息(一般是字符串,不是字符串也可以轉(zhuǎn)換為字符串)進行加密處理,或者說編碼處理。但同時需要滿足一下要求:
要求
- 可逆:加密(編碼)后的密文應當能通過解密(解碼)獲得原文,否則就無法獲得邀請者信息了;
- 長度:加密(編碼)后的密文應該盡可能與原文長度相當,可以略多(如果能更少也好,不過那需要涉及壓縮了,不是今天的重點);
- 內(nèi)容:加密(編碼)后的密文應當是可預知的字符集合,如果可設(shè)置更好;
- 算法可公開(意味著存在額外依賴):不單單依靠一個固定算法,即便算法公開仍舊能保證安全性,否則算法一旦被破解也就沒什么了;
- 復雜度適當:太復雜的一般對計算要求很高,對開發(fā)(本人)成本也高,能滿足目前的項目需求即可(我絕對不會說我懶);
常見算法
常見的一些加密解密、編碼解碼算法:
- 單向:md系列、sha1;
- 對稱:des、aes;
- 非對稱:rsa、dsa;
- 簡單編碼:base64;
移位法
本質(zhì)
- 維護全量字符可能的順序,對給定字符串的每個字符按照其位置進行移位轉(zhuǎn)換,得到結(jié)果;
- 位置到字符移位偏移量通過一套外部輸入的規(guī)則來指定;
- 上述兩步相當于將字符枚舉、順序、偏移量規(guī)則三套作為算法變量交由用戶控制,在不能完全知道三個信息的情況下,即便知道算法,也能保障密文安全性;
防篡改
- 雖然用移位法可以保障用戶無法通過密文還原原文,但用戶可以用大量無序密文來攻擊,造成大量垃圾數(shù)據(jù)或暴力猜測到一些信息,因此還需要支持能校驗密文是否合法的功能,也就是防篡改。
- 通用的思路是額外加一點校驗數(shù)據(jù),由原文構(gòu)造而成加入到密文中,解密(解碼)時校驗一遍作為驗證即可,比如網(wǎng)絡IP和TCP層的累加和就是這種思路。
實現(xiàn)
public function encrypt(string $value): string
{
if (0 == $this->sortCount) {
throw new EncrypterException('未設(shè)置字符排布陣列');
}
if (0 == $this->shiftingCount) {
throw new EncrypterException('未設(shè)置編碼密鑰');
}
$encrypted = '';
for ($i = 0; $i < strlen($value); $i++) {
if (!key_exists($value[$i], $this->sortList)) {
throw new EncrypterException('發(fā)現(xiàn)不期待的字符');
}
$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('未設(shè)置字符排布陣列');
}
if (0 == $this->shiftingCount) {
throw new EncrypterException('未設(shè)置編碼密鑰');
}
// 拿出校驗位
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('發(fā)現(xiàn)不期待的字符');
}
$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;
}
特點分析
- 需要知道原文全部字符構(gòu)成可能;
- 原文和密文的字符構(gòu)成是同一個集合;
- 密文與原文長度相等,即使加入校驗位也僅多2位;
- 適用于前端、用戶可見可感知的傳遞場景;
異或法
本質(zhì)
- 利用abb=a,也就是兩次異或復位的特性(字符可轉(zhuǎn)換成一個數(shù)值也就是一個多位的二進制,單位異或的特性在多位場景下同樣成立)
- 同樣與原文、密文每個字符進行異或操作的字符應該與其位置規(guī)則有關(guān),同移位法相關(guān)規(guī)則
可讀性
由于異或后的值可能超過輸入原值,字符轉(zhuǎn)換時可能轉(zhuǎn)換為非常用字符影響閱讀,因此可以選擇嵌套一層base64加解密方便閱讀。
實現(xiàn)
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;
}
分析
- 即便不知道原文字符全部可能也可以使用這套算法;
- 但原文和密文的字符構(gòu)成不是同一個集合;
- base64后密文一般較原文長,具體見base64編碼算法規(guī)則;
- 適用于接口間、服務間數(shù)據(jù)傳輸場景;