.NET加密技术 dotnet-cryptography

该技能提供 .NET 平台的现代加密技术指南,涵盖哈希算法(如 SHA-256)、对称加密(AES-GCM)、非对称加密(RSA、ECDSA)、密钥派生(PBKDF2、Argon2)以及后量子密码学(ML-KEM、ML-DSA、SLH-DSA),适用于 .NET 10+,并包括 TFM 感知指导、废弃算法警告和最佳实践,以确保应用安全。

密码学 0 次安装 0 次浏览 更新于 3/6/2026

名称: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 的应用:

  1. 现在使用经典算法: 签名使用 ECDSA(P-256/P-384),密钥交换/加密使用 ECDH + AES-GCM。这些算法仍对经典攻击安全。
  2. 准备迁移: 通过接口隔离加密操作,以便算法交换需要最少的代码更改。
  3. 准备好时多目标: 使用 #if NET10_0_OR_GREATER 条件或按 TFM 的单独程序集,以添加 PQC 支持与经典回退。
  4. 现在捕获-以后解密: 对于必须保持机密性超过 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],这是废弃安全模式警告的规范所有者。


代理注意事项

  1. 切勿在 AES-GCM 中重用 nonce——使用相同密钥重用 nonce 会破坏机密性和认证性。每个加密操作始终生成新的随机 nonce。
  2. 切勿使用 ECB 模式——ECB 将相同的明文块加密为相同的密文块,泄露模式。.NET 的 Aes.Create() 默认使用 CBC,但优先使用 AES-GCM 进行认证加密。
  3. 切勿使用 == 比较哈希——使用 CryptographicOperations.FixedTimeEquals 防止时序侧信道攻击。
  4. 切勿将 MD5 或 SHA-1 用于安全目的——它们已损坏。SHA-1 仅在非安全检查(如 git 对象哈希)中可接受,其中碰撞抵抗不是安全要求。
  5. 切勿硬编码加密密钥——使用 [技能:dotnet-secrets-management] 存储密钥。使用 RandomNumberGenerator.GetBytes 生成密钥。
  6. 最小 RSA 密钥大小为 2048 位——NIST 已废弃 1024 位 RSA 密钥。新系统使用 4096。
  7. PBKDF2 迭代计数必须高——OWASP 推荐 600,000 次迭代与 SHA-256(截至 2023)。较低计数可暴力破解。
  8. PQC IsSupported 检查是强制的——在不支持的平台上调用 PQC API 会抛出 PlatformNotSupportedException。始终在使用前检查。
  9. 不要全局抑制 SYSLIB5006——仅在有意使用实验性 PQC API 的具体调用点抑制实验性诊断。

参考资料