AES GCM 加解密:PHP VS C# BouncyCastle
Posted
技术标签:
【中文标题】AES GCM 加解密:PHP VS C# BouncyCastle【英文标题】:AES GCM encryption and decryption: PHP VS C# BouncyCastle 【发布时间】:2019-11-14 23:25:15 【问题描述】:我目前正在将我的 C# AES-GCM 加密代码转换为 php。但是,经过一番研究,我的PHP系统加密的文本不能被C#解密。我想知道这两个代码是否有任何区别:
C# 与 BouncyCastle:
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using System;
using System.IO;
using System.Text;
//the helper for all AES methods
public class AESHelper
private const int KEY_BIT_SIZE = 256;
private const int MAC_BIT_SIZE = 128;
private const int NONCE_BIT_SIZE = 128;
private readonly SecureRandom random;
private static AESHelper instance;
public static AESHelper Instance //property of this class. Create an instance if it is not created yet
get
if (instance == null)
instance = new AESHelper();
return instance;
public AESHelper()
random = new SecureRandom();
//decrypt with strings
public string Decrypt(string message, string key, int nonSecretPayloadLength = 0)
if (string.IsNullOrEmpty(message))
throw new ArgumentException("Message required!", "message");
var decodedKey = Convert.FromBase64String(key);
var cipherText = Convert.FromBase64String(message);
var plainText = DecryptWithKey(cipherText, decodedKey, nonSecretPayloadLength);
return Encoding.UTF8.GetString(plainText);
//encrypt with strings
public string Encrypt(string text, string key, byte[] nonSecretPayload = null)
if (string.IsNullOrEmpty(text))
throw new ArgumentException("Text required!", "text");
var decodedKey = Convert.FromBase64String(key);
var plainText = Encoding.UTF8.GetBytes(text);
var cipherText = EncryptWithKey(plainText, decodedKey, nonSecretPayload);
return Convert.ToBase64String(cipherText);
//create new key
public string NewKey()
var key = new byte[KEY_BIT_SIZE / 8];
random.NextBytes(key);
return Convert.ToBase64String(key);
//decrypt with byte array
private byte[] DecryptWithKey(byte[] message, byte[] key, int nonSecretPayloadLength = 0)
if (key == null || key.Length != KEY_BIT_SIZE / 8)
throw new ArgumentException(String.Format("Key needs to be 0 bit!", KEY_BIT_SIZE), "key");
if (message == null || message.Length == 0)
throw new ArgumentException("Message required!", "message");
using (var cipherStream = new MemoryStream(message))
using (var cipherReader = new BinaryReader(cipherStream))
var nonSecretPayload = cipherReader.ReadBytes(nonSecretPayloadLength);
var nonce = cipherReader.ReadBytes(NONCE_BIT_SIZE / 8);
var cipher = new GcmBlockCipher(new AesEngine());
var parameters = new AeadParameters(new KeyParameter(key), MAC_BIT_SIZE, nonce, nonSecretPayload);
cipher.Init(false, parameters);
var cipherText = cipherReader.ReadBytes(message.Length - nonSecretPayloadLength - nonce.Length);
var plainText = new byte[cipher.GetOutputSize(cipherText.Length)];
try
var len = cipher.ProcessBytes(cipherText, 0, cipherText.Length, plainText, 0);
cipher.DoFinal(plainText, len);
catch (InvalidCipherTextException)
return null;
return plainText;
//encrypt with byte array
private byte[] EncryptWithKey(byte[] text, byte[] key, byte[] nonSecretPayload = null)
if (key == null || key.Length != KEY_BIT_SIZE / 8)
throw new ArgumentException(String.Format("Key needs to be 0 bit!", KEY_BIT_SIZE), "key");
nonSecretPayload = nonSecretPayload ?? new byte[] ;
var nonce = new byte[NONCE_BIT_SIZE / 8];
random.NextBytes(nonce, 0, nonce.Length);
var cipher = new GcmBlockCipher(new AesEngine());
var parameters = new AeadParameters(new KeyParameter(key), MAC_BIT_SIZE, nonce, nonSecretPayload);
cipher.Init(true, parameters);
var cipherText = new byte[cipher.GetOutputSize(text.Length)];
var len = cipher.ProcessBytes(text, 0, text.Length, cipherText, 0);
cipher.DoFinal(cipherText, len);
using (var combinedStream = new MemoryStream())
using (var binaryWriter = new BinaryWriter(combinedStream))
binaryWriter.Write(nonSecretPayload);
binaryWriter.Write(nonce);
binaryWriter.Write(cipherText);
return combinedStream.ToArray();
这里是 PHP 系统:
<?php
echo '<pre>';
$hash_string = 'qIANSOwtdfF4y5Yk33ZLE5s6KwKBAeu6qzJRG84Sjjo=';
echo "password : ";
var_dump($hash_string);
echo '<hr>';
$decode_string = base64_decode($hash_string);
$app_cc_aes_key = substr($decode_string, 0, 32);
$cipher = 'aes-256-gcm';
$iv_len = openssl_cipher_iv_length($cipher);
echo "app_cc_aes_key : ";
var_dump($app_cc_aes_key);
echo '<br>';
echo "cipher :";
var_dump($cipher);
echo '<hr>';
$data = '7bc9d6ae-982f-11e9-bc42-526af7764f64';
echo "data : $data";
echo '<hr>';
$tag_length = 16;
$iv = openssl_random_pseudo_bytes($iv_len);
$tag = "";
$encrypt = openssl_encrypt($data, $cipher, $app_cc_aes_key, OPENSSL_RAW_DATA, $iv, $tag, "", $tag_length);
$encrypt_text = base64_encode($iv.$tag.$encrypt);
echo "encrypt :";
var_dump($encrypt);
echo '<br>';
echo "encrypt_text :";
var_dump($encrypt_text);
echo '<hr>';
$decoded_text = base64_decode($encrypt_text);
$iv = substr($decoded_text, 0, $iv_len);
$tag = substr($decoded_text, $iv_len, $tag_length);
$ciphertext = substr($decoded_text, $iv_len + $tag_length);
$decrypt_text = openssl_decrypt($ciphertext, $cipher, $app_cc_aes_key, OPENSSL_RAW_DATA, $iv, $tag);
echo "decrypt_text : $decrypt_text";
echo '<hr>';
?>
谁能告诉我 PHP 代码中是否有某些东西缺失或不同,导致它们以不同的方式完成?或者如果 PHP 函数和 BouncyCastle 函数之间存在一些内部差异导致它们不同?
【问题讨论】:
文本 ... 无法被 C# 解密 ... 这不是对问题的有用描述。究竟是什么错误?有例外吗?如果是这样,请在例外中包含完整信息。解密是否在进行,但结果不是您所期望的?如果是这样,请解释发生了什么以及您的预期。 【参考方案1】:在 C# 代码中,数据在加密过程中按以下顺序连接:
nonSecretPyload
nonce
cipherText
这里cipherText
由两部分组成,加密消息和认证标签。将标签附加到加密消息是由GcmBlockCipher#DoFinal
自动完成的。
在 PHP 代码中,数据在加密过程中按以下顺序连接:
$iv
$tag
$encrypt
这里的$iv
是nonce
的对应物。与GcmBlockCipher#DoFinal
相比,PHP 方法openssl_encrypt
仅返回加密消息($encrypt
)。身份验证标签在一个单独的变量中返回(第 6 个openssl_encrypt
-参数$tag
)。因此,$tag
和$encrypt
与cipherText
以相反的顺序对应。 额外的认证数据,即nonSecretPyload
的对应部分在PHP代码中根本不考虑。
很明显,两个代码中各个组件的顺序是不同的。这意味着在 C# 代码中加密的消息不能在 PHP 代码中解密(反之亦然)。为此,PHP 代码中的顺序必须更改如下:
$aad
$iv
$encrypt
$tag
这里的$aad
是nonSecretPyload
的对应物。必须在加密部分和解密部分调整顺序(以及对附加验证数据的考虑)。
【讨论】:
以上是关于AES GCM 加解密:PHP VS C# BouncyCastle的主要内容,如果未能解决你的问题,请参考以下文章