我需要在 c# 中复制由遗留存储过程(我无法修改)生成的相同输出,该存储过程在内部使用 DBMS_OBFUSCATION_TOOLKIT.DESEncrypt 来加密 ASCII 密码(我确信只有 ascii 字符)。
仅当输入字符串长度不超过 8 个字节时,我的 C# 代码才能获得与 Oracle 生成的相同结果。
我将问题范围缩小到这样一个事实:oracle 的 DESEncrypt 不会产生与 System.Security.Cryptography.DES 相同的输出,至少就我使用它的方式而言是这样。
我想出了这个 Oracle 示例代码来说明预期的行为:
declare
key raw(8);
src raw(16);
encrypted raw(16);
begin
key := UTL_RAW.cast_to_raw('MYCRYKEY');
src := utl_raw.cast_to_raw('123456789~~~~~~~');
encrypted := DBMS_OBFUSCATION_TOOLKIT.DESEncrypt(
input => src,
key => UTL_RAW.cast_to_raw('MYCRYKEY'));
dbms_output.put_line('KEY = ' || key);
dbms_output.put_line('SRC = ' || src);
dbms_output.put_line('ENCRYPTED = ' || encrypted);
end;
上面的代码打印了加密密钥字节、输入字节以及生成的加密字节的十六进制转储,如 oracle 所示:
KEY = 4D594352594B4559
SRC = 3132333435363738397E7E7E7E7E7E7E
ENCRYPTED = F2E238B83939CBC1C33331A198463076
因此,我尝试通过在 NUnit 测试用例中检查这些值来复制相同的结果:
static string ToHex(byte[] bytes)
{
return BitConverter.ToString(bytes).Replace("-", "");
}
[Test]
public void DESTest()
{
byte[] key = Encoding.ASCII.GetBytes("MYCRYKEY");
byte[] src = Encoding.ASCII.GetBytes("123456789~~~~~~~");
using DES des = DES.Create();
des.Key = key;
des.Mode = CipherMode.ECB;
des.Padding = PaddingMode.None;
using ICryptoTransform crypt = des.CreateEncryptor();
byte[] encrypted = crypt.TransformFinalBlock(src, 0, src.Length);
// these are OK
Assert.That(ToHex(key), Is.EqualTo("4D594352594B4559"));
Assert.That(ToHex(src), Is.EqualTo("3132333435363738397E7E7E7E7E7E7E"));
// this one fails
Assert.That(ToHex(encrypted), Is.EqualTo("F2E238B83939CBC1C33331A198463076"));
}
输入字节相同,但输出在八个字节之后有所不同:这是最后一个断言(失败的断言)产生的消息:
String lengths are both 32. Strings differ at index 17.
Expected: "F2E238B83939CBC1C33331A198463076"
But was: "F2E238B83939CBC1C4388144A4F16DA6"
----------------------------^
我尝试了 des.Mode 和 des.Padding 的各种组合,但我只会让事情变得更糟:至少使用上述设置,只要输入字符串长达 8 个字节,我就会得到相同的 oracle 结果,其他尝试较短的字符串也会失败。
有谁知道 Oracle 正在做什么来获取该字符串(我不是加密专家)?
谢谢你
如果使用 CBC 作为模式并且使用零向量(8 乘以 0x00 值)作为 IV,则可以再现密文:
byte[] key = Encoding.ASCII.GetBytes("MYCRYKEY");
byte[] src = Encoding.ASCII.GetBytes("123456789~~~~~~~");
using DES des = DES.Create();
des.Mode = CipherMode.CBC; // Fix 1: Apply CBC
des.Padding = PaddingMode.None;
des.Key = key;
des.IV = new byte[8]; // Fix 2: Apply an zero IV
using ICryptoTransform crypt = des.CreateEncryptor();
byte[] encrypted = crypt.TransformFinalBlock(src, 0, src.Length);
// these are OK
Assert.That(ToHex(key), Is.EqualTo("4D594352594B4559"));
Assert.That(ToHex(src), Is.EqualTo("3132333435363738397E7E7E7E7E7E7E"));
// this one fails
Assert.That(ToHex(encrypted), Is.EqualTo("F2E238B83939CBC1C33331A198463076"));