加密与签名实战
大约 9 分钟约 2793 字
加密与签名实战
简介
在现代软件开发中,数据安全是至关重要的环节。C# 和 .NET 提供了丰富的加密 API,涵盖对称加密(AES)、非对称加密(RSA)、哈希算法(SHA)、数字签名和证书操作等方面。本文将通过实际代码示例,系统介绍如何在 C# 中正确使用这些加密技术,以及在实际项目中需要注意的安全最佳实践。
特点
对称加密(AES)
AES(Advanced Encryption Standard)是最常用的对称加密算法,适合加密大量数据。
using System.Security.Cryptography;
/// <summary>
/// AES 对称加密工具类
/// </summary>
public static class AesEncryption
{
/// <summary>
/// 生成随机的 AES 密钥和 IV
/// </summary>
public static (byte[] Key, byte[] IV) GenerateKeyAndIV()
{
using var aes = Aes.Create();
aes.KeySize = 256; // 使用 256 位密钥
aes.GenerateKey();
aes.GenerateIV();
return (aes.Key, aes.IV);
}
/// <summary>
/// AES 加密
/// </summary>
public static byte[] Encrypt(byte[] plaintext, byte[] key, byte[] iv)
{
using var aes = Aes.Create();
aes.Key = key;
aes.IV = iv;
aes.Mode = CipherMode.CBC; // CBC 模式
aes.Padding = PaddingMode.PKCS7; // PKCS7 填充
using var encryptor = aes.CreateEncryptor();
using var ms = new MemoryStream();
// 先写入 IV(解密时需要)
ms.Write(iv, 0, iv.Length);
// 使用 CryptoStream 加密数据
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(plaintext, 0, plaintext.Length);
}
return ms.ToArray();
}
/// <summary>
/// AES 解密
/// </summary>
public static byte[] Decrypt(byte[] ciphertext, byte[] key)
{
using var aes = Aes.Create();
aes.Key = key;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
// 从密文前部提取 IV
var iv = new byte[aes.BlockSize / 8];
Buffer.BlockCopy(ciphertext, 0, iv, 0, iv.Length);
aes.IV = iv;
using var decryptor = aes.CreateDecryptor();
using var ms = new MemoryStream();
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write))
{
cs.Write(ciphertext, iv.Length, ciphertext.Length - iv.Length);
}
return ms.ToArray();
}
}
// 使用示例
var (key, iv) = AesEncryption.GenerateKeyAndIV();
var secret = "这是一段需要加密的敏感数据"u8.ToArray();
var encrypted = AesEncryption.Encrypt(secret, key, iv);
Console.WriteLine($"加密后: {Convert.ToBase64String(encrypted)}");
var decrypted = AesEncryption.Decrypt(encrypted, key);
Console.WriteLine($"解密后: {Encoding.UTF8.GetString(decrypted)}");AES-GCM 认证加密
/// <summary>
/// AES-GCM 提供加密和认证双重保护(.NET 8 推荐)
/// </summary>
public static class AesGcmEncryption
{
public static (byte[] Ciphertext, byte[] Tag, byte[] Nonce) Encrypt(
byte[] plaintext, byte[] key, byte[] associatedData = null)
{
// Nonce(随机数)每次加密必须不同
var nonce = new byte[AesGcm.NonceByteSizes.MaxSize]; // 12 bytes
RandomNumberGenerator.Fill(nonce);
var ciphertext = new byte[plaintext.Length];
var tag = new byte[AesGcm.TagByteSizes.MaxSize]; // 16 bytes
using var aes = new AesGcm(key, AesGcm.TagByteSizes.MaxSize);
aes.Encrypt(nonce, plaintext, ciphertext, tag, associatedData);
return (ciphertext, tag, nonce);
}
public static byte[] Decrypt(
byte[] ciphertext, byte[] key, byte[] nonce,
byte[] tag, byte[] associatedData = null)
{
var plaintext = new byte[ciphertext.Length];
using var aes = new AesGcm(key, AesGcm.TagByteSizes.MaxSize);
aes.Decrypt(nonce, ciphertext, tag, plaintext, associatedData);
return plaintext;
}
}
// 使用
var gcmKey = new byte[32]; // 256 位密钥
RandomNumberGenerator.Fill(gcmKey);
var data = "AES-GCM 认证加密数据"u8.ToArray();
var (ct, tag, nonce) = AesGcmEncryption.Encrypt(data, gcmKey);
var pt = AesGcmEncryption.Decrypt(ct, gcmKey, nonce, tag);非对称加密(RSA)
RSA 用于加密小量数据(如密钥交换)和数字签名。
/// <summary>
/// RSA 非对称加密工具
/// </summary>
public static class RsaEncryption
{
/// <summary>
/// 生成 RSA 密钥对
/// </summary>
public static (string PublicKey, string PrivateKey) GenerateKeyPair(int keySize = 2048)
{
using var rsa = RSA.Create(keySize);
var publicKey = rsa.ToXmlString(false); // 公钥
var privateKey = rsa.ToXmlString(true); // 私钥
return (publicKey, privateKey);
}
/// <summary>
/// RSA 加密(使用 OAEP 填充,更安全)
/// </summary>
public static byte[] Encrypt(byte[] data, string publicKeyXml)
{
using var rsa = RSA.Create();
rsa.FromXmlString(publicKeyXml);
return rsa.Encrypt(data, RSAEncryptionPadding.OaepSHA256);
}
/// <summary>
/// RSA 解密
/// </summary>
public static byte[] Decrypt(byte[] encryptedData, string privateKeyXml)
{
using var rsa = RSA.Create();
rsa.FromXmlString(privateKeyXml);
return rsa.Decrypt(encryptedData, RSAEncryptionPadding.OaepSHA256);
}
}
// RSA 加密有长度限制(2048位密钥最多加密 190 字节)
// 实际中通常用 RSA 加密 AES 密钥,再用 AES 加密数据(混合加密)
public static class HybridEncryption
{
public static HybridEncryptedData Encrypt(byte[] plaintext, string rsaPublicKey)
{
// 1. 生成随机的 AES 密钥
var aesKey = new byte[32];
var aesIV = new byte[16];
RandomNumberGenerator.Fill(aesKey);
RandomNumberGenerator.Fill(aesIV);
// 2. 用 AES 加密数据
var encryptedData = AesEncryption.Encrypt(plaintext, aesKey, aesIV);
// 3. 用 RSA 加密 AES 密钥
var encryptedKey = RsaEncryption.Encrypt(aesKey, rsaPublicKey);
return new HybridEncryptedData(encryptedKey, aesIV, encryptedData);
}
public static byte[] Decrypt(HybridEncryptedData data, string rsaPrivateKey)
{
// 1. 用 RSA 解密 AES 密钥
var aesKey = RsaEncryption.Decrypt(data.EncryptedAesKey, rsaPrivateKey);
// 2. 用 AES 解密数据
return AesEncryption.Decrypt(data.EncryptedData, aesKey);
}
}
public record HybridEncryptedData(
byte[] EncryptedAesKey,
byte[] AesIV,
byte[] EncryptedData);哈希算法
/// <summary>
/// 哈希工具类
/// </summary>
public static class HashHelper
{
/// <summary>
/// SHA-256 哈希
/// </summary>
public static string Sha256(string input)
{
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(input));
return Convert.ToHexString(bytes).ToLowerInvariant();
}
/// <summary>
/// SHA-512 哈希
/// </summary>
public static string Sha512(string input)
{
var bytes = SHA512.HashData(Encoding.UTF8.GetBytes(input));
return Convert.ToHexString(bytes).ToLowerInvariant();
}
/// <summary>
/// 文件哈希 - 用于校验文件完整性
/// </summary>
public static string FileSha256(string filePath)
{
using var stream = File.OpenRead(filePath);
var hash = SHA256.HashData(stream);
return Convert.ToHexString(hash).ToLowerInvariant();
}
/// <summary>
/// HMAC-SHA256 消息认证码
/// </summary>
public static string HmacSha256(string message, string key)
{
var keyBytes = Encoding.UTF8.GetBytes(key);
var messageBytes = Encoding.UTF8.GetBytes(message);
var hash = HMACSHA256.HashData(keyBytes, messageBytes);
return Convert.ToHexString(hash).ToLowerInvariant();
}
}
// 密码哈希 - 使用 PBKDF2
public static class PasswordHasher
{
private const int SaltSize = 16; // 128 位盐
private const int KeySize = 32; // 256 位密钥
private const int Iterations = 600000; // OWASP 推荐的最小迭代次数
/// <summary>
/// 对密码进行哈希(生成可存储的字符串)
/// </summary>
public static string HashPassword(string password)
{
var salt = new byte[SaltSize];
RandomNumberGenerator.Fill(salt);
var hash = Rfc2898DeriveBytes.Pbkdf2(
password, salt, Iterations, HashAlgorithmName.SHA256, KeySize);
// 格式:iterations.salt.hash
return $"{Iterations}.{Convert.ToBase64String(salt)}.{Convert.ToBase64String(hash)}";
}
/// <summary>
/// 验证密码
/// </summary>
public static bool VerifyPassword(string password, string storedHash)
{
var parts = storedHash.Split('.');
if (parts.Length != 3) return false;
var iterations = int.Parse(parts[0]);
var salt = Convert.FromBase64String(parts[1]);
var storedKey = Convert.FromBase64String(parts[2]);
var computedKey = Rfc2898DeriveBytes.Pbkdf2(
password, salt, iterations, HashAlgorithmName.SHA256, KeySize);
// 使用恒定时间比较防止时序攻击
return CryptographicOperations.FixedTimeEquals(computedKey, storedKey);
}
}
// 使用
var hashed = PasswordHasher.HashPassword("MyP@ssw0rd");
Console.WriteLine($"哈希值: {hashed}");
Console.WriteLine($"验证正确密码: {PasswordHasher.VerifyPassword("MyP@ssw0rd", hashed)}");
Console.WriteLine($"验证错误密码: {PasswordHasher.VerifyPassword("wrong", hashed)}");数字签名
/// <summary>
/// 数字签名工具
/// </summary>
public static class DigitalSignature
{
/// <summary>
/// 对数据签名
/// </summary>
public static byte[] SignData(byte[] data, string privateKeyXml)
{
using var rsa = RSA.Create();
rsa.FromXmlString(privateKeyXml);
return rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
/// <summary>
/// 验证签名
/// </summary>
public static bool VerifyData(byte[] data, byte[] signature, string publicKeyXml)
{
using var rsa = RSA.Create();
rsa.FromXmlString(publicKeyXml);
return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
/// <summary>
/// 对文件签名
/// </summary>
public static byte[] SignFile(string filePath, string privateKeyXml)
{
using var rsa = RSA.Create();
rsa.FromXmlString(privateKeyXml);
using var stream = File.OpenRead(filePath);
return rsa.SignData(stream, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
/// <summary>
/// 验证文件签名
/// </summary>
public static bool VerifyFile(string filePath, byte[] signature, string publicKeyXml)
{
using var rsa = RSA.Create();
rsa.FromXmlString(publicKeyXml);
using var stream = File.OpenRead(filePath);
return rsa.VerifyData(stream, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}
// JWT Token 签名示例
public class TokenService
{
private readonly RSA _rsa;
public TokenService(string privateKeyPem)
{
_rsa = RSA.Create();
_rsa.ImportFromPem(privateKeyPem);
}
public string GenerateToken(Dictionary<string, string> claims, TimeSpan expiration)
{
var header = new { alg = "RS256", typ = "JWT" };
var payload = new
{
sub = claims.GetValueOrDefault("sub", ""),
name = claims.GetValueOrDefault("name", ""),
role = claims.GetValueOrDefault("role", ""),
iat = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
exp = DateTimeOffset.UtcNow.Add(expiration).ToUnixTimeSeconds()
};
var headerBytes = Encoding.UTF8.GetBytes(System.Text.Json.JsonSerializer.Serialize(header));
var payloadBytes = Encoding.UTF8.GetBytes(System.Text.Json.JsonSerializer.Serialize(payload));
var headerB64 = Base64UrlEncode(headerBytes);
var payloadB64 = Base64UrlEncode(payloadBytes);
var signingInput = $"{headerB64}.{payloadB64}";
var signature = _rsa.SignData(
Encoding.ASCII.GetBytes(signingInput),
HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return $"{signingInput}.{Base64UrlEncode(signature)}";
}
private static string Base64UrlEncode(byte[] bytes) =>
Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_');
}证书操作
/// <summary>
/// X.509 证书操作工具
/// </summary>
public static class CertificateHelper
{
/// <summary>
/// 创建自签名证书
/// </summary>
public static X509Certificate2 CreateSelfSignedCertificate(
string subjectName, int validYears = 1)
{
using var rsa = RSA.Create(2048);
var request = new CertificateRequest(
$"CN={subjectName}",
rsa,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
// 添加扩展
request.CertificateExtensions.Add(
new X509BasicConstraintsExtension(false, false, 0, false));
request.CertificateExtensions.Add(
new X509KeyUsageExtension(
X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment,
true));
request.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection
{
new Oid("1.3.6.1.5.5.7.3.1"), // TLS Server Auth
new Oid("1.3.6.1.5.5.7.3.2") // TLS Client Auth
}, true));
var certificate = request.CreateSelfSigned(
DateTimeOffset.UtcNow.AddDays(-1),
DateTimeOffset.UtcNow.AddYears(validYears));
return certificate;
}
/// <summary>
/// 从 PFX 文件加载证书
/// </summary>
public static X509Certificate2 LoadFromPfx(string pfxPath, string password)
{
return new X509Certificate2(pfxPath, password,
X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.PersistKeySet |
X509KeyStorageFlags.Exportable);
}
/// <summary>
/// 从证书存储区加载证书
/// </summary>
public static X509Certificate2 LoadFromStore(string subjectName,
StoreName storeName = StoreName.My,
StoreLocation storeLocation = StoreLocation.CurrentUser)
{
using var store = new X509Store(storeName, storeLocation);
store.Open(OpenFlags.ReadOnly);
var certs = store.Certificates.Find(
X509FindType.FindBySubjectName, subjectName, validOnly: true);
if (certs.Count == 0)
throw new InvalidOperationException($"未找到证书: {subjectName}");
return certs[0];
}
/// <summary>
/// 导出证书公钥为 PEM 格式
/// </summary>
public static string ExportPublicKeyPem(X509Certificate2 cert)
{
return cert.GetPublicKeyString();
}
}优点
缺点
总结
加密是保护数据安全的核心手段。在 C# 中,应优先使用 AES-GCM 进行对称加密(同时提供加密和认证),使用 RSA-OAEP 进行非对称加密,使用 PBKDF2 进行密码哈希,使用 RSA-SHA256 进行数字签名。切记不要自行发明加密算法或使用已知的弱算法(如 MD5、DES)。密钥管理是加密系统中最薄弱的环节,建议使用 Azure Key Vault 或类似的密钥管理服务来安全地存储和管理密钥。
关键知识点
- 先明确这个主题影响的是语法层、运行时层,还是性能与可维护性层。
- 学习时要同时关注语言表面写法和编译器、JIT、GC 等底层行为。
- 真正有价值的是知道“为什么这样写”和“在什么边界下不能这样写”。
项目落地视角
- 把示例改成最小可运行样例,并观察编译输出、运行结果和异常行为。
- 如果它会进入团队代码规范,最好同步补充命名约定、禁用场景和替代方案。
- 涉及性能结论时,优先用 Benchmark 或实际热点链路验证,而不是凭感觉判断。
常见误区
- 只记语法糖,不知道底层成本。
- 把适用于小样例的写法直接搬到高并发或大对象场景里。
- 忽略框架版本、语言版本和运行时差异,导致结论失真。
进阶路线
- 继续向源码、IL、JIT 行为和 BCL 实现层深入。
- 把知识点和代码评审、性能诊断、面试复盘结合起来。
- 把同类主题做横向对比,例如值类型与引用类型、迭代器与 async 状态机、反射与 Source Generator。
适用场景
- 当你准备把《加密与签名实战》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在需要理解语言特性、运行时行为或 API 边界时阅读。
- 当代码开始出现性能瓶颈、可维护性问题或语义歧义时,这类主题会直接影响实现质量。
落地建议
- 先写最小可运行样例,再把结论迁移到真实业务代码。
- 同时记录这个特性的收益、限制和替代方案,避免为了“高级”而使用。
- 涉及内存、并发或序列化时,最好配合调试器或基准测试验证。
排错清单
- 先确认问题属于编译期、运行期还是语义误用。
- 检查是否存在隐式转换、装箱拆箱、闭包捕获或上下文切换等隐藏成本。
- 查看异常栈、日志和最小复现代码,优先排除使用姿势问题。
复盘问题
- 如果把《加密与签名实战》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《加密与签名实战》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《加密与签名实战》最大的收益和代价分别是什么?
