常常我看到项目中有人使用了对称加密算法,用来加密客户或项目传输中的部分数据。但我注意到开发 人员因为不熟悉原理,或者简单复制网上的代码示例,有致使代码存在安全风险。算法
我常常遇到的问题,有以下: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));