1. 概述

简单来说,加密就是将明文信息编码的过程,只有授权用户才能理解或访问这些信息。

原始消息称为明文,通过加密算法(即密码器)处理后生成密文,只有授权用户通过解密才能读取。

本文将详细解析 Java 中提供加密和解密核心功能的 Cipher

2. Cipher 类详解

Java 加密扩展(JCE)是 Java 加密架构(JCA)的一部分,为应用程序提供加密/解密密码器以及私有数据哈希功能。

位于 javax.crypto 包中的 Cipher 类是 JCE 框架的核心,提供加密和解密功能。

2.1. 实例化 Cipher

要创建 Cipher 对象,需调用静态 getInstance 方法,传入请求的转换名称。可选地,可指定提供者名称。

以下示例展示 Cipher 的实例化:

public class Encryptor {

    public byte[] encryptMessage(byte[] message, byte[] keyBytes) 
      throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        //...
    }
}

转换字符串 AES/ECB/PKCS5Padding 告诉 getInstance 方法创建:

也可仅指定算法名称实例化:

Cipher cipher = Cipher.getInstance("AES");

此时 Java 会使用提供者默认的模式和填充方案。

⚠️ 注意:当满足以下条件时 getInstance 会抛出异常:

  • NoSuchAlgorithmException:转换参数为 null/空/格式错误,或提供者不支持
  • NoSuchPaddingException:包含不支持的填充方案

2.2. 线程安全性

Cipher有状态且无内部同步的类。方法如 init()update() 会改变实例的内部状态。

因此 Cipher 类是线程不安全的! 每次加密/解密操作都应创建新实例。

2.3. 密钥管理

Key 接口表示加密操作的密钥。密钥是包含编码密钥、编码格式和算法的透明容器。

通常通过以下方式获取密钥:

以下从字节数组创建对称密钥:

SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");

2.4. 初始化 Cipher

**调用 init() 方法初始化 Cipher**,需传入:

可选参数:

可用操作模式:

  • ENCRYPT_MODE:加密模式
  • DECRYPT_MODE:解密模式
  • WRAP_MODE密钥包装模式
  • UNWRAP_MODE密钥解包模式

初始化示例:

Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
// ...

init 可能抛出 InvalidKeyException,常见原因:

使用证书的示例:

public byte[] encryptMessage(byte[] message, Certificate certificate) 
  throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
 
    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    cipher.init(Cipher.ENCRYPT_MODE, certificate);
    // ...
}

Cipher 会自动从证书获取公钥进行加密。

2.5. 加密与解密

初始化后调用 doFinal() 执行操作,返回加密/解密后的字节数组。

doFinal() 会重置 Cipher 状态到初始化后的状态,可复用实例处理新消息。

加密方法示例:

public byte[] encryptMessage(byte[] message, byte[] keyBytes)
  throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, 
    BadPaddingException, IllegalBlockSizeException {
 
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);
    return cipher.doFinal(message);
}

解密只需将模式改为 DECRYPT_MODE

public byte[] decryptMessage(byte[] encryptedMessage, byte[] keyBytes) 
  throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, 
    BadPaddingException, IllegalBlockSizeException {
 
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
    cipher.init(Cipher.DECRYPT_MODE, secretKey);
    return cipher.doFinal(encryptedMessage);
}

2.6. 安全提供者

JCE 采用提供者架构允许像 BouncyCastle 这样的加密库作为安全提供者无缝接入

添加 BouncyCastle 提供者有两种方式:

静态添加

修改 <JAVA_HOME>/jre/lib/security/java.security 文件,在列表末尾添加:

...
security.provider.4=com.sun.net.ssl.internal.ssl.Provider
security.provider.5=com.sun.crypto.provider.SunJCE
security.provider.6=sun.security.jgss.SunProvider
security.provider.7=org.bouncycastle.jce.provider.BouncyCastleProvider

⚠️ 属性键格式为 security.provider.NN 是列表中最后一个序号+1。

动态添加

无需修改配置文件,直接调用:

Security.addProvider(new BouncyCastleProvider());

初始化时指定提供者:

Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding", "BC");

BC 即 BouncyCastle 的简称。通过 Security.getProviders() 可获取所有已注册提供者。

3. 加密解密测试

编写测试验证加密解密流程。使用 AES-128 算法,断言解密结果与原始消息一致:

@Test
public void whenIsEncryptedAndDecrypted_thenDecryptedEqualsOriginal() 
  throws Exception {
 
    String encryptionKeyString =  "thisisa128bitkey";
    String originalMessage = "This is a secret message";
    byte[] encryptionKeyBytes = encryptionKeyString.getBytes();

    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    SecretKey secretKey = new SecretKeySpec(encryptionKeyBytes, "AES");
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);

    byte[] encryptedMessageBytes = cipher.doFinal(message.getBytes());

    cipher.init(Cipher.DECRYPT_MODE, secretKey);

    byte[] decryptedMessageBytes = cipher.doFinal(encryptedMessageBytes);
    assertThat(originalMessage).isEqualTo(new String(decryptedMessageBytes));
}

4. 总结

本文详细解析了 Cipher 类的核心功能及使用示例。更多细节可参考:

所有示例代码可在 GitHub 项目 中找到,基于 Maven 构建,可直接导入运行。


原始标题:Guide to the Cipher Class