Получить байтовый массив C #, равный шестнадцатеричной строке php - PullRequest
0 голосов
/ 23 января 2019

Итак, у меня есть этот фрагмент php-кода, который мне не разрешено изменять, в основном потому, что он старый и работает правильно.

Внимание! Очень плохой код в целом. IV не рандомизируется и не сохраняется вместе с выходом. Я не спрашиваю об этом, потому что хочу, Я спрашиваю, потому что мне нужно. Я также планирую рефакторинг, когда получу эту работу, и дополню свой код C # действительно надежным шифровальным кодом.

function encrypt($string) 
{
    $output = false;
    $encrypt_method = "AES-256-CBC";
    $param1 = 'ASasd564D564aAS64ads564dsfg54er8G74s54hjds346gf445gkG7';
    $param2 = '654dsfg54er8ASG74sdfg54hjdas346gf34kjdDJF56hfs2345gkFG';
    $ky = hash('sha256', $param1); // hash
    $iv = substr(hash('sha256', $param2), 0, 16);

    $output = openssl_encrypt($string, $encrypt_method, $ky, 0, $iv);
    $output = base64_encode($output);
    return $output;
}    

Я хочу сделать то же самое в C #, потому что я получаю сущность со всеми ее полями, зашифрованными этим кодом.

Я хочу иметь возможность зашифровать эти данные, чтобы я мог запросить свой список сущностей без необходимости расшифровывать все сущности. И я хочу расшифровать некоторые свойства отфильтрованных объектов, чтобы они действительно могли быть полезны.

Теперь, к тому же, я создал CryptoHelper, который будет делать это, кроме случаев, когда это не так.

Я пытаюсь вычислить Ключ и IV в конструкторе:

    public readonly byte[] Key;
    public readonly byte[] IV;

    public CryptoHelper()
    {
        Key = GetByteArraySha256Hash("ASasd564D564aAS64ads564dsfg54er8G74s54hjds346gf445gkG7", false);
        IV = GetByteArraySha256Hash("654dsfg54er8ASG74sdfg54hjdas346gf34kjdDJF56hfs2345gkFG", true);
    }

    private byte[] GetByteArraySha256Hash(string source, bool salt)
    {
        byte[] result;
        try
        {
            using (SHA256 sha256Hash = SHA256.Create())
            {
                result = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(source));
            }
        }
        catch (Exception)
        {
            throw;
        }
        if (salt)
        {
            return result.Take(16).ToArray();
        }
        return result;
    }

А затем используйте методы Encrypt и Decrypt, которые работают довольно хорошо, когда я тестирую их с помощью тестовой строки. Единственная проблема заключается в том, что в конце строки есть некоторый отступ, но это небольшая проблема, учитывая, что любая строка, зашифрованная с помощью метода php, приводит к бреду.

    private string Encrypt(string source)
    {
        try
        {
            string result = "";

            using (var aes = new AesManaged { Key = Key, IV = IV, Mode = CipherMode.CBC, Padding = PaddingMode.Zeros })
            {
                byte[] sourceByteArray = Encoding.UTF8.GetBytes(source);

                using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
                {
                    byte[] encriptedSource = encryptor.TransformFinalBlock(sourceByteArray, 0, sourceByteArray.Length);
                    result = Convert.ToBase64String(encriptedSource);
                    result = Convert.ToBase64String(Encoding.UTF8.GetBytes(result));
                }
            }

            return result;
        }
        catch (Exception ex)
        {
            throw;
        }
    }

    private string Decrypt(string source)
    {
        try
        {
            string result = "";
            //Double Base64 conversion, as it's done in the php code.
            byte[] sourceByte = Convert.FromBase64String(source);
            byte[] sourceFreeOfBase64 = Convert.FromBase64String(Encoding.UTF8.GetString(sourceByte));

            byte[] resultByte;
            int decryptedByteCount = 0;

            using (var aes = new AesManaged { Key = Key, IV = IV, Mode = CipherMode.CBC, Padding = PaddingMode.Zeros })
            {
                using (ICryptoTransform AESDecrypt = aes.CreateDecryptor(aes.Key, aes.IV))
                {
                    using (MemoryStream memoryStream = new MemoryStream(sourceFreeOfBase64))
                    {
                        using (CryptoStream cs = new CryptoStream(memoryStream, AESDecrypt, CryptoStreamMode.Read))
                        {
                            resultByte = new byte[sourceFreeOfBase64.Length];
                            decryptedByteCount = cs.Read(resultByte, 0, resultByte.Length);
                        }
                    }
                }

                //This returns the encoded string with a set of "\0" at the end.
                result = Encoding.UTF8.GetString(resultByte);
                result = result.Replace("\0", "");
            }

            return result;
        }
        catch (Exception ex)
        {
            throw;
        }
    }

Я почти уверен, что главная проблема здесь заключается в строке php $iv = substr(hash('sha256', $param2), 0, 16);. Я проверил результаты обеих хеш-функций в php и C # и они абсолютно одинаковы.

Из того, что я читал, php рассматривает строки как байтовые массивы (поправьте меня, если я ошибаюсь), поэтому строки из 16 символов должно быть достаточно для получения массива из 16 байтов и блока 128. Но в C #, когда я получаю 16-байтовый массив и преобразовываю его в строку, я получаю строку из 32 символов, такую ​​же, как если бы я сделал $iv = substr(hash('sha256', $param2), 0, 32);.

Итак, мой вопрос: как получить тот же результат массива байтов в C #, который я получаю в этой строке $iv = substr(hash('sha256', $param2), 0, 16); php? Это вообще возможно?

Ответы [ 2 ]

0 голосов
/ 29 января 2019

Что ж, мне удалось решить эту проблему не так уж и плохо.

Следуя совету @ ste-fu, я пытался избавиться от каждого фрагмента кодировки, который смог найти.

Но я все еще не был близок к тому, чтобы правильно получить Ключ и IV.Поэтому я провел тестирование с php.Я сделал var_dump из IV и получил аккуратный массив длиной 16 с байтами, показанными как целые числа.

var_dump массив результатов всегда начинается в [1].Имейте в виду.

    $iv = substr(hash('sha256', $param2), 0, 16);
    $byte_array = unpack('C*', $iv);
    var_dump($byte_array);

Это заинтересовало меня, подумав, что если бы у меня была правильная шестнадцатеричная строка, я мог бы преобразовать каждый символ в строке в его эквивалентный байт.И вот, я сделал эту функцию в C #:

    private byte[] StringToByteArray(string hex)
    {
        IList<byte> resultList = new List<byte>();
        foreach (char c in hex)
        {
            resultList.Add(Convert.ToByte(c));
        }
        return resultList.ToArray();
    }

И это хорошо сработало для IV.Теперь мне просто нужно было сделать то же самое для ключа.И я так и сделал, просто чтобы обнаружить, что у меня есть массив байтов длиной 64.Это странно, но хорошо.Дальнейшее тестирование в php.

Так как имеет смысл, что ключ php ведет себя так же, как IV, я не понял, как функции шифрования openssl допускают ключ длиной 64.Поэтому я попытался зашифровать и расшифровать те же данные с помощью ключа из первых 32 символов.$ky = substr(hash('sha256', $param1), 0, 32); И это дало мне тот же результат, что и с полным ключом.Итак, мое обоснованное предположение состоит в том, что openssl просто берет байты, необходимые для работы кодировки.На самом деле это займет что-нибудь, так как я тестировал с подстроки длиной 1, 16, 20, 32, 33 и 50.Если длина строки больше 32, функция сама обрежет ее.

В любом случае, мне просто нужно было получить первые 32 символа шестнадцатеричного ключа и использовать мою новую функцию, чтобы преобразовать их в байтовый массив.и я получил свой ключ.Итак, основной код C # сейчас выглядит следующим образом:

    public CryptoHelper(string keyFilePath, string ivFilePath)
    {
        //Reading bytes from txt file encoded in UTF8.
        byte[] key = File.ReadAllBytes(keyFilePath);
        byte[] iv = File.ReadAllBytes(ivFilePath);

        IV = StringToByteArray(GetStringHexSha256Hash(iv).Substring(0, 16));
        Key = StringToByteArray(GetStringHexSha256Hash(key).Substring(0, 32)); 

        //Tests
        var st = Encrypt("abcdefg");
        var en = Decrypt(st);
    }


    //Convert each char into a byte
    private byte[] StringToByteArray(string hex)
    {
        IList<byte> resultList = new List<byte>();
        foreach (char c in hex)
        {
            resultList.Add(Convert.ToByte(c));
        }
        return resultList.ToArray();
    }

    private string GetStringHexSha256Hash(byte[] source)
    {
        string result = "";
        try
        {
            using (SHA256 sha256Hash = SHA256.Create("SHA256"))
            {
                //Get rid of Encoding!
                byte[] hashedBytes = sha256Hash.ComputeHash(source);

                for (int i = 0; i < hashedBytes.Length; i++)
                {
                    result = string.Format("{0}{1}",
                                            result,
                                            hashedBytes[i].ToString("x2"));
                }
            }
        }
        catch (Exception)
        {
            throw;
        }

        return result;
    }


    private string Encrypt(string source)
    {
        try
        {
            string result = "";

            using (var aes = new AesManaged { Key = Key, IV = IV, Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7 })
            {
                byte[] sourceByteArray = Encoding.UTF8.GetBytes(source);

                using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
                {
                    byte[] encriptedSource = encryptor.TransformFinalBlock(sourceByteArray, 0, sourceByteArray.Length);
                    result = Convert.ToBase64String(encriptedSource);
                    //Nothing to see here, move along.
                    result = Convert.ToBase64String(Encoding.UTF8.GetBytes(result));
                }
            }

            return result;
        }
        catch (Exception ex)
        {
            throw;
        }
    }

    private string Decrypt(string source)
    {
        try
        {
            string result = "";
            byte[] sourceByte = Convert.FromBase64String(source);
            byte[] sourceFreeOfBase64 = Convert.FromBase64String(Encoding.UTF8.GetString(sourceByte));

            byte[] resultByte;
            int decryptedByteCount = 0;

            using (var aes = new AesManaged { Key = Key, IV = IV, Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7 })
            {
                using (ICryptoTransform AESDecrypt = aes.CreateDecryptor(aes.Key, aes.IV))
                {
                    using (MemoryStream memoryStream = new MemoryStream(sourceFreeOfBase64))
                    {
                        using (CryptoStream cs = new CryptoStream(memoryStream, AESDecrypt, CryptoStreamMode.Read))
                        {
                            resultByte = new byte[sourceFreeOfBase64.Length];
                            //Now that everything works as expected I actually get the number of bytes decrypted!
                            decryptedByteCount = cs.Read(resultByte, 0, resultByte.Length);
                        }
                    }
                }
                //Nothing to see here, move along.
                result = Encoding.UTF8.GetString(resultByte);
                //Use that byte count to get the actual data and discard the padding.
                result = result.Substring(0, decryptedByteCount);
            }

            return result;
        }
        catch (Exception ex)
        {
            throw;
        }
    }

Мне все еще нужно очистить весь код из моего класса от всех тестов, которые я проводил, но это все, что нужно для его работы.Я надеюсь, что это поможет любому с той же проблемой, с которой я столкнулся.

Приветствия.

0 голосов
/ 23 января 2019

Хеш-функция будет возвращать одинаковое количество байтов независимо от ввода, поэтому я подозреваю, что разница в том, как вы конвертируете полученный байт [] обратно в строку в C #, по сравнению с реализацией PHP.

Документы PHP говорят, что хеш-функция выводит результат в нижнем регистре. Это абсолютно не то же самое, что кодировка UTF8, которую вы возвращаете.

Не существует встроенного фреймворка для этого, но посмотрите этот вопрос для нескольких различных методов.

Также стоит отметить, что вы не указываете значение Padding в своем коде C #. AES-CBC является блочным шифром и должен будет использовать некоторую схему заполнения. Вы вполне можете получить исключение для отступов. Я думаю, что для этого потребуется заполнение нулями (документы)

aes.Padding = PaddingMode.Zeros

но я не на 100%

...