mcpField
D
dotnet-cryptography

dotnet-cryptography

github.com/wshaddix安全8685 次安装35 周前更新
代码仓库

关于 dotnet-cryptography

面向 .NET 开发者的现代密码学技能指南,涵盖哈希、加密、签名和密钥派生,并说明 .NET 10 的后量子算法及较早版本的回退方式。

主要功能

  1. 01SHA-2 哈希与 HMAC
  2. 02AES-GCM 对称加密
  3. 03RSA 与 ECDSA 签名
  4. 04PBKDF2 与 Argon2 密钥派生
  5. 05.NET 10 后量子算法指导
  6. 06旧版框架回退策略

适用场景

  1. 01为数据完整性计算哈希
  2. 02保护数据并验证其真实性
  3. 03实现密码存储与密钥派生
  4. 04规划后量子算法迁移

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-cryptography

安装后在 Claude Code 中输入/dotnet-cryptography确认已加载。

详细介绍

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 的备用策略。

**不在范围内:**机密管理和配置绑定——参见 [skill:dotnet-secrets-management]。OWASP 漏洞类别和已弃用的安全模式——参见 [skill:dotnet-security-owasp]。身份验证/授权实现(JWT、OAuth、Identity)——参见 [skill:dotnet-api-security] 和 [skill:dotnet-blazor-auth]。特定于云的密钥管理(Azure Key Vault、AWS KMS)——参见云端项目。TLS/HTTPS 配置——由 ASP.NET Core 中间件负责。

交叉引用:[skill:dotnet-security-owasp],了解 OWASP A02(密码学失效)和已弃用模式警告;[skill:dotnet-secrets-management],了解如何安全存储密钥和机密。


范围界定

**范围内:**算法选择、正确使用 System.Security.Cryptography API、密钥派生、用于完整性校验的哈希、对称/非对称加密、后量子密码学,以及已弃用算法警告。

**不在范围内:**密钥存储(使用机密管理)、TLS 终止(基础设施)、身份验证协议(参见 [skill:dotnet-api-security])、云 HSM/KMS 服务(云端项目)。


前提条件

  • .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;

// Hash a byte array
byte[] data = "Hello, world"u8.ToArray();
byte[] hash = SHA256.HashData(data);

// Hash a stream (efficient for large files)
await using var stream = File.OpenRead("largefile.bin");
byte[] fileHash = await SHA256.HashDataAsync(stream);

// Compare hashes securely (constant-time comparison prevents timing attacks)
bool isEqual = CryptographicOperations.FixedTimeEquals(hash1, hash2);
// HMAC for authenticated hashing (message authentication codes)
byte[] key = RandomNumberGenerator.GetBytes(32); // 256-bit key
byte[] mac = HMACSHA256.HashData(key, data);

// Verify HMAC
byte[] computedMac = HMACSHA256.HashData(key, receivedData);
if (!CryptographicOperations.FixedTimeEquals(mac, computedMac))
{
    throw new CryptographicException("Message authentication failed");
}

对称加密(AES-GCM)

AES-GCM 是 .NET 推荐的对称加密算法。它同时提供机密性和真实性(带关联数据的认证加密——AEAD)。

using System.Security.Cryptography;

public static class AesGcmEncryptor
{
    private const int NonceSize = 12; // 96-bit nonce (required by GCM)
    private const int TagSize = 16;   // 128-bit authentication tag

    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);

        // Prepend nonce + append tag for transport
        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 Data Protection API -- preferred for web application scenarios
// Handles key management, rotation, and storage automatically
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);
}

// Registration:
builder.Services.AddDataProtection()
    .SetApplicationName("MyApp")
    .PersistKeysToFileSystem(new DirectoryInfo("/keys"));

非对称密码学(RSA、ECDSA)

RSA

使用 RSA 加密较小的负载(密钥封装)和生成数字签名。密钥至少为 2048 位;新系统建议使用 4096 位。

using System.Security.Cryptography;

// Generate an RSA key pair
using var rsa = RSA.Create(4096);

// Sign data
byte[] signature = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);

// Verify signature (with public key)
byte[] publicKeyBytes = rsa.ExportRSAPublicKey();
using var rsaPublic = RSA.Create();
rsaPublic.ImportRSAPublicKey(publicKeyBytes, out _);
bool valid = rsaPublic.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);

// Encrypt with OAEP padding (never use PKCS#1 v1.5 for new code)
byte[] encrypted = rsaPublic.Encrypt(smallPayload, RSAEncryptionPadding.OaepSHA256);
byte[] decrypted = rsa.Decrypt(encrypted, RSAEncryptionPadding.OaepSHA256);
ECDSA

新项目的数字签名优先选择 ECDSA,而非 RSA——在提供同等安全性的同时,密钥更小。

using System.Security.Cryptography;

// Generate ECDSA key (P-256 = NIST curve, widely supported)
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);

// Sign data
byte[] signature = ecdsa.SignData(data, HashAlgorithmName.SHA256);

// Export public key for verification
byte[] publicKey = ecdsa.ExportSubjectPublicKeyInfo();

// Import and verify
using var ecdsaPublic = ECDsa.Create();
ecdsaPublic.ImportSubjectPublicKeyInfo(publicKey, out _);
bool valid = ecdsaPublic.VerifyData(data, signature, HashAlgorithmName.SHA256);

密钥派生(密码哈希)

PBKDF2(内置)

PBKDF2 是 .NET 内置的算法,可用于密码哈希。使用 SHA-256 时,迭代次数至少为 600,000(OWASP 建议)。

using System.Buffers.Binary;
using System.Security.Cryptography;

public static class PasswordHasher
{
    private const int SaltSize = 16;       // 128-bit salt
    private const int HashSize = 32;       // 256-bit derived key
    private const int Iterations = 600_000; // OWASP 2023 recommendation for SHA-256
    private const int PayloadSize = 4 + SaltSize + HashSize; // iteration count + salt + hash

    public static string HashPassword(string password)
    {
        byte[] salt = RandomNumberGenerator.GetBytes(SaltSize);
        byte[] hash = Rfc2898DeriveBytes.Pbkdf2(
            password,
            salt,
            Iterations,
            HashAlgorithmName.SHA256,
            HashSize);

        // Store iteration count (fixed little-endian), salt, and hash together
        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)
    {
        // Defensive parsing: reject malformed input without exceptions
        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 攻击。

// Requires: <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,  // threads
        MemorySize = 65536,       // 64 MB
        Iterations = 3
    };
    return argon2.GetBytes(32); // 256-bit hash
}

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 后量子密码学 API(类级别未标记为 [Experimental])。

#if NET10_0_OR_GREATER
using System.Security.Cryptography;

if (!MLKem.IsSupported)
{
    Console.WriteLine("ML-KEM not available on this platform");
    return;
}

// Generate a key pair
using MLKem privateKey = MLKem.GenerateKey(MLKemAlgorithm.MLKem768);

// Export public encapsulation key (share with peer)
byte[] publicKeyBytes = privateKey.ExportEncapsulationKey();

// Peer: import public key and encapsulate a shared secret
using MLKem publicKey = MLKem.ImportEncapsulationKey(
    MLKemAlgorithm.MLKem768, publicKeyBytes);
publicKey.Encapsulate(out byte[] ciphertext, out byte[] sharedSecret1);

// Original holder: decapsulate to recover the same shared secret
byte[] sharedSecret2 = privateKey.Decapsulate(ciphertext);

// Both parties now have the same shared secret for symmetric encryption
bool match = sharedSecret1.AsSpan().SequenceEqual(sharedSecret2);
#endif

参数集:

参数集安全级别封装密钥密文
MLKemAlgorithm.MLKem512NIST Level 1(128 位)800 字节768 字节
MLKemAlgorithm.MLKem768NIST Level 3(192 位)1,184 字节1,088 字节
MLKemAlgorithm.MLKem1024NIST Level 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 not available on this platform");
    return;
}

// Generate signing key
using MLDsa key = MLDsa.GenerateKey(MLDsaAlgorithm.MLDsa65);

// Sign data
byte[] data = "Document to sign"u8.ToArray();
byte[] signature = new byte[key.Algorithm.SignatureSizeInBytes];
key.SignData(data, signature);

// Export public key for verification
byte[] publicKeyBytes = key.ExportMLDsaPublicKey();

// Verify with public key
using MLDsa publicKey = MLDsa.ImportMLDsaPublicKey(
    MLDsaAlgorithm.MLDsa65, publicKeyBytes);
bool valid = publicKey.VerifyData(data, signature);
#endif

参数集:

参数集安全级别公钥签名
MLDsaAlgorithm.MLDsa44NIST Level 21,312 字节2,420 字节
MLDsaAlgorithm.MLDsa65NIST Level 31,952 字节3,309 字节
MLDsaAlgorithm.MLDsa87NIST Level 52,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 is [Experimental] -- suppress SYSLIB5006 only when intentional
#pragma warning disable SYSLIB5006
if (SlhDsa.IsSupported)
{
    using SlhDsa key = SlhDsa.GenerateKey(SlhDsaAlgorithm.SlhDsaSha2_128s);
    byte[] data = "Long-term document"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 证书可通过 SslStream 用于 TLS 1.3 及更高版本,但前提是操作系统加密库支持在 TLS 中使用 PQC。请在目标部署环境中验证。
  • **性能:**ML-KEM 和 ML-DSA 的速度很快。SLH-DSA 的签名速度明显较慢(以秒计,而非毫秒)——仅在需要基于哈希的安全保证时使用。

已弃用的加密 API

以下加密算法已被破解或过时。请勿在新代码中使用。

算法替代方案原因
MD5SHA-256+自 2004 年起出现碰撞攻击;可轻易破解
SHA-1SHA-256+已证明存在碰撞攻击(SHAttered,2017)
DESAES-GCM56 位密钥;数小时内即可暴力破解
3DES (TripleDES)AES-GCM已被 NIST 弃用(2023);存在 Sweet32 攻击
RC2AES-GCM密钥调度较弱;实际密钥长度低于标称长度
RSA PKCS#1 v1.5 加密RSA-OAEPBleichenbacher 填充预言攻击

如需查看加密之外已弃用安全模式的完整列表(CAS、APTCA、.NET Remoting、DCOM、BinaryFormatter),请参阅 [skill:dotnet-security-owasp],它是已弃用安全模式警告的权威来源。


Agent 易踩问题

  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. 切勿硬编码加密密钥 —— 请使用 [skill:dotnet-secrets-management] 存储密钥。使用 RandomNumberGenerator.GetBytes 生成密钥。
  6. RSA 密钥长度至少为 2048 位 —— NIST 已弃用 1024 位 RSA 密钥。新系统请使用 4096 位密钥。
  7. PBKDF2 迭代次数必须足够高 —— OWASP 建议使用 SHA-256 进行 600,000 次迭代(截至 2023 年)。较低的次数可能被暴力破解。
  8. 必须检查 PQC IsSupported —— 在不受支持的平台上调用 PQC API 会抛出 PlatformNotSupportedException。使用前务必检查。
  9. 不要全局抑制 SYSLIB5006 —— 仅在有意使用实验性 PQC API 的特定调用位置抑制该实验性诊断。

参考资料

评论

支持 Markdown