AES GCM encryption and decryption: PHP VS C# BouncyCastle

若如初见. 提交于 2021-02-20 03:38:29

问题


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:

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();
        }
    }
}

Here is the PHP system:

<?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? Or if there is some internal difference between the PHP functions and the BouncyCastle functions that make them different?


回答1:


  • In the C#-code, the data are concatenated in the following order during encryption:

    nonSecretPyload nonce cipherText

    Here cipherText consists of two parts, the encrypted message and the authentication tag. Appending the tag to the encrypted message is done automatically by GcmBlockCipher#DoFinal.

    In the PHP-code, the data are concatenated in the following order during encryption:

    $iv $tag $encrypt

    Here $iv is the counterpart to nonce. In contrast to GcmBlockCipher#DoFinal, the PHP-method openssl_encrypt returns only the encrypted message ($encrypt). The authentication tag is returned in a separate variable (6th openssl_encrypt-parameter $tag). Therefore, $tag and $encrypt correspond in reverse order to cipherText. The additional authenticated data, i.e. the counterpart to nonSecretPyload are not considered in the PHP-code at all.

    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

    Here $aad is the counterpart to nonSecretPyload. 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. For compatibility, a uniform IV length must be used in both codes!



来源:https://stackoverflow.com/questions/56884217/aes-gcm-encryption-and-decryption-php-vs-c-sharp-bouncycastle

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!