正确使用AES对称加密
正確使用AES對稱加密
經常我看到項目中有人使用了對稱加密算法,用來加密客戶或項目傳輸中的部分數據。但我注意到開發 人員由于不熟悉原理,或者簡單復制網上的代碼示例,有導致代碼存在安全風險。
我經常遇到的問題,有如下:
如使用了過時的加密算法(如DES)
設置了不安全的加密模式(ECB)
不正確地處理初始向量(IV)
對稱加密算法
| RC4 | 40 | ? |
| DES | 56 | ? |
| 3DES | 112 | ? |
| AES | 128 | ? |
TL;DR:
RC4/DES/3DES都?不符合?加密/破解的安全性要求。
DES是56位加密,聽起來感覺3DES應該是168位,但實際上其有效加密位長只有112位。
其它更長的加密算法,如AES 192位/AES 256位也符合要求。
加密模式
TL;DR: 不要使用ECB。
ECB不需要初始向量(IV),這個“驚人”的發現常常讓開發簡單粗暴地設計為ECB。ECB的問題在于輸入和輸出存在非常明顯的關聯,攻擊者可以從輸出輕松地猜出輸入數據。
C#的AES算法默認模式為CBC,該算法沒有上述的安全問題,而且最為通用,可以使用該模式。
初始向量
TL;DR:
初始向量?必須?為完全隨機數,完全隨機數應該使用RandomNumberGenerator進行加密。
回想這個問題,數據加密完后,該發送什么給接收方?僅數據?那么初始向量(IV)怎么辦?
大多數開發選擇的辦法是,寫一個固定的初始向量(IV)用于加密,然后解密時,也使用相同的初始向量。這樣就導致相同的輸入會產生相同的輸出。
為什么相同的輸入應該產生不同的輸出?因為根據歷史經驗,攻擊者可以獲取一些信息,知道某個確定輸入的含義。一旦再次捕獲到相同的加密數據,就能輕易破解。
所以,發送數據應該包含:版本+初始向量+數據。
面向字符串
加密是面向字節還是字符串?我認為應該面向字節。如果面向字符串,那么很多問題很難受到重視。
試著回答這個問題:
用戶的密碼是什么樣子的?
是長度為固定32位的HEX字符嗎?如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();}解釋:
Base64UrlEncode/Decode:用于將字符串在Url上傳輸,將+/=轉換成:-_
Pack/Unpack:將版本/初始向量/密文打包/解包
PasswordToKey:將長度不一樣密碼,加上purpose,轉換為長度一樣的key,其中改成HMACSHA256可以使用256位的AES算法。
測試代碼:
// 代碼按原樣提供,可隨意使用,但不對其安全性作任何保證。string purpose = "這個算法是用來搞SSO的";// 返回:AcfCe3AQcmNkeNThv-u09H_HyGKy_iRy-7uGiW0IZOHIEncrypt("密碼here", purpose, Encoding.UTF8.GetBytes("Hello World"));// 返回:Hello WorldEncoding.UTF8.GetString(Decrypt("AcfCe3AQcmNkeNThv-u09H_HyGKy_iRy-7uGiW0IZOHI", "密碼here", purpose));總結
以上是生活随笔為你收集整理的正确使用AES对称加密的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 对产品质量的一点思考
- 下一篇: 「数据分析」Sqlserver中的窗口函