.NET 密码学深入
大约 14 分钟约 4242 字
.NET 密码学深入
简介
密码学是信息安全的基石,.NET 提供了丰富的密码学 API,涵盖对称加密、非对称加密、哈希算法、数字签名和证书管理等各个方面。正确使用密码学 API 对应用安全至关重要——一个微小的实现错误就可能导致整个安全防线失效。
本文将深入 .NET 密码学的各个领域,从算法原理到实际应用,帮助开发者正确、安全地使用 .NET 密码学 API。
特点
- 算法丰富:支持 AES、RSA、ECDsa、SHA 等主流算法
- 平台适配:自动选择底层实现(Windows CNG / Linux OpenSSL)
- 类型安全:强类型的 API 设计减少使用错误
- 前向兼容:新版本持续添加现代密码学算法
- 跨平台:.NET 6+ 在所有平台提供一致的密码学行为
对称加密
AES-GCM(推荐)
AES-GCM(Galois/Counter Mode)是当前推荐的对称加密模式,同时提供加密和认证(AEAD)。
using System.Security.Cryptography;
/// <summary>
/// AES-GCM 加密解密工具
/// .NET 6+ 原生支持
/// </summary>
public static class AesGcmEncryption
{
// AES-GCM 的 nonce 长度固定为 12 字节
private const int NonceSize = 12;
// 标签长度推荐 16 字节(128 位)
private const int TagSize = 16;
/// <summary>
/// 加密数据
/// </summary>
public static byte[] Encrypt(byte[] plaintext, byte[] key, out byte[] nonce)
{
// 密钥长度必须是 16、24 或 32 字节(AES-128、AES-192、AES-256)
if (key.Length != 16 && key.Length != 24 && key.Length != 32)
throw new ArgumentException("密钥长度必须为 16、24 或 32 字节");
nonce = RandomNumberGenerator.GetBytes(NonceSize);
byte[] ciphertext = new byte[plaintext.Length];
byte[] tag = new byte[TagSize];
using var aes = new AesGcm(key, TagSize);
aes.Encrypt(nonce, plaintext, ciphertext, tag);
// 将 nonce、ciphertext 和 tag 组合输出
// 格式:[nonce(12)] [tag(16)] [ciphertext(N)]
var result = new byte[NonceSize + TagSize + ciphertext.Length];
Buffer.BlockCopy(nonce, 0, result, 0, NonceSize);
Buffer.BlockCopy(tag, 0, result, NonceSize, TagSize);
Buffer.BlockCopy(ciphertext, 0, result, NonceSize + TagSize, ciphertext.Length);
return result;
}
/// <summary>
/// 解密数据
/// </summary>
public static byte[] Decrypt(byte[] encryptedData, byte[] key)
{
if (encryptedData.Length < NonceSize + TagSize)
throw new ArgumentException("加密数据格式无效");
// 提取 nonce、tag 和 ciphertext
byte[] nonce = new byte[NonceSize];
byte[] tag = new byte[TagSize];
int ciphertextLength = encryptedData.Length - NonceSize - TagSize;
byte[] ciphertext = new byte[ciphertextLength];
Buffer.BlockCopy(encryptedData, 0, nonce, 0, NonceSize);
Buffer.BlockCopy(encryptedData, NonceSize, tag, 0, TagSize);
Buffer.BlockCopy(encryptedData, NonceSize + TagSize, ciphertext, 0, ciphertextLength);
byte[] plaintext = new byte[ciphertextLength];
using var aes = new AesGcm(key, TagSize);
aes.Decrypt(nonce, ciphertext, tag, plaintext);
return plaintext;
}
}
// 使用示例
public class AesGcmExample
{
public static void Run()
{
string sensitiveData = "这是一段需要加密的敏感数据";
// 生成 256 位密钥
byte[] key = RandomNumberGenerator.GetBytes(32);
// 加密
byte[] plaintext = System.Text.Encoding.UTF8.GetBytes(sensitiveData);
byte[] encrypted = AesGcmEncryption.Encrypt(plaintext, key, out byte[] nonce);
Console.WriteLine($"加密后长度: {encrypted.Length} 字节");
Console.WriteLine($"Nonce: {Convert.ToBase64String(nonce)}");
// 解密
byte[] decrypted = AesGcmEncryption.Decrypt(encrypted, key);
string result = System.Text.Encoding.UTF8.GetString(decrypted);
Console.WriteLine($"解密结果: {result}");
}
}AES-CBC(需要手动处理认证)
/// <summary>
/// AES-CBC 加密(配合 HMAC 提供认证)
/// 注意:推荐使用 AES-GCM 替代此方案
/// </summary>
public static class AesCbcEncryption
{
public static (byte[] Ciphertext, byte[] IV, byte[] HMAC) EncryptWithAuthentication(
byte[] plaintext, byte[] key, byte[] hmacKey)
{
using var aes = Aes.Create();
aes.KeySize = 256;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.GenerateIV();
using var encryptor = aes.CreateEncryptor();
byte[] ciphertext = encryptor.TransformFinalBlock(plaintext, 0, plaintext.Length);
// 使用 HMAC-SHA256 提供认证
using var hmac = new HMACSHA256(hmacKey);
// 对 IV + ciphertext 计算 HMAC
byte[] dataToAuthenticate = new byte[aes.IV.Length + ciphertext.Length];
Buffer.BlockCopy(aes.IV, 0, dataToAuthenticate, 0, aes.IV.Length);
Buffer.BlockCopy(ciphertext, 0, dataToAuthenticate, aes.IV.Length, ciphertext.Length);
byte[] hmacValue = hmac.ComputeHash(dataToAuthenticate);
return (ciphertext, aes.IV, hmacValue);
}
public static byte[] DecryptWithAuthentication(
byte[] ciphertext, byte[] key, byte[] iv, byte[] hmacValue, byte[] hmacKey)
{
// 先验证 HMAC
using var hmac = new HMACSHA256(hmacKey);
byte[] dataToAuthenticate = new byte[iv.Length + ciphertext.Length];
Buffer.BlockCopy(iv, 0, dataToAuthenticate, 0, iv.Length);
Buffer.BlockCopy(ciphertext, 0, dataToAuthenticate, iv.Length, ciphertext.Length);
byte[] computedHmac = hmac.ComputeHash(dataToAuthenticate);
if (!CryptographicOperations.FixedTimeEquals(hmacValue, computedHmac))
throw new CryptographicException("HMAC 验证失败,数据可能被篡改");
// HMAC 验证通过后再解密
using var aes = Aes.Create();
aes.Key = key;
aes.IV = iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using var decryptor = aes.CreateDecryptor();
return decryptor.TransformFinalBlock(ciphertext, 0, ciphertext.Length);
}
}非对称加密
RSA 加密与签名
using System.Security.Cryptography;
/// <summary>
/// RSA 非对称加密工具
/// 注意: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);
string publicKey = rsa.ExportRSAPublicKeyPem();
string privateKey = rsa.ExportRSAPrivateKeyPem();
return (publicKey, privateKey);
}
/// <summary>
/// RSA 加密(使用 OAEP 填充)
/// </summary>
public static byte[] Encrypt(byte[] data, string publicKeyPem)
{
using var rsa = RSA.Create();
rsa.ImportFromPem(publicKeyPem);
// 使用 OAEP-SHA256 填充(推荐)
return rsa.Encrypt(data, RSAEncryptionPadding.OaepSHA256);
}
/// <summary>
/// RSA 解密
/// </summary>
public static byte[] Decrypt(byte[] encryptedData, string privateKeyPem)
{
using var rsa = RSA.Create();
rsa.ImportFromPem(privateKeyPem);
return rsa.Decrypt(encryptedData, RSAEncryptionPadding.OaepSHA256);
}
/// <summary>
/// RSA 签名(使用 PSS 填充)
/// </summary>
public static byte[] SignData(byte[] data, string privateKeyPem)
{
using var rsa = RSA.Create();
rsa.ImportFromPem(privateKeyPem);
// 先对数据哈希,再签名
return rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
}
/// <summary>
/// RSA 验证签名
/// </summary>
public static bool VerifyData(byte[] data, byte[] signature, string publicKeyPem)
{
using var rsa = RSA.Create();
rsa.ImportFromPem(publicKeyPem);
return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
}
}
/// <summary>
/// 混合加密方案(RSA + AES)
/// 用于加密大数据
/// </summary>
public static class HybridEncryption
{
public static byte[] Encrypt(byte[] plaintext, string recipientPublicKeyPem)
{
// 1. 生成随机的 AES 密钥
byte[] aesKey = RandomNumberGenerator.GetBytes(32); // AES-256
// 2. 用 AES-GCM 加密数据
byte[] encryptedData = AesGcmEncryption.Encrypt(plaintext, aesKey, out byte[] nonce);
// 3. 用 RSA 加密 AES 密钥
byte[] encryptedKey = RsaEncryption.Encrypt(aesKey, recipientPublicKeyPem);
// 4. 组合输出:[encryptedKeyLength(4)] [encryptedKey] [nonce(12)] [encryptedData]
using var ms = new MemoryStream();
using var writer = new BinaryWriter(ms);
writer.Write(encryptedKey.Length); // 加密后的密钥长度
writer.Write(encryptedKey); // 加密后的 AES 密钥
writer.Write(nonce); // AES-GCM nonce
writer.Write(encryptedData); // AES-GCM 加密的数据
return ms.ToArray();
}
public static byte[] Decrypt(byte[] encryptedPackage, string recipientPrivateKeyPem)
{
using var ms = new MemoryStream(encryptedPackage);
using var reader = new BinaryReader(ms);
// 1. 读取加密的 AES 密钥
int encryptedKeyLength = reader.ReadInt32();
byte[] encryptedKey = reader.ReadBytes(encryptedKeyLength);
// 2. 读取 nonce
byte[] nonce = reader.ReadBytes(12);
// 3. 读取加密数据
byte[] encryptedData = reader.ReadBytes((int)(ms.Length - ms.Position));
// 4. 用 RSA 解密 AES 密钥
byte[] aesKey = RsaEncryption.Decrypt(encryptedKey, recipientPrivateKeyPem);
// 5. 重新组装 AES-GCM 加密数据(包含 nonce)
var fullEncrypted = new byte[12 + 16 + encryptedData.Length];
Buffer.BlockCopy(nonce, 0, fullEncrypted, 0, 12);
// 注意:这里需要根据实际格式调整
// 简化示例,实际应正确处理 tag
// 6. 用 AES-GCM 解密数据
return AesGcmEncryption.Decrypt(
CombineNonceTagAndCiphertext(nonce, encryptedData), aesKey);
}
private static byte[] CombineNonceTagAndCiphertext(byte[] nonce, byte[] encryptedData)
{
var result = new byte[nonce.Length + encryptedData.Length];
Buffer.BlockCopy(nonce, 0, result, 0, nonce.Length);
Buffer.BlockCopy(encryptedData, 0, result, nonce.Length, encryptedData.Length);
return result;
}
}ECDSA 椭圆曲线数字签名
/// <summary>
/// ECDSA 椭圆曲线数字签名
/// 比 RSA 更短的密钥和签名,性能更好
/// </summary>
public static class ECDsaSigning
{
/// <summary>
/// 生成 ECDSA 密钥对
/// </summary>
public static (string PublicKey, string PrivateKey) GenerateKeyPair()
{
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
string publicKey = ecdsa.ExportSubjectPublicKeyInfoPem();
string privateKey = ecdsa.ExportECPrivateKeyPem();
return (publicKey, privateKey);
}
/// <summary>
/// 签名数据
/// </summary>
public static byte[] SignData(byte[] data, string privateKeyPem)
{
using var ecdsa = ECDsa.Create();
ecdsa.ImportFromPem(privateKeyPem);
return ecdsa.SignData(data, HashAlgorithmName.SHA256);
}
/// <summary>
/// 验证签名
/// </summary>
public static bool VerifyData(byte[] data, byte[] signature, string publicKeyPem)
{
using var ecdsa = ECDsa.Create();
ecdsa.ImportFromPem(publicKeyPem);
return ecdsa.VerifyData(data, signature, HashAlgorithmName.SHA256);
}
/// <summary>
/// JWT Token 签名示例
/// </summary>
public static string CreateSignedToken(Dictionary<string, object> claims, string privateKeyPem)
{
using var ecdsa = ECDsa.Create();
ecdsa.ImportFromPem(privateKeyPem);
// 构造 JWT Header
string header = Base64UrlEncode(
System.Text.Json.JsonSerializer.Serialize(new { alg = "ES256", typ = "JWT" }));
// 构造 JWT Payload
long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var payload = new Dictionary<string, object>(claims)
{
["iat"] = now,
["exp"] = now + 3600 // 1小时过期
};
string payloadEncoded = Base64UrlEncode(
System.Text.Json.JsonSerializer.Serialize(payload));
// 签名
string signingInput = $"{header}.{payloadEncoded}";
byte[] signingBytes = System.Text.Encoding.UTF8.GetBytes(signingInput);
byte[] signature = ecdsa.SignData(signingBytes, HashAlgorithmName.SHA256);
string signatureEncoded = Base64UrlEncode(signature);
return $"{header}.{payloadEncoded}.{signatureEncoded}";
}
private static string Base64UrlEncode(string text)
{
return Base64UrlEncode(System.Text.Encoding.UTF8.GetBytes(text));
}
private static string Base64UrlEncode(byte[] data)
{
return Convert.ToBase64String(data)
.TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');
}
}数字签名
/// <summary>
/// 数字签名完整示例
/// 包含签名、验证、签名数据格式
/// </summary>
public class DigitalSignature
{
/// <summary>
/// 对文件进行签名
/// </summary>
public static byte[] SignFile(string filePath, string privateKeyPem)
{
using var ecdsa = ECDsa.Create();
ecdsa.ImportFromPem(privateKeyPem);
byte[] fileContent = File.ReadAllBytes(filePath);
return ecdsa.SignData(fileContent, HashAlgorithmName.SHA256);
}
/// <summary>
/// 验证文件签名
/// </summary>
public static bool VerifyFileSignature(
string filePath, byte[] signature, string publicKeyPem)
{
using var ecdsa = ECDsa.Create();
ecdsa.ImportFromPem(publicKeyPem);
byte[] fileContent = File.ReadAllBytes(filePath);
return ecdsa.VerifyData(fileContent, signature, HashAlgorithmName.SHA256);
}
/// <summary>
/// 签名并封装数据
/// </summary>
public static SignedData SignAndPackage(byte[] data, string privateKeyPem)
{
using var ecdsa = ECDsa.Create();
ecdsa.ImportFromPem(privateKeyPem);
byte[] signature = ecdsa.SignData(data, HashAlgorithmName.SHA256);
return new SignedData
{
Data = data,
Signature = signature,
Algorithm = "ECDSA-SHA256",
Timestamp = DateTime.UtcNow
};
}
}
public class SignedData
{
public byte[] Data { get; set; }
public byte[] Signature { get; set; }
public string Algorithm { get; set; }
public DateTime Timestamp { get; set; }
}证书与 PKI
X509 证书操作
using System.Security.Cryptography.X509Certificates;
/// <summary>
/// X509 证书管理工具
/// </summary>
public static class CertificateManager
{
/// <summary>
/// 创建自签名证书(仅用于开发/测试)
/// </summary>
public static X509Certificate2 CreateSelfSignedCertificate(
string subjectName,
int validityYears = 1,
int keySize = 2048)
{
using var rsa = RSA.Create(keySize);
var request = new CertificateRequest(
$"CN={subjectName}",
rsa,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
// 添加基本约束
request.CertificateExtensions.Add(
new X509BasicConstraintsExtension(
certificateAuthority: false,
hasPathLengthConstraint: false,
pathLengthConstraint: 0,
critical: true));
// 添加密钥用途
request.CertificateExtensions.Add(
new X509KeyUsageExtension(
X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment,
critical: true));
// 添加增强密钥用途
request.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection
{
new Oid("1.3.6.1.5.5.7.3.1"), // Server Authentication
new Oid("1.3.6.1.5.5.7.3.2") // Client Authentication
},
critical: false));
// 添加主题备用名称
var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddDnsName("localhost");
sanBuilder.AddDnsName($"{subjectName}.local");
sanBuilder.AddIpAddress(System.Net.IPAddress.Loopback);
request.CertificateExtensions.Add(sanBuilder.Build());
// 创建证书
var notBefore = DateTimeOffset.UtcNow;
var notAfter = notBefore.AddYears(validityYears);
return request.CreateSelfSigned(notBefore, notAfter);
}
/// <summary>
/// 从证书存储区加载证书
/// </summary>
public static X509Certificate2 LoadFromStore(
string subjectName,
StoreName storeName = StoreName.My,
StoreLocation storeLocation = StoreLocation.LocalMachine)
{
using var store = new X509Store(storeName, storeLocation);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(
X509FindType.FindBySubjectName,
subjectName,
validOnly: true);
if (certificates.Count == 0)
throw new InvalidOperationException($"未找到证书: {subjectName}");
return certificates[0];
}
/// <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 bool ValidateCertificateChain(X509Certificate2 certificate)
{
using var chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
bool isValid = chain.Build(certificate);
if (!isValid)
{
foreach (var status in chain.ChainStatus)
{
Console.WriteLine($"证书链验证失败: {status.StatusInformation}");
}
}
return isValid;
}
}使用证书进行加密和签名
/// <summary>
/// 基于证书的加密签名服务
/// </summary>
public class CertificateBasedSecurity
{
/// <summary>
/// 使用证书中的公钥加密
/// </summary>
public static byte[] EncryptWithCertificate(
byte[] data, X509Certificate2 certificate)
{
using var rsa = certificate.GetRSAPublicKey();
if (rsa == null)
throw new InvalidOperationException("证书不包含 RSA 公钥");
return rsa.Encrypt(data, RSAEncryptionPadding.OaepSHA256);
}
/// <summary>
/// 使用证书中的私钥解密
/// </summary>
public static byte[] DecryptWithCertificate(
byte[] encryptedData, X509Certificate2 certificate)
{
using var rsa = certificate.GetRSAPrivateKey();
if (rsa == null)
throw new InvalidOperationException("证书不包含 RSA 私钥");
return rsa.Decrypt(encryptedData, RSAEncryptionPadding.OaepSHA256);
}
/// <summary>
/// 使用证书签名
/// </summary>
public static byte[] SignWithCertificate(
byte[] data, X509Certificate2 certificate)
{
using var rsa = certificate.GetRSAPrivateKey();
if (rsa == null)
throw new InvalidOperationException("证书不包含 RSA 私钥");
return rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
}
/// <summary>
/// 使用证书验证签名
/// </summary>
public static bool VerifyWithCertificate(
byte[] data, byte[] signature, X509Certificate2 certificate)
{
using var rsa = certificate.GetRSAPublicKey();
if (rsa == null)
throw new InvalidOperationException("证书不包含 RSA 公钥");
return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
}
}哈希算法
/// <summary>
/// 哈希算法工具
/// </summary>
public static class HashUtilities
{
/// <summary>
/// SHA-256 哈希
/// </summary>
public static byte[] SHA256Hash(byte[] data)
{
return SHA256.HashData(data);
}
/// <summary>
/// SHA-256 哈希(字符串输出)
/// </summary>
public static string SHA256HashString(string text)
{
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(text);
byte[] hash = SHA256.HashData(bytes);
return Convert.ToHexString(hash).ToLowerInvariant();
}
/// <summary>
/// 文件哈希(流式处理大文件)
/// </summary>
public static string ComputeFileHash(string filePath)
{
using var stream = File.OpenRead(filePath);
byte[] hash = SHA256.HashData(stream);
return Convert.ToHexString(hash).ToLowerInvariant();
}
/// <summary>
/// HMAC-SHA256 消息认证码
/// </summary>
public static byte[] ComputeHmacSha256(byte[] data, byte[] key)
{
using var hmac = new HMACSHA256(key);
return hmac.ComputeHash(data);
}
/// <summary>
/// 密码哈希(使用 PBKDF2)
/// 注意:.NET 6+ 推荐使用 Rfc2898DeriveBytes.Pbkdf2
/// </summary>
public static string HashPassword(string password)
{
byte[] salt = RandomNumberGenerator.GetBytes(16);
int iterations = 600000; // OWASP 推荐的最小迭代次数
byte[] hash = Rfc2898DeriveBytes.Pbkdf2(
System.Text.Encoding.UTF8.GetBytes(password),
salt,
iterations,
HashAlgorithmName.SHA256,
outputLength: 32);
// 格式:iterations$salt$hash
return $"{iterations}${Convert.ToBase64String(salt)}${Convert.ToBase64String(hash)}";
}
/// <summary>
/// 验证密码
/// </summary>
public static bool VerifyPassword(string password, string storedHash)
{
string[] parts = storedHash.Split('$');
if (parts.Length != 3)
return false;
int iterations = int.Parse(parts[0]);
byte[] salt = Convert.FromBase64String(parts[1]);
byte[] storedPasswordHash = Convert.FromBase64String(parts[2]);
byte[] computedHash = Rfc2898DeriveBytes.Pbkdf2(
System.Text.Encoding.UTF8.GetBytes(password),
salt,
iterations,
HashAlgorithmName.SHA256,
outputLength: 32);
// 使用恒定时间比较,防止时序攻击
return CryptographicOperations.FixedTimeEquals(storedPasswordHash, computedHash);
}
/// <summary>
/// BCrypt 密码哈希(使用外部库 BCrypt.Net-Next)
/// </summary>
public static string HashPasswordBCrypt(string password)
{
// 需要安装 BCrypt.Net-Next NuGet 包
// return BCrypt.Net.BCrypt.EnhancedHashPassword(password, 12);
throw new NotImplementedException("需要安装 BCrypt.Net-Next 包");
}
}安全随机数生成
/// <summary>
/// 安全随机数生成器
/// 永远不要使用 System.Random 生成密码学相关的随机数!
/// </summary>
public static class SecureRandom
{
/// <summary>
/// 生成安全的随机字节序列
/// </summary>
public static byte[] GenerateBytes(int length)
{
return RandomNumberGenerator.GetBytes(length);
}
/// <summary>
/// 生成安全的随机整数
/// </summary>
public static int GenerateInt32(int min, int max)
{
return RandomNumberGenerator.GetInt32(min, max);
}
/// <summary>
/// 生成安全的随机字符串
/// </summary>
public static string GenerateString(int length, string allowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
{
var result = new char[length];
for (int i = 0; i < length; i++)
{
result[i] = allowedChars[RandomNumberGenerator.GetInt32(allowedChars.Length)];
}
return new string(result);
}
/// <summary>
/// 生成 API Key
/// </summary>
public static string GenerateApiKey(int length = 32)
{
byte[] bytes = RandomNumberGenerator.GetBytes(length);
return Convert.ToBase64String(bytes)
.Replace("+", "")
.Replace("/", "")
.Replace("=", "")
.Substring(0, length);
}
/// <summary>
/// 生成 TOTP 密钥
/// </summary>
public static byte[] GenerateTotpSecret()
{
return RandomNumberGenerator.GetBytes(20); // 160 位
}
}TLS 配置
/// <summary>
/// TLS 安全配置
/// </summary>
public static class TlsConfiguration
{
/// <summary>
/// 配置 HttpClient 使用安全 TLS 设置
/// </summary>
public static HttpClient CreateSecureHttpClient(
X509Certificate2? clientCertificate = null)
{
var handler = new SocketsHttpHandler
{
// 强制 TLS 1.2 或更高版本
SslOptions = new System.Net.Security.SslClientAuthenticationOptions
{
EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls13,
// 证书验证回调
RemoteCertificateValidationCallback = (sender, certificate, chain, errors) =>
{
// 生产环境应严格验证证书
return errors == System.Net.Security.SslPolicyErrors.None;
},
// 客户端证书
ClientCertificates = clientCertificate != null
? new X509CertificateCollection { clientCertificate }
: new X509CertificateCollection()
},
// 连接超时
ConnectTimeout = TimeSpan.FromSeconds(10),
// 响应超时
PooledConnectionLifetime = TimeSpan.FromMinutes(5)
};
return new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(30)
};
}
/// <summary>
/// ASP.NET Core 的 TLS 配置
/// </summary>
public static void ConfigureKestrelTls(WebApplicationBuilder builder, string certPath, string certPassword)
{
builder.WebHost.ConfigureKestrel(options =>
{
options.ConfigureHttpsDefaults(https =>
{
https.ServerCertificate = new X509Certificate2(certPath, certPassword);
https.SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls13;
// 强制客户端证书(双向 TLS)
// https.ClientCertificateMode = Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode.RequireCertificate;
});
});
}
}Azure Key Vault 集成
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Azure.Security.KeyVault.Keys.Cryptography;
/// <summary>
/// Azure Key Vault 集成
/// </summary>
public class KeyVaultService
{
private readonly SecretClient _secretClient;
private readonly CryptographyClient _cryptoClient;
public KeyVaultService(string keyVaultUrl, string tenantId, string clientId, string clientSecret)
{
var credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
_secretClient = new SecretClient(new Uri(keyVaultUrl), credential);
}
/// <summary>
/// 存储密钥
/// </summary>
public async Task SetSecretAsync(string secretName, string secretValue)
{
await _secretClient.SetSecretAsync(secretName, secretValue);
}
/// <summary>
/// 获取密钥
/// </summary>
public async Task<string> GetSecretAsync(string secretName)
{
KeyVaultSecret secret = await _secretClient.GetSecretAsync(secretName);
return secret.Value;
}
/// <summary>
/// 使用 Key Vault 进行加密
/// </summary>
public async Task<byte[]> EncryptAsync(string keyName, byte[] plaintext)
{
var cryptoClient = _cryptoClient;
EncryptResult result = await cryptoClient.EncryptAsync(
EncryptionAlgorithm.RsaOaep256, plaintext);
return result.Ciphertext;
}
/// <summary>
/// 使用 Key Vault 进行解密
/// </summary>
public async Task<byte[]> DecryptAsync(string keyName, byte[] ciphertext)
{
var cryptoClient = _cryptoClient;
DecryptResult result = await cryptoClient.DecryptAsync(
EncryptionAlgorithm.RsaOaep256, ciphertext);
return result.Plaintext;
}
}
/// <summary>
/// 使用 Azure Key Vault 的配置提供程序
/// </summary>
public static class KeyVaultConfigurationExtensions
{
public static WebApplicationBuilder AddAzureKeyVault(
this WebApplicationBuilder builder,
string keyVaultUrl)
{
// 使用 DefaultAzureCredential(支持 Managed Identity)
var credential = new DefaultAzureCredential();
builder.Configuration.AddAzureKeyVault(
new Uri(keyVaultUrl),
credential);
return builder;
}
}常见陷阱与最佳实践
常见错误示例
// 错误1:使用 ECB 模式
using var aesBad = Aes.Create();
aesBad.Mode = CipherMode.ECB; // 危险!ECB 模式不安全
// 错误2:硬编码密钥
byte[] hardCodedKey = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
// 永远不要这样做!
// 错误3:重用 IV/Nonce
byte[] reusedNonce = new byte[12]; // 全零 nonce,每次加密都相同
// AES-GCM 重用 nonce 会导致密钥泄露!
// 错误4:使用普通比较验证哈希
bool badCompare = storedHash == computedHash; // 时序攻击!
// 正确做法:
bool safeCompare = CryptographicOperations.FixedTimeEquals(
Convert.FromBase64String(storedHash), computedHash);
// 错误5:使用 MD5 或 SHA1
byte[] md5Hash = MD5.HashData(data); // 已不安全
byte[] sha1Hash = SHA1.HashData(data); // 已不安全
// 使用 SHA256 或更高密钥管理最佳实践
/// <summary>
/// 密钥轮换示例
/// </summary>
public class KeyRotation
{
private readonly Dictionary<string, List<KeyVersion>> _keyVersions = new();
public void RotateKey(string keyId, byte[] newKeyMaterial)
{
if (!_keyVersions.ContainsKey(keyId))
_keyVersions[keyId] = new List<KeyVersion>();
// 废弃旧密钥
foreach (var version in _keyVersions[keyId])
{
version.IsActive = false;
}
// 添加新密钥版本
_keyVersions[keyId].Add(new KeyVersion
{
Version = Guid.NewGuid().ToString(),
Key = newKeyMaterial,
CreatedAt = DateTime.UtcNow,
IsActive = true
});
}
public byte[] GetCurrentKey(string keyId)
{
return _keyVersions[keyId]
.First(v => v.IsActive)
.Key;
}
}
public class KeyVersion
{
public string Version { get; set; }
public byte[] Key { get; set; }
public DateTime CreatedAt { get; set; }
public bool IsActive { get; set; }
}总结
.NET 提供了全面而安全的密码学 API。正确使用这些 API 需要理解密码学基本原则:使用经过验证的算法和模式、安全地管理密钥、避免常见的实现陷阱。
关键知识点
- AES-GCM 是推荐的对称加密方案(AEAD)
- RSA 用于加密少量数据(密钥交换),大数据用混合加密
- ECDSA 比 RSA 更高效,适合签名场景
- PBKDF2 用于密码哈希,迭代次数至少 600000
- 使用
RandomNumberGenerator而非System.Random - 使用
CryptographicOperations.FixedTimeEquals防止时序攻击
常见误区
误区1:加密就是安全
加密只是安全的一部分。密钥管理、协议设计、实现正确性同样重要。
误区2:自己设计加密算法
永远不要自己设计加密算法或协议。使用经过同行评审的标准算法和库。
误区3:AES-CBC + HMAC 等同于 AES-GCM
AES-GCM 是经过严格分析的模式。手动组合 CBC + HMAC 容易出错(如先解密后验证)。
进阶路线
- 密码协议:TLS 1.3 协议细节
- 零知识证明:ZKP 在身份验证中的应用
- 同态加密:在加密数据上直接计算
- 后量子密码学:为量子计算时代做准备
- 硬件安全模块(HSM):密钥的硬件保护
适用场景
- 用户密码存储和验证
- API 认证(JWT 签名)
- 数据加密存储(敏感字段)
- 通信加密(TLS/mTLS)
- 数字签名和验证
- 密钥管理(Azure Key Vault)
落地建议
- 建立统一的加密工具库,避免各项目重复实现
- 使用 Azure Key Vault 管理生产密钥
- 定期轮换加密密钥
- 所有密钥通过配置或密钥管理服务获取,不要硬编码
- 在代码审查中专门检查密码学使用是否正确
排错清单
复盘问题
- 你的应用中哪些数据需要加密?使用了什么算法?
- 密钥是如何存储和管理的?
- 上次密钥轮换是什么时候?
- 是否有定期进行密码学审计?
- 如何应对算法被破解的风险?
延伸阅读
- .NET Cryptography Model
- OWASP Cryptographic Storage Cheat Sheet
- Azure Key Vault Documentation
- 《Applied Cryptography》- Bruce Schneier
- 《Real-World Cryptography》- David Wong
