加密解密
大约 13 分钟约 3774 字
加密解密
简介
加密(Encryption)是将原始信息数据通过特定算法转换为不可直接识别的密文形式的过程,解密(Decryption)则是将密文还原为原始信息数据的过程。加密解密技术是信息安全的基础,广泛应用于数据传输、密码存储、身份验证、数字签名等场景。
在 .NET 开发中,System.Security.Cryptography 命名空间提供了丰富的加密解密 API,涵盖对称加密、非对称加密、哈希算法和数字签名等功能。
加密的分类
| 分类 | 说明 | 代表算法 | 是否可逆 |
|---|---|---|---|
| 不可逆加密(哈希) | 将原文转换为固定长度的摘要,不可还原 | MD5、SHA-1、SHA-256、HMAC | 不可逆 |
| 对称加密 | 加密和解密使用相同的密钥,速度快 | AES、DES、3DES | 可逆 |
| 非对称加密 | 加密和解密使用不同的密钥对,安全性高 | RSA、DSA、ECDSA | 可逆 |
不可逆加密(哈希算法)
MD5
MD5(Message-Digest Algorithm 5)是一种广泛使用的哈希算法,可以将任意长度的数据转换为 128 位(32 字符十六进制)的摘要值。
规则:
作用:
.NET MD5 示例
using System.Security.Cryptography;
using System.Text;
/// <summary>
/// MD5 加密
/// </summary>
public static string Md5Encrypt(string input)
{
using (var md5 = MD5.Create())
{
var data = md5.ComputeHash(Encoding.UTF8.GetBytes(input));
var builder = new StringBuilder();
foreach (var b in data)
{
builder.Append(b.ToString("x2")); // 转为十六进制
}
return builder.ToString();
}
}
/// <summary>
/// MD5 加盐加密
/// </summary>
public static string Md5WithSalt(string input, string salt)
{
return Md5Encrypt(input + salt);
}SHA 系列
SHA(Secure Hash Algorithm)比 MD5 更安全,推荐使用 SHA-256 或更高版本。
/// <summary>
/// SHA-256 加密
/// </summary>
public static string Sha256Encrypt(string input)
{
using (var sha256 = SHA256.Create())
{
var data = sha256.ComputeHash(Encoding.UTF8.GetBytes(input));
var builder = new StringBuilder();
foreach (var b in data)
{
builder.Append(b.ToString("x2"));
}
return builder.ToString();
}
}HMAC(哈希消息认证码)
HMAC 使用密钥对消息进行哈希运算,可以同时验证数据的完整性和真实性。
/// <summary>
/// HMAC-SHA256 加密
/// </summary>
public static string HmacSha256(string message, string key)
{
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
{
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
return Convert.ToBase64String(hash);
}
}哈希算法对比
| 算法 | 摘要长度 | 安全性 | 速度 | 推荐场景 |
|---|---|---|---|---|
| MD5 | 128 位 | 低(已被破解) | 最快 | 文件校验、非安全场景 |
| SHA-1 | 160 位 | 较低 | 快 | 不推荐 |
| SHA-256 | 256 位 | 高 | 中等 | 密码存储、数字签名 |
| SHA-512 | 512 位 | 很高 | 较慢 | 高安全需求场景 |
| HMAC-SHA256 | 256 位 | 高(带密钥) | 中等 | API 签名验证 |
对称加密(AES/DES)
对称加密使用相同的密钥进行加密和解密,优点是速度快,缺点是密钥管理困难。
规则
特点
.NET AES 示例
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
/// <summary>
/// AES 加密
/// </summary>
public static string AesEncrypt(string plainText, string key, string iv)
{
using (var aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes(key.PadRight(32).Substring(0, 32));
aes.IV = Encoding.UTF8.GetBytes(iv.PadRight(16).Substring(0, 16));
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using (var encryptor = aes.CreateEncryptor())
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
byte[] inputBytes = Encoding.UTF8.GetBytes(plainText);
cs.Write(inputBytes, 0, inputBytes.Length);
cs.FlushFinalBlock();
}
return Convert.ToBase64String(ms.ToArray());
}
}
}
/// <summary>
/// AES 解密
/// </summary>
public static string AesDecrypt(string cipherText, string key, string iv)
{
using (var aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes(key.PadRight(32).Substring(0, 32));
aes.IV = Encoding.UTF8.GetBytes(iv.PadRight(16).Substring(0, 16));
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using (var decryptor = aes.CreateDecryptor())
using (var ms = new MemoryStream(Convert.FromBase64String(cipherText)))
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
using (var sr = new StreamReader(cs))
{
return sr.ReadToEnd();
}
}
}对称加密算法对比
| 算法 | 密钥长度 | 分组长度 | 安全性 | 说明 |
|---|---|---|---|---|
| DES | 56 位 | 64 位 | 低(已不推荐) | 最早的对称加密标准 |
| 3DES | 168 位 | 64 位 | 中等 | DES 的三重加密版本 |
| AES | 128/192/256 位 | 128 位 | 高 | 当前推荐的标准算法 |
非对称加密(RSA)
非对称加密使用一对密钥:公钥(Public Key)和私钥(Private Key)。公钥可以公开分享,私钥必须保密。
规则
特点
.NET RSA 示例
using System;
using System.Security.Cryptography;
using System.Text;
/// <summary>
/// 生成 RSA 密钥对
/// </summary>
public static (string publicKey, string privateKey) GenerateRsaKeyPair()
{
using (var rsa = RSA.Create(2048))
{
string publicKey = rsa.ToXmlString(false); // 公钥
string privateKey = rsa.ToXmlString(true); // 私钥
return (publicKey, privateKey);
}
}
/// <summary>
/// RSA 公钥加密
/// </summary>
public static string RsaEncrypt(string plainText, string publicKey)
{
using (var rsa = RSA.Create())
{
rsa.FromXmlString(publicKey);
byte[] data = Encoding.UTF8.GetBytes(plainText);
byte[] encrypted = rsa.Encrypt(data, RSAEncryptionPadding.OaepSHA256);
return Convert.ToBase64String(encrypted);
}
}
/// <summary>
/// RSA 私钥解密
/// </summary>
public static string RsaDecrypt(string cipherText, string privateKey)
{
using (var rsa = RSA.Create())
{
rsa.FromXmlString(privateKey);
byte[] data = Convert.FromBase64String(cipherText);
byte[] decrypted = rsa.Decrypt(data, RSAEncryptionPadding.OaepSHA256);
return Encoding.UTF8.GetString(decrypted);
}
}数字签名
数字签名用于验证消息的完整性和发送者的身份,是信息安全和身份认证的核心技术。
签名过程
- 发送方对原文进行哈希运算(如 SHA-256),得到摘要
- 发送方使用自己的私钥对摘要进行加密,生成数字签名
- 发送方将原文、数字签名和公钥一起发送给接收方
验签过程
- 接收方使用发送方的公钥解密数字签名,得到摘要
- 接收方对收到的原文进行相同的哈希运算,得到新的摘要
- 比较两个摘要,如果一致则验证通过
.NET 数字签名示例
/// <summary>
/// RSA 私钥签名
/// </summary>
public static string RsaSign(string data, string privateKey)
{
using (var rsa = RSA.Create())
{
rsa.FromXmlString(privateKey);
byte[] dataBytes = Encoding.UTF8.GetBytes(data);
byte[] signature = rsa.SignData(dataBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signature);
}
}
/// <summary>
/// RSA 公钥验签
/// </summary>
public static bool RsaVerify(string data, string signature, string publicKey)
{
using (var rsa = RSA.Create())
{
rsa.FromXmlString(publicKey);
byte[] dataBytes = Encoding.UTF8.GetBytes(data);
byte[] signatureBytes = Convert.FromBase64String(signature);
return rsa.VerifyData(dataBytes, signatureBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}数字证书
数字签名虽然能保证数据不被篡改,但无法保证发送者身份的真实性(公钥可能被伪造)。数字证书由权威的第三方证书颁发机构(CA,Certificate Authority)签发,用于证明公钥的归属者身份。
数字证书的工作流程
- 申请者向 CA 机构提交公钥和身份信息
- CA 机构验证申请者身份后,用自己的私钥对申请者的公钥和身份信息进行签名
- 生成数字证书(包含公钥、身份信息、CA 签名、有效期等)
- 接收方收到证书后,使用 CA 的公钥验证证书的真伪
- 验证通过后,从证书中提取公钥进行通信

数字证书的应用
实际应用中的最佳实践
密码存储
/// <summary>
/// 安全的密码哈希(使用 PBKDF2)
/// </summary>
public static string HashPassword(string password, out string salt)
{
// 生成随机盐
byte[] saltBytes = RandomNumberGenerator.GetBytes(16);
salt = Convert.ToBase64String(saltBytes);
// 使用 PBKDF2 进行哈希
using (var pbkdf2 = new Rfc2898DeriveBytes(password, saltBytes, 10000, HashAlgorithmName.SHA256))
{
byte[] hash = pbkdf2.GetBytes(32);
return Convert.ToBase64String(hash);
}
}
/// <summary>
/// 验证密码
/// </summary>
public static bool VerifyPassword(string password, string storedHash, string salt)
{
byte[] saltBytes = Convert.FromBase64String(salt);
using (var pbkdf2 = new Rfc2898DeriveBytes(password, saltBytes, 10000, HashAlgorithmName.SHA256))
{
byte[] hash = pbkdf2.GetBytes(32);
return Convert.ToBase64String(hash) == storedHash;
}
}混合加密方案
AES-GCM 认证加密
/// <summary>
/// AES-GCM — 同时提供加密和完整性验证
/// .NET 6+ 原生支持,推荐替代 CBC 模式
/// </summary>
public static class AesGcmEncryption
{
// 加密
public static (string Ciphertext, string Nonce, string Tag) Encrypt(
string plaintext, byte[] key)
{
// 生成随机 Nonce(每次加密必须不同)
byte[] nonce = new byte[AesGcm.NonceByteSizes.MaxSize]; // 12 bytes
RandomNumberGenerator.Fill(nonce);
byte[] plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
byte[] ciphertext = new byte[plaintextBytes.Length];
byte[] tag = new byte[AesGcm.TagByteSizes.MaxSize]; // 16 bytes
using var aes = new AesGcm(key, AesGcm.TagByteSizes.MaxSize);
aes.Encrypt(nonce, plaintextBytes, ciphertext, tag);
return (
Ciphertext: Convert.ToBase64String(ciphertext),
Nonce: Convert.ToBase64String(nonce),
Tag: Convert.ToBase64String(tag)
);
}
// 解密
public static string Decrypt(
string ciphertext, string nonce, string tag, byte[] key)
{
byte[] ciphertextBytes = Convert.FromBase64String(ciphertext);
byte[] nonceBytes = Convert.FromBase64String(nonce);
byte[] tagBytes = Convert.FromBase64String(tag);
byte[] plaintextBytes = new byte[ciphertextBytes.Length];
using var aes = new AesGcm(key, AesGcm.TagByteSizes.MaxSize);
aes.Decrypt(nonceBytes, ciphertextBytes, tagBytes, plaintextBytes);
return Encoding.UTF8.GetString(plaintextBytes);
}
}
// 使用示例
// 生成 256 位密钥
byte[] key = new byte[32]; // AES-256
RandomNumberGenerator.Fill(key);
var (ciphertext, nonce, tag) = AesGcmEncryption.Encrypt("机密数据", key);
Console.WriteLine($"密文: {ciphertext}");
string decrypted = AesGcmEncryption.Decrypt(ciphertext, nonce, tag, key);
Console.WriteLine($"解密: {decrypted}");加密模式对比
CBC vs GCM vs CTR
加密模式对比:
┌──────────┬──────────────┬──────────────┬──────────────┐
│ 特性 │ CBC │ GCM │ CTR │
├──────────┼──────────────┼──────────────┼──────────────┤
│ 认证 │ 无 │ 有(AEAD) │ 无 │
│ 并行 │ 仅解密 │ 加解密均可 │ 加解密均可 │
│ 填充 │ 需要 PKCS7 │ 不需要 │ 不需要 │
│ IV/Nonce │ 16 字节 IV │ 12 字节 Nonce│ 8 字节 Nonce │
│ 推荐度 │ 旧系统 │ ★ 推荐 │ 特殊场景 │
│ 风险 │ 填充 oracle │ Nonce 不能重复│ 重用则破解 │
└──────────┴──────────────┴──────────────┴──────────────┘
选择建议:
- 新项目:优先使用 AES-256-GCM
- 旧系统兼容:AES-256-CBC + HMAC
- 高性能:AES-256-GCM 或 ChaCha20-Poly1305密钥管理实践
密钥生成与存储
/// <summary>
/// 密钥管理最佳实践
/// </summary>
public static class KeyManagement
{
// 1. 安全的密钥生成
public static byte[] GenerateKey(int keySizeInBits = 256)
{
// 使用加密安全的随机数生成器
byte[] key = new byte[keySizeInBits / 8];
RandomNumberGenerator.Fill(key);
return key;
}
// 2. 从配置中安全加载密钥
public static byte[] LoadKeyFromConfiguration(IConfiguration configuration, string keyName)
{
var keyBase64 = configuration[$"EncryptionKeys:{keyName}"];
if (string.IsNullOrEmpty(keyBase64))
throw new InvalidOperationException($"密钥 {keyName} 未配置");
return Convert.FromBase64String(keyBase64);
}
// 3. 密钥派生 — 从密码生成密钥
public static byte[] DeriveKeyFromPassword(string password, byte[] salt, int iterations = 100000)
{
using var pbkdf2 = new Rfc2898DeriveBytes(
password, salt, iterations, HashAlgorithmName.SHA256);
return pbkdf2.GetBytes(32); // 256 位密钥
}
// 4. ASP.NET Core Data Protection — 框架级密钥管理
// Program.cs 配置
// builder.Services.AddDataProtection()
// .PersistKeysToFileSystem(new DirectoryInfo("/app/keys"))
// .ProtectKeysWithCertificate("thumbprint")
// .SetApplicationName("MyApp");
// 5. 密钥轮换策略
// - 对称密钥:每 90 天轮换
// - RSA 密钥:每 1-2 年轮换
// - API 密钥:每 30 天轮换
}
// .NET Data Protection 使用示例
public class SecureDataService
{
private readonly IDataProtector _protector;
public SecureDataService(IDataProtectionProvider provider)
{
_protector = provider.CreateProtector("MyApp.SensitiveData");
}
public string Protect(string plaintext)
{
return _protector.Protect(plaintext);
}
public string Unprotect(string protectedData)
{
return _protector.Unprotect(protectedData);
}
// 带用途的数据保护
public string ProtectForPurpose(string plaintext, string purpose)
{
var purposeProtector = _protector.CreateProtector(purpose);
return purposeProtector.Protect(plaintext);
}
}JWT 与加密
Token 安全实践
/// <summary>
/// JWT 安全配置
/// </summary>
public static class JwtSecurity
{
// 生成安全的 JWT 签名密钥
public static SecurityKey GenerateSigningKey()
{
// HMAC SHA-256(对称签名)
var key = new byte[64]; // 512 位
RandomNumberGenerator.Fill(key);
return new SymmetricSecurityKey(key);
// 或使用 RSA(非对称签名)
// using var rsa = RSA.Create(2048);
// return new RsaSecurityKey(rsa);
}
// Token 加密(JWE — JSON Web Encryption)
public static string CreateEncryptedToken(
string userId, string role, string encryptionKey)
{
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Role, role),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
}),
Expires = DateTime.UtcNow.AddHours(1),
Issuer = "https://myapp.com",
Audience = "https://api.myapp.com",
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(encryptionKey)),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
// Token 存储安全建议:
// - HttpOnly Cookie(推荐)— 防止 XSS 窃取
// - localStorage(SPA 常用)— 注意 XSS 防护
// - sessionStorage — 关闭浏览器后失效
// - 不要放在 URL 参数中
}混合加密方案
在实际应用中,通常采用混合加密方案:使用非对称加密交换对称密钥,再使用对称加密传输数据。HTTPS 就是这种方案的典型应用。
优点
缺点
关键知识点
- 先明确这个主题影响的是语法层、运行时层,还是性能与可维护性层。
- 学习时要同时关注语言表面写法和编译器、JIT、GC 等底层行为。
- 真正有价值的是知道“为什么这样写”和“在什么边界下不能这样写”。
项目落地视角
- 把示例改成最小可运行样例,并观察编译输出、运行结果和异常行为。
- 如果它会进入团队代码规范,最好同步补充命名约定、禁用场景和替代方案。
- 涉及性能结论时,优先用 Benchmark 或实际热点链路验证,而不是凭感觉判断。
常见误区
- 只记语法糖,不知道底层成本。
- 把适用于小样例的写法直接搬到高并发或大对象场景里。
- 忽略框架版本、语言版本和运行时差异,导致结论失真。
进阶路线
- 继续向源码、IL、JIT 行为和 BCL 实现层深入。
- 把知识点和代码评审、性能诊断、面试复盘结合起来。
- 把同类主题做横向对比,例如值类型与引用类型、迭代器与 async 状态机、反射与 Source Generator。
适用场景
- 当你准备把《加密解密》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在需要理解语言特性、运行时行为或 API 边界时阅读。
- 当代码开始出现性能瓶颈、可维护性问题或语义歧义时,这类主题会直接影响实现质量。
落地建议
- 先写最小可运行样例,再把结论迁移到真实业务代码。
- 同时记录这个特性的收益、限制和替代方案,避免为了“高级”而使用。
- 涉及内存、并发或序列化时,最好配合调试器或基准测试验证。
排错清单
- 先确认问题属于编译期、运行期还是语义误用。
- 检查是否存在隐式转换、装箱拆箱、闭包捕获或上下文切换等隐藏成本。
- 查看异常栈、日志和最小复现代码,优先排除使用姿势问题。
复盘问题
- 如果把《加密解密》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《加密解密》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《加密解密》最大的收益和代价分别是什么?
