不要这样做!编写自己的加密系统很容易导致出错.最好使用现有系统,如果没有,请让知道密码学的人去做.如果您必须自己动手,请阅读Practical Cryptography.
请记住:“我们已经拥有足够快速,不安全的系统.” (布鲁斯施奈尔) – 做正确的事情并担心以后的表现.
也就是说,如果你坚持使用AES来推销自己的,那么这里有一些指示.
初始化矢量
AES是分组密码.给定一个密钥和一个明文块,它将其转换为特定的密文.这样做的问题是,每次相同的数据块都会生成相同密钥的相同密文.所以假设你发送这样的数据:
它们是两个独立的块,UserRoles加密每次都有相同的密文,无论名称如何.我需要的只是管理员的密文,我可以使用我的密码用户名将其删除.哎呀.
所以,有cipher operation modes.主要思想是你将获取一个块的密文,并将其异或成为下一个块的密文.这样我们就会进行加密(UserRoles,用户名),并且用户名密文会受到UserRoles的影响.
问题是第一个块仍然容易受到攻击 – 仅仅通过查看某人的密文,我可能知道他们的角色.输入initialization vector. IV“启动”密码并确保其具有随机数据以加密流的其余部分.所以现在UserRoles密文具有随机IV异或的密文.问题解决了.
因此,请确保为每条消息生成随机IV. IV不敏感,可以用密文发送明文.使用足够大的IV – 对于许多情况,块的大小应该没问题.
廉正
AES不提供完整性功能.任何人都可以修改您的密文,解密仍然有效.它一般不太可能是有效数据,但可能很难知道有效数据是什么.例如,如果您正在传输加密的GUID,则很容易修改某些位并生成完全不同的位.这可能会导致应用程序错误等.
修复是在明文上运行哈希算法(使用SHA256或SHA512),并将其包含在您传输的数据中.因此,如果我的消息是(UserName,Roles),您将发送(UserName,Roles,Hash(UserName,Roles)).现在,如果有人通过翻转来篡改密文,则哈希将不再计算,您可以拒绝该消息.
密钥派生
如果需要从密码生成密钥,请使用内置类:System.Security.Cryptography.PasswordDeriveBytes.这提供了salting和迭代,这可以提高派生密钥的强度,并降低密钥泄露时发现密码的机会.
定时/重播
编辑:很抱歉没有提到这个:P.您还需要确保您有一个反重播系统.如果您只是加密邮件并传递它,那么获取邮件的任何人都可以重新发送邮件.为避免这种情况,您应该为邮件添加时间戳.如果时间戳与特定阈值不同,请拒绝该消息.您可能还希望包含一次性ID(可能是IV)并拒绝来自使用相同ID的其他IP的时间有效消息.
在包含时序信息时,确保进行哈希验证非常重要.否则,如果您没有检测到这种强力尝试,有人可能会篡改一些密文并可能生成有效的时间戳.
示例代码
由于显然正确地使用IV对某些人来说是有争议的,这里有一些代码可以生成随机IV并将它们添加到您的输出中.它还将执行身份验证步骤,确保未修改加密数据.
using System; using System.Security.Cryptography; using System.Text; class AesDemo { const int HASH_SIZE = 32; //SHA256 /// <summary>Performs encryption with random IV (prepended to output),and includes hash of plaintext for verification.</summary> public static byte[] Encrypt(string password,byte[] passwordSalt,byte[] plainText) { // Construct message with hash var msg = new byte[HASH_SIZE + plainText.Length]; var hash = computeHash(plainText,plainText.Length); Buffer.BlockCopy(hash,msg,HASH_SIZE); Buffer.BlockCopy(plainText,HASH_SIZE,plainText.Length); // Encrypt using (var aes = createAes(password,passwordSalt)) { aes.GenerateIV(); using (var enc = aes.CreateEncryptor()) { var encBytes = enc.TransformFinalBlock(msg,msg.Length); // Prepend IV to result var res = new byte[aes.IV.Length + encBytes.Length]; Buffer.BlockCopy(aes.IV,res,aes.IV.Length); Buffer.BlockCopy(encBytes,aes.IV.Length,encBytes.Length); return res; } } } public static byte[] Decrypt(string password,byte[] cipherText) { using (var aes = createAes(password,passwordSalt)) { var iv = new byte[aes.IV.Length]; Buffer.BlockCopy(cipherText,iv,iv.Length); aes.IV = iv; // Probably could copy right to the byte array,but that's not guaranteed using (var dec = aes.CreateDecryptor()) { var decBytes = dec.TransformFinalBlock(cipherText,iv.Length,cipherText.Length - iv.Length); // Verify hash var hash = computeHash(decBytes,decBytes.Length - HASH_SIZE); var existingHash = new byte[HASH_SIZE]; Buffer.BlockCopy(decBytes,existingHash,HASH_SIZE); if (!compareBytes(existingHash,hash)){ throw new CryptographicException("Message hash incorrect."); } // Hash is valid,we're done var res = new byte[decBytes.Length - HASH_SIZE]; Buffer.BlockCopy(decBytes,res.Length); return res; } } } static bool compareBytes(byte[] a1,byte[] a2) { if (a1.Length != a2.Length) return false; for (int i = 0; i < a1.Length; i++) { if (a1[i] != a2[i]) return false; } return true; } static Aes createAes(string password,byte[] salt) { // Salt may not be needed if password is safe if (password.Length < 8) throw new ArgumentException("Password must be at least 8 characters.","password"); if (salt.Length < 8) throw new ArgumentException("Salt must be at least 8 bytes.","salt"); var pdb = new PasswordDeriveBytes(password,salt,"SHA512",129); var key = pdb.GetBytes(16); var aes = Aes.Create(); aes.Mode = CipherMode.CBC; aes.Key = pdb.GetBytes(aes.KeySize / 8); return aes; } static byte[] computeHash(byte[] data,int offset,int count) { using (var sha = SHA256.Create()) { return sha.ComputeHash(data,offset,count); } } public static void Main() { var password = "1234567890!"; var salt = new byte[] { 1,2,3,4,5,6,7,8,9,0 }; var ct1 = Encrypt(password,Encoding.UTF8.GetBytes("Alice; Bob; Eve;: PerformAct1")); Console.WriteLine(Convert.ToBase64String(ct1)); var ct2 = Encrypt(password,Encoding.UTF8.GetBytes("Alice; Bob; Eve;: PerformAct2")); Console.WriteLine(Convert.ToBase64String(ct2)); var pt1 = Decrypt(password,ct1); Console.WriteLine(Encoding.UTF8.GetString(pt1)); var pt2 = Decrypt(password,ct2); Console.WriteLine(Encoding.UTF8.GetString(pt2)); // Now check tampering try { ct1[30]++; Decrypt(password,ct1); Console.WriteLine("Error: tamper detection Failed."); } catch (Exception ex) { Console.WriteLine("Success: tampering detected."); Console.WriteLine(ex.ToString()); } } }
输出:
JZVaD327sDmCmdzY0PsysnRgHbbC3eHb7YXALb0qxFVlr7Lkj8WaOZWc1ayWCvfhTUz/y0QMz+uv0PwmuG8VBVEQThaNTD02JlhIs1DjJtg=
QQvDujNJ31qTu/foDFUiVMeWTU0jKL/UJJfFAvmFtz361o3KSUlk/zH+4701mlFEU4Ce6VuAAuaiP1EENBJ74Wc8mE/QTofkUMHoa65/5e4=
Alice; Bob; Eve;: PerformAct1 Alice;
Bob; Eve;: PerformAct2 Success:
tampering detected.
System.Security.Cryptography.CryptographicException:
Message hash incorrect. at
AesDemo.Decrypt(String password,
Byte[] passwordSalt,Byte[]
cipherText) in
C:\Program.cs:line
46 at AesDemo.Main() in
C:\Program.cs:line
100
tZfHJSFTXYX8V38AqEfYVXU5Dl/meUVAond70yIKGHY=
tZfHJSFTXYX8V38AqEfYVcf9a3U8vIEk1LuqGEyRZXM=
注意第一个块是如何对应的“Alice; Bob; Eve;”是一样的.确实是“角落案”.
没有散列的示例
这是传递64位整数的简单示例.只是加密,你就会受到攻击.事实上,即使使用CBC填充,攻击也很容易完成.
public static void Main() { var buff = new byte[8]; new Random().NextBytes(buff); var v = BitConverter.ToUInt64(buff,0); Console.WriteLine("Value: " + v.ToString()); Console.WriteLine("Value (bytes): " + BitConverter.ToString(BitConverter.GetBytes(v))); var aes = Aes.Create(); aes.GenerateIV(); aes.GenerateKey(); var encBytes = aes.CreateEncryptor().TransformFinalBlock(BitConverter.GetBytes(v),8); Console.WriteLine("Encrypted: " + BitConverter.ToString(encBytes)); var dec = aes.CreateDecryptor(); Console.WriteLine("Decrypted: " + BitConverter.ToUInt64(dec.TransformFinalBlock(encBytes,encBytes.Length),0)); for (int i = 0; i < 8; i++) { for (int x = 0; x < 250; x++) { encBytes[i]++; try { Console.WriteLine("Attacked: " + BitConverter.ToUInt64(dec.TransformFinalBlock(encBytes,0)); return; } catch { } } } }
输出:
Value: 6598637501946607785 Value
(bytes): A9-38-19-D1-D8-11-93-5B
Encrypted:
31-59-B0-25-FD-C5-13-D7-81-D8-F5-8A-33-2A-57-DD
Decrypted: 6598637501946607785
Attacked: 14174658352338201502
因此,如果这是您要发送的ID类型,则可以很容易地将其更改为其他值.您需要在邮件之外进行身份验证.有时,消息结构不太可能落实到位,并且可以作为一种保护措施,但为什么要依赖可能发生变化的事情呢?无论应用程序如何,您都需要能够依赖加密才能正常工作.