KeyStore 简述

最近做 Auth 会用到 RSA 和 KeyStore 的知识, 补一下~

Java KeyStore 是一个存储密钥记录的"数据库". 一个 java.security.KeyStore class 就代表这样一个 KeyStore. 一个 KeyStore 可以被写到磁盘反复去读, 作为一个整体, 它可以被密码保护, 并且每一个在 KeyStore 中的 key 都可以被其完全独立的密码保护, 大大提高了其安全性.

KeyStore 中 Key 的类型

KeyStore 支持以下类型的 key:

  • Private keys & Public keys + certificates: 公私钥用于非对称加密. 其中公钥可以拥有一个与之匹配的证书. 而证书本身是一个验证拥有这个公钥的个人, 组织或是设备的文件. 证书一般是由认证机构 (CA) 数字签名的, 也可以是自签名的 (但是不会受信任);
  • Secret keys: 用于对称加密;
  • Certificate: 证书本身 (密钥库可以单独存储证书条目), 一张证书包含了一个能认证证书中声明的主语的公钥, 通常被用于服务器授信;

基于 KeyStore 中 Key 的类型以及存储方式, 有一下类型的 KeyStore:

KeyStore 类型

Oracle’s Java Cryptography Architecture

Different types of keystore in Java – Overview

常用:

格式扩展名描述
JKS.jsk/ks[Java Keystore] 密钥库的 Java 实现版本 (sun.security.provider.JavaKeyStore), Provider 为 SUN; 可以保存私钥和其对应公钥的证书; 或是单独的证书;
JCEKS.jce[JCE Keystore (Java Cryptography Extension KeyStore)] 密钥库的 JCE 实现版本 (com.sun.crypto.provider.JceKeyStore), Provider为 SunJCE, JDK1.4 之后才提供; 相对于 JKS 安全级别更高, 是 JKS 的超集, 支持更多算法, 可以保存全部 3 种类型的 Key
PKCS12.p12/.pfx[PKCS #12] 个人信息交换语法标准. 可以用在 JAVA 和其他语言 (C, C++, C#) 中. (sun.security.pkcs12.PKCS12KeyStore), 支持全部 3 种类型的 Key

Api

Code

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Base64;
import java.util.Collections;

/**
 * {@link KeyStore} Helper
 *
 * @author LiKe
 * @version 1.0.0
 * @date 2020-07-11 10:03
 */
public final class KeyStoreHelper {

    /**
     * 获取 KeyStore 的实例
     *
     * @return {@link KeyStore}
     */
    public static KeyStore create() throws KeyStoreException {
        // defaultType: jks
        return KeyStore.getInstance(KeyStore.getDefaultType());
    }

    /**
     * 向 KeyStore 中设值 Key 记录
     *
     * @param keyStore {@link KeyStore}
     */
    public static KeyStore setKeyEntry(KeyStore keyStore) throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException {
        // ~ KeyStore 使用前必须加载, 加载一个空的 KeyStore
        keyStore.load(null, "My First Key Store's Password".toCharArray());

        // ~ 生成公私钥
        final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(512);
        final KeyPair keyPair = keyPairGenerator.generateKeyPair();
        final PrivateKey privateKey = keyPair.getPrivate();
        
        System.out.println("Generated PrivateKey: ");
        System.out.println(Base64.getEncoder().encodeToString(privateKey.getEncoded()));

        final PublicKey publicKey = keyPair.getPublic();

        // ~ 把指定的 Key 与指定的别名绑定, 并用指定的密码保护它
        keyStore.setKeyEntry(
                // 别名
                "My First Key Entry",
                privateKey,
                "My First Key Entry's Password".toCharArray(),
                Collections.singletonList(certificate(publicKey)).toArray(new Certificate[1])
        );

        return keyStore;
    }

    public static void main(String[] args) throws Exception {
        final KeyStore keyStore = setKeyEntry(create());

        final KeyStore.ProtectionParameter protectionParameter = new KeyStore.PasswordProtection("My First Key Entry's Password".toCharArray());
        final KeyStore.Entry keyEntry = keyStore.getEntry("My First Key Entry", protectionParameter);

        final KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyEntry;
        final PrivateKey privateKey = privateKeyEntry.getPrivateKey();

        System.out.println("PrivateKey got from keyStore: ");
        System.out.println(Base64.getEncoder().encodeToString(privateKey.getEncoded()));
    }

    /**
     * Description: 生成自签名的证书
     *
     * @param publicKey 公钥
     * @return java.security.cert.Certificate
     * @author LiKe
     * @date 2020-07-14 11:39:09
     */
    private static Certificate certificate(PublicKey publicKey) {
        return new Certificate("Self.Signed") {
            @Override
            public byte[] getEncoded() {
                return publicKey.getEncoded();
            }

            @Override
            public void verify(PublicKey key) {
                // do nothing
            }

            @Override
            public void verify(PublicKey key, String sigProvider) {
                // do nothing
            }

            @Override
            public String toString() {
                return Base64.getEncoder().encodeToString(getEncoded());
            }

            @Override
            public PublicKey getPublicKey() {
                return publicKey;
            }
        };
    }

    /**
     * Description: 生成证书 (X.509)
     *
     * @return java.security.cert.Certificate
     * @author LiKe
     * @date 2020-07-13 15:55:15
     */
    private static Certificate certificate() throws CertificateException {
        final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

        final String encodedPublicKey =
                "-----BEGIN CERTIFICATE-----\n" +
                        "MIICxDCCAi0CBECcV/wwDQYJKoZIhvcNAQEEBQAwgagxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIEwVU\n" +
                        "ZXhhczEPMA0GA1UEBxMGQXVzdGluMSowKAYDVQQKEyFUaGUgVW5pdmVyc2l0eSBvZiBUZXhhcyBh\n" +
                        "dCBBdXN0aW4xKDAmBgNVBAsTH0luZm9ybWF0aW9uIFRlY2hub2xvZ3kgU2VydmljZXMxIjAgBgNV\n" +
                        "BAMTGXhtbGdhdGV3YXkuaXRzLnV0ZXhhcy5lZHUwHhcNMDQwNTA4MDM0NjA0WhcNMDQwODA2MDM0\n" +
                        "NjA0WjCBqDELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMQ8wDQYDVQQHEwZBdXN0aW4xKjAo\n" +
                        "BgNVBAoTIVRoZSBVbml2ZXJzaXR5IG9mIFRleGFzIGF0IEF1c3RpbjEoMCYGA1UECxMfSW5mb3Jt\n" +
                        "YXRpb24gVGVjaG5vbG9neSBTZXJ2aWNlczEiMCAGA1UEAxMZeG1sZ2F0ZXdheS5pdHMudXRleGFz\n" +
                        "LmVkdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsmc+6+NjLmanvh+FvBziYdBwTiz+d/DZ\n" +
                        "Uy2jyvij6f8Xly6zkhHLSsuBzw08wPzr2K+F359bf9T3uiZMuao//FBGtDrTYpvQwkn4PFZwSeY2\n" +
                        "Ynw4edxp1JEWT2zfOY+QJDfNgpsYQ9hrHDwqnpbMVVqjdBq5RgTKGhFBj9kxEq0CAwEAATANBgkq\n" +
                        "hkiG9w0BAQQFAAOBgQCPYGXF6oRbnjti3CPtjfwORoO7ab1QzNS9Z2rLMuPnt6POlm1A3UPEwCS8\n" +
                        "6flTlAqg19Sh47H7+Iq/LuzotKvUE5ugK52QRNMa4c0OSaO5UEM5EfVox1pT9tZV1Z3whYYMhThg\n" +
                        "oC4y/On0NUVMN5xfF/GpSACga/bVjoNvd8HWEg==\n" +
                        "-----END CERTIFICATE-----";

        return certificateFactory.generateCertificate(new ByteArrayInputStream(encodedPublicKey.getBytes()));
    }

}

控制台输出:

Generated PrivateKey: 
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAjVUzHn6SyyBY0McJ8n/0JpvLN+KmSNopMWpkztlIG2aWk8as0QkI0AeBcPY/CQ6SkCFvA+rxzjrit56UHhMS7wIDAQABAkBbnkOJHRwjuVBZ5u032mJ3NL9D9xU8XNAfTI0U2h24RIkOyYAYSwerstf7uF2N2qY7IfYLCUa2Z8FsGwfnIzuhAiEA6A/I2NmYU74e3av4R/R8Zd0pr5BndgWSy0C7EZq4PEUCIQCb6XmCGe/sllcz9qR8hr8gZU5eHdSzZEzWh//OkdiXowIhALneyrRFtOh+QyKx9y9K98hvGFByjSvO0wDRXASBtEcxAiAYXZpxrKAEN1KVelwmYeCIrYxbxQmyp2AivspJfB1/RQIhANl28BoNkX6zYBALqKq+Pncjl5F4GC6DplKtk9j/IIuc
PrivateKey got from keyStore: 
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAjVUzHn6SyyBY0McJ8n/0JpvLN+KmSNopMWpkztlIG2aWk8as0QkI0AeBcPY/CQ6SkCFvA+rxzjrit56UHhMS7wIDAQABAkBbnkOJHRwjuVBZ5u032mJ3NL9D9xU8XNAfTI0U2h24RIkOyYAYSwerstf7uF2N2qY7IfYLCUa2Z8FsGwfnIzuhAiEA6A/I2NmYU74e3av4R/R8Zd0pr5BndgWSy0C7EZq4PEUCIQCb6XmCGe/sllcz9qR8hr8gZU5eHdSzZEzWh//OkdiXowIhALneyrRFtOh+QyKx9y9K98hvGFByjSvO0wDRXASBtEcxAiAYXZpxrKAEN1KVelwmYeCIrYxbxQmyp2AivspJfB1/RQIhANl28BoNkX6zYBALqKq+Pncjl5F4GC6DplKtk9j/IIuc

获取 KeyStore 实例

可以通过如下方式创建一个 KeyStore:

// 默认类型为 jks (Java Key Store), 就是利用 Java Keytool 工具生成的 KeyStore 文件
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
KeyStore keyStore = KeyStore.getInstance("PKCS12");

加载 KeyStore

使用前, KeyStore 必须被加载. KeyStore 实例通常被写在磁盘上或是以其他类型的方式存储. 这就是为什么 KeyStore 会要求用户在使用前必须先加载. 但是, 初始化一个空的 KeyStore 也是有可能的.

可以调用 KeyStore#load(InputStream, char[]) 方法加载一个 KeyStore:

  1. InputStream 加载 KeyStore 数据的输入流;
  2. KeyStore 密码的字符数组;
char[] keyStorePassword = "123abc".toCharArray();
try(InputStream keyStoreData = new FileInputStream("keystore.ks")){
    keyStore.load(keyStoreData, keyStorePassword);
}

(从 KeyStore 文件中加载)

如果你不想加载任何数据到 KeyStore, 只需要向 InputStream 参数传入 null 值, 例如:

keyStore.load(null, keyStorePassword);

用户总是需要加载一个 KeyStore 的实例, 无论是有数据还是没数据 (inputStream or null). 调用一个没有初始化的 KeyStore 的方法会抛出异常.

获取 KeyEntry

可以通过 getEntry() 方法获取 Key, 一个 KeyStore Entry 被映射到一个标识了 key 的别名, 并且它被 Key 的密码保护. 因此, 要想访问一个 Key, 用户必须向 getEntry() 方法传递 Key 的别名和密码. 以下是访问一个 Key Entry 的例子:

char[] keyPassword = "789xyz".toCharArray();
KeyStore.ProtectionParameter entryPassword =
        new KeyStore.PasswordProtection(keyPassword);

KeyStore.Entry keyEntry = keyStore.getEntry("keyAlias", entryPassword);

如果用户已经知道要访问的 Key Entry 是一个私钥, 可以显示将 KeyStore.Entry 转换成 KeyStore.PrivateKeyEntry:

KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry("keyAlias", entryPassword);

这样做了之后用户可以调用如下方法:

  • getPrivateKey()
  • getCertificate()
  • getCertificateChain()

设值 KeyEntry

我们也可以手动向 KeyStore 中设值:

SecretKey secretKey = getSecretKey();
KeyStore.SecretKeyEntry secretKeyEntry = new KeyStore.SecretKeyEntry(secretKey);

keyStore.setEntry("keyAlias2", secretKeyEntry, entryPassword);

保存 KeyStore

很多时候, 我们需要将 KeyStore 保存, 例如在磁盘, 数据库等地方, 供稍后有需要的时候再使用. 可以调用 KeyStore 的 store() 方法完成这个操作:

char[] keyStorePassword = "123abc".toCharArray();
try (FileOutputStream keyStoreOutputStream = new FileOutputStream("data/keystore.ks")) {
    keyStore.store(keyStoreOutputStream, keyStorePassword);
}

Reference

Tomcat 9 默认情况下并不直接支持将CA证书密码明文存储。为了启用HTTPS并配置自签名证书或者受信任的CA签发的证书,你需要按照以下步骤操作: 1. **配置SSL/TLS**: 在`conf/server.xml`文件中,找到`<Connector>`元素,通常在`<Engine>`标签下。然后添加或更新`sslProtocol`属性,指定TLS版本,并配置`keystore`、`keystorePass`和`truststore`等信息。例如: ```xml <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" sslProtocol="TLS" maxThreads="150" scheme="https" secure="true" SSLEnabled="true" keystoreFile="/path/to/your/keystore.jks" keystorePassword="your-password" truststoreFile="/path/to/your/truststore.jks" truststorePassword="your-trust-store-password"/> ``` 确保替换`/path/to/your`和实际的密码。 2. **创建Keystore和Truststore**: 如果没有,你需要先生成keystore和truststore,可以使用Java Keytool命令行工具: ```sh keytool -file your-ca.crt -keystore path/to/truststore.jks -storepass your-trust-store-password ``` 这里的`ca.crt`是你的CA证书文件,需要替换为实际路径。 3. **安全考虑**: 密码明文存储并不是最佳实践,建议通过环境变量或者其他安全机制来管理敏感信息,避免明文暴露。如果不希望在配置文件中明写密码,可以将其作为环境变量传递给Tomcat进程。 **相关问题--:** 1. 如何修改Tomcat SSL配置而不显示密码? 2. Tomcat如何导入自签名或CA证书? 3. 简述为什么推荐不在配置中明文保存密码?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值