名称:dotnet-cryptography 描述:选择加密算法和使用。哈希、AES-GCM、RSA、ECDSA、后量子密钥派生。 用户可调用:false
dotnet-cryptography
现代 .NET 加密技术,涵盖哈希(SHA-256/384/512)、对称加密(AES-GCM)、非对称加密(RSA、ECDSA)、密钥派生(PBKDF2、Argon2)和后量子算法(ML-KEM、ML-DSA、SLH-DSA),适用于 .NET 10+。包括 TFM 感知指导:在 net10.0 上的可用性与 net8.0/net9.0 的回退策略。
范围
- 算法选择和 System.Security.Cryptography API 的正确使用
- 完整性哈希(SHA-256/384/512)
- 对称加密(AES-GCM)
- 非对称加密(RSA、ECDSA)
- 密钥派生(PBKDF2、Argon2)
- 后量子加密(ML-KEM、ML-DSA、SLH-DSA),适用于 .NET 10+
- 废弃算法警告
不在范围内
- 密钥管理和配置绑定——参见 [技能:dotnet-secrets-management]
- OWASP 漏洞类别和废弃安全模式——参见 [技能:dotnet-security-owasp]
- 身份验证/授权实现(JWT、OAuth、Identity)——参见 [技能:dotnet-api-security] 和 [技能:dotnet-blazor-auth]
- 云特定密钥管理(Azure Key Vault、AWS KMS)——参见 [技能:dotnet-advisor]
- TLS/HTTPS 配置——参见 [技能:dotnet-advisor]
交叉引用:关于 OWASP A02(加密失败)和废弃模式警告,请参见 [技能:dotnet-security-owasp];关于安全存储密钥和机密,请参见 [技能:dotnet-secrets-management]。
前提条件
- .NET 8.0+(经典算法的 LTS 基线)
- .NET 10.0+ 用于后量子算法(ML-KEM、ML-DSA、SLH-DSA)
- 平台对 PQC 的支持:Windows 11(2025 年 11 月+)或 Linux/macOS 上的 OpenSSL 3.5+
哈希(SHA-2 系列)
使用 SHA-256/384/512 进行完整性验证、校验和和内容寻址存储。切勿单独使用哈希处理密码(参见下方的密钥派生)。
using System.Security.Cryptography;
// 哈希字节数组
byte[] data = "Hello, world"u8.ToArray();
byte[] hash = SHA256.HashData(data);
// 哈希流(对大文件高效)
await using var stream = File.OpenRead("largefile.bin");
byte[] fileHash = await SHA256.HashDataAsync(stream);
// 安全比较哈希(恒定时间比较防止时序攻击)
bool isEqual = CryptographicOperations.FixedTimeEquals(hash1, hash2);
// HMAC 用于认证哈希(消息认证码)
byte[] key = RandomNumberGenerator.GetBytes(32); // 256 位密钥
byte[] mac = HMACSHA256.HashData(key, data);
// 验证 HMAC
byte[] computedMac = HMACSHA256.HashData(key, receivedData);
if (!CryptographicOperations.FixedTimeEquals(mac, computedMac))
{
throw new CryptographicException("消息认证失败");
}
对称加密(AES-GCM)
AES-GCM 是推荐的 .NET 对称加密。它提供机密性和认证性(带关联数据的认证加密——AEAD)。
using System.Security.Cryptography;
public static class AesGcmEncryptor
{
private const int NonceSize = 12; // 96 位 nonce(GCM 要求)
private const int TagSize = 16; // 128 位认证标签
public static byte[] Encrypt(byte[] plaintext, byte[] key)
{
var nonce = RandomNumberGenerator.GetBytes(NonceSize);
var ciphertext = new byte[plaintext.Length];
var tag = new byte[TagSize];
using var aes = new AesGcm(key, TagSize);
aes.Encrypt(nonce, plaintext, ciphertext, tag);
// 前置 nonce + 后置标签以传输
var result = new byte[NonceSize + ciphertext.Length + TagSize];
nonce.CopyTo(result, 0);
ciphertext.CopyTo(result, NonceSize);
tag.CopyTo(result, NonceSize + ciphertext.Length);
return result;
}
public static byte[] Decrypt(byte[] encryptedData, byte[] key)
{
var nonce = encryptedData.AsSpan(0, NonceSize);
var ciphertext = encryptedData.AsSpan(NonceSize, encryptedData.Length - NonceSize - TagSize);
var tag = encryptedData.AsSpan(encryptedData.Length - TagSize);
var plaintext = new byte[ciphertext.Length];
using var aes = new AesGcm(key, TagSize);
aes.Decrypt(nonce, ciphertext, tag, plaintext);
return plaintext;
}
}
// ASP.NET Core 数据保护 API——推荐用于 Web 应用场景
// 自动处理密钥管理、轮换和存储
using Microsoft.AspNetCore.DataProtection;
public sealed class TokenProtector(IDataProtectionProvider provider)
{
private readonly IDataProtector _protector =
provider.CreateProtector("Tokens.V1");
public string Protect(string plaintext) => _protector.Protect(plaintext);
public string Unprotect(string ciphertext) => _protector.Unprotect(ciphertext);
}
// 注册:
builder.Services.AddDataProtection()
.SetApplicationName("MyApp")
.PersistKeysToFileSystem(new DirectoryInfo("/keys"));
非对称加密(RSA、ECDSA)
RSA
使用 RSA 进行小负载加密(密钥包装)和数字签名。最小 2048 位密钥;新系统优先使用 4096 位。
using System.Security.Cryptography;
// 生成 RSA 密钥对
using var rsa = RSA.Create(4096);
// 签名数据
byte[] signature = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
// 验证签名(使用公钥)
byte[] publicKeyBytes = rsa.ExportRSAPublicKey();
using var rsaPublic = RSA.Create();
rsaPublic.ImportRSAPublicKey(publicKeyBytes, out _);
bool valid = rsaPublic.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
// 使用 OAEP 填充加密(新代码切勿使用 PKCS#1 v1.5)
byte[] encrypted = rsaPublic.Encrypt(smallPayload, RSAEncryptionPadding.OaepSHA256);
byte[] decrypted = rsa.Decrypt(encrypted, RSAEncryptionPadding.OaepSHA256);
ECDSA
在新项目中,优先使用 ECDSA 而非 RSA 进行数字签名——密钥更小,安全性相当。
using System.Security.Cryptography;
// 生成 ECDSA 密钥(P-256 = NIST 曲线,广泛支持)
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
// 签名数据
byte[] signature = ecdsa.SignData(data, HashAlgorithmName.SHA256);
// 导出公钥以验证
byte[] publicKey = ecdsa.ExportSubjectPublicKeyInfo();
// 导入并验证
using var ecdsaPublic = ECDsa.Create();
ecdsaPublic.ImportSubjectPublicKeyInfo(publicKey, out _);
bool valid = ecdsaPublic.VerifyData(data, signature, HashAlgorithmName.SHA256);
密钥派生(密码哈希)
PBKDF2(内置)
PBKDF2 内置在 .NET 中,适用于密码哈希。使用至少 600,000 次迭代与 SHA-256(OWASP 推荐)。
using System.Buffers.Binary;
using System.Security.Cryptography;
public static class PasswordHasher
{
private const int SaltSize = 16; // 128 位盐
private const int HashSize = 32; // 256 位派生密钥
private const int Iterations = 600_000; // OWASP 2023 对 SHA-256 的推荐
private const int PayloadSize = 4 + SaltSize + HashSize; // 迭代计数 + 盐 + 哈希
public static string HashPassword(string password)
{
byte[] salt = RandomNumberGenerator.GetBytes(SaltSize);
byte[] hash = Rfc2898DeriveBytes.Pbkdf2(
password,
salt,
Iterations,
HashAlgorithmName.SHA256,
HashSize);
// 存储迭代计数(固定小端序)、盐和哈希在一起
byte[] result = new byte[PayloadSize];
BinaryPrimitives.WriteInt32LittleEndian(result, Iterations);
salt.CopyTo(result.AsSpan(4));
hash.CopyTo(result.AsSpan(4 + SaltSize));
return Convert.ToBase64String(result);
}
public static bool VerifyPassword(string password, string stored)
{
// 防御性解析:拒绝格式错误的输入而不抛出异常
Span<byte> decoded = stackalloc byte[PayloadSize];
if (!Convert.TryFromBase64String(stored, decoded, out int bytesWritten)
|| bytesWritten != PayloadSize)
{
return false;
}
int iterations = BinaryPrimitives.ReadInt32LittleEndian(decoded);
if (iterations <= 0)
return false;
var salt = decoded.Slice(4, SaltSize);
var expectedHash = decoded.Slice(4 + SaltSize, HashSize);
byte[] actualHash = Rfc2898DeriveBytes.Pbkdf2(
password,
salt,
iterations,
HashAlgorithmName.SHA256,
HashSize);
return CryptographicOperations.FixedTimeEquals(expectedHash, actualHash);
}
}
Argon2(通过 NuGet)
当可接受 NuGet 依赖时,Argon2id 是推荐的密码哈希算法。它是内存硬化的,比 PBKDF2 更好地抵抗 GPU/ASIC 攻击。
// 需要:<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.*" />
using Konscious.Security.Cryptography;
public static byte[] HashWithArgon2(string password, byte[] salt)
{
using var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password))
{
Salt = salt,
DegreeOfParallelism = 4, // 线程
MemorySize = 65536, // 64 MB
Iterations = 3
};
return argon2.GetBytes(32); // 256 位哈希
}
对于 Web 应用,优先使用 ASP.NET Core Identity 的
PasswordHasher<T>——它自动处理带正确参数和格式版本的 PBKDF2。仅在非 Identity 场景中使用自定义哈希。
后量子加密(.NET 10+)
.NET 10 通过 System.Security.Cryptography 命名空间引入后量子加密(PQC)。这些算法抵抗经典和量子计算机的攻击。
平台要求
PQC API 需要操作系统级支持:
- Windows: Windows 11(2025 年 11 月更新)或带 PQC 更新的 Windows Server 2025
- Linux/macOS: OpenSSL 3.5 或更新版本
在使用 PQC 类型前,始终检查 IsSupported。在不支持的平台上,回退到经典算法。
ML-KEM(FIPS 203)——密钥封装
ML-KEM 替换经典密钥交换(ECDH)以建立共享秘密。它是最成熟的 .NET 10 PQC API(在类级别未标记 [Experimental])。
#if NET10_0_OR_GREATER
using System.Security.Cryptography;
if (!MLKem.IsSupported)
{
Console.WriteLine("此平台上 ML-KEM 不可用");
return;
}
// 生成密钥对
using MLKem privateKey = MLKem.GenerateKey(MLKemAlgorithm.MLKem768);
// 导出公封装密钥(与对等方共享)
byte[] publicKeyBytes = privateKey.ExportEncapsulationKey();
// 对等方:导入公钥并封装共享秘密
using MLKem publicKey = MLKem.ImportEncapsulationKey(
MLKemAlgorithm.MLKem768, publicKeyBytes);
publicKey.Encapsulate(out byte[] ciphertext, out byte[] sharedSecret1);
// 原始持有者:解封装以恢复相同的共享秘密
byte[] sharedSecret2 = privateKey.Decapsulate(ciphertext);
// 双方现在拥有相同的共享秘密用于对称加密
bool match = sharedSecret1.AsSpan().SequenceEqual(sharedSecret2);
#endif
参数集:
| 参数集 | 安全等级 | 封装密钥 | 密文 |
|---|---|---|---|
MLKemAlgorithm.MLKem512 |
NIST 级别 1(128 位) | 800 字节 | 768 字节 |
MLKemAlgorithm.MLKem768 |
NIST 级别 3(192 位) | 1,184 字节 | 1,088 字节 |
MLKemAlgorithm.MLKem1024 |
NIST 级别 5(256 位) | 1,568 字节 | 1,568 字节 |
一般使用优先选择 MLKem768(平衡安全性和性能)。
ML-DSA(FIPS 204)——数字签名
ML-DSA 替换 RSA/ECDSA 进行抗量子数字签名。
#if NET10_0_OR_GREATER
using System.Security.Cryptography;
if (!MLDsa.IsSupported)
{
Console.WriteLine("此平台上 ML-DSA 不可用");
return;
}
// 生成签名密钥
using MLDsa key = MLDsa.GenerateKey(MLDsaAlgorithm.MLDsa65);
// 签名数据
byte[] data = "待签名文档"u8.ToArray();
byte[] signature = new byte[key.Algorithm.SignatureSizeInBytes];
key.SignData(data, signature);
// 导出公钥以验证
byte[] publicKeyBytes = key.ExportMLDsaPublicKey();
// 使用公钥验证
using MLDsa publicKey = MLDsa.ImportMLDsaPublicKey(
MLDsaAlgorithm.MLDsa65, publicKeyBytes);
bool valid = publicKey.VerifyData(data, signature);
#endif
参数集:
| 参数集 | 安全等级 | 公钥 | 签名 |
|---|---|---|---|
MLDsaAlgorithm.MLDsa44 |
NIST 级别 2 | 1,312 字节 | 2,420 字节 |
MLDsaAlgorithm.MLDsa65 |
NIST 级别 3 | 1,952 字节 | 3,309 字节 |
MLDsaAlgorithm.MLDsa87 |
NIST 级别 5 | 2,592 字节 | 4,627 字节 |
SLH-DSA(FIPS 205)——基于哈希的签名
SLH-DSA(无状态基于哈希的数字签名算法)提供极其保守的长期签名。当基于格方案(ML-DSA)的数学结构令人担忧时使用。整个 SlhDsa 类是 [Experimental](SYSLIB5006)——Windows 尚未添加本地支持。
#if NET10_0_OR_GREATER
using System.Security.Cryptography;
// SlhDsa 是 [Experimental]——仅在有意时抑制 SYSLIB5006
#pragma warning disable SYSLIB5006
if (SlhDsa.IsSupported)
{
using SlhDsa key = SlhDsa.GenerateKey(SlhDsaAlgorithm.SlhDsaSha2_128s);
byte[] data = "长期文档"u8.ToArray();
byte[] signature = new byte[key.Algorithm.SignatureSizeInBytes];
key.SignData(data, signature);
bool valid = key.VerifyData(data, signature);
}
#pragma warning restore SYSLIB5006
#endif
net8.0/net9.0 的回退策略
后量子算法仅适用于 .NET 10+。对于针对早期 TFM 的应用:
- 现在使用经典算法: 签名使用 ECDSA(P-256/P-384),密钥交换/加密使用 ECDH + AES-GCM。这些算法仍对经典攻击安全。
- 准备迁移: 通过接口隔离加密操作,以便算法交换需要最少的代码更改。
- 准备好时多目标: 使用
#if NET10_0_OR_GREATER条件或按 TFM 的单独程序集,以添加 PQC 支持与经典回退。 - 现在捕获-以后解密: 对于必须保持机密性超过 10 年的数据,考虑更早迁移到 .NET 10,以防止未来量子解密捕获的密文。
互操作性注意事项
- 密钥和签名大小: PQC 密钥和签名比经典对应物大得多(例如,ML-DSA-65 签名为 3,309 字节,而 ECDSA P-256 为 64 字节)。这影响存储、带宽和协议消息大小。
- 尚无跨平台 PQC: PQC API 依赖于操作系统加密库。为 net10.0 编译的应用将在较旧的操作系统版本上运行时失败。始终通过
IsSupported控制。 - PKCS#8/X.509 格式是实验性的: 在标准证书格式中导入/导出 PQC 密钥是
[Experimental],等待 IETF RFC 最终确定。在生产中尚勿以 PKCS#8 格式持久化 PQC 密钥。 - 复合/混合签名:
CompositeMLDsa(混合 ML-DSA + 经典)完全[Experimental],没有本地操作系统支持。仅用于原型设计。 - TLS 集成: ML-DSA 和 SLH-DSA 证书在 TLS 1.3+ 中通过
SslStream工作,但仅当操作系统加密库在 TLS 中支持 PQC 时。请验证您的部署目标。 - 性能: ML-KEM 和 ML-DSA 快速。SLH-DSA 签名明显较慢(秒级,而非毫秒级)——仅在需要基于哈希的安全保证时使用。
废弃的加密 API
以下加密算法已损坏或过时。新代码中切勿使用。
| 算法 | 替代品 | 原因 |
|---|---|---|
| MD5 | SHA-256+ | 自 2004 年起碰撞攻击;易破解 |
| SHA-1 | SHA-256+ | 碰撞攻击已证明(SHAttered, 2017) |
| DES | AES-GCM | 56 位密钥;几小时内可暴力破解 |
| 3DES(TripleDES) | AES-GCM | NIST 废弃(2023);Sweet32 攻击 |
| RC2 | AES-GCM | 弱密钥调度;有效密钥长度低于广告 |
| RSA PKCS#1 v1.5 加密 | RSA-OAEP | Bleichenbacher 填充预言攻击 |
有关加密之外的废弃安全模式完整列表(CAS、APTCA、.NET Remoting、DCOM、BinaryFormatter),请参见 [技能:dotnet-security-owasp],这是废弃安全模式警告的规范所有者。
代理注意事项
- 切勿在 AES-GCM 中重用 nonce——使用相同密钥重用 nonce 会破坏机密性和认证性。每个加密操作始终生成新的随机 nonce。
- 切勿使用 ECB 模式——ECB 将相同的明文块加密为相同的密文块,泄露模式。.NET 的
Aes.Create()默认使用 CBC,但优先使用 AES-GCM 进行认证加密。 - 切勿使用
==比较哈希——使用CryptographicOperations.FixedTimeEquals防止时序侧信道攻击。 - 切勿将 MD5 或 SHA-1 用于安全目的——它们已损坏。SHA-1 仅在非安全检查(如 git 对象哈希)中可接受,其中碰撞抵抗不是安全要求。
- 切勿硬编码加密密钥——使用 [技能:dotnet-secrets-management] 存储密钥。使用
RandomNumberGenerator.GetBytes生成密钥。 - 最小 RSA 密钥大小为 2048 位——NIST 已废弃 1024 位 RSA 密钥。新系统使用 4096。
- PBKDF2 迭代计数必须高——OWASP 推荐 600,000 次迭代与 SHA-256(截至 2023)。较低计数可暴力破解。
- PQC
IsSupported检查是强制的——在不支持的平台上调用 PQC API 会抛出PlatformNotSupportedException。始终在使用前检查。 - 不要全局抑制 SYSLIB5006——仅在有意使用实验性 PQC API 的具体调用点抑制实验性诊断。