Java安全机制详解
一、概述与历史
1.1 Java安全架构全景
Java从设计之初就将安全作为核心目标之一。Java安全体系可分为四个层次:
┌─────────────────────────────────────────────────────────────┐
│ 第一层: 语言安全 │
│ · 类型安全(无指针运算、数组越界检查) │
│ · 内存安全(GC管理、无悬垂指针) │
│ · 强封装(private/protected/package) │
├─────────────────────────────────────────────────────────────┤
│ 第二层: 运行时安全 │
│ · 字节码校验(Verifier) │
│ · 类加载隔离(不同ClassLoader命名空间隔离) │
│ · SecurityManager/权限检查(已废弃) │
├─────────────────────────────────────────────────────────────┤
│ 第三层: 认证与授权 │
│ · JAAS(Subject/LoginModule) │
│ · 应用层框架(Spring Security, Shiro) │
├─────────────────────────────────────────────────────────────┤
│ 第四层: 加密与通信安全 │
│ · JCA/JCE(加密算法框架) │
│ · JSSE(TLS/SSL安全通信) │
│ · PKI(数字证书体系) │
└─────────────────────────────────────────────────────────────┘
1.2 历史演进
| 版本 | 安全特性 |
|---|---|
| JDK 1.0 (1996) | 引入SecurityManager和沙箱模型,保护Applet |
| JDK 1.1 (1997) | 签名Applet、基本权限模型 |
| JDK 1.2 (1998) | 完整安全架构:JAAS、JCA、AccessController、ProtectionDomain |
| JDK 1.4 (2002) | JCE正式集成、JSSE |
| JDK 8 (2014) | AES-GCM、PBKDF2增强 |
| JDK 11 (2018) | TLS 1.3、ChaCha20/Poly1305、默认禁用TLS 1.0/1.1 |
| JDK 15 (2020) | EdDSA签名(Ed25519/Ed448) |
| JDK 17 (2021) | SecurityManager标记废弃(forRemoval=true) |
| JDK 24 (2025) | SecurityManager彻底移除(JEP 486) |
二、核心概念
2.1 SecurityManager与沙箱模型(历史)
重要提示:SecurityManager在JDK 17被标记为废弃(forRemoval=true),JDK 18默认禁用,JDK 24彻底移除。本节作为历史知识保留。
沙箱模型
沙箱(Sandbox)是Java早期为Applet设计的安全模型,核心思想:不可信代码只能在受限环境中运行。
沙箱限制(Applet时代):
├── 禁止读写本地文件系统
├── 网络连接只能连回源主机
├── 禁止启动本地进程(Runtime.exec)
├── 禁止加载本地库
├── 禁止访问敏感系统属性
└── 类加载受限
SecurityManager API
java
import java.security.Permission;
/**
* SecurityManager 历史用法示例(JDK 17前)
* JDK 17+ 运行需: -Djava.security.manager=allow
*/
public class SecurityManagerDemo {
public static void main(String[] args) {
// 设置SecurityManager(JDK 18+ 默认禁用,需显式启用)
// System.setSecurityManager(new SecurityManager());
// SecurityManager 定义了100+个检查方法:
// checkRead(String file) - 检查文件读取
// checkWrite(String file) - 检查文件写入
// checkDelete(String file) - 检查文件删除
// checkConnect(String host, int port) - 检查网络连接
// checkExec(String cmd) - 检查进程执行
// checkExit(int status) - 检查JVM退出
// checkPropertyAccess(String key) - 检查属性访问
// checkPermission(Permission) - 通用权限检查
// 自定义SecurityManager示例(历史代码)
SecurityManager sm = new SecurityManager() {
@Override
public void checkPermission(Permission perm) {
// 自定义权限检查逻辑
// 例如: 禁止所有文件写入
if (perm instanceof java.io.FilePermission
&& perm.getActions().contains("write")) {
throw new SecurityException("禁止文件写入: " + perm.getName());
}
// 其他权限放行
}
@Override
public void checkExit(int status) {
throw new SecurityException("禁止退出JVM");
}
};
System.out.println("SecurityManager API 演示(历史知识)");
System.out.println("JDK 17: @Deprecated(forRemoval=true)");
System.out.println("JDK 24: 彻底移除(JEP 486)");
}
}
废弃原因
SecurityManager被废弃的原因:
├── 1. 复杂性: 100+个检查方法,Policy配置复杂
├── 2. 性能开销: 每次敏感操作都要遍历调用栈检查权限
├── 3. 维护成本: JDK内部每个敏感操作都要调用checkXxx
├── 4. 时代变迁: Applet已死,沙箱场景消失
├── 5. 更好的替代:
│ ├── 容器隔离(Docker)
│ ├── 操作系统级沙箱
│ ├── 模块化系统(JPMS强封装)
│ └── 应用层权限控制
└── 6. 实际使用率极低: 绝大多数应用从未启用
2.2 权限检查机制(Permission / AccessController)
java
import java.io.FilePermission;
import java.security.*;
/**
* 权限体系示例
* Permission类层次是Java安全架构的基础组件
* 即使SecurityManager被移除,Permission类仍在JDK中保留
*/
public class PermissionDemo {
public static void main(String[] args) {
// Permission 是抽象类,定义了权限的三元组:
// (名称/目标, 动作, 隐含关系)
// 常见Permission子类:
FilePermission fp = new FilePermission("/tmp/data.txt", "read,write");
System.out.println("FilePermission: " + fp.getName()
+ " 动作: " + fp.getActions());
// implies() 判断一个权限是否隐含另一个权限
FilePermission dirPerm = new FilePermission("/tmp/-", "read");
FilePermission filePerm = new FilePermission("/tmp/test.txt", "read");
System.out.println("目录权限隐含文件权限: " + dirPerm.implies(filePerm));
// 输出: true(/tmp/- 表示/tmp下所有文件)
// RuntimePermission 控制运行时操作
RuntimePermission rp = new RuntimePermission("exitVM");
System.out.println("RuntimePermission: " + rp.getName());
// PermissionCollection 权限集合
PermissionCollection pc = fp.newPermissionCollection();
pc.add(fp);
pc.add(dirPerm);
System.out.println("集合中权限数: " +
java.util.Collections.list(pc.elements()).size());
// AccessController(JDK 17废弃,历史知识)
// AccessController.checkPermission(new FilePermission("x", "read"));
// AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
// // 提权代码块
// return null;
// });
}
}
AccessController.doPrivileged 机制(历史):
┌──────────────────────────────────────────────────────────────┐
│ 问题: 框架代码(有权限)替应用代码(无权限)执行敏感操作 │
│ │
│ 正常检查: 遍历整个调用栈,所有栈帧都需要权限 │
│ → 应用代码无权限 → 拒绝 │
│ │
│ doPrivileged: 检查到该栈帧时停止向上检查 │
│ → 框架代码有权限 → 允许 │
│ │
│ 风险: 如果框架代码被恶意利用,可能产生权限提升漏洞 │
└──────────────────────────────────────────────────────────────┘
2.3 JAAS(Java Authentication and Authorization Service)
JAAS是JDK 1.4引入的标准认证授权框架,基于PAM(Pluggable Authentication Modules)思想:
java
import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import javax.security.auth.Subject;
import java.io.IOException;
import java.security.Principal;
import java.util.Map;
/**
* JAAS 完整示例
* 包含: LoginModule实现 + CallbackHandler + 认证流程
*/
public class JaasDemo {
// ========== 1. 自定义Principal ==========
static class SimplePrincipal implements Principal {
private final String name;
SimplePrincipal(String name) { this.name = name; }
@Override public String getName() { return name; }
@Override public String toString() { return "SimplePrincipal[" + name + "]"; }
}
// ========== 2. 自定义LoginModule ==========
/**
* 简单的用户名/密码认证模块
* 实际生产中应连接LDAP/数据库等
*/
static class SimpleLoginModule implements LoginModule {
private Subject subject;
private CallbackHandler callbackHandler;
private boolean authenticated = false;
@Override
public void initialize(Subject subject, CallbackHandler handler,
Map<String, ?> sharedState, Map<String, ?> options) {
this.subject = subject;
this.callbackHandler = handler;
}
@Override
public boolean login() throws LoginException {
// 通过Callback获取用户名和密码
NameCallback nameCallback = new NameCallback("用户名: ");
PasswordCallback pwdCallback = new PasswordCallback("密码: ", false);
try {
callbackHandler.handle(new Callback[]{nameCallback, pwdCallback});
} catch (IOException | UnsupportedCallbackException e) {
throw new LoginException(e.getMessage());
}
String username = nameCallback.getName();
char[] password = pwdCallback.getPassword();
pwdCallback.clearPassword(); // 安全: 清除密码
// 简单验证(实际应查询数据库)
if ("admin".equals(username) && "secret123".equals(new String(password))) {
authenticated = true;
return true;
}
throw new LoginException("认证失败: 用户名或密码错误");
}
@Override
public boolean commit() throws LoginException {
if (authenticated) {
// 认证成功: 向Subject添加Principal
subject.getPrincipals().add(new SimplePrincipal("admin"));
return true;
}
return false;
}
@Override
public boolean abort() throws LoginException {
authenticated = false;
return true;
}
@Override
public boolean logout() throws LoginException {
subject.getPrincipals().clear();
authenticated = false;
return true;
}
}
// ========== 3. CallbackHandler ==========
static class SimpleCallbackHandler implements CallbackHandler {
private final String username;
private final char[] password;
SimpleCallbackHandler(String username, String password) {
this.username = username;
this.password = password.toCharArray();
}
@Override
public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
for (Callback cb : callbacks) {
if (cb instanceof NameCallback nc) {
nc.setName(username);
} else if (cb instanceof PasswordCallback pc) {
pc.setPassword(password);
} else {
throw new UnsupportedCallbackException(cb, "不支持的Callback类型");
}
}
}
}
// ========== 4. 主流程 ==========
public static void main(String[] args) throws Exception {
// 方式1: 通过JAAS配置文件(需要外部文件 + 系统属性)
// java -Djava.security.auth.login.config=jaas.config JaasDemo
// 方式2: 编程式配置(JDK 9+推荐)
javax.security.auth.login.Configuration config =
new javax.security.auth.login.Configuration() {
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
if ("SimpleConfig".equals(name)) {
return new AppConfigurationEntry[]{
new AppConfigurationEntry(
SimpleLoginModule.class.getName(),
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
Map.of())
};
}
return null;
}
};
// 创建LoginContext并认证
Subject subject = new Subject();
LoginContext loginContext = new LoginContext("SimpleConfig", subject,
new SimpleCallbackHandler("admin", "secret123"), config);
try {
loginContext.login();
System.out.println("认证成功!");
System.out.println("Subject Principals: " + subject.getPrincipals());
// 以认证后的Subject身份执行操作
Subject.doAs(subject, (java.security.PrivilegedAction<Void>) () -> {
System.out.println("以认证身份执行操作");
System.out.println("当前用户: " +
Subject.getSubject(java.security.AccessController.getContext()));
return null;
});
// 登出
loginContext.logout();
System.out.println("已登出, Principals: " + subject.getPrincipals());
} catch (LoginException e) {
System.out.println("认证失败: " + e.getMessage());
}
}
}
2.4 JCA/JCE 加密架构
JCA(Java Cryptography Architecture)和JCE(Java Cryptography Extension)是Java加密体系的核心,采用Provider(SPI)架构:
2.4.1 MessageDigest - 消息摘要
java
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;
/**
* 消息摘要(哈希)示例
* MessageDigest 是 JCA 的核心引擎类之一
*/
public class MessageDigestDemo {
public static void main(String[] args) throws Exception {
String data = "Hello, Java Security!";
// SHA-256 摘要(推荐)
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] hash = sha256.digest(data.getBytes(StandardCharsets.UTF_8));
System.out.println("SHA-256: " + HexFormat.of().formatHex(hash));
System.out.println("摘要长度: " + hash.length + " 字节 (256位)");
// SHA-512 摘要
MessageDigest sha512 = MessageDigest.getInstance("SHA-512");
byte[] hash512 = sha512.digest(data.getBytes(StandardCharsets.UTF_8));
System.out.println("SHA-512: " + HexFormat.of().formatHex(hash512));
// 流式更新(处理大数据)
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update("Hello, ".getBytes(StandardCharsets.UTF_8));
md.update("Java ".getBytes(StandardCharsets.UTF_8));
md.update("Security!".getBytes(StandardCharsets.UTF_8));
byte[] streamHash = md.digest();
System.out.println("流式结果一致: " +
java.util.Arrays.equals(hash, streamHash)); // true
// MD5(不推荐,仅用于非安全场景如校验和)
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] md5Hash = md5.digest(data.getBytes(StandardCharsets.UTF_8));
System.out.println("MD5(不推荐): " + HexFormat.of().formatHex(md5Hash));
// clone() 支持: 创建摘要的中间状态副本
MessageDigest base = MessageDigest.getInstance("SHA-256");
base.update("prefix-".getBytes(StandardCharsets.UTF_8));
MessageDigest copy1 = (MessageDigest) base.clone();
MessageDigest copy2 = (MessageDigest) base.clone();
copy1.update("branch1".getBytes(StandardCharsets.UTF_8));
copy2.update("branch2".getBytes(StandardCharsets.UTF_8));
System.out.println("分支1: " + HexFormat.of().formatHex(copy1.digest()));
System.out.println("分支2: " + HexFormat.of().formatHex(copy2.digest()));
}
}
2.4.2 Cipher - 对称加密
java
import javax.crypto.*;
import javax.crypto.spec.*;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.HexFormat;
/**
* AES对称加密完整示例
* 推荐: AES-256-GCM(认证加密,防篡改)
*/
public class CipherDemo {
public static void main(String[] args) throws Exception {
String plaintext = "机密信息: 密码是P@ssw0rd!";
// ========== AES-GCM 加密(推荐方式)==========
// 1. 生成密钥
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256); // 256位密钥
SecretKey key = keyGen.generateKey();
System.out.println("AES密钥(Base64): " +
Base64.getEncoder().encodeToString(key.getEncoded()));
// 2. 生成随机IV/Nonce(GCM推荐12字节)
byte[] nonce = new byte[12];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(nonce);
// 3. 加密
Cipher encryptCipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, nonce); // 128位认证标签
encryptCipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec);
byte[] ciphertext = encryptCipher.doFinal(
plaintext.getBytes(StandardCharsets.UTF_8));
System.out.println("密文(Base64): " +
Base64.getEncoder().encodeToString(ciphertext));
// 4. 解密(需要相同的key和nonce)
Cipher decryptCipher = Cipher.getInstance("AES/GCM/NoPadding");
decryptCipher.init(Cipher.DECRYPT_MODE, key, gcmSpec);
byte[] decryptedBytes = decryptCipher.doFinal(ciphertext);
String decrypted = new String(decryptedBytes, StandardCharsets.UTF_8);
System.out.println("解密结果: " + decrypted);
// ========== 从密码派生密钥(PBKDF2)==========
SecretKey derivedKey = deriveKeyFromPassword("用户密码", new byte[16]);
System.out.println("派生密钥长度: " + derivedKey.getEncoded().length + " 字节");
// ========== 篡改检测演示 ==========
// GCM模式自带认证,篡改密文会导致解密失败
byte[] tampered = ciphertext.clone();
tampered[5] ^= 0xFF; // 篡改一个字节
try {
decryptCipher.init(Cipher.DECRYPT_MODE, key, gcmSpec);
decryptCipher.doFinal(tampered);
} catch (AEADBadTagException e) {
System.out.println("检测到数据被篡改! (AEADBadTagException)");
}
}
/**
* 从用户密码派生AES密钥
* 使用PBKDF2(密码学标准密钥派生函数)
*/
static SecretKey deriveKeyFromPassword(String password, byte[] salt)
throws Exception {
// PBKDF2WithHmacSHA256: 基于HMAC-SHA256的密钥派生
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
// 迭代次数建议: >= 600000(OWASP 2023建议)
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 600000, 256);
SecretKey tmp = factory.generateSecret(spec);
return new SecretKeySpec(tmp.getEncoded(), "AES");
}
}
2.4.3 KeyGenerator与KeyPairGenerator
java
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.*;
import java.util.Base64;
/**
* 密钥生成示例
* KeyGenerator: 对称密钥
* KeyPairGenerator: 非对称密钥对
*/
public class KeyGeneratorDemo {
public static void main(String[] args) throws Exception {
// ===== 对称密钥生成 =====
KeyGenerator aesGen = KeyGenerator.getInstance("AES");
aesGen.init(256, new SecureRandom()); // 256位
SecretKey aesKey = aesGen.generateKey();
System.out.println("AES密钥算法: " + aesKey.getAlgorithm());
System.out.println("AES密钥长度: " + aesKey.getEncoded().length * 8 + "位");
KeyGenerator hmacGen = KeyGenerator.getInstance("HmacSHA256");
SecretKey hmacKey = hmacGen.generateKey();
System.out.println("HMAC密钥长度: " + hmacKey.getEncoded().length * 8 + "位");
// ===== 非对称密钥对生成 =====
// RSA 密钥对
KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA");
rsaGen.initialize(2048, new SecureRandom());
KeyPair rsaPair = rsaGen.generateKeyPair();
System.out.println("\nRSA公钥长度: " + rsaPair.getPublic().getEncoded().length + " 字节");
System.out.println("RSA私钥长度: " + rsaPair.getPrivate().getEncoded().length + " 字节");
// EC(椭圆曲线)密钥对 - 更高效
KeyPairGenerator ecGen = KeyPairGenerator.getInstance("EC");
ecGen.initialize(256, new SecureRandom()); // P-256曲线
KeyPair ecPair = ecGen.generateKeyPair();
System.out.println("\nEC公钥长度: " + ecPair.getPublic().getEncoded().length + " 字节");
// EdDSA(JDK 15+)- 现代签名算法
KeyPairGenerator edGen = KeyPairGenerator.getInstance("Ed25519");
KeyPair edPair = edGen.generateKeyPair();
System.out.println("Ed25519密钥对已生成");
// ===== 密钥编码格式 =====
// X.509: 公钥编码格式
System.out.println("\nRSA公钥格式: " + rsaPair.getPublic().getFormat()); // X.509
// PKCS#8: 私钥编码格式
System.out.println("RSA私钥格式: " + rsaPair.getPrivate().getFormat()); // PKCS#8
}
}
2.4.4 Signature - 数字签名
java
import java.security.*;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/**
* 数字签名完整示例
* 用途: 验证消息完整性 + 不可否认性
*/
public class SignatureDemo {
public static void main(String[] args) throws Exception {
String message = "转账: 从Alice到Bob, 金额10000元";
// 1. 生成密钥对
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair keyPair = keyGen.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
// 2. 签名
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(privateKey);
signer.update(message.getBytes(StandardCharsets.UTF_8));
byte[] signature = signer.sign();
System.out.println("签名: " + Base64.getEncoder().encodeToString(signature));
// 3. 验签
Signature verifier = Signature.getInstance("SHA256withRSA");
verifier.initVerify(publicKey);
verifier.update(message.getBytes(StandardCharsets.UTF_8));
boolean valid = verifier.verify(signature);
System.out.println("验签结果: " + (valid ? "有效" : "无效"));
// 4. 篡改后验签失败
verifier.initVerify(publicKey);
verifier.update((message + " 金额改为99999").getBytes(StandardCharsets.UTF_8));
boolean tamperedValid = verifier.verify(signature);
System.out.println("篡改后验签: " + (tamperedValid ? "有效" : "无效"));
// 5. Ed25519签名(JDK 15+,更现代)
KeyPairGenerator edGen = KeyPairGenerator.getInstance("Ed25519");
KeyPair edPair = edGen.generateKeyPair();
Signature edSigner = Signature.getInstance("Ed25519");
edSigner.initSign(edPair.getPrivate());
edSigner.update(message.getBytes(StandardCharsets.UTF_8));
byte[] edSignature = edSigner.sign();
Signature edVerifier = Signature.getInstance("Ed25519");
edVerifier.initVerify(edPair.getPublic());
edVerifier.update(message.getBytes(StandardCharsets.UTF_8));
System.out.println("\nEd25519验签: " + (edVerifier.verify(edSignature) ? "有效" : "无效"));
}
}
2.4.5 SecureRandom - 安全随机数
java
import java.security.SecureRandom;
/**
* 密码学安全随机数
* 必须使用SecureRandom,禁止使用java.util.Random生成安全相关随机数
*/
public class SecureRandomDemo {
public static void main(String[] args) {
// 默认构造(自动选择最佳算法)
SecureRandom sr = new SecureRandom();
// 生成随机字节(如IV、盐值、令牌)
byte[] randomBytes = new byte[32]; // 256位
sr.nextBytes(randomBytes);
// 生成随机整数
int randomInt = sr.nextInt();
long randomLong = sr.nextLong(); // JDK 17+
// 指定算法(特殊需求)
// SecureRandom drbg = SecureRandom.getInstance("DRBG"); // JDK 9+
// SecureRandom native_ = SecureRandom.getInstance("NativePRNG");
System.out.println("安全随机数已生成(不打印敏感值)");
System.out.println("算法: " + sr.getAlgorithm());
System.out.println("Provider: " + sr.getProvider().getName());
}
}
2.5 数字证书与KeyStore
java
import java.security.KeyStore;
import java.security.cert.*;
import java.io.*;
import java.nio.file.*;
import java.util.Base64;
/**
* KeyStore操作示例
* KeyStore是存储私钥和证书的容器
*/
public class KeyStoreDemo {
public static void main(String[] args) throws Exception {
String keystorePath = "demo-keystore.p12";
char[] password = "changeit".toCharArray();
// ===== 创建新的PKCS12 KeyStore =====
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(null, password); // null表示创建新库
// 生成密钥对并存入
java.security.KeyPairGenerator kpg =
java.security.KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
java.security.KeyPair kp = kpg.generateKeyPair();
// 自签名证书(实际需要CA签发)
// 此处简化: 直接存储密钥对(生产环境需要证书)
// ks.setKeyEntry("mykey", kp.getPrivate(), password, certChain);
// 保存
try (OutputStream os = Files.newOutputStream(Path.of(keystorePath))) {
ks.store(os, password);
}
System.out.println("KeyStore已保存: " + keystorePath);
// ===== 加载已有KeyStore =====
KeyStore loaded = KeyStore.getInstance("PKCS12");
try (InputStream is = Files.newInputStream(Path.of(keystorePath))) {
loaded.load(is, password);
}
System.out.println("KeyStore类型: " + loaded.getType());
System.out.println("别名列表: " + java.util.Collections.list(loaded.aliases()));
// ===== JDK默认信任库 =====
// 位置: $JAVA_HOME/lib/security/cacerts
// 默认密码: changeit
String javaHome = System.getProperty("java.home");
Path cacerts = Path.of(javaHome, "lib", "security", "cacerts");
if (Files.exists(cacerts)) {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
try (InputStream is = Files.newInputStream(cacerts)) {
trustStore.load(is, "changeit".toCharArray());
}
System.out.println("JDK信任库证书数量: " + trustStore.size());
}
// ===== 解析PEM证书 =====
// X.509证书可从PEM/DER格式加载
// CertificateFactory cf = CertificateFactory.getInstance("X.509");
// X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
// cert.getSubjectX500Principal() - 主体
// cert.getIssuerX500Principal() - 颁发者
// cert.getNotAfter() - 过期时间
// cert.verify(caPublicKey) - 验证CA签名
}
}
2.6 TLS/SSL(JSSE)
java
import javax.net.ssl.*;
import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.security.KeyStore;
/**
* JSSE TLS通信示例
* 展示: SSLContext配置、HTTPS客户端、TLS服务端
*/
public class TlsDemo {
public static void main(String[] args) throws Exception {
// ===== 1. 查看支持的TLS协议版本 =====
SSLContext defaultCtx = SSLContext.getDefault();
SSLParameters params = defaultCtx.getDefaultSSLParameters();
System.out.println("支持的协议: " + String.join(", ", params.getProtocols()));
System.out.println("启用的协议: " + String.join(", ", params.getProtocols()));
// JDK 11+ 默认支持TLS 1.3
// 查看密码套件
System.out.println("\n启用的部分密码套件:");
String[] suites = params.getCipherSuites();
for (int i = 0; i < Math.min(5, suites.length); i++) {
System.out.println(" " + suites[i]);
}
// ===== 2. HTTPS客户端示例 =====
httpsClientExample();
// ===== 3. SSLContext自定义配置 =====
sslContextExample();
}
/**
* HTTPS客户端(使用系统默认信任库)
*/
static void httpsClientExample() throws Exception {
URL url = URI.create("https://www.example.com").toURL();
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
int responseCode = conn.getResponseCode();
System.out.println("\nHTTPS响应码: " + responseCode);
// 查看TLS会话信息
SSLSession session = conn.getSSLSession().orElse(null);
if (session != null) {
System.out.println("TLS协议: " + session.getProtocol());
System.out.println("密码套件: " + session.getCipherSuite());
}
conn.disconnect();
}
/**
* 自定义SSLContext(加载自定义信任库)
*/
static void sslContextExample() throws Exception {
// 加载KeyStore作为信任库
// KeyStore trustStore = KeyStore.getInstance("PKCS12");
// trustStore.load(inputStream, password);
// TrustManagerFactory - 验证服务器证书
// TrustManagerFactory tmf = TrustManagerFactory.getInstance(
// TrustManagerFactory.getDefaultAlgorithm());
// tmf.init(trustStore);
// KeyManagerFactory - 提供客户端证书(双向TLS/mTLS)
// KeyManagerFactory kmf = KeyManagerFactory.getInstance(
// KeyManagerFactory.getDefaultAlgorithm());
// kmf.init(keyStore, password);
// 创建SSLContext
SSLContext ctx = SSLContext.getInstance("TLSv1.3");
// ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
ctx.init(null, null, null); // 使用默认
System.out.println("\nSSLContext协议: " + ctx.getProtocol());
// 获取SocketFactory
SSLSocketFactory factory = ctx.getSocketFactory();
System.out.println("SSLSocketFactory: " + factory.getClass().getSimpleName());
}
}
TLS 1.3 握手流程(简化):
┌────────┐ ┌────────┐
│ Client │ │ Server │
└────┬───┘ └────┬───┘
│ ClientHello │
│ + supported_versions (TLS1.3) │
│ + key_share (ECDHE公钥) │
│ + cipher_suites │
│──────────────────────────────────────▶│
│ │
│ ServerHello │
│ + key_share (ECDHE公钥) │
│ + EncryptedExtensions │
│ + Certificate │
│ + CertificateVerify │
│ + Finished │
│◀──────────────────────────────────────│
│ │
│ Finished │
│──────────────────────────────────────▶│
│ │
│◀════════ 加密通信 ════════════════════▶│
TLS 1.3 vs TLS 1.2:
├── 握手从2-RTT减少到1-RTT(0-RTT可选)
├── 仅支持ECDHE密钥交换(前向保密)
├── 移除不安全算法: RC4, 3DES, MD5, SHA-1, RSA密钥交换
├── 加密更多握手消息
└── 会话恢复使用PSK(Pre-Shared Key)
三、实际应用
3.1 密码安全存储
java
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
/**
* 用户密码安全存储(生产级示例)
* 使用PBKDF2 + 随机盐 + 高迭代次数
*/
public class PasswordStorage {
private static final int ITERATIONS = 600000; // OWASP 2023建议
private static final int KEY_LENGTH = 256;
private static final int SALT_LENGTH = 16;
/**
* 哈希密码(注册时调用)
* @return 格式: 迭代次数:盐(Base64):哈希(Base64)
*/
public static String hashPassword(String password) throws Exception {
// 生成随机盐
byte[] salt = new byte[SALT_LENGTH];
new SecureRandom().nextBytes(salt);
// PBKDF2派生
PBEKeySpec spec = new PBEKeySpec(
password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] hash = factory.generateSecret(spec).getEncoded();
spec.clearPassword(); // 安全: 清除内存中的密码
// 存储格式: iterations:salt:hash
return ITERATIONS + ":" +
Base64.getEncoder().encodeToString(salt) + ":" +
Base64.getEncoder().encodeToString(hash);
}
/**
* 验证密码(登录时调用)
* 使用常量时间比较防止时序攻击
*/
public static boolean verifyPassword(String password, String stored)
throws Exception {
String[] parts = stored.split(":");
int iterations = Integer.parseInt(parts[0]);
byte[] salt = Base64.getDecoder().decode(parts[1]);
byte[] expectedHash = Base64.getDecoder().decode(parts[2]);
PBEKeySpec spec = new PBEKeySpec(
password.toCharArray(), salt, iterations, expectedHash.length * 8);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] actualHash = factory.generateSecret(spec).getEncoded();
spec.clearPassword();
// 常量时间比较(防止时序攻击)
return MessageDigest.isEqual(expectedHash, actualHash);
}
public static void main(String[] args) throws Exception {
String stored = hashPassword("MyP@ssw0rd");
System.out.println("存储格式: " + stored);
System.out.println("验证正确密码: " + verifyPassword("MyP@ssw0rd", stored));
System.out.println("验证错误密码: " + verifyPassword("wrong", stored));
}
}
3.2 HMAC消息认证
java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
/**
* HMAC消息认证码
* 用途: API请求签名、数据完整性验证
*/
public class HmacDemo {
public static void main(String[] args) throws Exception {
// 生成HMAC密钥
byte[] keyBytes = new byte[32];
new SecureRandom().nextBytes(keyBytes);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "HmacSHA256");
String message = "order_id=12345&amount=99.99×tamp=1699999999";
// 计算HMAC
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(keySpec);
byte[] hmacBytes = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
String hmac = Base64.getEncoder().encodeToString(hmacBytes);
System.out.println("HMAC-SHA256: " + hmac);
// 验证
mac.init(keySpec);
byte[] expected = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
boolean valid = java.security.MessageDigest.isEqual(hmacBytes, expected);
System.out.println("验证: " + (valid ? "通过" : "失败"));
}
}
3.3 API请求签名(实际应用模式)
java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;
/**
* API请求签名(常见开放平台模式)
* 防篡改 + 防重放 + 身份认证
*/
public class ApiSignature {
private final String appKey;
private final String appSecret;
public ApiSignature(String appKey, String appSecret) {
this.appKey = appKey;
this.appSecret = appSecret;
}
/**
* 生成请求签名
* 签名内容: appKey + timestamp + nonce + 排序后的参数 + body摘要
*/
public String sign(Map<String, String> params, String body,
long timestamp, String nonce) throws Exception {
// 1. 参数按key排序拼接
TreeMap<String, String> sorted = new TreeMap<>(params);
StringBuilder sb = new StringBuilder();
sorted.forEach((k, v) -> sb.append(k).append("=").append(v).append("&"));
// 2. 拼接签名原文
String signContent = appKey + "\n" + timestamp + "\n" + nonce + "\n"
+ sb + "\n" + sha256Hex(body);
// 3. HMAC签名
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8),
"HmacSHA256"));
byte[] sig = mac.doFinal(signContent.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(sig);
}
/**
* 验证签名
*/
public boolean verify(Map<String, String> params, String body,
long timestamp, String nonce, String signature)
throws Exception {
// 防重放: 检查时间戳(允许5分钟偏差)
if (Math.abs(System.currentTimeMillis() - timestamp) > 300_000) {
return false; // 过期
}
String expected = sign(params, body, timestamp, nonce);
return MessageDigest.isEqual(
expected.getBytes(StandardCharsets.UTF_8),
signature.getBytes(StandardCharsets.UTF_8));
}
private String sha256Hex(String data) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(data.getBytes(StandardCharsets.UTF_8));
return HexFormat.of().formatHex(hash);
}
public static void main(String[] args) throws Exception {
ApiSignature api = new ApiSignature("myAppKey", "myAppSecret123");
Map<String, String> params = Map.of("userId", "1001", "action", "query");
long ts = System.currentTimeMillis();
String nonce = UUID.randomUUID().toString();
String sig = api.sign(params, "{}", ts, nonce);
System.out.println("签名: " + sig);
System.out.println("验证: " + api.verify(params, "{}", ts, nonce, sig));
}
}
四、最佳实践与陷阱
4.1 最佳实践总结
| 场景 | 推荐 | 禁止 |
|---|---|---|
| 密码存储 | PBKDF2(≥60万次)/BCrypt/Argon2 | 明文、MD5、SHA-1 |
| 对称加密 | AES-256-GCM | DES、3DES、ECB模式 |
| 非对称加密 | RSA-2048+ / EC P-256 | RSA-1024 |
| 哈希 | SHA-256/SHA-384/SHA-512 | MD5、SHA-1(安全场景) |
| 签名 | SHA256withRSA / Ed25519 | SHA1withRSA |
| 随机数 | SecureRandom | java.util.Random |
| TLS | TLS 1.3(至少1.2) | SSLv3、TLS 1.0/1.1 |
| 密钥存储 | KeyStore/HSM/密钥管理服务 | 硬编码、配置文件明文 |
| IV/Nonce | 每次加密随机生成 | 固定/重复使用 |
4.2 常见陷阱
java
// 陷阱1: ECB模式不安全
// ECB对相同明文块产生相同密文块,泄露模式
// 错误: Cipher.getInstance("AES/ECB/PKCS5Padding")
// 正确: Cipher.getInstance("AES/GCM/NoPadding")
// 陷阱2: 复用IV/Nonce
// GCM模式下,同一密钥+同一Nonce加密两条消息 = 灾难性泄露
// 每次加密必须生成新的随机Nonce
// 陷阱3: 使用java.util.Random生成安全随机数
// Random是伪随机,种子可预测
// 必须使用SecureRandom
// 陷阱4: 自实现加密协议
// "不要自己发明密码学" - 使用标准协议(TLS)
// 陷阱5: 信任所有证书
// 以下代码极度危险,完全禁用TLS验证:
// TrustManager[] trustAll = new TrustManager[]{ new X509TrustManager() {
// public void checkClientTrusted(...) {}
// public void checkServerTrusted(...) {}
// public X509Certificate[] getAcceptedIssuers() { return null; }
// }};
// 陷阱6: 密钥硬编码
// private static final String KEY = "mySecretKey123"; // 极度危险!
// 陷阱7: 不验证HMAC就处理数据
// 必须先验证完整性,再处理/反序列化
// 陷阱8: 日志中打印敏感信息
// log.info("用户密码: " + password); // 禁止!
4.3 安全编码检查清单
□ 所有网络通信使用TLS 1.2+
□ 密码使用PBKDF2/BCrypt存储,加盐
□ 使用SecureRandom(非Random)
□ SQL使用PreparedStatement(防注入)
□ 用户输入全部验证和转义(防XSS)
□ 反序列化使用白名单(防RCE)
□ XML解析禁用外部实体(防XXE)
□ 敏感信息不写入日志/异常信息
□ 密钥定期轮换
□ 依赖库及时更新(修复CVE)
□ 使用OWASP依赖检查工具
□ 最小权限原则
五、版本演进
┌───────────────────────────────────────────────────────────────────────┐
│ Java安全机制版本演进 │
├───────────┬───────────────────────────────────────────────────────────┤
│ JDK 1.0 │ SecurityManager + 沙箱(Applet安全) │
│ JDK 1.1 │ 签名Applet、基本权限 │
│ JDK 1.2 │ JAAS, JCA, AccessController, ProtectionDomain │
│ JDK 1.4 │ JCE正式集成, JSSE │
│ JDK 5 │ JAAS完善 │
│ JDK 7 │ TLS 1.1/1.2 │
│ JDK 8 │ AES-GCM, PBKDF2增强, 默认密钥长度提升 │
│ JDK 9 │ PKCS12为默认, SHA3, DRBG, 模块化 │
│ JDK 11 │ TLS 1.3, ChaCha20/Poly1305, XDH, 禁用TLS1.0/1.1 │
│ JDK 15 │ EdDSA (Ed25519/Ed448) │
│ JDK 17 │ SecurityManager @Deprecated(forRemoval=true) │
│ JDK 18 │ SecurityManager默认禁用 │
│ JDK 24 │ SecurityManager彻底移除 (JEP 486) │
└───────────┴───────────────────────────────────────────────────────────┘
现代Java安全趋势:
├── 代码访问安全 → 容器/模块化/进程隔离替代
├── 加密算法 → 持续淘汰弱算法,引入后量子密码学(研究中)
├── TLS → 1.3为默认,0-RTT, ECH(Encrypted Client Hello)
└── 密钥管理 → 云KMS/HSM集成成为主流
