Java 加密技术(Java Cryptography)

更新于 2025-12-29

Jakob Jenkov 2019-10-14

Java 加密 API(Java Cryptography API)使你能够在 Java 中对数据进行加密和解密,以及管理密钥、对消息进行签名与验证、计算加密哈希值等更多功能。术语“加密”(cryptography)通常简称为“crypto”,因此有时你会看到“Java crypto”而不是“Java Cryptography”的说法,但这两个术语指的是同一个主题。

在本篇 Java 加密教程中,我将解释如何使用 Java 加密 API 来执行安全加密所需的各种任务。

注意:本教程不会涵盖底层的加密理论。如需了解相关理论,请参考其他资料。


Java 加密扩展(Java Cryptography Extension, JCE)

Java 加密 API 由官方称为 Java 加密扩展(Java Cryptography Extension,简称 JCE)的组件提供。

JCE 已经成为 Java 平台的一部分很长时间了。最初,由于美国对加密技术的出口限制,JCE 并未直接包含在标准 Java 平台中。因此,最强的加密算法并未包含在标准 Java 中。美国境内的公司可以单独获取这些更强的加密算法,而世界其他地区只能使用较弱的算法(或自行实现加密算法并接入 JCE)。

如今(2017 年以后),美国的加密出口管制已大幅放宽,因此全球大多数地区都可以通过 Java JCE 使用国际通用的加密标准。


Java 加密架构(Java Cryptography Architecture, JCA)

Java 加密架构(JCA)是 Java 加密 API 的内部设计名称。

JCA 围绕一些核心的通用类和接口构建。这些接口背后的实际功能由提供者(Provider)实现。例如,你可以使用 Cipher 类来加密和解密数据,但具体的加密算法实现取决于所使用的提供者。

你也可以实现并插入自己的提供者,但应谨慎操作。正确地实现无安全漏洞的加密算法非常困难!除非你清楚自己在做什么,否则最好使用 Java 内置的提供者,或者使用像 Bouncy Castle 这样广受认可的第三方提供者。


核心类和接口

Java 加密 API 分布在以下 Java 包中:

  • java.security
  • java.security.cert
  • java.security.spec
  • java.security.interfaces
  • javax.crypto
  • javax.crypto.spec
  • javax.crypto.interfaces

这些包中的核心类和接口包括:

  • Provider
  • SecureRandom
  • Cipher
  • MessageDigest
  • Signature
  • Mac
  • AlgorithmParameters
  • AlgorithmParameterGenerator
  • KeyFactory
  • SecretKeyFactory
  • KeyPairGenerator
  • KeyGenerator
  • KeyAgreement
  • KeyStore
  • CertificateFactory
  • CertPathBuilder
  • CertPathValidator
  • CertStore

本教程后续部分将详细介绍其中最常用的类。


提供者(Provider)

Providerjava.security.Provider)类是 Java 加密 API 中的核心类。要使用 Java 加密 API,你需要设置一个 Provider。Java SDK 自带一个默认的加密提供者。如果你没有显式设置提供者,则会使用 Java SDK 的默认提供者。然而,该默认提供者可能不支持你所需的加密算法,因此你可能需要设置自己的提供者。

最流行的 Java 加密提供者之一是 Bouncy Castle。下面是一个设置 BouncyCastleProvider 的示例:

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;

public class ProviderExample {
    public static void main(String[] args) {
        Security.addProvider(new BouncyCastleProvider());
    }
}

Cipher(密码器)

Cipherjavax.crypto.Cipher)类代表一种加密算法。Cipher 可用于加密和解密数据。

创建 Cipher 实例的方式如下:

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

此示例创建了一个使用 AES 加密算法的 Cipher 实例。

Cipher.getInstance(...) 方法接收一个字符串,用于指定要使用的加密算法及其配置。上例中:

  • AES 是加密算法;
  • CBC 是 AES 的工作模式;
  • PKCS5Padding 指定如何处理最后一个不足块大小的数据(如 64 或 128 位)。

注:具体含义属于通用加密知识范畴,不在本 Java API 教程范围内。

初始化 Cipher

在使用 Cipher 实例前,必须先调用其 init() 方法进行初始化。init() 接收两个参数:

  1. 加密/解密模式Cipher.ENCRYPT_MODECipher.DECRYPT_MODE
  2. 密钥

示例(加密模式):

byte[] keyBytes = new byte[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
String algorithm = "RawBytes";
SecretKeySpec key = new SecretKeySpec(keyBytes, algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key);

⚠️ 注意:上述硬编码密钥的方式极不安全,仅用于演示。实际应用中应使用更安全的密钥生成方式(见下文)。

解密模式初始化:

cipher.init(Cipher.DECRYPT_MODE, key);

加密或解密数据

初始化完成后,可调用 update()doFinal() 方法进行加解密:

  • update():用于处理大数据块的一部分;
  • doFinal():用于处理最后一部分,或一次性处理完整数据。

加密示例:

byte[] plainText = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8");
byte[] cipherText = cipher.doFinal(plainText);

解密时,将密文传入 doFinal() 即可。


密钥(Keys)

加密/解密需要密钥,主要有两类:

  • 对称密钥(Symmetric keys):加密和解密使用同一密钥(如 AES)。
  • 非对称密钥(Asymmetric keys):使用公钥加密、私钥解密(如 RSA、DSA)。

通信双方需通过密钥交换(key exchange)机制安全地共享密钥。

密钥安全性

密钥应难以猜测。避免使用简单或可预测的值(如上例中的连续字节)。理想情况下,密钥应由强随机源生成,且长度足够(如 AES-256)。

生成对称密钥

使用 KeyGenerator 生成安全的对称密钥:

KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = new SecureRandom();
int keyBitSize = 256;
keyGenerator.init(keyBitSize, secureRandom);
SecretKey secretKey = keyGenerator.generateKey();

// 使用生成的密钥
cipher.init(Cipher.ENCRYPT_MODE, secretKey);

生成非对称密钥对

使用 KeyPairGenerator 生成公私钥对:

SecureRandom secureRandom = new SecureRandom();
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
KeyPair keyPair = keyPairGenerator.generateKeyPair();

密钥库(KeyStore)

KeyStorejava.security.KeyStore)是一个可存储以下类型密钥的数据库:

  • 私钥(Private keys)
  • 公钥 + 证书(Public keys + certificates)
  • 对称密钥(Secret keys)

证书用于验证公钥持有者的身份,通常由可信机构数字签名。


Keytool 工具

Keytool 是 Java 自带的命令行工具,用于管理 KeyStore 文件,支持:

  • 生成密钥对
  • 导出/导入证书
  • 管理密钥库内容

消息摘要(MessageDigest)

如何确保接收到的加密数据未被篡改?常用方法是:

  1. 发送方计算原始数据的消息摘要(哈希值);
  2. 将数据和摘要一起加密发送;
  3. 接收方解密后,重新计算摘要并与收到的摘要比对。

若一致,则数据极大概率未被修改(非 100% 绝对,但实践中足够可靠)。

使用 MessageDigest 计算摘要:

MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");

// 方式一:分多次更新
byte[] data1 = "0123456789".getBytes("UTF-8");
byte[] data2 = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8");
messageDigest.update(data1);
messageDigest.update(data2);
byte[] digest = messageDigest.digest();

// 方式二:一次性计算
byte[] data = "0123456789".getBytes("UTF-8");
byte[] digest = messageDigest.digest(data);

MAC(消息认证码)

Mac(Message Authentication Code)类似于消息摘要,但使用密钥对摘要进行加密,只有拥有密钥的一方才能验证数据完整性,安全性更高。

使用示例:

// 创建 Mac 实例
Mac mac = Mac.getInstance("HmacSHA256");

// 初始化密钥(同样不应硬编码!)
byte[] keyBytes = new byte[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
SecretKeySpec key = new SecretKeySpec(keyBytes, "RawBytes");
mac.init(key);

// 计算 MAC
byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8");
byte[] macBytes = mac.doFinal(data);

数字签名(Signature)

Signature 类用于对数据进行数字签名。签名过程:

  1. 对数据计算消息摘要;
  2. 私钥加密该摘要 → 得到数字签名。

验证过程:

  1. 公钥解密签名得到原始摘要;
  2. 对收到的数据重新计算摘要;
  3. 比对两个摘要是否一致。

签名数据

Signature signature = Signature.getInstance("SHA256WithDSA");
signature.initSign(keyPair.getPrivate(), secureRandom);

byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8");
signature.update(data);
byte[] digitalSignature = signature.sign();

验证签名

Signature signature2 = Signature.getInstance("SHA256WithDSA");
signature2.initVerify(keyPair.getPublic());
signature2.update(data);
boolean verified = signature2.verify(digitalSignature); // true 表示验证通过

完整签名与验证示例

SecureRandom secureRandom = new SecureRandom();
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
KeyPair keyPair = keyPairGenerator.generateKeyPair();

// 签名
Signature signature = Signature.getInstance("SHA256WithDSA");
signature.initSign(keyPair.getPrivate(), secureRandom);
byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8");
signature.update(data);
byte[] digitalSignature = signature.sign();

// 验证
Signature signature2 = Signature.getInstance("SHA256WithDSA");
signature2.initVerify(keyPair.getPublic());
signature2.update(data);
boolean verified = signature2.verify(digitalSignature);

System.out.println("verified = " + verified); // 输出: verified = true