AES GCM加密和解密:PHP VS C#BouncyCastle [英] AES GCM encryption and decryption: PHP VS C# BouncyCastle
问题描述
我目前正在将C#AES-GCM密码学代码转换为PHP.但是,经过一番研究,我的PHP系统加密的文本无法被C#解密.我想知道两个代码是否有任何区别:
I am currently working on transforming my C# AES-GCM cryptography code to PHP. However, after some research, the text encrypted by my PHP system cannot be decrypted by the C# one. I want to know if there is any difference from both codes:
带有BouncyCastle的C#:
C# with 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函数之间存在一些内部差异,从而使它们有所不同?
Can anyone tell me if there is something missing or different in the PHP code that makes them done differently? Or if there is some internal difference between the PHP functions and the BouncyCastle functions that make them different?
推荐答案
-
在C#代码中,加密期间按以下顺序连接数据:
In the C#-code, the data are concatenated in the following order during encryption:
nonSecretPyload
nonce
cipherText
此处
cipherText
由两部分组成,加密的消息和身份验证标签.标签由Here
cipherText
consists of two parts, the encrypted message and the authentication tag. Appending the tag to the encrypted message is done automatically byGcmBlockCipher#DoFinal
.在PHP代码中,加密期间按以下顺序连接数据:
In the PHP-code, the data are concatenated in the following order during encryption:
$ iv
$ tag
$ encrypt
此处
$ iv
与nonce
对应.与GcmBlockCipher#DoFinal
相比,PHP方法Here
$iv
is the counterpart tononce
. In contrast toGcmBlockCipher#DoFinal
, the PHP-methodopenssl_encrypt
returns only the encrypted message ($encrypt
). The authentication tag is returned in a separate variable (6thopenssl_encrypt
-parameter$tag
). Therefore,$tag
and$encrypt
correspond in reverse order tocipherText
. The additional authenticated data, i.e. the counterpart tononSecretPyload
are not considered in the PHP-code at all.很明显,两个代码中各个组件的顺序是不同的.这意味着用C#代码加密的消息不能用PHP代码解密(反之亦然).为此,必须按如下所示更改PHP代码中的顺序:
It is immediately apparent that the orders of the individual components in the two codes are different. This means that a message encrypted in the C#-code cannot be decrypted in the PHP-code (and vice versa). For this to be possible, the order in the PHP-code must be changed as follows:
$ aad
$ iv
$ encrypt
$ tag
此处
$ aad
是nonSecretPyload
的副本.必须同时在加密部分和解密部分中调整顺序(以及考虑其他经过身份验证的数据).Here
$aad
is the counterpart tononSecretPyload
. The order (as well the consideration of the additional authenticated data) must be adapted both in the encryption part and in the decryption part.此外,使用了不同的IV长度:在C#代码中为16个字节,在PHP代码中为12个字节(之所以这样,是因为
openssl_cipher_iv_length('aes-256-gcm')
返回12
),其中12个字节实际上是推荐长度.为了兼容,两个代码中必须使用统一的IV长度!In addition, different IV lengths are used: In the C#-code 16 bytes, in the PHP-code 12 bytes (the latter because
openssl_cipher_iv_length('aes-256-gcm')
returns12
), where 12 bytes is actually the recommended length. For compatibility, a uniform IV length must be used in both codes!这篇关于AES GCM加密和解密:PHP VS C#BouncyCastle的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!