dongzou7134
2018-06-18 17:28
浏览 240

使用来自yii2的数据进行C#AES加密解密

Ok, So for several days now I've been trying to figure out what it is I'm doing wrong trying to decrypt information being encrypted in Yii2 and then sent to my windows form program.

I'm using Yii2 encrypt by key method which returns a string in the following format.

[keySalt][MAC][IV][ciphertext]

KeySalt is the key size in bytes. MAC is the length same as the output of MAC_HASH. IV is the length of blocksize.

I have it set in Yii2 to use AES-192-CBC. So the according to the yiiframwork yii-base-security, the block size is 16 and the key size is 24.

My web request looks like the following.

 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)
        {

        }

The macHash algo is set sha256 so I'm assuming the length of mac hash is 32 bytes.

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;
    }

I'm having trouble getting the password to hash correctly. Which means the plain text decryption is definitely wrong.

In fact now that I'm thinking about it again. It's actually throwing a incomplete block exception.

The string bring passed to the function is collected from the server via a web request that returns the ciphertext string from yii2 encrypt by key method. The password being sent to the function is a hard coded string. I'm looking into more info about yii2 and the base type of the steing.

So yii2 says it's just returning a string but I looked up hash_hmac function in php and its returning raw binary output when rawData is set to true which yii2 does.

Update, I went ahead and copied over my web request to the above text above because I'm almost positive there is a issue between the server sending the data and the program receiving the data. I have also followed The advice below and changed Yii2's format to raw and pretty much copied his $response from below. Now I am receiving a error of "Padding is invalid and can't be removed". I am going to continue to trouble shoot and see if I can get it to work. I have tried to set the padding in aes and have returned the same result.

Answer, Thanks to vstm for all of his help in this problem. I would not have been able to solve it without his help. I have changed my code above to reflect the correct code needed to decrypt a string from a server running yii2 as a framwork. I noticed while I was trouble shooting that I had oneByte and computedHash flip flopped. So I have changed the code above to reflect the correct way. Again it was vstm's help by instructing me to set the output from yii2 to raw output and in the reading of the bytes that made this so difficult.

  • 写回答
  • 好问题 提建议
  • 关注问题
  • 收藏
  • 邀请回答

1条回答 默认 最新

  • dongpeihui1051 2018-06-18 20:14
    已采纳

    Your Key derivation (HKDF) is not entirely correct.

    This line does not the same as chr(1):

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

    This would return in a byte array like [0x31], and with chr(1) should actually be [0x01]:

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

    Then when computing the encryption key you have the key and the payload mixed up. You supply one as the key and hash the computedHash when in fact it should be reversed:

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

    I also added another step to only copy the needed bytes into prKey (otherwise it would use a key length of 32, and in my tests it failed).

    You can also set aes.Padding = PaddingMode.PKCS7; since this is what is used in YII2 (openssl).

    So the following worked in my tests:

        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;
        }
    

    I had to change the first argument to byte[] because I used base64 encoding to exchange the data. I could not create a string from the binary data (I tried the System.Text.Encoding.Default.GetString but it would never result in the same binary array). So you should really check that the bytes are transferred correctly (like with a hex-editor or somesuch).

    This is what I meant with getting the response as raw bytes[]:

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

    This worked on my side, using the stringResponse also gave me the "incomplete block" exception. Of course this is a simple example, I don't know how you make the HTTP request to the yii app, if this simple example does not help you should post your http-request on the .net side as well.

    Also on the YII side I used the following code in the controller to ensure the result is treated as binary:

    $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();
    
    已采纳该答案
    评论
    解决 无用
    打赏 举报

相关推荐 更多相似问题