使用3DES在Java中加密,在SQL Oracle中解密

时间:2013-06-10 07:42:07

标签: java sql oracle encryption

我是SQL和Java的新手,正在开发一个项目,用Java加密数据,发送它并在SQL oracle数据库中解密。我正在使用3DES,盐水和发送带有加密字符串的MAC(连接)。我可以分别在Java和SQL端解码/解码消息,但是当我发送字符串时我遇到了很多麻烦,我认为它与编码有关(hex vs base64 vs UTF-8)。我目前没有检查SQL端的MAC,但我稍后会改变它。如果你们可以看看我的代码,我真的很感激:)哦,对不起评论是德语:/

我收到了错误:

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; 

编辑1: 我已经找到了几个问题,我想我已将其缩小到最后一个问题。我现在正在生成相同的密钥和数据来加密/解密Java和SQL,但是当我为每一个做最后一步时,我会得到一个不同的答案。我认为问题是加密的“来源”。我认为该错误与在SQL和Java中处理字节的方式有关。在Java中,我们使用byte []作为密码,在SQL中我们使用RAW,通常以HEX表示(至少我是这么认为的,因为当我输出raw时会打印十六进制值)。所以我的猜测是SQL正在用HEX做些什么,而Java正在做......别的什么。在同一行发生相同的错误:

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

这是我的新代码:

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;

1 个答案:

答案 0 :(得分:0)

全部修好了!代码现在也更容易阅读。

警告提示:无论出于何种原因,这都不会解密大字符串(我的测试中有1800个字符失败),它会抛出错误。

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是密码)

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;