简体   繁体   English

AES GCM加密和解密:PHP VS C#BouncyCastle

[英]AES GCM encryption and decryption: PHP VS C# BouncyCastle

I am currently working on transforming my C# AES-GCM cryptography code to PHP. 我目前正在将C#AES-GCM加密代码转换为PHP。 However, after some research, the text encrypted by my PHP system cannot be decrypted by the C# one. 但是,经过一番研究,我的PHP系统加密的文本无法被C#解密。 I want to know if there is any difference from both codes: 我想知道两个代码是否有任何区别:

C# with BouncyCastle: 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();
        }
    }
}

Here is the PHP system: 这是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>';
?>

Can anyone tell me if there is something missing or different in the PHP code that makes them done differently? 谁能告诉我PHP代码中是否缺少某些内容或有所不同,从而使它们有所不同? Or if there is some internal difference between the PHP functions and the BouncyCastle functions that make them different? 还是PHP函数和BouncyCastle函数之间存在一些内部差异,使它们有所不同?

  • In the C#-code, the data are concatenated in the following order during encryption: 在C#代码中,加密过程中按以下顺序连接数据:

    nonSecretPyload nonce cipherText nonSecretPyload nonce cipherText

    Here cipherText consists of two parts, the encrypted message and the authentication tag. 这里的cipherText由两部分组成,加密的消息和身份验证标签。 Appending the tag to the encrypted message is done automatically by GcmBlockCipher#DoFinal . GcmBlockCipher#DoFinal自动完成将标签添加到加密邮件的GcmBlockCipher#DoFinal

    In the PHP-code, the data are concatenated in the following order during encryption: 在PHP代码中,加密期间按以下顺序连接数据:

    $iv $tag $encrypt $iv $tag $encrypt

    Here $iv is the counterpart to nonce . $ivnonce的对应物。 In contrast to GcmBlockCipher#DoFinal , the PHP-method openssl_encrypt returns only the encrypted message ( $encrypt ). GcmBlockCipher#DoFinal ,PHP方法openssl_encrypt仅返回加密的消息( $encrypt )。 The authentication tag is returned in a separate variable (6th openssl_encrypt -parameter $tag ). 身份验证标签在单独的变量中返回(第六个openssl_encrypt -parameter $tag )。 Therefore, $tag and $encrypt correspond in reverse order to cipherText . 因此, $tag$encrypt对应于cipherText The additional authenticated data , ie the counterpart to nonSecretPyload are not considered in the PHP-code at all. 在PHP代码中根本不考虑其他经过身份验证的数据 ,即与nonSecretPyload对应的数据

    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). 这意味着用C#代码加密的消息不能用PHP代码解密(反之亦然)。 For this to be possible, the order in the PHP-code must be changed as follows: 为此,必须按如下所示更改PHP代码中的顺序:

    $aad $iv $encrypt $tag $aad $iv $encrypt $tag

    Here $aad is the counterpart to nonSecretPyload . 这里$aad nonSecretPyloadnonSecretPyload的对应物。 The order (as well the consideration of the additional authenticated data) must be adapted both in the encryption part and in the decryption part. 必须同时在加密部分和解密部分中调整顺序(以及考虑其他经过身份验证的数据)。

  • 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') returns 12 ), where 12 bytes is actually the recommended length . 此外,使用了不同的IV长度:在C#代码中为16个字节,在PHP代码中为12个字节(后者是因为openssl_cipher_iv_length('aes-256-gcm')返回12 ),其中12个字节实际上是建议的长度 For compatibility, a uniform IV length must be used in both codes! 为了兼容,两个代码中必须使用统一的IV长度!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM