常常我看到項目中有人使用了對稱加密算法,用來加密客戶或項目傳輸中的部分數據。但我注意到開發 人員因爲不熟悉原理,或者簡單複製網上的代碼示例,有致使代碼存在安全風險。算法
我常常遇到的問題,有以下:c#
算法 | 位長 | 建議 |
---|---|---|
RC4 | 40 | |
DES | 56 | |
3DES | 112 | |
AES | 128 | ✔ |
TL;DR:數組
RC4/DES/3DES都 不符合 加密/破解的安全性要求。安全
DES是56位加密,聽起來感受3DES應該是168位,但實際上其有效加密位長只有112位。dom
其它更長的加密算法,如AES 192位/AES 256位也符合要求。測試
TL;DR: 不要使用ECB。加密
ECB不須要初始向量(IV),這個「驚人」的發現經常讓開發簡單粗暴地設計爲ECB。ECB的問題在於輸入和輸出存在很是明顯的關聯,攻擊者能夠從輸出輕鬆地猜出輸入數據。
設計
C#的AES算法默認模式爲CBC,該算法沒有上述的安全問題,並且最爲通用,可使用該模式。code
TL;DR:
初始向量 必須 爲徹底隨機數,徹底隨機數應該使用RandomNumberGenerator
進行加密。orm
回想這個問題,數據加密完後,該發送什麼給接收方?僅數據?那麼初始向量(IV)怎麼辦?
大多數開發選擇的辦法是,寫一個固定的初始向量(IV)用於加密,而後解密時,也使用相同的初始向量。這樣就致使相同的輸入會產生相同的輸出。
爲何相同的輸入應該產生不一樣的輸出?由於根據歷史經驗,攻擊者能夠獲取一些信息,知道某個肯定輸入的含義。一旦再次捕獲到相同的加密數據,就能輕易破解。
因此,發送數據應該包含:版本+初始向量+數據。
加密是面向字節仍是字符串?我認爲應該面向字節。若是面向字符串,那麼不少問題很難受到重視。
試着回答這個問題:
1C8F7B2C9759209C6ACC3C105D39BBAC
?My-Super-Str0ng-Password!!
?我認爲加密算法應該面向字節流/字節數據,而不是字符串。將字符串發送給客戶、放在JSON中進行端對端傳輸,是沒什麼毛病的作法。但基於如下緣由,我強烈建議加密/解密算法要基於字節數據:
// 代碼按原樣提供,可隨意使用,但不對其安全性做任何保證。 string Encrypt(string password, string purpose, byte[] plainBytes) { byte[] key = PasswordToKey(password, purpose); using (var aes = Aes.Create()) { aes.Key = key; using (ICryptoTransform encryptor = aes.CreateEncryptor()) { byte[] cipherBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length); byte[] packedBytes = Pack( version: 1, iv: aes.IV, cipherBytes: cipherBytes); return Base64UrlEncode(packedBytes); } } } byte[] Decrypt(string packedString, string password, string purpose) { byte[] key = PasswordToKey(password, purpose); byte[] packedBytes = Base64UrlDecode(packedString); (byte version, byte[] iv, byte[] cipherBytes) = Unpack(packedBytes); using (var aes = Aes.Create()) { using (ICryptoTransform decryptor = aes.CreateDecryptor(key, iv)) { return decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length); } } }
其中公共方法:
// 代碼按原樣提供,可隨意使用,但不對其安全性做任何保證。 byte[] PasswordToKey(string password, string purpose) { using (var hmac = new HMACMD5(Encoding.UTF8.GetBytes(purpose))) { return hmac.ComputeHash(Encoding.UTF8.GetBytes(password)); } } string Base64UrlEncode(byte[] bytes) { return Convert.ToBase64String(bytes) .Replace("/", "_") .Replace("+", "-") .Replace("=", ""); } byte[] Base64UrlDecode(string base64Url) { return Convert.FromBase64String(base64Url .Replace("_", "/") .Replace("-", "+")); } (byte version, byte[] iv, byte[] cipherBytes) Unpack(byte[] packedBytes) { if (packedBytes[0] == 1) { // version 1 return (1, packedBytes[1..1 + 16], packedBytes[1 + 16..]); } else { throw new NotImplementedException("unknown version"); } } byte[] Pack(byte version, byte[] iv, byte[] cipherBytes) { return new[] { version }.Concat(iv).Concat(cipherBytes).ToArray(); }
解釋:
+/=
轉換成:-_
purpose
,轉換爲長度同樣的key
,其中改爲HMACSHA256可使用256位的AES算法。測試代碼:
// 代碼按原樣提供,可隨意使用,但不對其安全性做任何保證。 string purpose = "這個算法是用來搞SSO的"; // 返回:AcfCe3AQcmNkeNThv-u09H_HyGKy_iRy-7uGiW0IZOHI Encrypt("密碼here", purpose, Encoding.UTF8.GetBytes("Hello World")); // 返回:Hello World Encoding.UTF8.GetString(Decrypt("AcfCe3AQcmNkeNThv-u09H_HyGKy_iRy-7uGiW0IZOHI", "密碼here", purpose));