使用 C# 与 PHP 的 AES GCM 加密
Posted
技术标签:
【中文标题】使用 C# 与 PHP 的 AES GCM 加密【英文标题】:AES GCM encryption using C# vs PHP 【发布时间】:2022-01-15 15:36:39 【问题描述】:我正在使用 C# .NET Framework 和 php 在我的客户端/服务器应用程序中执行一些加密方法。加密方法是 AES-256-GCM,在 PHP 中可以非常简单。我从here 复制的.NET 代码稍作修改。 .NET 版本产生不同的价值。
在PHP版本中我可以这样写
<?php
$dynamicSecretKey = "2192B39425BBD08B6E8E61C5D1F1BC9F428FC569FBC6F78C0BC48FCCDB0F42AE";
$iv = "E1E592E87225847C11D948684F3B070D";
$plainText = 'Test only. PHP and C# encryption';
$tag = null;
$content = openssl_encrypt($plainText, 'AES-256-GCM', $dynamicSecretKey, OPENSSL_RAW_DATA,$iv, $tag);
echo bin2hex($tag)."\n";
$cipherText = base64_encode($content.$tag);
echo $cipherText."\n";
$cipherTextWithTag = base64_decode($cipherText);
echo bin2hex($cipherTextWithTag);
echo "\n";
$tag = substr($cipherTextWithTag , -16, 16);
$cipherText = substr($cipherTextWithTag, 0, - 16);
$decryptedText = openssl_decrypt($cipherText, 'AES-256-GCM', $dynamicSecretKey, OPENSSL_RAW_DATA, $iv,$tag);
echo $decryptedText;
?>
在 C# 中我尝试使其相同
private RunTest()
//$dynamicSecretKey = "2192B39425BBD08B6E8E61C5D1F1BC9F428FC569FBC6F78C0BC48FCCDB0F42AE";
string dynamicSecretKey = "3777217A25432A462D4A614E645267556B58703272357538782F413F4428472B";
//$iv = "E1E592E87225847C11D948684F3B070D";
string iv = "E1E592E87225847C11D94868";
//$plainText = 'Test only. PHP and C# encryption';
string plainText = "Test only. PHP and C# encryption";
//$tag = null;
byte[] tag = null;
//$content = openssl_encrypt($plainText, 'AES-256-GCM', $dynamicSecretKey, OPENSSL_RAW_DATA,$iv, $tag);
var content = AesGcm.Openssl_Encrypt(plainText, dynamicSecretKey, iv);
//$cipherText = base64_encode($content.$tag);
string cipherText = AesGcm.Base64Encode(AesGcm.Combine(content.cipherText, content.tag));
//bool same = cipherText.Equals("+u3YJpiHMy6p0mDxlquYq7utAOk7r6ppKGcnYAVD9iMPSe7fhWNNoMNto6Z6p0tM");
//$cipherTextWithTag = base64_decode($cipherText);
string cipherTextWithTag = AesGcm.Base64Decode(cipherText); //RESULTED UNREAD CHARACTERS
//$tag = substr($cipherTextWithTag, -16, 16);
//tag = cipherTextWithTag.Substring(cipherTextWithTag.Length - 16, 16);
//$cipherText = substr($cipherTextWithTag, 0, -16);
//cipherText = cipherTextWithTag.Substring(0, cipherTextWithTag.Length - 16);
string decryptedText = AesGcm.Openssl_Decrypt(cipherText, dynamicSecretKey, iv, AesGcm.ByteArrayToHex(content.tag)); //ERROR
internal static class AesGcm
internal static (byte[] ciphertext, byte[] nonce, byte[] tag) Encrypt(string plaintext, byte[] key, byte[] iv)
const int nonceLength = 12; // in bytes
const int tagLenth = 16; // in bytes
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, iv);
cipher.Init(true, parameters);
var offset = cipher.ProcessBytes(plaintextBytes, 0, plaintextBytes.Length, bcCiphertext, 0);
cipher.DoFinal(bcCiphertext, offset);
// Bouncy Castle includes the authentication tag in the ciphertext
var ciphertext = new byte[plaintextBytes.Length];
var tag = new byte[tagLenth];
Buffer.BlockCopy(bcCiphertext, 0, ciphertext, 0, plaintextBytes.Length);
Buffer.BlockCopy(bcCiphertext, plaintextBytes.Length, tag, 0, tagLenth);
return (ciphertext, iv, tag);
private static string Decrypt(byte[] ciphertext, byte[] key, byte[] iv, byte[] tag)
var plaintextBytes = new byte[ciphertext.Length];
var cipher = new GcmBlockCipher(new AesEngine());
var parameters = new AeadParameters(new KeyParameter(key), tag.Length * 8, iv);
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);
internal static (byte[] cipherText, byte[] tag) Openssl_Encrypt(string plaintext, string key, string iv)
var plainTextBytes = Encoding.UTF8.GetBytes(plaintext);
var bKey = HexToByteArray(key);
var bIv = HexToByteArray(iv);
var result = Encrypt(plaintext, bKey, bIv);
return (result.ciphertext, result.tag);
internal static string Openssl_Decrypt(string ciphertext, string key, string iv, string tag)
var bKey = HexToByteArray(key);
var bIv = HexToByteArray(iv);
var bTag = HexToByteArray(tag);
var bCipherText = Convert.FromBase64String(ciphertext);
var result = Decrypt(bCipherText, bKey, bIv, bTag);
return Base64Decode(result);
public static byte[] HexToByteArray(string hex)
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
public static string ByteArrayToHex(byte[] ba) => BitConverter.ToString(ba).Replace("-", "").ToLower();
public static string Base64Encode(string plainText)
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
return Convert.ToBase64String(plainTextBytes);
public static string Base64Decode(string base64EncodedData)
var base64EncodedBytes = Convert.FromBase64String(base64EncodedData);
return Encoding.UTF8.GetString(base64EncodedBytes);
public static string Base64Encode(byte[] plainText)
return Convert.ToBase64String(plainText);
public static string Base64Decode(byte[] base64EncodedData)
return Encoding.UTF8.GetString(base64EncodedData);
public static byte[] Combine(params byte[][] arrays)
byte[] rv = new byte[arrays.Sum(a => a.Length)];
int offset = 0;
foreach (byte[] array in arrays)
System.Buffer.BlockCopy(array, 0, rv, offset, array.Length);
offset += array.Length;
return rv;
编辑:
所有方法都包括在内。
此问题与here不重复
【问题讨论】:
你得到什么错误? @CodeCaster'Key length not 128/192/256 bits.'
C# 中的 Utf-8 编码提供了一个 64 字节的密钥。然而,AES-256 需要 32 字节的密钥,因此会出现错误消息。要解决此问题,必须显式缩短密钥。在 PHP 中,这不是必需的,因为这里的键被隐式删除。或者,可以在两种代码中对密钥和 IV 进行十六进制解码(这可能无论如何都是有意的)。这会产生一个 32 字节的密钥和 16 字节的 IV。请注意,建议 GCM 的 IV 大小为 12 字节,AesGcm
only 支持此大小(与 BouncyCaste 不同)。
@Topaco 我已经编辑了我的问题。这是因为我使用 Encoding.UTF8.GetBytes
而不是将十六进制字符串转换为字节数组。现在错误消失了,但结果与 PHP 版本不同。
缺少HexToByteArray()
的定义。您是否也对 PHP 代码中的比较进行了十六进制解码密钥和 IV?请发布您获得的 PHP 和 C# 代码的密文。
【参考方案1】:
你做的第一件奇怪的事情是在 Encrypt 结束时这样做:
// BlockCopy(Array src, int srcOffset, Array dst, int dstOffset, int count)
Buffer.BlockCopy(bcCiphertext, 0, ciphertext, 0, plaintextBytes.Length);
Buffer.BlockCopy(bcCiphertext, plaintextBytes.Length, tag, 0, tagLenth);
您正在将字节复制到密文和标签(本地数组)中!?
另一个奇怪的事情:
// new AeadParameters(KeyParameter key, int macSize, byte[] nonce)
var parameters = new AeadParameters(new KeyParameter(key), tagLenth * 8, iv);
您正在将 IV 扇区写入 Nonce 字段?!
【讨论】:
以上是关于使用 C# 与 PHP 的 AES GCM 加密的主要内容,如果未能解决你的问题,请参考以下文章
AES对称加解密工具类(AES/GCM/PKCS5Padding)
如何使用 AES-GCM 对 C#.NET 加密()然后 JS WebCryptoApi 解密()?