Java深入解析篇六十二之安全机制详解

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&timestamp=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集成成为主流

相关推荐
小刘在重生~1 小时前
Java 常用类|包装类 装箱拆箱、Integer 缓存面试题
java·python·缓存
Orange_sparkle1 小时前
Pi Agent vs LangGraph:从人机交互、企业 RAG 到持久化工作流的完整选型指南
java·网络·人机交互
菜鸟~noob2332 小时前
【电子战】 第01篇:噪声、门限与虚警/检测概率(PFA/PD)【含matlab代码】
开发语言·matlab
奇牙coding1232 小时前
GPT-6-Astra API 接入教程:OpenRouter 路由配置 + Python/curl 示例 + 静默降级踩坑
开发语言·python·gpt·ai
lzx_0023 小时前
C++11(一)
开发语言·c++·算法
未来之窗软件服务3 小时前
计算机二级[英文]-C 字符串移动—东方仙盟
c语言·开发语言·仙盟创梦ide·东方仙盟
Wang's Blog3 小时前
Java框架快速入门: Spring Security+OAuth2之过滤器与过滤器链
java·开发语言·spring
谢尔登3 小时前
01_Java基础速通
java·开发语言
前端炒粉3 小时前
DOM 常用 API 速查表
开发语言·javascript·ecmascript