C# AES 256 位加密库(带盐)
一个通用的C# AES加密库。
核心加密代码
using System.Security.Cryptography;
using System.IO;
加密
public byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
{
byte[] encryptedBytes = null;
// Set your salt here, change it to meet your flavor:
// The salt bytes must be at least 8 bytes.
byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
using (MemoryStream ms = new MemoryStream())
{
using (RijndaelManaged AES = new RijndaelManaged())
{
AES.KeySize = 256;
AES.BlockSize = 128;
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
AES.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
cs.Close();
}
encryptedBytes = ms.ToArray();
}
}
return encryptedBytes;
}
解密
public byte[] AES_Decrypt(byte[] bytesToBeDecrypted, byte[] passwordBytes)
{
byte[] decryptedBytes = null;
// Set your salt here, change it to meet your flavor:
// The salt bytes must be at least 8 bytes.
byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
using (MemoryStream ms = new MemoryStream())
{
using (RijndaelManaged AES = new RijndaelManaged())
{
AES.KeySize = 256;
AES.BlockSize = 128;
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
AES.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
cs.Close();
}
decryptedBytes = ms.ToArray();
}
}
return decryptedBytes;
}
字符串加密示例
加密字符串
public string EncryptText(string input, string password)
{
// Get the bytes of the string
byte[] bytesToBeEncrypted = Encoding.UTF8.GetBytes(input);
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
// Hash the password with SHA256
passwordBytes = SHA256.Create().ComputeHash(passwordBytes);
byte[] bytesEncrypted = AES_Encrypt(bytesToBeEncrypted, passwordBytes);
string result = Convert.ToBase64String(bytesEncrypted);
return result;
}
解密字符串
public string DecryptText(string input, string password)
{
// Get the bytes of the string
byte[] bytesToBeDecrypted = Convert.FromBase64String(input);
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
passwordBytes = SHA256.Create().ComputeHash(passwordBytes);
byte[] bytesDecrypted = AES_Decrypt(bytesToBeDecrypted, passwordBytes);
string result = Encoding.UTF8.GetString(bytesDecrypted);
return result;
}
您会注意到加密后的字符串以base64编码模式存储。对于不熟悉base64编码的人,您可能需要阅读Base What? Base编码的实用介绍[^]
使用盐值获取随机加密结果
如果我们对相同的上下文(例如“Hello World”字符串)进行加密10次,加密结果将相同。如果我们希望每次加密的结果都不同呢?
我的做法是在原始字节之前附加随机盐值字节,并在解密后将其删除。
在加密字符串之前附加随机盐值的示例
public static string EncryptString(string text, string password)
{
byte[] baPwd = Encoding.UTF8.GetBytes(password);
// Hash the password with SHA256
byte[] baPwdHash = SHA256Managed.Create().ComputeHash(baPwd);
byte[] baText = Encoding.UTF8.GetBytes(text);
byte[] baSalt = GetRandomBytes();
byte[] baEncrypted = new byte[baSalt.Length + baText.Length];
// Combine Salt + Text
for (int i = 0; i < baSalt.Length; i++)
baEncrypted[i] = baSalt[i];
for (int i = 0; i < baText.Length; i++)
baEncrypted[i + baSalt.Length] = baText[i];
baEncrypted = AES_Encrypt(baEncrypted, baPwdHash);
string result = Convert.ToBase64String(baEncrypted);
return result;
}
解密后删除盐值的示例
public static string DecryptString(string text, string password)
{
byte[] baPwd = Encoding.UTF8.GetBytes(password);
// Hash the password with SHA256
byte[] baPwdHash = SHA256Managed.Create().ComputeHash(baPwd);
byte[] baText = Convert.FromBase64String(text);
byte[] baDecrypted = AES_Decrypt(baText, baPwdHash);
// Remove salt
int saltLength = GetSaltLength();
byte[] baResult = new byte[baDecrypted.Length - saltLength];
for (int i = 0; i < baResult.Length; i++)
baResult[i] = baDecrypted[i + saltLength];
string result = Encoding.UTF8.GetString(baResult);
return result;
}
获取随机字节的代码
public static byte[] GetRandomBytes()
{
int saltLength = GetSaltLength();
byte[] ba = new byte[saltLength];
RNGCryptoServiceProvider.Create().GetBytes(ba);
return ba;
}
public static int GetSaltLength()
{
return 8;
}
获取随机字节的另一种方法是使用System.Random
。但是,强烈不建议在密码学中使用System.Random。这是因为System.Random不是真正的随机数。值的变化遵循特定的序列和模式,并且是可预测的。RNGCrypto是真正随机的,生成的数值不遵循模式且不可预测。
关于在System.String中存储密码
如果您在桌面PC应用程序中使用此功能,建议不要以纯字符串形式存储密码。字符串无法手动释放。它将由垃圾回收器释放,但是很难确定字符串在完全释放之前将保留在内存中多长时间。在此期间,很容易检索到它。建议的方法之一是捕获密码按键并将其存储到字节数组(或字节列表)中,并在使用后立即擦除字节数组(例如,用0(零)填充字节数组等)。
另一种选择是使用System.Security.SecureString
。System.Security.SecureString可以随时手动销毁。存储在System.Security.SecureString中的值是加密的。
使用SecureString的示例
using System.Security;
using System.Runtime.InteropServices;
将文本存储到SecureString
SecureString secureString = new SecureString();
secureString.AppendChar('h');
secureString.AppendChar('e');
secureString.AppendChar('l');
secureString.AppendChar('l');
secureString.AppendChar('o');
从SecureString检索数据
byte[] secureStringBytes = null; // Convert System.SecureString to Pointer IntPtr unmanagedBytes = Marshal.SecureStringToGlobalAllocAnsi(secureString); try { unsafe { byte* byteArray = (byte*)unmanagedBytes.ToPointer(); // Find the end of the string byte* pEnd = byteArray; while (*pEnd++ != 0) { } // Length is effectively the difference here (note we're 1 past end) int length = (int)((pEnd - byteArray) - 1); secureStringBytes = new byte[length]; for (int i = 0; i < length; ++i) { // Work with data in byte array as necessary, via pointers, here secureStringBytes[i] = *(byteArray + i); } } } finally { // This will completely remove the data from memory Marshal.ZeroFreeGlobalAllocAnsi(unmanagedBytes); } return secureStringBytes;
从SecureString检索数据涉及执行不安全代码(用于处理非托管字节)。因此,您必须允许您的项目以允许使用不安全代码的方式进行构建。此选项在您的项目的属性设置中可用。
否则,您将收到错误消息
Unsafe code may only appear if compiling with /unsafe