C# BouncyCastle Mac 使用标记作为 Base 64 字符串时检查 GCM 失败错误

C# BouncyCastle Mac check in GCM failed Error when used Tag as Base 64 String

我正在尝试使用 BouncyCastle 库通过 GCM 实施 AES 256 加密。

到目前为止,我已经设法通过将 Key and Nonce 作为字符串和 Tag 作为字节数组传递来使其工作。

这是加密方式

private static byte[] EncryptWithGCM(string plaintext, string KeyString, string NonceString, byte[] tag)
{
    byte[] key = Convert.FromBase64String(KeyString);
    byte[] nonce = Convert.FromBase64String(NonceString);
        
    var plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
    var bcCiphertext = new byte[plaintextBytes.Length + tagLenth];

    var cipher = new GcmBlockCipher(new AesEngine());
    var parameters = new AeadParameters(new KeyParameter(key), tagLenth * 8, nonce);
    cipher.Init(true, parameters);

    var offset = cipher.ProcessBytes(plaintextBytes, 0, plaintextBytes.Length, bcCiphertext, 0);
    cipher.DoFinal(bcCiphertext, offset);
                    
    var ciphertext = new byte[plaintextBytes.Length];            
    Buffer.BlockCopy(bcCiphertext, 0, ciphertext, 0, plaintextBytes.Length);
    Buffer.BlockCopy(bcCiphertext, plaintextBytes.Length, tag, 0, tagLenth);

    return ciphertext;
}

这是解密代码。

private static string DecryptWithGCM(string EncryptedString, string KeyString, string NonceString, byte[] tag)
{
    byte[] key = Convert.FromBase64String(KeyString);
    byte[] nonce = Convert.FromBase64String(NonceString);
        
    byte[] ciphertext = Convert.FromBase64String(EncryptedString);
    var plaintextBytes = new byte[ciphertext.Length];

    var cipher = new GcmBlockCipher(new AesEngine());
    var parameters = new AeadParameters(new KeyParameter(key), tag.Length * 8, nonce);
    cipher.Init(false, parameters);

    var bcCiphertext = ciphertext.Concat(tag).ToArray();

    var offset = cipher.ProcessBytes(bcCiphertext, 0, bcCiphertext.Length, plaintextBytes, 0);
    cipher.DoFinal(plaintextBytes, offset);

    return Encoding.UTF8.GetString(plaintextBytes);
}

如您所见,我将除 Tag 之外的所有内容都作为字符串传递。因为当我将 Tag 作为字符串传递并将其转换为字节数组时,它不起作用。它显示错误 "Mac check in GCM failed"

因此,此代码有效:

var rnd = new Random();
var tag = new Byte[16]; //16 bytes
rnd.NextBytes(tag);
string TagString = Convert.ToBase64String(tag);

byte[] EncryptedText = EncryptWithGCM(PlainText, KeyString, NonceString, tag);
string EncryptedString = Convert.ToBase64String(EncryptedText);
string DecryptdText = DecryptWithGCM(EncryptedString, KeyString, NonceString, tag);

但是当我在 encryption/decryption 函数中传递 TagString 并将其转换回字节数组时,它会抛出 "Mac check in GCM failed" 错误。

// this code does not work.
private static string DecryptWithGCM(string EncryptedString, string KeyString, string NonceString, string TagString)
{
    byte[] key = Convert.FromBase64String(KeyString);
    byte[] nonce = Convert.FromBase64String(NonceString);
    byte[] tag = Convert.FromBase64String(TagString);
    ...
    ...

为什么会这样?

标签在加密期间自动创建并在解密期间用于验证数据(在这两种情况下都在DoFinal())。
由于C#/BC自动将标签与密文连接起来,所以在加密或解密过程中不需要显式传递标签:

private static string EncryptWithGCM(string plaintext, string keyString, string nonceString)
{
    var tagLength = 16;
    var key = Convert.FromBase64String(keyString);
    var nonce = Convert.FromBase64String(nonceString);

    var plaintextBytes = Encoding.UTF8.GetBytes(plaintext);
    var ciphertextTagBytes = new byte[plaintextBytes.Length + tagLength];

    var cipher = new GcmBlockCipher(new AesEngine());
    var parameters = new AeadParameters(new KeyParameter(key), tagLength * 8, nonce);
    cipher.Init(true, parameters);

    var offset = cipher.ProcessBytes(plaintextBytes, 0, plaintextBytes.Length, ciphertextTagBytes, 0);
    cipher.DoFinal(ciphertextTagBytes, offset); // create and append tag: ciphertext | tag

    return Convert.ToBase64String(ciphertextTagBytes);
}

private static string DecryptWithGCM(string ciphertextTag, string keyString, string nonceString)
{
    var tagLength = 16;
    var key = Convert.FromBase64String(keyString);
    var nonce = Convert.FromBase64String(nonceString);
    
    var ciphertextTagBytes = Convert.FromBase64String(ciphertextTag);
    var plaintextBytes = new byte[ciphertextTagBytes.Length - tagLength];

    var cipher = new GcmBlockCipher(new AesEngine());
    var parameters = new AeadParameters(new KeyParameter(key), tagLength * 8, nonce);
    cipher.Init(false, parameters);

    var offset = cipher.ProcessBytes(ciphertextTagBytes, 0, ciphertextTagBytes.Length, plaintextBytes, 0); 
    cipher.DoFinal(plaintextBytes, offset); // authenticate data via tag

    return Encoding.UTF8.GetString(plaintextBytes);
}

请注意,对于固定密钥,静态随机数是 GCM (here) 的 致命 错误。 (non-secret)随机数应随机生成并与密文和标签一起传递给解密方(通常按以下顺序连接:随机数|密文|标签)。