java 实现 oracle 的 des3 加密、解密

java 实现 oracle 的 des3 加密 解密 。 dbms_obfuscation_toolkit.des3encrypt、dbms_obfuscation_toolkit.des3decrypt

由于业务需要对接接口,接口提供方需要的校验token是一个利用oracle数据库的 dbms_obfuscation_toolkit.des3encryptdbms_obfuscation_toolkit.des3decrypt 实现加解密的字符串,开始以为就是通用的 des3 加密,直接实现了一个,可是怎么也跟 oracle 的对不上。

那就先在网上找一下实现吧,倒是找到了一个 des 的对应实现,对应的改为了 des3 ,可惜还是对不上,但是关注到了一些细节, des3 的算法是一回事,具体实现却是有好几种方式,比如 ECB CBC 还有填充方式等,所以需要知道 oracle 到底如何实现的。

查找 oracle 官方文档,只能确认了是 CBC 以及 dbms_obfuscation_toolkit.des3encryptwhich 参数未指定时,默认是 two-key 加密, iv 未指定默认为 Null ,好家伙查找相关资料未果。那就继续代码试验一下, java 中的 des3 要求 key24 位,不是就报错,但是接口方提供的只有 17 位, what?!  oracle 中说要求 16 及以上,怎么还不一样,难道...,在 oracle 中试验,结果发现 16 位以上的字符串没有任何效果与 16 位加密的结果一致。然后又搜索了一下相关资料,确认 oracle 把前 8 位拷贝到了后面 8 位,组成了 24 位。

好吧, key 的问题解决了,这次总行了吧,结果尝试还是不一致,但是有点奇怪的是解密的字符串前面的 8 位是乱码,但是后面的 8 位是正确的,这是什么道理?百度不着,手头没办法翻墙,于是就 bing 国际搜索一下,这次找到了原因,有道友发现是 iv 的值引起的, oracle 使用了 iv 对前 8 位做了一些运算,肯定有一个默认的 iv 且不是 new byte[8] 初始值。这道友倒是说自己通过运算得到了 iv 是什么,并且处理成功,可是没贴出来结果...... 咱也试试,想着8位,就试试有规律的,可是白折腾。

没办法,再 bing 一下,多翻了几页,还真找到了(所以早翻墙,多搜索,不知道 chatgpt 是不是能直接有答案)。 iv0123456789abcdef 字符数组的 16 进制。 参考链接: Oracle Triple DES Encryption -> Java Decryption

下面是整理后,具体的实现代码(用到了 commons-codec 包):

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

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

import static org.apache.commons.codec.CharEncoding.UTF_8;


/**
 * 对应 oracle 数据库的 dbms_obfuscation_toolkit.des3encrypt(加密)、dbms_obfuscation_toolkit.des3decrypt(解密)
 * dbms_obfuscation_toolkit.des3encrypt 的which假如未指定,则默认为0 也就是 two-keyMode,
 * 且采用的是 CBC 方式,需要有 iv 参数,这里oracle在方法内做了处理,虽然方法说明得iv是Null,
 * 但实际使用了 0123456789abcdef 的16进制作为了向量,对字符串的前8位做了一定的运算处理(官方文档并未说明,由网友推算出来的)
 */

public class Des3Helper {

    private final Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
    ;

    private final IvParameterSpec iv = new IvParameterSpec(Hex.decodeHex("0123456789abcdef".toCharArray()));

    private final SecretKeySpec secretKeySpec;


    /**
     * 构造函数-要求key为16位及以上,实际16位之后的没有作用
     * @param key 必须16位及以上,实际16位之后的没有作用
     * @throws Exception 处理异常
     */
    public Des3Helper(String key) throws Exception {
        // 处理为24位,前8位填充到最后8位
        byte[] keyBytes = new byte[24];
        System.arraycopy(key.getBytes(UTF_8), 0, keyBytes, 0, 16);
        System.arraycopy(key.getBytes(UTF_8), 0, keyBytes, 16, 8);
        this.secretKeySpec = new SecretKeySpec(keyBytes, "DESede");
    }


    /**
     * 解密-对应 oracle des3解密  dbms_obfuscation_toolkit.des3decrypt
     * @param str 待解密的16进制字符串
     * @return 加密前的原始字符串
     * @throws Exception 处理异常
     */
    public String decrypt(final String str) throws Exception {
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
        return new String(cipher.doFinal(Hex.decodeHex(str))).trim();
    }

    /**
     * 加密-对应 oracle des3加密  dbms_obfuscation_toolkit.des3encrypt
     * @param str 待加密的字符串
     * @return 加密后的16进制字符串
     * @throws Exception 处理异常
     */
    public String encrypt(String str) throws Exception {
        // 需要填充原始字符串为8的倍数
        byte[] inBytes = new byte[((str.length() / 8) + 1) * 8];
        System.arraycopy(str.getBytes(UTF_8), 0, inBytes, 0, str.length());
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv);
        return Hex.encodeHexString(cipher.doFinal(inBytes), false);
    }

}