文章目录
最近做 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 类型
常用:
格式 | 扩展名 | 描述 |
---|---|---|
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
:
- InputStream 加载
KeyStore
数据的输入流; 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);
}