Расшифровка шифрования 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();