Search code examples
c#oracleplsqldes

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


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)?


Solution

  • 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).