Skip to content

.NET Core 实战 [No.321~325] 加密算法

🏷️ 《.NET Core 实战》

MD5

在哈希算法中 MD5 是最常见的,多用于校验密码。一般做法是,先用密码字符串计算出 MD5 值,再把 MD5 值转换为字符串,存进数据库。

csharp
Console.Write("请输入文本:");
string input = Console.ReadLine();
byte[] data = Encoding.UTF8.GetBytes(input);
MD5 md5 = MD5.Create();
byte[] result = md5.ComputeHash(data);
Console.WriteLine(BitConverter.ToString(result).Replace("-", string.Empty).ToLower());
// 请输入文本:佳佳的博客
// 0ee5d025905e0e602d8ee997dc1f128

SHA1

对于数量不是很大的情况,例如一般文件,可以使用 SHA1 算法校验。

csharp
// 随机填充文件内容
using (FileStream fsin = File.Create("ver1.smp"))
{
    byte[] buffer = new byte[256];
    Random rand = new Random();
    for (int i = 0; i < 50; i++)
    {
        rand.NextBytes(buffer);
        fsin.Write(buffer);
    }
}
// 复制文件
File.Copy("ver1.smp", "ver2.smp", true);
// 计算文件的哈希码
string curdir = Directory.GetCurrentDirectory();
string[] files = Directory.GetFiles(curdir, "*.smp");
SHA1 sha = SHA1.Create();
foreach (var f in files)
{
    using (FileStream fs = File.OpenRead(f))
    {
        byte[] result = sha.ComputeHash(fs);
        Console.WriteLine($"文件 {Path.GetFileName(f)} 的哈希码:{BitConverter.ToString(result).Replace("-", string.Empty).ToLower()}");
    }
}
// 文件 ver1.smp 的哈希码:7a75165e69d4fafb9b9d356ae3f2c828bcb3685d
// 文件 ver2.smp 的哈希码:7a75165e69d4fafb9b9d356ae3f2c828bcb3685d

AES(1)

AES 属于双向加密算法(即数据被加密后可以解密),通常需要两个元素:秘钥(Key)初始向量(IV)

必须提供相同的 KeyIV 才可以成功解密。

双向加密需要 CryptoStream 类作为数据内容的读写中介。

加密/解密方法:

csharp
/// <summary>
/// 加密数据
/// </summary>
/// <param name="key">秘钥</param>
/// <param name="iv">初始向量</param>
/// <param name="content">带加密内容</param>
/// <returns></returns>
static byte[] EncryptData(byte[] key, byte[] iv, string content)
{
    byte[] res = null;
    using (Aes aes = Aes.Create())
    {
        using (MemoryStream ms = new MemoryStream())
        {
            using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(key, iv), CryptoStreamMode.Write))
            {
                using (StreamWriter write = new StreamWriter(cs))
                {
                    write.Write(content);
                }
            }
            res = ms.ToArray();
        }
    }
    return res;
}

/// <summary>
/// 解密数据
/// </summary>
/// <param name="key">秘钥</param>
/// <param name="iv">初始向量</param>
/// <param name="dataContent">待解密数据</param>
/// <returns></returns>
static string DecryptData(byte[] key, byte[] iv, byte[] dataContent)
{
    string res = null;
    using (Aes aes = Aes.Create())
    {
        using (MemoryStream ms = new MemoryStream(dataContent))
        {
            using (CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(key, iv), CryptoStreamMode.Read))
            {
                using (StreamReader reader = new StreamReader(cs))
                {
                    res = reader.ReadToEnd();
                }
            }
        }
    }
    return res;
}

测试代码:

csharp
// 待加密文本
string msgToEnc = "佳佳的博客(www.liujiajia.me)";
// 加密用的秘钥
byte[] key;
// 初始向量
byte[] iv;
// 使用 Aes 类的方法产生随机的秘钥和初始向量
using (Aes aes = Aes.Create())
{
    aes.GenerateKey();
    key = aes.Key;
    aes.GenerateIV();
    iv = aes.IV;
}
// 加密数据
byte[] encData = EncryptData(key, iv, msgToEnc);
Console.WriteLine($"原文本:{msgToEnc}");
Console.WriteLine($"加密后:{BitConverter.ToString(encData)}");
// 解密数据
string decMsg = DecryptData(key, iv, encData);
Console.WriteLine($"解密后:{decMsg}");

打印结果:

bash
原文本:佳佳的博客(www.liujiajia.me)
加密后:96G2B+8rf7z9jk/P5OIqwQR29nY0PVbCENV43lkRlnxtT32UzUc0QVxPmX24zdfA
解密后:佳佳的博客(www.liujiajia.me)

AES(2)

Aes 提供了一个 Mode 属性类型为 CipherMode 枚举,默认值为 CBC

若将 Mode 属性指定为 CipherMode.ECB ,在加密/解密时可以忽略初始向量(IV),仅使用秘钥(Key)即可。但是 ECB 模式存在一些安全隐患,建议用于加密一些简单的、不太重要的文本信息。

加密/解密方法:

csharp
/// <summary>
/// 加密数据
/// </summary>
/// <param name="key">秘钥</param>
/// <param name="text">待加密数据</param>
/// <returns></returns>
static byte[] EncryptoText(byte[] key, string text)
{
    byte[] resData = null;
    using (Aes aes = Aes.Create())
    {
        aes.Mode = CipherMode.ECB;
        using (MemoryStream ms = new MemoryStream())
        {
            using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(key, null), CryptoStreamMode.Write))
            {
                using (StreamWriter writer = new StreamWriter(cs))
                {
                    writer.Write(text);
                }
            }
            resData = ms.ToArray();
        }
    }
    return resData;
}

/// <summary>
/// 解密数据
/// </summary>
/// <param name="key">秘钥</param>
/// <param name="data">带解密数据</param>
/// <returns></returns>
static string DecryptoText(byte[] key, byte[] data)
{
    string text = null;
    using (Aes aes = Aes.Create())
    {
        aes.Mode = CipherMode.ECB;
        using (MemoryStream ms = new MemoryStream(data))
        {
            using (CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(key, null), CryptoStreamMode.Read))
            {
                using (StreamReader reader = new StreamReader(cs))
                {
                    text = reader.ReadToEnd();
                }
            }

        }
    }
    return text;
}

测试代码:

csharp
// 待加密文本
string msgToEnc = "佳佳的博客(www.liujiajia.me)";
// 加密用的秘钥
byte[] key;
// 使用 Aes 类的方法产生随机的秘钥和初始向量
using (Aes aes = Aes.Create())
{
    aes.GenerateKey();
    key = aes.Key;
}
// 加密数据
byte[] encData = EncryptoText(key, msgToEnc);
Console.WriteLine($"原文本:{msgToEnc}");
Console.WriteLine($"加密后:{BitConverter.ToString(encData)}");
// 解密数据
string decMsg = DecryptoText(key, encData);
Console.WriteLine($"解密后:{decMsg}");

打印结果:

bash
原文本:佳佳的博客(www.liujiajia.me)
加密后:4ypzQHeNAaLSc2TPfQJlSHI8nU5QJctfBI4PClIE2i7D/QyMptshHsH6fo/fXXYk
解密后:佳佳的博客(www.liujiajia.me)

RSA

RSA 算法使用 公钥私钥 来完成加密和解密。公钥用来加密,可以对外公开;私钥用于解密,不可以公开。

RSA 算法常用于对网络数据进行保护。

示例如下:

csharp
string text = "www.liujiajia.me";
Console.WriteLine($"原字符串:{text}");
byte[] key = null;
byte[] encryptData = null;
// 加密数据
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
    // 导出公钥和私钥
    key = rsa.ExportCspBlob(true);
    encryptData = rsa.Encrypt(Encoding.ASCII.GetBytes(text), RSAEncryptionPadding.Pkcs1);
    Console.WriteLine($"加密后:{Convert.ToBase64String(encryptData)}");
}
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
    // 导入公钥和私钥
    rsa.ImportCspBlob(key);
    byte[] buffer = rsa.Decrypt(encryptData, RSAEncryptionPadding.Pkcs1);
    string restoreText = Encoding.ASCII.GetString(buffer);
    Console.WriteLine($"解密后的字符串:{restoreText}");
}

运行结果:

bash
原字符串:www.liujiajia.me
加密后:KYfaLb2SepY/zEPXZ/NkEbkCVi7oL1eBBJRgZg9L78fI5dvINDNrcg/U14kDhtuZ0Vf8sDcG
OVRc4L7nFZEkh34dDDWlEoMmvsavBWQq/ZbO75PiaBcTkK/yoYb32uelquptDWYJ0jyQcsV3ijOyCQ3T
8OyGq7dMXHVrzDNE3vE=
解密后的字符串:www.liujiajia.me

参考:《.NET Core 实战:手把手教你掌握 380 个精彩案例》 -- 周家安 著