Victor
Victor

Reputation: 1271

Different DES Encryption in Oracle and .NET

For some reason, I am getting different encoding results from Oracle DMBS_CRYPTO and .NET implementation of DESCryptoServiceProvider using the same encryption key.

For the DB I am using DBMS_CRYPTO.ENCRYPT function with the following encryption type:

   encryption_type    PLS_INTEGER := DBMS_CRYPTO.ENCRYPT_DES
                                + DBMS_CRYPTO.CHAIN_CBC
                                +DBMS_CRYPTO.PAD_PKCS5;

The DB Function

 FUNCTION encrypt (p_plainText VARCHAR2) RETURN RAW DETERMINISTIC
 IS
    encrypted_raw      RAW (2000);
 BEGIN
    encrypted_raw := DBMS_CRYPTO.ENCRYPT
    (
       src => UTL_RAW.CAST_TO_RAW (p_plainText),
       typ => encryption_type,
       key => encryption_key
    );
   RETURN encrypted_raw;
 END encrypt;

And here's the C# piece:

            DESCryptoServiceProvider cryptoProvider = new DESCryptoServiceProvider();
        MemoryStream memoryStream = new MemoryStream();
        CryptoStream cryptoStream = new CryptoStream(memoryStream,
            cryptoProvider.CreateEncryptor(bytes, bytes), CryptoStreamMode.Write);
        StreamWriter writer = new StreamWriter(cryptoStream);
        writer.Write(originalString);
        writer.Flush();
        cryptoStream.FlushFinalBlock();
        writer.Flush();
        return Convert.ToBase64String(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);

What could be the reason for having different encryption results?

Upvotes: 0

Views: 2405

Answers (3)

Yves Rochon
Yves Rochon

Reputation: 1562

There are basic differences between .NET and Oracle encryption.

For instance, Oracle's default Initialization Value (IV) in hex is "0123456789ABCDEF". .NET 's default Initialization Value (IV) in hex is "C992C3154997E0FB". Also, there are several options for padding modes in .NET: ANSIX923, Zeros, ISO10126, PKCS7 and None.

In the sample code below, you should be able to do without the two lines of code used for the custom padding, and specify ANSIX923 for the padding mode. We had to accommodate a faux pas from the DBAs who decided to pad strings with the tilde "~" character, so I included the code to serve as an example that may help others in a similar situation.

Here's a simple set of methods that worked for our solution:

    private static string EncryptForOracle(string message, string key)
    {

        string iv = "0123456789ABCDEF";

        int lengthOfPaddedString;
        message = PadMessageWithCustomChar(message, out lengthOfPaddedString);

        byte[] textBytes = new byte[lengthOfPaddedString];
        textBytes = ASCIIEncoding.ASCII.GetBytes(message);

        byte[] keyBytes = new byte[key.Length];
        keyBytes = ASCIIEncoding.ASCII.GetBytes(key);

        byte[] ivBytes = new byte[iv.Length];
        ivBytes = StringUtilities.HexStringToByteArray(iv);
        byte[] encrptedBytes = Encrypt(textBytes, keyBytes, ivBytes);

        return StringUtilities.ByteArrayToHexString(encrptedBytes);
    }

    /// <summary>
    // On the Oracle side, our DBAs wrapped the call to the toolkit encrytion function to pad with a ~, I don't recommend
    // doing down this path, it is prone to error.
    // we are working with blocks of size 8 bytes, this method pads the last block with ~ characters.
    /// </summary>
    /// <param name="message"></param>
    /// <param name="lengthOfPaddedString"></param>
    /// <returns></returns>
    private static string PadMessageWithCustomChar(string message, out int lengthOfPaddedString)
    {
        int lengthOfData = message.Length;
        int units;
        if ((lengthOfData % 8) != 0)
        {
            units = (lengthOfData / 8) + 1;
        }
        else
        {
            units = lengthOfData / 8;
        }

        lengthOfPaddedString = units * 8;

        message = message.PadRight(lengthOfPaddedString, '~');
        return message;
    }


    public static byte[] Encrypt(byte[] clearData, byte[] Key, byte[] IV)
    {
        MemoryStream ms = new MemoryStream();
        // Create a symmetric algorithm.
        TripleDES alg = TripleDES.Create();
        alg.Padding = PaddingMode.None;
        // You should be able to specify ANSIX923 in a normal implementation 
        // We have to use none because of the DBA's wrapper
        //alg.Padding = PaddingMode.ANSIX923;

        alg.Key = Key;
        alg.IV = IV;

        CryptoStream cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write);
        cs.Write(clearData, 0, clearData.Length);
        cs.Close();

        byte[] encryptedData = ms.ToArray();
        return encryptedData;
    }

Put these methods in a static StringUtilities class:

    /// <summary>
    /// Method to convert a string of hexadecimal character pairs
    /// to a byte array.
    /// </summary>
    /// <param name="hexValue">Hexadecimal character pair string.</param>
    /// <returns>A byte array </returns>
    /// <exception cref="System.ArgumentNullException">Thrown when argument is null.</exception>
    /// <exception cref="System.ArgumentException">Thrown when argument contains an odd number of characters.</exception>
    /// <exception cref="System.FormatException">Thrown when argument contains non-hexadecimal characters.</exception>
    public static byte[] HexStringToByteArray(string hexValue)
    {
        ArgumentValidation.CheckNullReference(hexValue, "hexValue");

        if (hexValue.Length % 2 == 1)
            throw new ArgumentException("ERROR: String must have an even number of characters.", "hexValue");

        byte[] values = new byte[hexValue.Length / 2];

        for (int i = 0; i < values.Length; i++)
            values[i] = byte.Parse(hexValue.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber);

        return values;
    }   // HexStringToByteArray()


    /// <summary>
    /// Method to convert a byte array to a hexadecimal string.
    /// </summary>
    /// <param name="values">Byte array.</param>
    /// <returns>A hexadecimal string.</returns>
    /// <exception cref="System.ArgumentNullException">Thrown when argument is null.</exception>
    public static string ByteArrayToHexString(byte[] values)
    {
        ArgumentValidation.CheckNullReference(values, "values");

        StringBuilder hexValue = new StringBuilder();

        foreach (byte value in values)
        {
            hexValue.Append(value.ToString("X2"));
        }

        return hexValue.ToString();
    }   // ByteArrayToHexString()

    public static byte[] GetStringToBytes(string value)
    {
        SoapHexBinary shb = SoapHexBinary.Parse(value);
        return shb.Value;
    }

    public static string GetBytesToString(byte[] value)
    {
        SoapHexBinary shb = new SoapHexBinary(value);
        return shb.ToString();
    } 

If you are using the ANSIX923 padding mode from the .NET side, your PL/SQL code will look like this because you have to read the last two bytes to figure out how many bytes were padded and remove them from the string so you return the original string.

create or replace FUNCTION DecryptPassword(EncryptedText IN VARCHAR2,EncKey IN VARCHAR2) RETURN VARCHAR2
IS
encdata RAW(2000);
numpad NUMBER;
result VARCHAR2(100);
BEGIN
  encdata:=dbms_obfuscation_toolkit.DES3Decrypt(input=&amp;gt;hextoraw(EncryptedText),key=&amp;gt;UTL_RAW.CAST_TO_RAW(EncKey));

  result :=rawtohex(encdata);
  numpad:=substr(result,length(result)-1);
  result:= substr(result,1,length(result)-(numpad*2));
  result := hextoraw(result);
  result := utl_raw.cast_to_varchar2(result);
  return result;

END DecryptPassword;

Upvotes: 1

Pete Baughman
Pete Baughman

Reputation: 3034

According to the MSDN documentation, your C# code uses a default padding of PKCS7 while it looks like your Oracle code is using PKCS5. That would probably be a good place to start. While you're at it, you should probably explicitly set your block chaining mode, too.

Edit: Sorry, PKCS5 and PKCS7 should pad the plaintext with the same bytes out to the same length. That may not be it. Vincent Malgrat's suggestion to try with raw bytes for the plaintext to eliminate an encoding issue sounds like a good place to start. The C# code will treat your string as unicode. In Oracle, it will use whatever the encoding of the database is set to, I think.

Upvotes: 1

techno
techno

Reputation: 6500

The problem may be with the Padding of the Key you have used.Check with different Keys/Different PADDING

Upvotes: 0

Related Questions