【问题标题】:C# AES encryption decryption with data from yii2使用来自 yii2 的数据进行 C# AES 加密解密
【发布时间】:2018-11-27 14:34:36
【问题描述】:

好的,所以几天来我一直在试图弄清楚我在尝试解密在 Yii2 中加密的信息然后发送到我的 Windows 窗体程序时做错了什么。

我正在使用 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;
    }

我无法让密码正确散列。这意味着纯文本解密肯定是错误的。

事实上,现在我又在考虑它了。它实际上抛出了一个不完整的块异常。

传递给函数的字符串是通过 web 请求从服务器收集的,该请求返回来自 yii2 加密方法的密文字符串。发送给函数的密码是硬编码字符串。我正在研究有关 yii2 和 steing 的基本类型的更多信息。

所以 yii2 说它只是返回一个字符串,但我在 php 中查找了 hash_hmac 函数,当 rawData 设置为 true 时,它​​返回的原始二进制输出是 yii2 所做的。

更新,我继续将我的网络请求复制到上面的文本,因为我几乎可以肯定发送数据的服务器和接收数据的程序之间存在问题。我也遵循了下面的建议并将 Yii2 的格式更改为原始格式,并且几乎从下面复制了他的 $response。现在我收到“填充无效且无法删除”的错误。我将继续排除故障,看看我是否可以让它工作。我试图在 aes 中设置填充并返回相同的结果。

回答,感谢 vstm 在这个问题上提供的所有帮助。如果没有他的帮助,我将无法解决它。我已经更改了上面的代码,以反映从运行 yii2 作为框架的服务器中解密字符串所需的正确代码。我在解决问题时注意到我的 oneByte 和 computedHash 失败了。所以我改变了上面的代码以反映正确的方法。同样是 vstm 的帮助,它指示我将 yii2 的输出设置为原始输出,并在读取字节时使这变得如此困难。

【问题讨论】:

  • 那么到底是什么问题,是有异常还是生成的明文错误?你是在 YII 端使用基于密码还是基于密钥的加密?
  • 我在yii2中使用的是密钥加密方式。而且我似乎什至无法获得正确散列的密钥。更不用说解密字符串了。
  • 您将密文和密钥作为string 值传递,您如何传输这些?您在两者之间使用 base64 吗?
  • 密文是通过仅返回字符串的 Web 请求从服务器收集的。我不完全确定 Yii2 ir php 使用的是什么基础。我想我需要调查一下。但是随后字符串只是作为 c# 中的字符串传递给函数。密码只是一个硬编码字符串。
  • Yii2 说它是一个字符串值,但是 php 中的 hash_hmac 说如果 rawData 设置为 true,yii2 会返回原始二进制数据。

标签: c# php encryption yii2 aes


【解决方案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,但它永远不会产生相同的二进制数组)。所以你真的应该检查字节是否正确传输(比如使用十六进制编辑器或类似的东西)。

这就是我将响应作为原始bytes[] 的意思:

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

这对我有用,使用stringResponse 也给了我“不完整块”异常。当然这是一个简单的例子,我不知道你是如何向 yii 应用发出 HTTP 请求的,如果这个简单的例子没有帮助你也应该在 .net 端发布你的 http-request。

同样在 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();

【讨论】:

  • 那么我应该以不同的方式从 Web 请求中收集数据吗?就像将内存流变成字节数组而不是将其收集为字符串一样?
  • 您是否像以前一样尝试过?就像只提供带有ToByteArray(data) 的字符串?也许它有效。
  • 我得到一个异常被抛出,“输入数据不是一个完整的块”。
  • 我在尝试将 base64 编码数据转换为字符串时遇到了同样的异常。如果可能的话,我会对它进行 base64 编码。否则我必须稍后检查是否可以修复此异常。
  • 嗯,你能从服务器获取响应,因为原始字节[]数组可能会起作用。
猜你喜欢
  • 2021-11-04
  • 1970-01-01
  • 1970-01-01
  • 2014-01-14
  • 1970-01-01
  • 2011-03-06
  • 2018-12-18
  • 2011-02-28
相关资源
最近更新 更多