简体   繁体   English

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

[英]Get a C# byte array equal to a php hex string

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. 因此,我有这段PHP代码目前暂时不允许修改,主要是因为它很旧并且可以正常工作。

Warning! 警告! Very bad code overal. 非常糟糕的代码总体。 the IV is not being randomized neither stored with the output. IV不会被随机化,也不会与输出一起存储。 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. 我还计划在工作时进行重构,并使用可靠的密码程序完成C#代码。

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. 我想在C#中执行相同的操作,因为我正在获取一个实体,该实体的所有字段均已使用该代码加密。

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. 现在,出于这个原因,我创建了一个CryptoHelper,它将执行此操作,除非它不会。

I try to calculate the Key and IV in the constructor: 我尝试在构造函数中计算Key和IV:

    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. 唯一的问题是字符串的末尾有一些填充,但是考虑到任何用php方法加密的字符串都会产生乱码,这是一个小问题。

    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); 我很确定这里的主要问题在于php行$iv = substr(hash('sha256', $param2), 0, 16); . I checked the results of both hash functions in php and C# and are exactly the same. 我检查了php和C#中两个哈希函数的结果,它们完全相同。

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. 从我一直在阅读的php可以将字符串视为字节数组(如果我错了,请纠正我),因此16个字符的字符串应足以获取16个字节的数组和128个块。 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); 但是在C#中,当我得到16个字节的数组并将其转换为字符串时,我得到了一个32个字符的字符串,这与我执行$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); 所以我的问题是,如何在C#中获得与$iv = substr(hash('sha256', $param2), 0, 16);相同的字节数组结果$iv = substr(hash('sha256', $param2), 0, 16); of php? 的PHP? Is this even possible? 这有可能吗?

The hash function will return the same number of bytes whatever the input, so I suspect it is a difference in how you convert the resulting byte[] back to a string in C# compared to the PHP implementation. 不管输入什么,哈希函数将返回相同数量的字节,因此与PHP实现相比,我怀疑在将所得的byte []转换回C#中的字符串方面存在差异。

The PHP docs say that the hash function output the result in lower case hexits. PHP文档说,哈希函数以小写的十六进制形式输出结果。 This is absolutely not the same as the UTF8 encoding that you are returning. 这与您要返回的UTF8编码绝对不同。

There isn't a built in framework way to do this, but check out this SO question for several different methods. 没有内置的框架方法可以执行此操作,但请查看此SO问题以了解几种不同的方法。

Also worth noting is that you do not specify the Padding value in your C# code. 另外值得注意的是,您没有在C#代码中指定Padding值。 AES-CBC is a block cipher and will need to use some padding scheme. AES-CBC是分组密码,将需要使用某些填充方案。 You may well get a padding exception. 您很可能会得到填充异常。 I think that it will need Zero padding (docs) 我认为这将需要零填充(docs)

aes.Padding = PaddingMode.Zeros

but I'm not 100% 但我不是100%

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. 遵循@ ste-fu的建议,我试图摆脱我能找到的每一个编码。

But I still wasn't anywhere close to getting the Key and IV right. 但是我仍然离正确的Key和IV还差得远。 So I did some testing with php. 所以我用php做了一些测试。 I made a var_dump of the IV and got a neat 16 length array with bytes shown as integers. 我制作了IV的var_dump ,得到了一个整齐的16长度数组,其字节显示为整数。

var_dump result array starts allways in [1]. var_dump结果数组始终从[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#: 瞧,我在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. 这对于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. 所以我做了,只是发现我有一个64个长度的字节数组。 That's weird, but ok. 那很奇怪,但是还可以。 More testing in php. 在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. 由于php Key的行为与IV完全相同,因此我没有得到openssl加密功能如何允许使用64位长度的Key。 So I tryed to encrypt and decrypt the same data with a Key made from the first 32 chars. 因此,我尝试使用前32个字符组成的密钥对相同的数据进行加密和解密。 $ky = substr(hash('sha256', $param1), 0, 32); And it gave me the same result as with the full Key. 它给了我与完整Key相同的结果。 So, my educated guess is that openssl just takes the bytes necesary for the encoding to work. 因此,我的有根据的猜测是,openssl仅需要编码所需的字节即可。 In fact it will take anything since I tested with substrings of 1, 16, 20, 32, 33 and 50 length. 实际上,自从我测试了长度为1、16、20、32、33和50的子字符串以来,它将花费任何时间。 If the length of the string is bigger than 32 the function itself will cut it. 如果字符串的长度大于32,则函数本身将对其进行剪切。

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. 无论如何,我只需要获取Key hex的前32个字符,并使用新函数将它们转换为字节数组,就可以得到Key。 So, the main C# code right now looks like this: 因此,现在的主要C#代码如下所示:

    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. 干杯。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM