Расшифровка шифрования C# AES с данными из yii2

Итак, вот уже несколько дней я пытаюсь понять, что я делаю неправильно, пытаясь расшифровать информацию, зашифрованную в Yii2, а затем отправленную в мою программу Windows Form.

Я использую метод шифрования Yii2 по ключу, который возвращает строку в следующем формате.

[KeySalt][MAC][IV][шифротекста]

KeySalt - это размер ключа в байтах. MAC - длина, совпадающая с выводом MAC_HASH. IV - длина блока.

В Yii2 он настроен на использование AES-192-CBC. Таким образом, согласно yiiframwork yii-base-security, размер блока равен 16, а размер ключа равен 24.

Мой веб-запрос выглядит следующим образом.

 try
        {

            var data = new MemoryStream();
            var WR = (HttpWebRequest)WebRequest.Create(url);
            ServicePointManager.Expect100Continue = true;
            ServicePointManager.DefaultConnectionLimit = 9999;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3;
            WR.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
            WR.UserAgent = "MultiPoolMiner V" + Application.ProductVersion;
            var Response = WR.GetResponse();

            var SS = Response.GetResponseStream();
            SS.ReadTimeout = 20 * 100;
            SS.CopyTo(data);

            Response.Close();

            byte[] dataByteArray = data.ToArray();

            string plainTextData = Utils.AesCipher.DecryptString(dataByteArray, password);
            //check if ticks from the db is bigger than 0;

        }
        catch (Exception e)
        {

        }

Алгоритм macHash установлен на sha256, поэтому я предполагаю, что длина mac-хэша составляет 32 байта.

public static string DecryptString(string data, string password)
    {

        byte[] allBytes = ToByteArray(data);
        byte[] one = ToByteArray("1");
        string plaintext = null;
        // this is all of the bytes

        byte[] passwordByteArray = ToByteArray(password);

        using (var aes = Aes.Create())
        {
            aes.KeySize = KeySize;
            aes.BlockSize = BlockSize;
            aes.Mode = CipherMode.CBC;

            // get the key salt
            byte[] keySalt = new byte[KeySize / 8];
            Array.Copy(allBytes, keySalt, keySalt.Length);

            // Yii2 says 
            //$key = $this->hkdf($this->kdfHash, $secret, $keySalt, $info, $keySize);
            //
            //Yii2 hkdf says
            //$prKey = hash_hmac($algo, $inputKey, $salt, true);
            //$hmac = '';
            //$outputKey = '';
            //for ($i = 1; $i <= $blocks; $i++) {
            //  $hmac = hash_hmac($algo, $hmac . $info . chr($i), $prKey, true);
            //  $outputKey .= $hmac;
            //}
            // chr($i) is the char byte of 1; 
            // the blocksize is 1
            // info here is nothing

            // hash first key with keysalt and password
            HMACSHA256 hmac = new HMACSHA256(keySalt);
            byte[] computedHash = hmac.ComputeHash(passwordByteArray);

            // hash primary key with one byte and computed hash
            HMACSHA256 hmac2 = new HMACSHA256(computedHash);
            byte[] prKey = hmac2.ComputeHash(one);
            byte[] key = new byte[KeySize/8];
            Array.Copy(prKey, 0, key, key.Length);


            // if we want to verify the mac hash this is where we would do it. 
            // Yii2 encryption data. 
            // $encrypted = openssl_encrypt($data, $this->cipher, $key, OPENSSL_RAW_DATA, $iv);
            //
            //$authKey = $this->hkdf($this->kdfHash, $key, null, $this->authKeyInfo, $keySize);
            //hashed = $this->hashData($iv. $encrypted, $authKey);
            //hashed = [macHash][data]

            // get the MAC code
            byte[] MAC = new byte[MacHashSize / 8];
            Array.Copy(allBytes, keySalt.Length, MAC, 0, MAC.Length);

            // get our IV
            byte[] iv = new byte[BlockSize / 8];
            Array.Copy(allBytes, (keySalt.Length + MAC.Length), iv, 0, iv.Length);

            // get the data we need to decrypt
            byte[] cipherBytes = new byte[allBytes.Length - iv.Length - MAC.Length - keySalt.Length];
            Array.Copy(allBytes, (keySalt.Length + MAC.Length + iv.Length), cipherBytes, 0, cipherBytes.Length);

            // Create a decrytor to perform the stream transform.
            var decryptor = aes.CreateDecryptor(key, iv);

            // Create the streams used for decryption. 
            using (MemoryStream msDecrypt = new MemoryStream(cipherBytes))
            {
                using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                    {
                         //Read the decrypted bytes from the decrypting stream 
                         //and place them in a string.
                        plaintext = srDecrypt.ReadToEnd();
                    }
                }
            }
        }

        return plaintext;
    }

    public static byte[] ToByteArray(string value)
    {
        byte[] allBytes = new byte[value.Length];
        int i = 0;
        foreach (byte bite in value)
        {
            allBytes[i] = Convert.ToByte(bite);
            i++;
        }

        return allBytes;
    }

У меня проблемы с получением пароля для правильного хеширования. Что означает, что расшифровка обычного текста определенно неверна.

На самом деле теперь, когда я снова думаю об этом. Это на самом деле вызывает исключение неполного блока.

Строка переноса, переданная функции, собирается с сервера через веб-запрос, который возвращает строку зашифрованного текста из yii2, зашифрованного методом ключа. Пароль, отправляемый в функцию, является жестко закодированной строкой. Я изучаю дополнительную информацию о yii2 и базовом типе steing.

Таким образом, yii2 говорит, что он просто возвращает строку, но я посмотрел функцию hash_hmac в php и возвращает необработанный двоичный вывод, когда rawData имеет значение true, что делает yii2.

Обновление, я продолжил и скопировал свой веб-запрос к приведенному выше тексту, потому что я почти уверен, что существует проблема между сервером, отправляющим данные, и программой, получающей данные. Я также следовал приведенным ниже советам и изменил формат Yii2 на raw и в значительной степени скопировал его ответ $ снизу. Теперь я получаю сообщение об ошибке "Заполнение недействительно и не может быть удалено". Я собираюсь продолжать стрелять в проблемы и посмотреть, смогу ли я заставить его работать. Я попытался установить отступы в aes и вернул тот же результат.

Ответ, спасибо vstm за всю его помощь в этой проблеме. Я бы не смог решить это без его помощи. Я изменил свой код выше, чтобы отразить правильный код, необходимый для расшифровки строки с сервера, на котором запущен yii2, как фреймворк. Во время стрельбы я заметил, что у меня есть oneByte и computedHash триггер. Поэтому я изменил код выше, чтобы отразить правильный путь. Опять же, это была помощь vstm, указав мне установить вывод из yii2 в необработанный вывод и в чтении байтов, которые сделали это настолько трудным.

1 ответ

Решение

Ваш ключ ключа (HKDF) не совсем правильно.

Эта строка не совпадает с chr(1):

byte[] one = ToByteArray("1");

Это будет возвращаться в байтовом массиве, как [0x31], и с chr(1) должно быть на самом деле [0x01]:

byte[] one = new byte[]{1};

Затем при вычислении ключа шифрования у вас есть ключ и полезная нагрузка. Вы поставляете one в качестве ключа и хэш computedHash когда на самом деле это должно быть наоборот:

HMACSHA256 hmac2 = new HMACSHA256(computedHash);                
byte[] prKeyFull = hmac2.ComputeHash(one);                      
byte[] prKey = new byte[KeySize / 8];                           
Array.Copy(prKeyFull, 0, prKey, 0, prKey.Length); 

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

Вы также можете установить aes.Padding = PaddingMode.PKCS7; так как это то, что используется в YII2 (openssl).

Так что в моих тестах работало следующее:

    public static string DecryptString(byte[] data, string password)
    {

        byte[] allBytes = data;
        byte[] one = new byte[]{1};
        string plaintext = null;
        // this is all of the bytes

        byte[] passwordByteArray = ToByteArray(password);

        using (var aes = Aes.Create())
        {
            aes.KeySize = KeySize;
            aes.BlockSize = BlockSize;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;

            // get the key salt
            byte[] keySalt = new byte[KeySize / 8];
            Array.Copy(allBytes, keySalt, keySalt.Length);

            // Yii2 says
            //$key = $this->hkdf($this->kdfHash, $secret, $keySalt, $info, $keySize);
            //
            //Yii2 hkdf says
            //$prKey = hash_hmac($algo, $inputKey, $salt, true);
            //$hmac = '';
            //$outputKey = '';
            //for ($i = 1; $i <= $blocks; $i++) {
            //  $hmac = hash_hmac($algo, $hmac . $info . chr($i), $prKey, true);
            //  $outputKey .= $hmac;
            //}
            // chr($i) is the char byte of 1;
            // the blocksize is 1
            // info here is nothing

            // hash first key with keysalt and password
            HMACSHA256 hmac = new HMACSHA256(keySalt);
            byte[] computedHash = hmac.ComputeHash(passwordByteArray);

            // hash primary key with one byte and computed hash
            HMACSHA256 hmac2 = new HMACSHA256(computedHash);
            byte[] prKeyFull = hmac2.ComputeHash(one);
            byte[] prKey = new byte[KeySize / 8];
            Array.Copy(prKeyFull, 0, prKey, 0, prKey.Length);

            // if we want to verify the mac hash this is where we would do it.
            // Yii2 encryption data.
            // $encrypted = openssl_encrypt($data, $this->cipher, $key, OPENSSL_RAW_DATA, $iv);
            //
            //$authKey = $this->hkdf($this->kdfHash, $key, null, $this->authKeyInfo, $keySize);
            //hashed = $this->hashData($iv. $encrypted, $authKey);
            //hashed = [macHash][data]

            // get the MAC code
            byte[] MAC = new byte[MacHashSize / 8];
            Array.Copy(allBytes, keySalt.Length, MAC, 0, MAC.Length);

            // get our IV
            byte[] iv = new byte[BlockSize / 8];
            Array.Copy(allBytes, (keySalt.Length + MAC.Length), iv, 0, iv.Length);

            // get the data we need to decrypt
            byte[] cipherBytes = new byte[allBytes.Length - iv.Length - MAC.Length - keySalt.Length];
            Array.Copy(allBytes, (keySalt.Length + MAC.Length + iv.Length), cipherBytes, 0, cipherBytes.Length);

            // Create a decrytor to perform the stream transform.
            var decryptor = aes.CreateDecryptor(prKey, iv);

            // Create the streams used for decryption.
            using (MemoryStream msDecrypt = new MemoryStream(cipherBytes))
            {
                using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                    {
                        //Read the decrypted bytes from the decrypting stream
                        //and place them in a string.
                        plaintext = srDecrypt.ReadToEnd();
                    }
                }
            }
        }

        return plaintext;
    }

Мне пришлось изменить первый аргумент на byte[], потому что я использовал кодировку base64 для обмена данными. Я не мог создать string из двоичных данных (я попробовал System.Text.Encoding.Default.GetString но это никогда не приведет к тому же двоичному массиву). Таким образом, вы должны убедиться, что байты переданы правильно (например, с помощью hex-редактора или чего-то подобного).

Это то, что я имел в виду, получая ответ как сырой bytes[]:

// instead of using this: 
//string stringResponse = await client.GetStringAsync(url);
byte[] newBytes = await client.GetByteArrayAsync(url);
string plaintext = Decrypt.DecryptString(newBytes, "yourpassword");

Это сработало на моей стороне, используя stringResponse также дал мне исключение "неполный блок". Конечно, это простой пример, я не знаю, как вы делаете HTTP-запрос к приложению yii, если этот простой пример не поможет, вы также должны опубликовать свой http-запрос на стороне.net.

Также на стороне YII я использовал следующий код в контроллере, чтобы гарантировать, что результат обрабатывается как двоичный:

$result = $security->encryptByKey($message, $key);

$response = Yii::$app->getResponse();
$response->headers->set('Content-Type', 'application/binary');
$response->format = Response::FORMAT_RAW;
$response->content = $result;
return $response->send();
Другие вопросы по тегам