This discussion is archived
10 Replies Latest reply: Mar 25, 2010 9:16 AM by 843811 RSS

Oracle Triple DES Encryption -> Java Decryption

843811 Newbie
Currently Being Moderated
I'm working with a legacy Oracle system that uses Triple DES encryption, and I'm having problems decrypting the data in java. Here is the pl/sql for the encryption:
   FUNCTION encrypt(p_str IN VARCHAR2, p_key IN RAW) RETURN RAW IS
      v_data   VARCHAR2(255);
      v_retval RAW(255);
   BEGIN
      v_data := RPAD(p_str, CEIL(LENGTH(p_str)/8)*8, CHR(0));
      dbms_obfuscation_toolkit.DES3Encrypt
         (
            input => utl_raw.cast_to_raw(v_data),
            key   => p_key,
            which => g_which,
            encrypted_data => v_retval
         );
      RETURN v_retval;
   END encrypt;

   FUNCTION decrypt(p_raw IN RAW, p_key IN RAW) RETURN VARCHAR2 IS
      v_retval RAW(255);
   BEGIN
      dbms_obfuscation_toolkit.DES3Decrypt
         (
            input => p_raw,
            key   => p_key,
            which => g_which,
            decrypted_data => v_retval
         );
      RETURN RTRIM(utl_raw.cast_to_varchar2(v_retval), CHR(0));
   END decrypt;
Here is the java code I'm using:
import java.security.GeneralSecurityException;
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;


public class TripleDES {
    private String algorithm = "DESede/CBC/NoPadding";
    private IvParameterSpec iv = 
        new IvParameterSpec(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 });
    private Cipher cipher;
    private SecretKey key;

    public TripleDES(String secretString) throws GeneralSecurityException {
        key = new SecretKeySpec(secretString.getBytes(), "DESede");
        cipher = Cipher.getInstance(algorithm);
    }    

    private byte[] encrypt(byte[] bytes) throws GeneralSecurityException {
        cipher.init(Cipher.ENCRYPT_MODE, (Key)key, iv);
        return cipher.doFinal(bytes);
    }

    private byte[] decrypt(byte[] bytes) throws GeneralSecurityException {
        cipher.init(Cipher.DECRYPT_MODE, (Key)key, iv);
        return cipher.doFinal(bytes);
    }

}
I'm using the same key, 123456789123456789123456, but something doesn't match up.

Oracle:
SELECT encrypt('testing!', RAWTOHEX('123456789123456789123456'))
  FROM dual;
returns: "7F6C925B08D478C4"

Java:
    public static void main(String[] args) throws Exception {
    
        try {
            TripleDES x = new TripleDES("123456789123456789123456");
            String value = "testing!";
    
            byte[] encrypted = x.encrypt(value.getBytes());
            String encoded = new String(Hex.encodeHex(encrypted));
            System.out.println("Encrypted: \"" + encoded + "\"");
    
            byte[] decoded = Hex.decodeHex(encoded.toCharArray());
            String decrypted = new String(x.decrypt(decoded));
            System.out.println("Decrypted: \"" + decrypted + "\"");
        }
        catch (Exception e) {
            System.out.println("Error: " + e.toString());
        }
        
    }
Encrypted: "6352d3ff779ab3fa"

Everywhere I've read said that DESede/CBC/NoPadding is the correct algorithm for Triple DES on Oracle. Is something happening to key perhaps to cause this discrepancy?

Thanks for looking and for the replies!
  • 1. Re: Oracle Triple DES Encryption -> Java Decryption
    843811 Newbie
    Currently Being Moderated
    This looks suspicious
    RAWTOHEX('123456789123456789123456')
    because you seem to be hex encoding the bytes of the key string. If the
    key => p_key
    expects the bytes of the key hex encoded then it is OK but of it just expects the bytes of the key then it is wrong.

    Also, what is the value of
    which => g_which
    This seems to determine whether a 16 or 24 byte key is to be used. If a 16 byte key is to be used then you need to duplication that functionality in the Java.
  • 2. Re: Oracle Triple DES Encryption -> Java Decryption
    843811 Newbie
    Currently Being Moderated
    Thanks for the reply. g_which is defined as dbms_obfuscation_toolkit.ThreeKeyMode, so we are using 24 byte keys on the Oracle side.

    I think the reason for RAWTOHEX is to provide the key in the format Oracle expects. If I run the following:
    SELECT encrypt('abc', '123456789123456789123456')
      FROM dual;
    Then I get:
    ORA-28234: key length too short
    ORA-06512: at "SYS.DBMS_OBFUSCATION_TOOLKIT_FFI", line 59
    ORA-06512: at "SYS.DBMS_OBFUSCATION_TOOLKIT", line 180
    But if I run:
    SELECT encrypt('abc', utl_raw.cast_to_raw('123456789123456789123456')), encrypt('abc', RAWTOHEX('123456789123456789123456')),
      FROM dual;
    Then I get:
    D1C4B08530389781 , D1C4B08530389781

    So it doesn't matter if you run an explicit cast_to_raw, or rawtohex, but the API definitely wants the key this way.

    On the java side, I thought that the .getBytes() would do the same thing.
    public TripleDES(String secretString) throws GeneralSecurityException {
         key = new SecretKeySpec(secretString.getBytes(), "DESede");
         cipher = Cipher.getInstance(algorithm);
    }
    But something is still wrong, because this:
        public static void main(String[] args) throws Exception {
            try {
                TripleDES x = new TripleDES("123456789123456789123456");
                String original = "abc";
                System.out.println("Oringal: \"" + original + "\"");
        
                byte[] encrypted = x.encrypt(original);
                String encoded = new String(Hex.encodeHex(encrypted));
                System.out.println("Encrypted: \"" + encoded + "\"");
            }
            catch (Exception e) {
                System.out.println("Error: " + e.toString());
            }
        }
    Returns:
    Oringal: "abc"
    Encrypted: "aae373e9495ac24c"

    What else needs to be done on the java side to match Oracle's return value of D1C4B08530389781?

    Thanks for looking!
  • 3. Re: Oracle Triple DES Encryption -> Java Decryption
    843811 Newbie
    Currently Being Moderated
    I can see nothing obvious wrong with your code. If I can find some documentation of the algorithm I will have another go at solving this.
  • 4. Re: Oracle Triple DES Encryption -> Java Decryption
    843811 Newbie
    Currently Being Moderated
    The closest I can get to documentation for the encrypt/decrypt is [http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/d_obtool.htm#6518|http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96612/d_obtool.htm#6518] but this is pretty pathetic as documentation goes. For example, the statement "The DES algorithm ignores 8 bits of the 64-bit key that is supplied; however, developers must supply a 64-bit key to the algorithm." fails to say which 8 bits are ignored (the norm is the lsb of each byte). It also does not mention the padding or block modes used.

    Your Java assumes CBC mode with an IV of 8 bytes of zero but I see nothing in the Oracle documentation about this. Your test data is 8 bytes long so no padding is needed and with the IV being 8 bytes of zero then even if the block mode is ECB your result should be valid for the first block.

    I can see these possible causes for the mismatch -

    1) The test data used with the stored procedure is not the same as you have used in the Java. I don't have access to Oracle to check this.
    2) The IV used by Oracle is not 8 bytes of zeros. If I could be sure of the key I could work out the IV.
    3) The ignored 8 bits of the key are not the least significant bits of each byte (the norm in all I have met outside of Oracle).
    4) Some other transformation of the key is being applied before use. I have tried the obvious such as reversing the order of the key bytes and reversing the order of the key bits. Without further information I can do no more.

    Looks like I'm beaten. Given some reasonable specification of the internals of the DBMS_OBFUSCATION_TOOLKIT it should be easy to emulate in Java. Without this specification it could take a long time.

    What documentation did you use to tell you that CBC mode was being used and the IV was all zeros?

    !!!!!!!!!!Cracked it (I think) !!!!!!!!!!!

    One interesting observation I made using your Java was if I decrypt D1C4B08530389781 using
                TripleDES agent = new TripleDES("123456789123456789123456");
                byte[] decoded = hexDecoder.decode("D1C4B08530389781");
                byte[] decrypted = agent.decrypt(decoded);
                System.out.println("Decrypted: \"" + hexEncoder.encodeAsString(decrypted) + "\"");
    I get the result "6041266789abcdef" . It is surely against the odds that if the key is totally wrong the decrypted data should contain the ascending hex sequence "6789abcde". This leads one to deduce that the IV bytes are actually the hex decode of "0123456789abcdef" !

    The only thing causing me concern is that I'm not sure about your encryption of "testing!" giving "7F6C925B08D478C4" as stated in your OP. You need to check this because it looks to me like you hindered getting a quick solution to the problem because when I used it with my standard technique for working out the IV it did not give a value that seemed logical.

    Bloody Oracle!

    Edited by: sabre150 on Mar 23, 2010 8:35 AM
  • 5. Re: Oracle Triple DES Encryption -> Java Decryption
    843811 Newbie
    Currently Being Moderated
    Wow, thanks for all the info! Your detective work is amazing! To be honest, I don't know that CBC and the IV are correct for Triple DES, but I found this link regarding Oracle DES to Java, and that's what they used. I was hoping it would be the same for Triple DES http://stackoverflow.com/questions/115503/duplicate-oracle-des-encrypting-in-java

    I tried what I think you said is the solution. I made "0123456789abcdef" the IV.
    public TripleDES(String secretString) throws GeneralSecurityException {
         key = new SecretKeySpec(secretString.getBytes(), "DESede");
         cipher = Cipher.getInstance(algorithm);
         try {
              iv = new IvParameterSpec(Hex.decodeHex("0123456789abcdef".toCharArray()));
         } catch (DecoderException e) {
              // TODO
         }
    }
    Is that what you were talking about? With this, I now get

    Oringal: "abc"
    Encrypted: "2629d6b9c433837f"

    Which doesn't match Oracle's "abc" encryption result of "D1C4B08530389781". Did I still not initialize the IV correctly?

    Regarding the OP, you are correct. I double checked the encryption result of "testing!", and I'm embarrassed to say I don't know how I got "7F6C...". I get "64ED96DF07449CBE" now. It's an open dev box, so maybe someone was messing around, but I've double checked the key, and I know for sure that the key is now "123456789123456789123456".

    Thanks again for the detailed posts. You have been a huge help so far!
  • 6. Re: Oracle Triple DES Encryption -> Java Decryption
    843811 Newbie
    Currently Being Moderated
    I am now certain that the Oracle IV is hexDecode("0123456789abcdef"). If using Java you are not getting "D1C4B08530389781" for the encryption of "abc" using a key of "123456789123456789123456" then you are doing something else wrong because I do when I pad with bytes of zero. If using SQL you are no longer getting "D1C4B08530389781" for the encryption of "abc" then why did you get that answer before?

    You need to be very accurate in reporting here because I know that your original post was flawed and that this wasted a lot of time.
  • 7. Re: Oracle Triple DES Encryption -> Java Decryption
    843811 Newbie
    Currently Being Moderated
    Sabre, thanks for all your help! I am now getting the same encrypted value in Java as in Oracle! I feel like it's a small miracle actually. I've scoured the web for a week for information on this, and this was a last resort. I'm amazed you were able figure out the IV based on some encrypted values. There's so much to cryptography I need to learn. Here's the complete code to help the next unfortunate guy that has to support a legacy Oracle encryption scheme.
    import java.security.Key;
    import java.security.GeneralSecurityException;
    import javax.crypto.Cipher;
    import javax.crypto.SecretKey;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import org.apache.commons.codec.DecoderException;
    import org.apache.commons.codec.binary.Hex;
    
    
    public class TripleDES {
        private String algorithm = "DESede/CBC/NoPadding";
        private IvParameterSpec iv = null;
        private Cipher cipher;
        private SecretKey key;
    
    
        public TripleDES(String secretString) throws GeneralSecurityException {
            key = new SecretKeySpec(secretString.getBytes(), "DESede");
            cipher = Cipher.getInstance(algorithm);
            try {
                iv = new IvParameterSpec(Hex.decodeHex("0123456789abcdef".toCharArray()));
            } catch (DecoderException e) {
                // TODO
            }
        }
        
    
        private byte[] encrypt(String input) throws GeneralSecurityException {
            byte[] bytes = input.getBytes();
            cipher.init(Cipher.ENCRYPT_MODE, (Key)key, iv);
            return cipher.doFinal(bytes);
        }
    
    
        private byte[] decrypt(byte[] bytes) throws GeneralSecurityException {
            cipher.init(Cipher.DECRYPT_MODE, (Key)key, iv);
            return cipher.doFinal(bytes);
        }
        
    
        public static void main(String[] args) throws Exception {
            try {
                TripleDES x = new TripleDES("123456789123456789123456");
                String original = "original";
                System.out.println("Oringal: \"" + original + "\"");
        
                byte[] encrypted = x.encrypt(original);
                String encoded = new String(Hex.encodeHex(encrypted));
                System.out.println("Encrypted: \"" + encoded + "\"");
        
                byte[] decoded = Hex.decodeHex(encoded.toCharArray());
                String decrypted = new String(x.decrypt(decoded));
                System.out.println("Decrypted: \"" + decrypted + "\"");
                
                if (decrypted.equals(original)) {
                    System.out.println("Encryption ==> Decryption Successful");
                }
            }
            catch (Exception e) {
                System.out.println("Error: " + e.toString());
            }
        }
    
    }
    Thanks again to Sabre for his tremendous help on this! You really saved the day!
  • 8. Re: Oracle Triple DES Encryption -> Java Decryption
    843811 Newbie
    Currently Being Moderated
    I'm glad you finally got it working but I think you have to deal with character encoding before you celebrate too much. Currently you get the bytes from the input using
           byte[] bytes = input.getBytes();
     
    which uses the default character encoding. This assumes that the character encoding used is the default for the computer/jdk/OS/Oracle but my experience is that this is not the case. Since you are trying to match Oracle you need to define the character encoding to be the same as the Oracle system you are emulating so you need something like
           byte[] bytes = input.getBytes(Charset.forName("utf-8"));
     
    where you will replace the "utf-8" with the character encoding used by Oracle.

    Of course you need to do the reverse character encoding when recovering the input from the decrypted data.

    I note that your encryption and decryption methods are not symmetric. i.e you do
    encrypt string to bytes
    and
    decrypt bytes to bytes

    Is this deliberate and if so why? I would expect to see
    encrypt string to bytes
    and
    decrypt bytes to string.

    OR

    encrypt bytes to bytes
    and
    decrypt bytes to bytes.

    Of course it is trivial to overload the methods so that there is symmetry of both forms.

    As far as deducing the IV is concerned. Nothing really clever here. For a given key, if one knows the cleartext and ciphertext of the first block of a CBC encrypted value but not the IV then it is easy to compute the IV. One just decrypts the first block of ciphertext and XORs with the first block of cleartext. The result is the IV. The problem I had with your data was that the ciphertext and cleartext you first posted for the Oracle encryption were not related so when I calculated the IV I got random bytes. Only in your second posting did you have a consistent set of data to work with and by then I was chasing other red-herrings and wasted a lot of time chasing them.

    I can't say I'm that impressed with the Oracle encryption procedure. A fixed IV is better than no IV but a random IV is better. I suppose Oracle did not want the 8 byte overhead per encrypted value of a random IV.
  • 9. Re: Oracle Triple DES Encryption -> Java Decryption
    843811 Newbie
    Currently Being Moderated
    I guess I did celebrate too soon. The PL/SQL is generating session keys for the encryption and I'm finding about 20% of these keys produce a different result in pl/sql and java.

    Here is the function that produces the session key.
       FUNCTION get_session_key RETURN RAW IS
          v_keyr  RAW(255);
          v_seedr RAW(255);
       BEGIN
          v_seedr := utl_raw.cast_to_raw(dbms_random.string('A', 60)||dbms_random.string('A', 20));
          dbms_obfuscation_toolkit.DES3GetKey
             (
                seed  => v_seedr,
                which => g_which,
                key   => v_keyr
             );
         RETURN CONVERT(v_keyr, 'UTF8');
       END get_session_key;
    I took your advice about UTF8, and added the charset in my java calls, and in the pl/sql key function you see above, but it didn't seem effective.

    Here is the revised java piece. I added the string functions, as well as padding, so I can pass any length string to it.
    import java.io.UnsupportedEncodingException;
    import java.security.GeneralSecurityException;
    import java.security.Key;
    import javax.crypto.Cipher;
    import javax.crypto.SecretKey;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import org.apache.commons.codec.DecoderException;
    import org.apache.commons.codec.binary.Hex;
    
    public class TripleDES {
        private Cipher cipher = null;
        private SecretKey key = null;
        private byte[] bytes = null;
        private IvParameterSpec iv = null;
    
        public static void main(String[] args) throws Exception {
            try {
                String hexKey = "EF7A3B8663C3A6CD8856AD60E762D02268E24DEC015DF2B7";
                TripleDES encryptor = new TripleDES(new String(Hex.decodeHex(hexKey.toCharArray())));
                String original = "abc";
                System.out.println("Oringal: \"" + original + "\"");
                
                String enc = encryptor.encrypt(original);
                System.out.println("Encrypted: \"" + enc + "\"");
                
                String dec = encryptor.decrypt(enc);
                System.out.println("Decrypted: \"" + dec + "\"");
                
                if (dec.equals(original)) {
                    System.out.println("Encryption ==> Decryption Successful");
                }
            }
            catch (Exception e) {
                System.out.println("Error: " + e.toString());
            }
        }
    
        public TripleDES(String encryptionKey) throws GeneralSecurityException,  DecoderException{
            cipher = Cipher.getInstance("DESede/CBC/NoPadding");
            key = new SecretKeySpec(encryptionKey.getBytes(), "DESede");
            iv = new IvParameterSpec(Hex.decodeHex("0123456789abcdef".toCharArray()));
        }
    
        public String encrypt(String input) throws GeneralSecurityException, UnsupportedEncodingException {
            int len = input.length();
            char nulls[] = {(char)0, (char)0, (char)0, (char)0, (char)0, (char)0, (char)0};
            input += new String(nulls);     
            input = input.substring(0, (int)(Math.ceil((double)len/(double)8) * 8));        
            bytes = input.getBytes("UTF-8");
            return new String(Hex.encodeHex(encryptB(bytes)));
        }
    
        public String decrypt(String input) throws GeneralSecurityException, DecoderException, UnsupportedEncodingException {
            bytes = Hex.decodeHex(input.toCharArray());
            String decrypted = new String(decryptB(bytes), "UTF-8");
            if (decrypted.indexOf((char)0) > 0) {
                decrypted = decrypted.substring(0, decrypted.indexOf((char)0));
            }
            return decrypted;
        }
    
        public byte[] encryptB(byte[] bytes) throws GeneralSecurityException {
            cipher.init(Cipher.ENCRYPT_MODE, (Key)key, iv);
            return cipher.doFinal(bytes);
        }
    
        public byte[] decryptB(byte[] bytes) throws GeneralSecurityException {
            cipher.init(Cipher.DECRYPT_MODE, (Key)key, iv);
            return cipher.doFinal(bytes);
        }
    
    }
    The following are keys produced by the pl/sql that resulted in the same encryption:
    v_original: abc
    v_temp_key: E0E373C4B436662F0CAA7AC78000E2B7421ADC30F9A429F5
    Oracle Encrypted: 25F2D0143DEEDDF5
    Java Encrypted: 25F2D0143DEEDDF5
    SUCCESS
    _
    v_original: abc
    v_temp_key: EF7A3B8663C3A6CD8856AD60E762D02268E24DEC015DF2B7
    Oracle Encrypted: 249DC0E69831E78A
    Java Encrypted: 249DC0E69831E78A
    SUCCESS
    _
    v_original: abc
    v_temp_key: 07647467778A18D0015D7CF35A1E1A36C367E9B07B52825B
    Oracle Encrypted: 39323AADA181E488
    Java Encrypted: 39323AADA181E488
    SUCCESS
    Now, here are three keys that produced different results:
    v_original: abc
    v_temp_key: F78F011A683B1C6390D99AED24A034AA2F712DF428420CED
    Oracle Encrypted: BD58254422848520
    Java Encrypted: 63E56E66BCE5081C
    FAILED
    _
    v_original: abc
    v_temp_key: 4DC73FA2D455739FCDB764F1E2DB539BE8819DC3F5518E92
    Oracle Encrypted 64C4346A4D8F8257
    Java Encrypted: 370D6178B7F8563A
    FAILED
    _
    v_original: abc
    v_temp_key: 90F68B8DD667594188172F48AEF175202F004F2BBA865299
    Oracle Encrypted: 642752C209509014
    Java Encrypted: 0B6F2E68CFE86D39
    FAILED
    Any idea why some of these keys are failing? Do you think it's a character set issue? I get "UTF8" when I run this on the database "select value from nls_database_parameters where parameter = 'NLS_CHARACTERSET';" Is there something I should do differently in the java? Thanks again for you help!

    Edit: I forgot to mention, the PL/SQL is now padding the end of the input strings with NULL just like the java, just so that you don't wonder how they ever match at all. Thanks!
  • 10. Re: Oracle Triple DES Encryption -> Java Decryption
    843811 Newbie
    Currently Being Moderated
    1) My Java code matches the Oracle perfectly.
    2) I didn't say use utf-8! I said use whatever encoding Oracle is using!
    3) This code is rubbish!
    int len = input.length();
            char nulls[] = {(char)0, (char)0, (char)0, (char)0, (char)0, (char)0, (char)0};
            input += new String(nulls);     
            input = input.substring(0, (int)(Math.ceil((double)len/(double)8) * 8));        
            bytes = input.getBytes("UTF-8");
    All you need is
    bytes = input.getBytes("whatever your Oracle encoding is");
    bytes = Arrays.copyOf(bytes, ((bytes.length+7)/8)*8);
    4) This code is very poor
         String decrypted = new String(decryptB(bytes), "UTF-8");
            if (decrypted.indexOf((char)0) > 0) {
                decrypted = decrypted.substring(0, decrypted.indexOf((char)0));
            }
    Strip the bytes from the decrypted date before converting to a String. To save having to create a new byte array you can use the String constructor
    String(byte[] bytes, int offset, int length, Charset charset) 
    An elementary error seen frequently in this forum is working in chars and not bytes. Encryption works with bytes and one should only use characters when one is certain that they are applicable.