微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

.net 中的 AES_GCM 与流

如何解决.net 中的 AES_GCM 与流

在我之前的问题 (RAM not being freed in c# after working with files) 中,我询问了一种清除 RAM 的方法。有人建议使用流而不是将其读入变量。 我发现 Encrypting/Decrypting large files (.NET) 使用流,但不使用 AesGcm。 问题是我找不到如何将 AesGcm 与流一起使用。 AesGcm.decrypt 只接受密文字段中的 Byte[], 并且 AesManaged 没有 CihperMode.GCM。

目前,解密 800MB 文件时需要 4GB 内存。 如何在不填满 RAM 的情况下使用 AesGcm 解密文件

谢谢。

解决方法

我会说 .NET 中的 AesGcm(可能还有 AesCcm)不支持“流”模式,而且似乎共识(https://crypto.stackexchange.com/questions/51537/delayed-tag-checks-in-aes-gcm-for-streaming-data)是你不应该t 创建流模式 AesGcm。我将添加另一个关于此 https://github.com/dotnet/runtime/issues/27348 的参考。我不是密码学专家,因此我不清楚流式传输加密文档并仅在最后检查其身份验证标签有什么问题。

如果可能,您应该更改算法。否则可以找到其他解决方案。 Bouncycastle 库支持 AesGcm。

,

我发布了一个非流式答案,因为我有一个相当不错的 AesGcm 低分配实现,可以满足您的需求。您可以将 ArraySegment<byte> 直接放入流中并使用 FileStream 写入磁盘。内存分配不应超过文件本身的两倍(显然是 x2,因为您将文件加载到内存中并且必须存储加密的字节。)它的性能也很好,但我对此不以为然,显然只是 Net5.0 增强。

如果您需要使用替代解密机制,则字节结构很简单。

  12 bytes       16 bytes    n bytes up to int.IntMax - 28 bytes.
[ Nonce / IV ][ Tag / MAC ][               Ciphertext           ]

链接到我的 Github Repo

使用示例)

// HashKey or PassKey or Passphrase in 16/24/32 byte format.
var encryptionProvider = new AesGcmEncryptionProvider(hashKey,"ARGON2ID");

// This is an ArraySegment<byte>,this allows a defer allocation of byte[]
var encryptedData = encryptionProvider.Encrypt(_data);

// When you are ready for a byte[]
encryptedData.ToArray()

// You can also use
encryptedData.Array
// but this is a buffer and often exceeds the actual size of your bytes.
// Use conscientiously but does prevent a copy / allocation of the bytes.

// To Decrypt - same return type in case you need to serialize / decompress etc.
var decryptedData = encryptionProvider.Decrypt(encryptedData);

decrypted.ToArray() // the proper decrypted bytes of data.
decrypted.Array // the buffer used.

// Convert to a string
Encoding.UTF8.GetString(_encryptedData.ToArray())

如果您发现任何问题,请告诉我,很乐意进行更改/修复 - 或者甚至更好地在 Github 上提交问题/PR,以便我可以使真实代码保持最新。

基准

// * Summary *

BenchmarkDotNet=v0.13.0,OS=Windows 10.0.18363.1556 (1909/November2019Update/19H2)
Intel Core i7-9850H CPU 2.60GHz,1 CPU,12 logical and 6 physical cores
.NET SDK=5.0.203
  [Host]     : .NET 5.0.6 (5.0.621.22011),X64 RyuJIT
  Job-ADZLQM : .NET 5.0.6 (5.0.621.22011),X64 RyuJIT
  .NET 5.0   : .NET 5.0.6 (5.0.621.22011),X64 RyuJIT

Runtime=.NET 5.0

|                  Method |        Job | IterationCount |          Mean |         Error |        StdDev |        Median | Ratio | RatioSD |     Gen 0 |    Gen 1 |    Gen 2 | Allocated |
|------------------------ |----------- |--------------- |--------------:|--------------:|--------------:|--------------:|------:|--------:|----------:|---------:|---------:|----------:|
|          Encrypt1KBytes |   .NET 5.0 |        Default |      1.512 us |     0.0298 us |     0.0398 us |      1.504 us |  1.00 |    0.00 |    0.1926 |        - |        - |      1 KB |
|          Encrypt2KBytes |   .NET 5.0 |        Default |      1.965 us |     0.0382 us |     0.0408 us |      1.951 us |  1.30 |    0.04 |    0.3548 |        - |        - |      2 KB |
|          Encrypt4kBytes |   .NET 5.0 |        Default |      2.946 us |     0.0583 us |     0.0942 us |      2.948 us |  1.96 |    0.07 |    0.6828 |        - |        - |      4 KB |
|          Encrypt8KBytes |   .NET 5.0 |        Default |      4.630 us |     0.0826 us |     0.0733 us |      4.631 us |  3.09 |    0.08 |    1.3351 |        - |        - |      8 KB |
|          Decrypt1KBytes |   .NET 5.0 |        Default |      1.234 us |     0.0247 us |     0.0338 us |      1.216 us |  0.82 |    0.03 |    0.1869 |        - |        - |      1 KB |
|          Decrypt2KBytes |   .NET 5.0 |        Default |      1.644 us |     0.0328 us |     0.0378 us |      1.630 us |  1.09 |    0.04 |    0.3510 |        - |        - |      2 KB |
|          Decrypt4kBytes |   .NET 5.0 |        Default |      2.462 us |     0.0274 us |     0.0214 us |      2.460 us |  1.64 |    0.04 |    0.6752 |        - |        - |      4 KB |
|          Decrypt8KBytes |   .NET 5.0 |        Default |      4.167 us |     0.0828 us |     0.1016 us |      4.179 us |  2.76 |    0.12 |    1.3275 |        - |        - |      8 KB |

代码的及时快照。

public class AesGcmEncryptionProvider : IEncryptionProvider
{
    /// <summary>
    /// Safer way of generating random bytes.
    /// https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.rngcryptoserviceprovider?redirectedfrom=MSDN&view=net-5.0
    /// </summary>
    private readonly RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider();
    private readonly ArrayPool<byte> _pool = ArrayPool<byte>.Create();

    private readonly byte[] _key;

    public string Type { get; private set; }

    public AesGcmEncryptionProvider(byte[] key,string hashType)
    {
        if (!Constants.Aes.ValidKeySizes.Contains(key.Length)) throw new ArgumentException("Keysize is an invalid length.");
        _key = key;

        switch (_key.Length)
        {
            case 16: Type = "AES128"; break;
            case 24: Type = "AES192"; break;
            case 32: Type = "AES256"; break;
        }

        if (!string.IsNullOrWhiteSpace(hashType)) { Type = $"{hashType}-{Type}"; }
    }

    public ArraySegment<byte> Encrypt(ReadOnlyMemory<byte> data)
    {
        using var aes = new AesGcm(_key);

        // Slicing Version
        // Rented arrays sizes are minimums,not guarantees.
        // Need to perform extra work managing slices to keep the byte sizes correct but the memory allocations are lower by 200%
        var encryptedBytes = _pool.Rent(data.Length);
        var tag = _pool.Rent(AesGcm.TagByteSizes.MaxSize); // MaxSize = 16
        var nonce = _pool.Rent(AesGcm.NonceByteSizes.MaxSize); // MaxSize = 12
        _rng.GetBytes(nonce,AesGcm.NonceByteSizes.MaxSize);

        aes.Encrypt(
            nonce.AsSpan().Slice(0,AesGcm.NonceByteSizes.MaxSize),data.Span,encryptedBytes.AsSpan().Slice(0,data.Length),tag.AsSpan().Slice(0,AesGcm.TagByteSizes.MaxSize));

        // Prefix ciphertext with nonce and tag,since they are fixed length and it will simplify decryption.
        // Our pattern: Nonce Tag Cipher
        // Other patterns people use: Nonce Cipher Tag // couldn't find a solid source.
        var encryptedData = new byte[AesGcm.NonceByteSizes.MaxSize + AesGcm.TagByteSizes.MaxSize + data.Length];
        Buffer.BlockCopy(nonce,encryptedData,AesGcm.NonceByteSizes.MaxSize);
        Buffer.BlockCopy(tag,AesGcm.NonceByteSizes.MaxSize,AesGcm.TagByteSizes.MaxSize);
        Buffer.BlockCopy(encryptedBytes,AesGcm.NonceByteSizes.MaxSize + AesGcm.TagByteSizes.MaxSize,data.Length);

        _pool.Return(encryptedBytes);
        _pool.Return(tag);
        _pool.Return(nonce);

        return encryptedData;
    }

    public async Task<MemoryStream> EncryptAsync(Stream data)
    {
        using var aes = new AesGcm(_key);

        var buffer = _pool.Rent((int)data.Length);
        var bytesRead = await data
            .ReadAsync(buffer.AsMemory(0,(int)data.Length))
            .ConfigureAwait(false);

        if (bytesRead == 0) throw new InvalidDataException();

        // Slicing Version
        // Rented arrays sizes are minimums,not guarantees.
        // Need to perform extra work managing slices to keep the byte sizes correct but the memory allocations are lower by 200%
        var encryptedBytes = _pool.Rent((int)data.Length);
        var tag = _pool.Rent(AesGcm.TagByteSizes.MaxSize); // MaxSize = 16
        var nonce = _pool.Rent(AesGcm.NonceByteSizes.MaxSize); // MaxSize = 12
        _rng.GetBytes(nonce,buffer.AsSpan().Slice(0,(int)data.Length),since they are fixed length and it will simplify decryption.
        // Our pattern: Nonce Tag Cipher
        // Other patterns people use: Nonce Cipher Tag // couldn't find a solid source.
        var encryptedStream = new MemoryStream(new byte[AesGcm.NonceByteSizes.MaxSize + AesGcm.TagByteSizes.MaxSize + (int)data.Length]);
        using (var binaryWriter = new BinaryWriter(encryptedStream,Encoding.UTF8,true))
        {
            binaryWriter.Write(nonce,AesGcm.NonceByteSizes.MaxSize);
            binaryWriter.Write(tag,AesGcm.TagByteSizes.MaxSize);
            binaryWriter.Write(encryptedBytes,(int)data.Length);
        }

        _pool.Return(buffer);
        _pool.Return(encryptedBytes);
        _pool.Return(tag);
        _pool.Return(nonce);

        encryptedStream.Seek(0,SeekOrigin.Begin);
        return encryptedStream;
    }

    public MemoryStream EncryptToStream(ReadOnlyMemory<byte> data)
    {
        return new MemoryStream(Encrypt(data).ToArray());
    }

    public ArraySegment<byte> Decrypt(ReadOnlyMemory<byte> encryptedData)
    {
        using var aes = new AesGcm(_key);

        // Slicing Version
        var nonce = encryptedData
            .Slice(0,AesGcm.NonceByteSizes.MaxSize)
            .Span;

        var tag = encryptedData
            .Slice(AesGcm.NonceByteSizes.MaxSize,AesGcm.TagByteSizes.MaxSize)
            .Span;

        var encryptedBytes = encryptedData
            .Slice(AesGcm.NonceByteSizes.MaxSize + AesGcm.TagByteSizes.MaxSize)
            .Span;

        var decryptedBytes = new byte[encryptedBytes.Length];

        aes.Decrypt(nonce,encryptedBytes,tag,decryptedBytes);

        return decryptedBytes;
    }

    public MemoryStream Decrypt(Stream stream)
    {
        using var aes = new AesGcm(_key);
        using var binaryReader = new BinaryReader(stream);

        var nonce = binaryReader.ReadBytes(AesGcm.NonceByteSizes.MaxSize);
        var tag = binaryReader.ReadBytes(AesGcm.TagByteSizes.MaxSize);
        var encryptedBytes = binaryReader.ReadBytes((int)binaryReader.BaseStream.Length - AesGcm.NonceByteSizes.MaxSize - AesGcm.TagByteSizes.MaxSize);
        var decryptedBytes = new byte[encryptedBytes.Length];

        aes.Decrypt(nonce,decryptedBytes);

        return new MemoryStream(decryptedBytes);
    }

    public MemoryStream DecryptToStream(ReadOnlyMemory<byte> data)
    {
        return new MemoryStream(Decrypt(data).ToArray());
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。