65.9K
CodeProject 正在变化。 阅读更多。
Home

C# AES 256 位加密库(带盐)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (92投票s)

2014年5月6日

公共领域

2分钟阅读

viewsIcon

569865

downloadIcon

23566

一个通用的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 
© . All rights reserved.