Java KeyStore API – Java KeyStore API

最后修改: 2018年 4月 23日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll learn how to manage cryptographic keys and certificates in Java using the KeyStore API.

在本教程中,我们将学习如何使用KeyStoreAPI在Java中管理加密密钥和证书。

2. Keystores

2.钥匙库

If we need to manage keys and certificates in Java, we need a keystore, which is simply a secure collection of aliased entries of keys and certificates.

如果我们需要在Java中管理钥匙和证书,我们需要一个keystore,它只是一个钥匙和证书的别名entries的安全集合。

We typically save keystores to a file system, and we can protect it with a password.

我们通常将密钥存储保存在一个文件系统中,我们可以用密码来保护它。

By default, Java has a keystore file located at JAVA_HOME/jre/lib/security/cacerts. We can access this keystore using the default keystore password changeit.

默认情况下,Java有一个位于JAVA_HOME/jre/lib/security/cacerts的keystore文件。我们可以使用默认的keystore密码changeit来访问这个keystore。

Now that we’ve established some background, let’s create our first one.

现在我们已经建立了一些背景,让我们来创建我们的第一个。

3. Creating a Keystore

3.创建一个密钥库

3.1. Construction

3.1.建筑

We can easily create a keystore using keytool, or we can do it programmatically using the KeyStore API:

我们可以使用keytool轻松地创建一个钥匙库,也可以使用KeyStore API以编程方式完成。

KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());

Here we used the default type, though there are a few keystore types available, like jceks or pkcs12.

这里我们使用了默认的类型,尽管有一些可用的钥匙库类型,如jcekspkcs12

We can override the default “JKS”(an Oracle-proprietary keystore protocol) type using a -Dkeystore.type parameter:

我们可以使用-Dkeystore.type参数覆盖默认的 “JKS”(Oracle专有的密钥库协议)类型。

-Dkeystore.type=pkcs12

Or we can list one of the supported formats in getInstance:

或者我们可以在 getInstance中列出支持的格式之一。

KeyStore ks = KeyStore.getInstance("pkcs12");

3.2. Initialization

3.2.初始化

Initially, we need to load the keystore:

最初,我们需要加载密钥库。

char[] pwdArray = "password".toCharArray();
ks.load(null, pwdArray);

We use load whether we’re creating a new keystore or opening up an existing one. We’ll tell KeyStore to create a new one by passing null as the first parameter.

无论我们是创建一个新的钥匙库还是打开一个现有的钥匙库,我们都要使用load。我们将告诉KeyStore创建一个新的,把null作为第一个参数。

We also provide a password, which will be used for accessing the keystore in the future. We can also set this to null, though that would make our secrets open.

我们还提供了一个密码,它将被用来在未来访问钥匙库。我们也可以将其设置为null,不过这将使我们的秘密公开。

3.3. Storage

3.3.储存

Finally, we save our new keystore to the file system:

最后,我们把新的密钥库保存到文件系统中。

try (FileOutputStream fos = new FileOutputStream("newKeyStoreFileName.jks")) {
    ks.store(fos, pwdArray);
}

Note that not shown above are the several checked exceptions that getInstanceload, and store each throw.

注意,上面没有显示的是getInstanceload、store各自抛出的几个被检查的异常。

4. Loading a Keystore

4.加载一个密钥库

To load a keystore, we first need to create a KeyStore instance, like before.

要加载一个钥匙库,我们首先需要创建一个钥匙库实例,就像之前一样。

This time though, we’ll specify the format, since we’re loading an existing one:

不过,这次我们将指定格式,因为我们正在加载一个现有的格式。

KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("newKeyStoreFileName.jks"), pwdArray);

If our JVM doesn’t support the keystore type we passed, or if it doesn’t match the type of the keystore on the filesystem that we’re opening, we’ll get a KeyStoreException:

如果我们的JVM不支持我们传递的keystore类型,或者它与我们正在打开的文件系统上的keystore类型不匹配,我们将得到一个KeyStoreException

java.security.KeyStoreException: KEYSTORE_TYPE not found

Also, if the password is wrong, we’ll get an UnrecoverableKeyException:

此外,如果密码错误,我们会得到一个UnrecoverableKeyException:

java.security.UnrecoverableKeyException: Password verification failed

5. Storing Entries

5.储存条目

In the keystore, we can store three different kinds of entries, each under its alias:

在钥匙库中,我们可以存储三种不同的条目,每一种都在其别名之下。

  • Symmetric Keys (referred to as Secret Keys in the JCE)
  • Asymmetric Keys (referred to as Public and Private Keys in the JCE)
  • Trusted Certificates

Let’s take a look at each one.

让我们来看看每一个人。

5.1. Saving a Symmetric Key

5.1.保存一个对称密钥

The simplest thing we can store in a keystore is a Symmetric Key.

我们可以在钥匙库中存储的最简单的东西是一个对称钥匙。

To save a symmetric key, we’ll need three things:

要保存一个对称密钥,我们需要三样东西。

  1. an alias – this is simply the name that we’ll use in the future to refer to the entry
  2. a key – which is wrapped in a KeyStore.SecretKeyEntry
  3. a password – which is wrapped in what is called a ProtectionParam
KeyStore.SecretKeyEntry secret
 = new KeyStore.SecretKeyEntry(secretKey);
KeyStore.ProtectionParameter password
 = new KeyStore.PasswordProtection(pwdArray);
ks.setEntry("db-encryption-secret", secret, password);

Keep in mind that the password can’t be null; however, it can be an empty String. If we leave the password null for an entry, we’ll get a KeyStoreException:

请记住,密码不能是空的;然而,它可以是一个空的字符串。如果我们让密码的条目,我们会得到一个KeyStoreException:

java.security.KeyStoreException: non-null password required to create SecretKeyEntry

It may seem a little weird that we need to wrap the key and the password in wrapper classes.

我们需要用封装类来封装密钥和密码,这可能看起来有点奇怪。

We wrap the key because setEntry is a generic method that can be used for the other entry types as well. The type of entry allows the KeyStore API to treat it differently.

我们将密钥包裹起来,因为setEntry是一个通用方法,也可用于其他条目类型。条目类型允许KeyStore API以不同的方式对待它。

We wrap the password because the KeyStore API supports callbacks to GUIs and CLIs to collect the password from the end user. We can check out the KeyStore.CallbackHandlerProtection Javadoc for more details.

我们封装了密码,因为KeyStoreAPI支持对GUI和CLI的回调,以便从最终用户那里收集密码。我们可以查看KeyStore.CallbackHandlerProtection Javadoc以了解更多细节。

We can also use this method to update an existing key; we just need to call it again with the same alias and password and our new secret.

我们也可以使用这个方法来更新一个现有的密钥;我们只需要用相同的别名和密码以及我们的新secret.再次调用它。

5.2. Saving a Private Key

5.2.保存一个私钥

Storing asymmetric keys is a bit more complex, since we need to deal with certificate chains.

存储非对称密钥要复杂一些,因为我们需要处理证书链。

The KeyStore API gives us a dedicated method called setKeyEntry, which is more convenient than the generic setEntry method.

KeyStoreAPI为我们提供了一个名为setKeyEntry的专用方法,它比通用的setEntry方法更方便。

So to save an asymmetric key, we’ll need four things:

所以要保存一个非对称密钥,我们需要四样东西。

  1. an alias – like before
  2. a private key – because we aren’t using the generic method, the key won’t get wrapped. Also, in our case, it should be an instance of PrivateKey.
  3. a password – used to access the entry. This time, the password is mandatory.
  4. a certificate chain – this certifies the corresponding public key
X509Certificate[] certificateChain = new X509Certificate[2];
chain[0] = clientCert;
chain[1] = caCert;
ks.setKeyEntry("sso-signing-key", privateKey, pwdArray, certificateChain);

Lots can go wrong here, of course, like if pwdArray is null:

当然,这里可能会出现很多问题,比如pwdArraynull

java.security.KeyStoreException: password can't be null

But there’s a really strange exception to be aware of, which occurs if pwdArray is an empty array:

但是有一个非常奇怪的异常需要注意,如果pwdArray是一个空数组,就会出现这种情况。

java.security.UnrecoverableKeyException: Given final block not properly padded

To update, we can simply call the method again with the same alias and a new privateKey and certificateChain.

要更新,我们可以简单地用相同的别名和一个新的privateKeycertificateChain再次调用该方法。

It might also be valuable to do a quick refresher on how to generate a certificate chain.

如何生成证书链进行快速复习可能也是有价值的。

5.3. Saving a Trusted Certificate

5.3.保存受信任的证书

Storing trusted certificates is quite simple. It only requires the alias and the certificate itself, which is of type Certificate:

存储受信任的证书是非常简单的。它只需要别名和证书本身,它的类型是Certificate

ks.setCertificateEntry("google.com", trustedCertificate);

Usually, the certificate is one that we didn’t generate, and instead came from a third-party.

通常情况下,该证书不是我们生成的,而是来自第三方。

Because of that, it’s important to note here that KeyStore doesn’t actually verify this certificate. We should verify it on our own before storing it.

正因为如此,这里要注意的是,KeyStore实际上并没有验证这个证书。我们应该在存储它之前自己验证它。

To update, we can simply call the method again with the same alias and a new trustedCertificate.

要更新,我们可以简单地用相同的别名和一个新的trustedCertificate再次调用该方法。

6. Reading Entries

6.阅读条目

Now that we’ve written some entries, we’ll certainly want to read them.

现在我们已经写了一些条目,我们当然希望能读到它们。

6.1. Reading a Single Entry

6.1.读取单个条目

First, we can pull keys and certificates out by their alias:

首先,我们可以通过他们的别名把钥匙和证书拉出来。

Key ssoSigningKey = ks.getKey("sso-signing-key", pwdArray);
Certificate google = ks.getCertificate("google.com");

If there’s no entry by that name, or it’s of a different type, then getKey simply returns null:

如果没有这个名字的条目,或者它是一个不同的类型,那么getKey就简单地返回null

public void whenEntryIsMissingOrOfIncorrectType_thenReturnsNull() {
    // ... initialize keystore
    // ... add an entry called "widget-api-secret"

   Assert.assertNull(ks.getKey("some-other-api-secret"));
   Assert.assertNotNull(ks.getKey("widget-api-secret"));
   Assert.assertNull(ks.getCertificate("widget-api-secret")); 
}

But if the password for the key is wrong, we’ll get that same odd error we talked about earlier:

但是,如果钥匙的密码是错误的,我们将得到我们先前谈到的那个奇怪的错误:

java.security.UnrecoverableKeyException: Given final block not properly padded

6.2. Checking if a Keystore Contains an Alias

6.2.检查一个钥匙库是否包含一个别名

Since KeyStore just stores entries using a Map, it exposes the ability to check for existence without retrieving the entry:

由于KeyStore只是使用Map来存储条目,它暴露了检查是否存在而不检索条目的能力。

public void whenAddingAlias_thenCanQueryWithoutSaving() {
    // ... initialize keystore
    // ... add an entry called "widget-api-secret"
    assertTrue(ks.containsAlias("widget-api-secret"));
    assertFalse(ks.containsAlias("some-other-api-secret"));
}

6.3. Checking the Kind of Entry

6.3.检查条目的种类

KeyStore#entryInstanceOf is a bit more powerful.

KeyStore#entryInstanceOf更强大一些。

It’s similar to containsAlias, except it also checks the entry type:

它类似于containsAlias,除了它还检查条目类型。

public void whenAddingAlias_thenCanQueryByType() {
    // ... initialize keystore
    // ... add a secret entry called "widget-api-secret"
    assertTrue(ks.containsAlias("widget-api-secret"));
    assertFalse(ks.entryInstanceOf(
      "widget-api-secret",
      KeyType.PrivateKeyEntry.class));
}

7. Deleting Entries

7.删除条目

KeyStore, of course, supports deleting the entries we’ve added:

KeyStore,当然,支持删除我们已经添加的条目。

public void whenDeletingAnAlias_thenIdempotent() {
    // ... initialize a keystore
    // ... add an entry called "widget-api-secret"
    assertEquals(ks.size(), 1);
    ks.deleteEntry("widget-api-secret");
    ks.deleteEntry("some-other-api-secret");
    assertFalse(ks.size(), 0);
}

Fortunately, deleteEntry is idempotent, so the method reacts the same whether the entry exists or not.

幸运的是,deleteEntry是empotent的,所以无论该条目是否存在,该方法的反应都是一样的。

8. Deleting a Keystore

8.删除一个密钥库

If we want to delete our keystore, the API is no help to us, but we can still use Java to do it:

如果我们想删除我们的钥匙库,API对我们没有帮助,但我们仍然可以用Java来做。

Files.delete(Paths.get(keystorePath));

Or, as an alternative, we can keep the keystore around and just remove entries:

或者,作为一种选择,我们可以保留钥匙库,只是删除条目。

Enumeration<String> aliases = keyStore.aliases();
while (aliases.hasMoreElements()) {
    String alias = aliases.nextElement();
    keyStore.deleteEntry(alias);
}

9. Conclusion

9.结论

In this article, we learned how to manage certificates and keys using KeyStore API. We discussed what a keystore is, and explored how to create, load and delete one. We also demonstrated how to store a key or certificate in the keystore, and how to load and update existing entries with new values.

在这篇文章中,我们学习了如何使用KeyStore API管理证书和密钥。我们讨论了什么是钥匙库,并探讨了如何创建、加载和删除一个钥匙库。我们还演示了如何在钥匙库中存储钥匙或证书,以及如何用新值加载和更新现有条目。

The full implementation of the example can be found over on Github.

该示例的完整实现可以在Github上找到over