Common 常用模块

Date 日期处理

Reflection 反射

Input/Output 输入/输出

HTTP 请求

加密/解密

基本流程

加密最原始的表达是密文=加密函数(明文)。这里顺便说说 MD5,它符合密文=加密函数(明文)的定义,看似对输入进行了加密,实则不然,它是哈希函数的一种,跟加密完全不是一个概念,更准确地说它返回了输入参数的“特征”结果,这个特征是唯一的。又因为每次执行都是返回相同的结果,这样的话,可以构成字典表去查对。只要这个字典表足够大,那么 MD5 是非常不安全的。

于是乎,我们对这个加密过程改造,增加入参 key 密钥,希望根据 key 的不同每次返回的密文也不一样,即密文=加密函数(明文, 密钥)

AES(Advanced Encryption Standard) 对称加密也是符合这个原始的流程,粗糙的 Java API 实现如下:

/**
 * 是解密模式还是加密模式?
 */
private final int mode;

/**
 * The name of the algorithm
 */
private final String algorithmName;

/**
 * 密钥
 */
private Key key;

private byte[] data;
    
public byte[] doCipher() {
    try {
        Cipher cipher = Cipher.getInstance(algorithmName);

        if (spec != null)
            cipher.init(mode, key, spec);
        else
            cipher.init(mode, key);

        if (associatedData != null)
            cipher.updateAAD(associatedData);

        return cipher.doFinal(data);
    } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
        throw new RuntimeException(Constant.NO_SUCH_ALGORITHM + algorithmName, e);
    } catch (IllegalBlockSizeException | BadPaddingException e) {
        throw new RuntimeException("加密原串的长度不能超过214字节", e);
    } catch (InvalidKeyException e) {
        throw new IllegalArgumentException("Invalid Key.", e);
    } catch (InvalidAlgorithmParameterException e) {
        throw new IllegalArgumentException("Invalid Algorithm Parameter.", e);
    }
}

首先可见我们没有采用函数风格去定义doCipher(),而是通过 getter/setter 去入参(前面有 lombak 去生成)。这些入参都是doCipher()执行所需的基本原始数据类型。其次我们抽象一下主要的流程:

  1. 确定是哪种算法:AES or DES or RSA?
  2. 确定解密模式还是加密模式?
  3. key 传入密钥(是byte[]?还是 Java Key对象),另外还有是否需要AlgorithmParameterSpec spec入参?
  4. 输入数据 data(是byte[]?还是 String 还是 Base64 String?),然后执行加密/解密
  5. 原始返回是byte[],那么调用者希望是要直接返回 String,还是 Base64 String,抑或 HexString?

看来加解密过程核心数据类型都是byte[]。无论哪种类型入参都要最终转换到byte[]。每多考虑一种数据类型入参,便利性就多一点(不需要 API 调用者去手动转换),那么相应地就要安排多一个 setter 来进行转换。

大致的代码风格思路确定了,接着就可以着手进行编码了。