所以我现在不允许修改这段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);
相同的字节数组结果?这有可能吗?
答案 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;
}
}
我仍然需要从我所做的所有测试中清除班级中的所有代码,但这仅是使其工作所需的全部。 我希望这对遇到我同样问题的人有帮助。
干杯。