1

I'm new to SQL and Java and working on a project encrypting data in Java, sending it and decrypting it in an SQL oracle database. I'm using 3DES, salting and sending a MAC with the encrypted string as well (Concatenated). I can en/decode a message on both the Java and SQL sides individually, but I run into a lot of trouble when I send the string and I think it has something to do with the encodings (hex vs base64 vs UTF-8). I'm currently not checking the MAC on the SQL side, but I'm going to change that later. If you guys could take a look at my code I#d really appreciate it :) Oh and sorry that the comments are in German :/

I'm receiving the errors:

ORA-28817: PL/SQL function returned an error.
ORA-06512: at "SYS.DBMS_CRYPTO_FFI", line 67
ORA-06512: at "SYS.DBMS_CRYPTO", line 44
ORA-06512: at "SCOTT.PASSWORD", line 272
ORA-06512: at line 9

JAVA

import java.util.Arrays;
import java.util.Random;
import java.io.ByteArrayOutputStream;
import java.security.MessageDigest;
import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

import verschlüsseln.FalscheMACOderSaltException;


    public static byte[] verschlüsseln(String daten) throws Exception {
        // Benötigt: daten, DreifachDES.password, DreifachDES.macString
        // Ändert: saltString
        // Ausführt: Verschlüsselt "daten," 3DES mit Salt und ein MAC wird
        // benutzt.
        // hash(DreifachDES.password + salt) ist der Schlüssel.
        // Der Output ist ein byte[]

        // Erzeugen Digest für Passwort + Salt
        password="key12345key54321key15243";
        final MessageDigest md = MessageDigest.getInstance("SHA1");

        // Erzeugen zufällig 24 Byte Salt
        Random züfallig = new SecureRandom();
        byte[] salt = new byte[24];
        züfallig.nextBytes(salt);
        String saltString = Arrays.toString(salt);

        // Digest Passwort + Salt um der Schlüssel zu erzeugen
        final byte[] digestVonPassword = md.digest((password + saltString)
                .getBytes("UTF-8"));
        new Base64(true);
        String b64Daten = Base64.encodeBase64String(digestVonPassword);

        // Wir brauchen nur 24 Bytes, benutze die Erste 24 von der Digest
        final byte[] keyBytes = Arrays.copyOf(digestVonPassword, 24);

        // Erzeugen der Schlüssel
        final SecretKey key = new SecretKeySpec(keyBytes, "DESede");

        // Erzeugen eine züfallig IV
        byte[] ivSeed = new byte[8];
        züfallig.nextBytes(ivSeed);
        final IvParameterSpec iv = new IvParameterSpec(ivSeed);

        // Erzeugen Cipher mit 3DES, CBC und PKCS5Padding
        final Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key, iv);

        // Erzeugen byte[] von String message
        final byte[] plainTextBytes = daten.getBytes("UTF-8");
        byte[] vorIvCipherText = cipher.doFinal(plainTextBytes);

        // Erzeugen die MAC (Message Authentication Code, Mesage
        // Authentifizierung Chiffre)
        // Später mache ich einmal ein zufällig String, und wir benutzen das
        // immer.
        SecretKeySpec macSpec = new SecretKeySpec(
                (password + saltString).getBytes("UTF-8"), "HmacSHA1");
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(macSpec);
        byte[] macBytes = mac.doFinal(macString.getBytes());

        // Erzeugen byte outputStream um die Arrays zu verbinden
        ByteArrayOutputStream ostream = new ByteArrayOutputStream();

        // Verbinden IV, Salt, MAC, und verschlüsselt String
        ostream.write(cipher.getIV());
        ostream.write(salt);
        ostream.write(macBytes);
        ostream.write(vorIvCipherText);

        final byte[] cipherText = ostream.toByteArray();

        return cipherText;
    }

SQL

 FUNCTION Decryptraw (datas         VARCHAR2, 
                       c_encrypt_key VARCHAR2) 
  RETURN VARCHAR2 
  IS 
    l_enc_val      RAW (4000); 
    v_receivedsalt RAW(4000); 
    v_receivedmac  RAW(4000); 
    mac            RAW(4000); 
    macsource      RAW(4000); 
    rawtohash      RAW(4000); 
    l_enc_algo     PLS_INTEGER; 
    l_in           RAW (4000); 
    l_iv           RAW (4000); 
    l_ret          VARCHAR2 (4000); 
    daten          RAW(4000); 
  BEGIN 

      daten := utl_encode.Base64_decode(utl_raw.cast_to_raw(datas)); 

      --Parse the received string for data required for decryption 
      --String    | IV | Salt | MAC | EncryptedData 
      --Bytes       8     16    20     The rest 
      --HexChars    16    32    40     The rest 
      l_iv := Substr(daten, 1, 16); 

      v_receivedsalt := Substr(daten, 17, 24); 

      v_receivedmac := Substr(daten, 41, 40); 

      l_in := Substr(daten, 81); 

      --Source of the MAC, the same value is hardcoded in here 
      --and Decrypt and in the Java Code 
      macsource := utl_raw.Cast_to_raw('myMacString'); 

      --Concatenate the key (password) and salt so that they can be 
      --hashed together 
      rawtohash := Concat(utl_raw.Cast_to_raw(c_encrypt_key), v_receivedsalt); 


      --Hash the key+salt string using SHA1 
      --A stronger algorithm is preferred but not currently available in 
      --oracle SQL 
            rawtohash := dbms_crypto.Hash(rawtohash, dbms_crypto.hash_sh1);  
      --Hash the MAC source using hmac_sh1, with the  
      --password + receivedsalt as the key 
      mac := dbms_crypto.Mac(src => macsource, KEY => rawtohash, typ => 
             dbms_crypto.hmac_sh1); 


      --Decrypt the data using the hashed password+salt as the key 
      l_enc_val := dbms_crypto.Decrypt (src => l_in, KEY => rawtohash, iv => 
                   l_iv, 
                   typ 
                   => 
                                dbms_crypto.des_cbc_pkcs5); 

      RETURN utl_raw.Cast_to_varchar2(l_enc_val); 
  END decryptraw; 

Edit 1: I've tracked down several problems, and I think I have it narrowed down to the last one. I'm now producing the same Key and data to encrypt/decrypt in both Java and SQL, but when I do the final step for each one, I get a different answer. I believe the problem is the "source" for the encryptions. I believe the error has something to do with the way the bytes are processed in SQL and in Java. In Java, we use a byte[] for the cipher, and in SQL we use a RAW, normally represented in HEX (at least I think so, because when I output a raw it prints hex values). So my guess is that the SQL is doing something with HEX, and Java is doing... something else. The same error occurs at the same line:

      l_enc_val := dbms_crypto.Decrypt (src => l_in, KEY => rawtohash, iv => 
                   l_iv, 
                   typ 
                   => 
                                dbms_crypto.des_cbc_pkcs5); 

Here is my new Code:

JAVA

    public static byte[] verschlüsseln(String daten) throws Exception {
        // Benötigt: daten, DreifachDES.password, DreifachDES.macString
        // Ändert: saltString
        // Ausführt: Verschlüsselt "daten," 3DES mit Salt und ein MAC wird
        // benutzt.
        // hash(DreifachDES.password + salt) ist der Schlüssel.
        // Der Output ist ein byte[]

        // Erzeugen Digest für Passwort + Salt
        password="testForNathan";
        final MessageDigest md = MessageDigest.getInstance("SHA1");

        // Erzeugen zufällig 24 Byte Salt
        Random züfallig = new SecureRandom();
        byte[] salt = new byte[24];
        String saltString = Arrays.toString(salt);
        new Base64(true);
        saltString = new String(salt, "UTF-8");
        byte[] unhashedBytes = (password+saltString).getBytes("UTF-8");

        final byte[] keyBytes2 = unhashedBytes;

        System.out.println("Hex key before hash: " + bytesToHex(unhashedBytes));



        //Hash the pw+salt
        byte[] digestVonPassword = md.digest(keyBytes2);

        byte[] digestVonPassword2 = new byte[digestVonPassword.length + salt.length];
        System.arraycopy(digestVonPassword, 0, digestVonPassword2, 0, digestVonPassword.length);
        System.arraycopy(salt, 0, digestVonPassword2, digestVonPassword.length, salt.length);

        // Wir brauchen nur 24 Bytes, benutze die Erste 24 von der Digest
        final byte[] keyBytes = Arrays.copyOf(digestVonPassword2, 24);

        // Erzeugen der Schlüssel
        final SecretKey key = new SecretKeySpec(keyBytes, "DESede");

        // Erzeugen eine züfallig IV
        byte[] ivSeed = new byte[8];
        final IvParameterSpec iv = new IvParameterSpec(ivSeed);

        // Erzeugen Cipher mit 3DES, CBC und PKCS5Padding
        final Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key, iv);

        // Erzeugen byte[] von String message
        final byte[] plainTextBytes = daten.getBytes("UTF-8");
        byte[] vorIvCipherText = cipher.doFinal(plainTextBytes);

        // Erzeugen die MAC (Message Authentication Code, Mesage
        // Authentifizierung Chiffre)
        // Später mache ich einmal ein zufällig String, und wir benutzen das
        // immer.
        SecretKeySpec macSpec = new SecretKeySpec(
                keyBytes2, "HmacSHA1");
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(macSpec);
        byte[] macBytes = mac.doFinal(macString.getBytes());
        System.out.println("Hex version of MAC: " + bytesToHex(macBytes));


        // Erzeugen byte outputStream um die Arrays zu verbinden
        ByteArrayOutputStream ostream = new ByteArrayOutputStream();

        // Verbinden IV, Salt, MAC, und verschlüsselt String
        ostream.write(cipher.getIV());
        ostream.write(salt);
        ostream.write(macBytes);
        ostream.write(vorIvCipherText);

        final byte[] cipherText = ostream.toByteArray();

        return cipherText;
    }

SQL

FUNCTION Decryptraw (datas         VARCHAR2, 
                       c_encrypt_key VARCHAR2) 
  RETURN VARCHAR2 
  IS 
    l_enc_val      RAW (4000); 
    v_receivedsalt RAW(4000); 
    v_receivedmac  RAW(4000); 
    mac            RAW(4000); 
    macsource      RAW(4000); 
    rawtohash      RAW(4000); 
    l_enc_algo     PLS_INTEGER; 
    l_in           RAW (4000); 
    l_iv           RAW (4000); 
    l_ret          VARCHAR2 (4000); 
    daten          RAW(4000); 
  BEGIN 

      daten := utl_encode.Base64_decode(utl_raw.cast_to_raw(datas)); 


      l_iv := Substr(daten, 1, 16); 

      v_receivedsalt := Substr(daten, 17, 48); 

      v_receivedmac := Substr(daten, 65, 40); 

      l_in := Substr(daten, 105); 


      --Source of the MAC, the same value is hardcoded in here 
      --and Decrypt and in the Java Code 
      macsource := utl_raw.Cast_to_raw('myMacString'); 

      --Concatenate the key (password) and salt so that they can be 
      --hashed together 
      rawtohash := Concat(utl_raw.Cast_to_raw(c_encrypt_key), v_receivedsalt);


      --Hash the key+salt string using SHA1 
      --A stronger algorithm is preferred but not currently available in 
      --oracle SQL 
            rawtohash := dbms_crypto.Hash(rawtohash, dbms_crypto.hash_sh1); 

            rawToHash := utl_raw.concat(rawtohash, v_receivedsalt);
            rawtohash := utl_raw.substr(rawtohash, 1, 24);

      --Hash the MAC source using hmac_sh1, with the  
      --password + receivedsalt as the key 
      mac := dbms_crypto.Mac(src => macsource, KEY => rawtohash, typ => 
             dbms_crypto.hmac_sh1); 

      l_enc_val := dbms_crypto.Decrypt (src => l_in, KEY => rawtohash, iv => 
                   l_iv, 
                   typ 
                   => 
                                dbms_crypto.des_cbc_pkcs5); 

      RETURN utl_raw.Cast_to_varchar2(l_enc_val); 
  END decryptraw;
PunDefeated
  • 566
  • 5
  • 18

1 Answers1

0

Got it all fixed! Code is a lot easier to read now, too.

A note of warning: this doesn't decrypt large strings for whatever reason (failed on my test with 1800 characters), it throws an error.

JAVA

import java.util.Arrays;
import java.util.Random;
import java.io.ByteArrayOutputStream;
import java.security.MessageDigest;
import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

import verschlüsseln.FalscheMACOderSaltException;

public static String verschluesselnZuBase64String(String daten) throws Exception{
    String b64Daten;
    byte[] datenArray = verschlüsseln(daten);
    new Base64(true);
    b64Daten = Base64.encodeBase64String(datenArray);
    return b64Daten;
}

public static String bytesToHex(byte[] bytes) {
    final char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    char[] hexChars = new char[bytes.length * 2];
    int v;
    for ( int j = 0; j < bytes.length; j++ ) {
        v = bytes[j] & 0xFF;
        hexChars[j * 2] = hexArray[v >>> 4];
        hexChars[j * 2 + 1] = hexArray[v & 0x0F];
    }
    return new String(hexChars);
}

public static byte[] verschlüsseln(String daten) throws Exception {
    // Benötigt: daten, DreifachDES.password, DreifachDES.macString
    // Ändert: saltString
    // Ausführt: Verschlüsselt "daten," 3DES mit Salt und ein MAC wird
    // benutzt.
    // hash(DreifachDES.password + salt) ist der Schlüssel.
    // Der Output ist ein byte[]

    // Erzeugen Digest für Passwort + Salt
    password="pw";
            macstring="mac";
    final MessageDigest md = MessageDigest.getInstance("SHA1");

    // Erzeugen zufällig 24 Byte Salt
    Random züfallig = new SecureRandom();
    byte[] salt = new byte[24];
    züfallig.nextBytes(salt);

    ByteArrayOutputStream pwsalt = new ByteArrayOutputStream();
    pwsalt.write(password.getBytes("UTF-8"));
    pwsalt.write(salt);
    byte[] unhashedBytes = pwsalt.toByteArray();

    //Hash the pw+salt
    byte[] digestVonPassword = md.digest(unhashedBytes);

    //SHA1 only generates 20 bytes and we need more, so concatenate the salt onto the end.
    byte[] digestVonPassword2 = new byte[digestVonPassword.length + salt.length];
    System.arraycopy(digestVonPassword, 0, digestVonPassword2, 0, digestVonPassword.length);
    System.arraycopy(salt, 0, digestVonPassword2, digestVonPassword.length, salt.length);

    // Erzeugen die MAC (Message Authentication Code, Mesage
    // Authentifizierung Chiffre)
    // Später mache ich einmal ein zufällig String, und wir benutzen das
    // immer.
    SecretKeySpec macSpec = new SecretKeySpec(
            digestVonPassword, "HmacSHA1");
    Mac mac = Mac.getInstance("HmacSHA1");
    mac.init(macSpec);
    byte[] macBytes = mac.doFinal(macString.getBytes());

    // Wir brauchen nur 24 Bytes, benutze die Erste 24 von der Digest
    final byte[] keyBytes = Arrays.copyOf(digestVonPassword2, 24);

    // Erzeugen eine züfallig IV
    byte[] ivSeed = new byte[8];
    züfallig.nextBytes(ivSeed);
    final IvParameterSpec iv = new IvParameterSpec(ivSeed);

    // Erzeugen der Schlüssel
    final SecretKey key = new SecretKeySpec(keyBytes, "DESede");

    // Erzeugen Cipher mit 3DES, CBC und PKCS5Padding
    final Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, key, iv);

    // Erzeugen byte[] von String message
    final byte[] plainTextBytes = daten.getBytes("UTF-8");
    byte[] vorIvCipherText = cipher.doFinal(plainTextBytes);

    // Erzeugen byte outputStream um die Arrays zu verbinden
    ByteArrayOutputStream ostream = new ByteArrayOutputStream();

    // Verbinden IV, Salt, MAC, und verschlüsselt String
    ostream.write(cipher.getIV());
    ostream.write(salt);
    ostream.write(macBytes);
    ostream.write(vorIvCipherText);

    final byte[] cipherText = ostream.toByteArray();

    return cipherText;
}

SQL (c_encypy_key is the password)

FUNCTION Decryptraw (datas         VARCHAR2, 
                       c_encrypt_key VARCHAR2) 
  RETURN VARCHAR2 
  IS 
    l_enc_val      RAW (8000); 
    v_receivedsalt RAW(8000); 
    v_receivedmac  RAW(8000); 
    mac            RAW(8000); 
    macsource      RAW(8000);
    mackey         RAW(8000);
    rawtohash      RAW(8000); 
    l_enc_algo     PLS_INTEGER; 
    l_in           RAW (8000); 
    l_iv           RAW (8000); 
    l_ret          VARCHAR2 (8000); 
    daten          RAW(8000); 
  BEGIN 
      daten := utl_encode.Base64_decode(utl_raw.Cast_to_raw(datas));  
      macSource := utl_raw.cast_to_raw('mac');

      --Parse the received string for data required for decryption   
      --String    | IV | Salt | MAC | EncryptedData   
      --Bytes       8     24    20     The rest   
      --HexChars    16    48    40     The rest    
      l_iv := Substr(daten, 1, 16); 
      v_receivedsalt := Substr(daten, 17, 48);
      v_receivedmac := Substr(daten, 65, 40); 
      l_in := Substr(daten, 105); 

      --Concatenate the key (password) and salt so that they can be   
      --hashed together   
      rawtohash := Concat(utl_raw.Cast_to_raw(c_encrypt_key), v_receivedsalt); 


      --Hash the key+salt string using SHA1   
      --A stronger algorithm is preferred but not currently available in   
      --oracle SQL   
      rawtohash := dbms_crypto.Hash(rawtohash, dbms_crypto.hash_sh1); 
      macKey := rawtohash;
      rawtohash := utl_raw.Concat(rawtohash, v_receivedsalt); 
      rawtohash := utl_raw.Substr(rawtohash, 1, 24); 

      --Hash the MAC source using hmac_sh1, with the    
      --password + receivedsalt as the key   
      mac := dbms_crypto.Mac(src => macsource, KEY => macKey, typ => 
             dbms_crypto.hmac_sh1);

      --In the case that the MAC generated in this function   
      --does not match the MAC parsed from the received data,   
      --something has gone wrong during the data sending process.   
      --The data will not be decrypted or parsed, because it has   
      --most likely been tampered with or corrupted.   
            IF ( mac != v_receivedmac ) THEN   
              Raise_application_error(-20101,   
              'Recieved MAC or Salt don''t match the generated MAC.'   
              );   
            END IF;     

      --Decrypt the data using the hashed password+salt as the key    
      l_enc_val := dbms_crypto.Decrypt (src => l_in, KEY => rawtohash, iv => 
                   l_iv, 
                   typ 
                   => 
                                dbms_crypto.des3_cbc_pkcs5); 

      RETURN utl_raw.Cast_to_varchar2(l_enc_val); 
  END decryptraw;
PunDefeated
  • 566
  • 5
  • 18