How to replicate oracle DBMS_OBFUSCATION_TOOLKIT.DESEncrypt() in C# using System.Security.Cryptography.DES?

huangapple go评论85阅读模式
英文:

How to replicate oracle DBMS_OBFUSCATION_TOOLKIT.DESEncrypt() in C# using System.Security.Cryptography.DES?

问题

我需要在C#中复制与一个旧的存储过程生成的相同输出,该存储过程(我无法修改)在内部使用DBMS_OBFUSCATION_TOOLKIT.DESEncrypt来加密ASCII密码(我确定只有ASCII字符)。

我的C#代码只有在输入字符串长度不超过8个字节时才能得到与Oracle相同的结果。

我将问题缩小到Oracle的DESEncryptSystem.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       = 3132333435363738397E7E7E7E7E7E
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.Modedes.Padding的组合,但只能使情况变得更糟:至少使用上述设置,只要输入字符串长度不超过8个字节,我就可以得到与Oracle相同的结果,其他尝试都失败了,甚至更短的字符串也不行。

有人知道Oracle是如何得到那个字符串的吗(我不是加密专家)?

英文:

I need to replicate in C# the same output generated by a legacy stored procedure (that I cannot modify) that internally uses DBMS_OBFUSCATION_TOOLKIT.DESEncrypt to encrypt an ASCII password (I am sure there are only ascii characters).

My C# code gets same results produced by oracle only when the input string not exceed 8 bytes in lenght.

I narrowed down the problem to the fact that oracle's DESEncrypt does not produce the same output as System.Security.Cryptography.DES, at least for how I am using it.

I came up with this Oracle example code to illustrate the expected behavior:

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;

The above code prints the HEX dump of the the encryption key bytes, of the input bytes, and of the resulting encrypted bytes, as seen by oracle:

KEY       = 4D594352594B4559
SRC       = 3132333435363738397E7E7E7E7E7E7E
ENCRYPTED = F2E238B83939CBC1C33331A198463076

So, I tried to replicate the same result by checking these values in a NUnit test case:

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

The input bytes are the same, but the output differs after the eight byte: this is the message produced by the last assert (the one that fails):

String lengths are both 32. Strings differ at index 17.
Expected: "F2E238B83939CBC1C33331A198463076"
But was:  "F2E238B83939CBC1C4388144A4F16DA6"
----------------------------^

I tried various combinations of des.Mode and des.Padding, but I only managed to get things worse: at least with the above settings I get same oracle results as long as the input string is long up to 8 bytes, other attempts made fail also shorter strings.

Does anyone have any idea of what is Oracle doing to get that string (I am no encryption expert)?

答案1

得分: 1

如果使用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;                                // 修复1:应用CBC模式
des.Padding = PaddingMode.None;
des.Key = key;
des.IV = new byte[8];                                     // 修复2:应用零IV
using ICryptoTransform crypt = des.CreateEncryptor();

byte[] encrypted = crypt.TransformFinalBlock(src, 0, src.Length);

// 这些是正确的
Assert.That(ToHex(key), Is.EqualTo("4D594352594B4559"));
Assert.That(ToHex(src), Is.EqualTo("3132333435363738397E7E7E7E7E7E7E"));

// 在修复之前失败,在修复之后成功
Assert.That(ToHex(encrypted), Is.EqualTo("F2E238B83939CBC1C33331A198463076"));

通过DESENCRYPT的文档,我找不到关于模式的参考,只有相关的DES3ENCRYPT有提到。在那里,描述了使用CBC作为模式(但这里也没有提到使用哪个IV)。
可能已经知道,但为了安全起见,应该再次提到:DES已经被弃用且不安全。同时,使用静态IV(如零IV)是一种漏洞。

英文:

The ciphertext can be reproduced if CBC is used as mode and a zero vector (8 times 0x00 values) is applied as 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 before the fix and works after the fix
Assert.That(ToHex(encrypted), Is.EqualTo("F2E238B83939CBC1C33331A198463076"));

With the documentation of DESENCRYPT I could find no reference to the mode, only with the related DES3ENCRYPT. There it is described that CBC is used as mode (it is missing however also here which IV is used).
Probably already known but to be on the safe side it should be mentioned again: DES is deprecated and insecure. Also the use of a static IV (like a zero IV is a vulnerability).

huangapple
  • 本文由 发表于 2023年7月27日 17:09:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/76778185.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定