使用 AesGcm 类

Jan*_*ann 5 c# encryption aes-gcm

我刚刚注意到 .NET Standard 2.1/.NET Core 3.0 终于添加了一个用于 AES-GCM 加密的类

然而,它的 API 似乎与通常的 .NET 加密类略有不同:它的Encrypt功能要求为密文和标签预先分配字节数组,而不是自己提供它们。不幸的是,文档中没有显示正确使用该类的示例。

我知道如何在理论上计算 AES 加密的预期密文大小,但我想知道这是否真的是“猜测”密文缓冲区大小的预期方法。通常加密库提供处理这些计算的函数。

有人有关于如何使用正确加密字节数组的示例AesGcm吗?

Jan*_*ann 13

我现在想通了。

我忘了在 GCM 中,密文与明文的长度相同;与 CBC 等其他加密模式相反,不需要填充。随机数和标签长度分别由NonceByteSizes和 的TagByteSizes属性决定AesGcm

使用它,可以通过以下方式进行加密:

public string Encrypt(string plain)
{
    // Get bytes of plaintext string
    byte[] plainBytes = Encoding.UTF8.GetBytes(plain);
    
    // Get parameter sizes
    int nonceSize = AesGcm.NonceByteSizes.MaxSize;
    int tagSize = AesGcm.TagByteSizes.MaxSize;
    int cipherSize = plainBytes.Length;
    
    // We write everything into one big array for easier encoding
    int encryptedDataLength = 4 + nonceSize + 4 + tagSize + cipherSize;
    Span<byte> encryptedData = encryptedDataLength < 1024
                             ? stackalloc byte[encryptedDataLength]
                             : new byte[encryptedDataLength].AsSpan();
    
    // Copy parameters
    BinaryPrimitives.WriteInt32LittleEndian(encryptedData.Slice(0, 4), nonceSize);
    BinaryPrimitives.WriteInt32LittleEndian(encryptedData.Slice(4 + nonceSize, 4), tagSize);
    var nonce = encryptedData.Slice(4, nonceSize);
    var tag = encryptedData.Slice(4 + nonceSize + 4, tagSize);
    var cipherBytes = encryptedData.Slice(4 + nonceSize + 4 + tagSize, cipherSize);
    
    // Generate secure nonce
    RandomNumberGenerator.Fill(nonce);
    
    // Encrypt
    using var aes = new AesGcm(_key);
    aes.Encrypt(nonce, plainBytes.AsSpan(), cipherBytes, tag);
    
    // Encode for transmission
    return Convert.ToBase64String(encryptedData);
}
Run Code Online (Sandbox Code Playgroud)

相应地,解密过程如下:

public string Decrypt(string cipher)
{
    // Decode
    Span<byte> encryptedData = Convert.FromBase64String(cipher).AsSpan();
    
    // Extract parameter sizes
    int nonceSize = BinaryPrimitives.ReadInt32LittleEndian(encryptedData.Slice(0, 4));
    int tagSize = BinaryPrimitives.ReadInt32LittleEndian(encryptedData.Slice(4 + nonceSize, 4));
    int cipherSize = encryptedData.Length - 4 - nonceSize - 4 - tagSize;
    
    // Extract parameters
    var nonce = encryptedData.Slice(4, nonceSize);
    var tag = encryptedData.Slice(4 + nonceSize + 4, tagSize);
    var cipherBytes = encryptedData.Slice(4 + nonceSize + 4 + tagSize, cipherSize);
    
    // Decrypt
    Span<byte> plainBytes = cipherSize < 1024
                          ? stackalloc byte[cipherSize]
                          : new byte[cipherSize];
    using var aes = new AesGcm(_key);
    aes.Decrypt(nonce, cipherBytes, tag, plainBytes);
    
    // Convert plain bytes back into string
    return Encoding.UTF8.GetString(plainBytes);
}
Run Code Online (Sandbox Code Playgroud)

有关完整实现和示例,请参阅dotnetfiddle

请注意,我是为网络传输而编写的,因此所有内容都被编码为一个大的 base-64 字符串;或者,您可以返回nonce,tagcipherBytes通过out参数单独返回。

网络设置也是我发送 nonce 和标签大小的原因:该类可能由具有不同运行时环境的不同应用程序使用,这些应用程序可能具有不同的支持参数大小。

  • 小心堆栈分配任意数量的数据——在某些时候,根据平台等,它会失败。一般建议是将其保持在 1024 字节左右以下,并在必要时分配一个数组。另外,如果您使用 Span,请使用“MemoryMarshal”而不是“BitConverter”——这使您可以直接使用 Span,而无需分配中间数组。 (2认同)
  • @canton7 感谢您的评论!我只发送小数据包,所以我没有考虑堆栈空间限制 - 这对于使用此代码的其他人来说显然可能成为一个问题。另外,感谢“MemoryMarshal”提示,我还不知道这个类。我已经相应地调整了代码。 (2认同)
  • 抱歉,我的意思是“BinaryPrimitives”,而不是“MemoryMarshal”。大多数人将 stackalloc 的内容写为 `Span&lt;byte&gt; plainBytes = cipherSize &lt;= 1024 ?stackalloc[cipherSize] : 新字节[cipherSize]` (2认同)
  • @vikky是的,[有](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.aesgcm.encrypt?view=netcore-3.1#System_Security_Cryptography_AesGcm_Encrypt_System_Byte___System_Byte___System_Byte___System_Byte___System_Byte___)!它是“Encrypt”和“Decrypt”的可选第五个参数(“linkedData”)。 (2认同)
  • 谢谢你,这对我来说效果很好!请注意,“AesGcm”是“IDisposable”,在我们获得有关该类的任何文档之前,最好再次正确释放创建的实例。您可能还想在定义“_key”的同一范围内重用该实例。 (2认同)