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

时间:2019-01-23 13:48:33

标签: c# php encryption aes

所以我现在不允许修改这段php代码,主要是因为它很旧并且可以正常工作。

  

警告!非常糟糕的代码说明。 IV不会被随机化,也不会与输出一起存储。我不是问这个,因为我想   我问是因为我需要。我还计划在工作时进行重构,并使用可靠的密码程序完成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;
}    

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

我希望能够加密该数据,以便可以查询我的实体列表,而不必解密所有实体。而且我想解密过滤实体的某些属性,以便它们实际上可以有用。

现在,为此,我创建了一个CryptoHelper,它将执行此操作,除非它不会。

我尝试在构造函数中计算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;
    }

然后使用“加密”和“解密”方法,当我使用测试字符串对其进行测试时,它们工作得很好。唯一的问题是字符串的末尾有一些填充,但是考虑到任何用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;
        }
    }

我非常确定这里的主要问题在于php行$iv = substr(hash('sha256', $param2), 0, 16);。我检查了php和C#中两个哈希函数的结果,它们完全相同。

根据我一直在阅读的php,将字符串视为字节数组(如果我错了,请纠正我),因此16个字符的字符串应足以获取16个字节的数组和128个块。但是在C#中,当我得到16个字节的数组并将其转换为字符串时,我得到的32个字符的字符串与执行$iv = substr(hash('sha256', $param2), 0, 32);时相同。

所以我的问题是,如何在C#中获得与该行$iv = substr(hash('sha256', $param2), 0, 16);相同的字节数组结果?这有可能吗?

2 个答案:

答案 0 :(得分:1)

无论输入什么内容,哈希函数都将返回相同数量的字节,因此与PHP实现相比,我怀疑在将所得的byte []转换回C#中的字符串方面有所不同。

PHP docs说,哈希函数以小写的十六进制形式输出结果。这绝对不同于您要返回的UTF8编码。

没有内置的框架方法可以执行此操作,但请查看this SO question中的几种不同方法。

还值得注意的是,您没有在C#代码中指定Padding值。 AES-CBC是分组密码,将需要使用某些填充方案。您很可能会得到填充异常。我认为它将需要零填充(docs)

aes.Padding = PaddingMode.Zeros

但我不是100%

答案 1 :(得分:0)

好吧,我设法以一种不错的方式解决了这个问题。

按照@ ste-fu的建议,我试图摆脱我能找到的每一个编码。

但是我仍然离正确的Key和IV还差得远。所以我用php做了一些测试。我制作了var_dump的IV,得到了一个整齐的16长度数组,字节显示为整数。

  

var_dump结果数组始终在[1]中开始。被告知。

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

这引起了我的极大兴趣,认为如果我正确使用了十六进制字符串,我应该能够将字符串中的每个字符转换为等效字节。瞧,我在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();
    }

这对于IV来说效果很好。现在,我只需要对密钥执行相同的操作。所以我做了,只是发现我有一个64个长度的字节数组。那很奇怪,但是还可以。在php中进行更多测试。

因为php Key的行为与IV相同,这是很有意义的,但我不知道openssl加密函数如何允许64位长度的Key。因此,我尝试使用前32个字符组成的密钥对相同的数据进行加密和解密。 $ky = substr(hash('sha256', $param1), 0, 32); 它给了我与完整Key相同的结果。因此,我的有根据的猜测是,openssl仅需要编码所需的字节即可。实际上,自从我测试了长度为1、16、20、32、33和50的子字符串以来,它将花费任何时间。如果字符串的长度大于32,则函数本身将对其进行剪切。

无论如何,我只需要获取Key hex的前32个字符,并使用新函数将它们转换为字节数组,就可以得到Key。 因此,主要的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;
        }
    }

我仍然需要从我所做的所有测试中清除班级中的所有代码,但这仅是使其工作所需的全部。 我希望这对遇到我同样问题的人有帮助。

干杯。