dqd3690 2019-01-23 13:48
浏览 95
已采纳

获取一个等于php十六进制字符串的C#字节数组

So I have this piece of php code that I'm not allowed to modify for now, mainly because it's old and works properly.

Warning! Very bad code overal. the IV is not being randomized neither stored with the output. I'm not asking this because I want to, I'm asking because I need to. I'm also planning on refactoring when I get this working and completing my C# code with actually reliable cyphering code.

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

I want to do the same in C# because I'm getting an entity with all its fields encrypted with that code.

I want to be able to encrypt that data so I can query my entity list whithout having to decrypt all the entities. And I want to decrypt some properties of the filtered entities so they can actually be useful.

Now, for that matter I created a CryptoHelper that will do this, except it doesn't.

I try to calculate the Key and IV in the constructor:

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

And then use a Encrypt and Decrypt methods that are working pretty well when I test them with a test string. The only problem is that the string have some padding at the end, but it's kind of a minor problem considering that any string encrypted with the php method results in gibberish.

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

I'm pretty sure that the main problem here lies in the php line $iv = substr(hash('sha256', $param2), 0, 16);. I checked the results of both hash functions in php and C# and are exactly the same.

From what I've been reading php treats strings as byte arrays (correct me if I'm wrong) so a 16 char string should be enough to get a 16 byte array and a 128 block. But in C#, when I get the 16 byte array and convert it to a string I get a 32 char string that is the same as if I did $iv = substr(hash('sha256', $param2), 0, 32);.

So my question is, how do I get the same byte array result in C# that I get in this line $iv = substr(hash('sha256', $param2), 0, 16); of php? Is this even possible?

  • 写回答

2条回答 默认 最新

  • dongqia3502 2019-01-29 08:05
    关注

    Well, I managed to solve this in a not so bad manner.

    Following @ste-fu advice I tried to get rid of every piece of encoding that I could find.

    But I still wasn't anywhere close to getting the Key and IV right. So I did some testing with php. I made a var_dump of the IV and got a neat 16 length array with bytes shown as integers.

    var_dump result array starts allways in [1]. Be advised.

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

    That peaked my interest, thinking that if I had the hex string right I should be able to convert each char in the string to it's equivalent byte. Lo and behold, I made this function in 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();
        }
    

    And this worked very well for the IV. Now I just had to do the same thing for the key. And so I did, just to find that I had a 64 length byte array. That's weird, but ok. More testing in php.

    Since it does make sense that the php Key behaves the same as the IV I didn't get how the openssl encryption functions allowed a 64 length Key. So I tryed to encrypt and decrypt the same data with a Key made from the first 32 chars. $ky = substr(hash('sha256', $param1), 0, 32); And it gave me the same result as with the full Key. So, my educated guess is that openssl just takes the bytes necesary for the encoding to work. In fact it will take anything since I tested with substrings of 1, 16, 20, 32, 33 and 50 length. If the length of the string is bigger than 32 the function itself will cut it.

    Anyway, i just had to get the first 32 chars of the Key hex and use my new function to convert them into a byte array and I got my Key. So, the main C# code right now looks like this:

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

    I still need to clean all the code from my class from all the testing I did, but this is all that's needed to make it work. I hope this helps anybody with the same problem that I faced.

    Cheers.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥50 导入文件到网吧的电脑并且在重启之后不会被恢复
  • ¥15 (希望可以解决问题)ma和mb文件无法正常打开,打开后是空白,但是有正常内存占用,但可以在打开Maya应用程序后打开场景ma和mb格式。
  • ¥15 绘制多分类任务的roc曲线时只画出了一类的roc,其它的auc显示为nan
  • ¥20 ML307A在使用AT命令连接EMQX平台的MQTT时被拒绝
  • ¥20 腾讯企业邮箱邮件可以恢复么
  • ¥15 有人知道怎么将自己的迁移策略布到edgecloudsim上使用吗?
  • ¥15 错误 LNK2001 无法解析的外部符号
  • ¥50 安装pyaudiokits失败
  • ¥15 计组这些题应该咋做呀
  • ¥60 更换迈创SOL6M4AE卡的时候,驱动要重新装才能使用,怎么解决?