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