Шифрование и дешифрование AES GCM: PHP VS C # BouncyCastle - PullRequest
0 голосов
/ 04 июля 2019

В настоящее время я работаю над преобразованием своего криптографического кода 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, которые отличают их?

1 Ответ

2 голосов
/ 04 июля 2019
  • В коде 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. Порядок (а также рассмотрение дополнительных аутентифицированных данных) должен быть адаптирован как в части шифрования, так и в части расшифровки.

  • Кроме того, используются разные длины IV: в C # -коде 16 байтов, в PHP-коде 12 байтов (последний, потому что openssl_cipher_iv_length('aes-256-gcm') возвращает 12), где 12 байтов на самом деле рекомендуемая длина . Для совместимости необходимо использовать одинаковую длину IV в обоих кодах!

...