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;