10 Replies Latest reply: Mar 25, 2010 11:16 AM by 843811 RSS

    Oracle Triple DES Encryption -> Java Decryption

    843811
      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
          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
            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
              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
                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
                  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
                    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
                      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
                        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
                          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
                            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.