Java KeyStore API

Java Top

Ich habe gerade den neuen Learn Spring- Kurs angekündigt , der sich auf die Grundlagen von Spring 5 und Spring Boot 2 konzentriert:

>> Überprüfen Sie den Kurs

1. Übersicht

In diesem Tutorial beschäftigen wir uns mit der Verwaltung kryptografischer Schlüssel und Zertifikate in Java mithilfe der KeyStore- API.

2. Keystores

Wenn wir Schlüssel und Zertifikate in Java verwalten müssen, benötigen wir einen Keystore , bei dem es sich lediglich um eine sichere Sammlung von Alias- Einträgen von Schlüsseln und Zertifikaten handelt.

Wir speichern Keystores normalerweise in einem Dateisystem und können es mit einem Kennwort schützen.

Standardmäßig verfügt Java über eine Keystore-Datei unter JAVA_HOME / jre / lib / security / cacerts . Wir können auf diesen Keystore mit der Standardänderung des Keystore-Passworts zugreifen .

Lassen Sie uns nun mit diesem Hintergrund unser erstes erstellen.

3. Erstellen eines Keystores

3.1. Konstruktion

Wir können einen Keystore einfach mit Keytool erstellen oder programmgesteuert mit der KeyStore- API:

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

Hier verwenden wir den Standardtyp, obwohl einige Keystore-Typen wie jceks oder pcks12 verfügbar sind .

Wir können den Standardtyp "JKS" (ein Oracle-proprietäres Keystore-Protokoll) mit einem Parameter -Dkeystore.type überschreiben :

-Dkeystore.type=pkcs12

Oder wir können natürlich eines der unterstützten Formate in getInstance auflisten :

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

3.2. Initialisierung

Zunächst müssen wir den Keystore laden :

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

Wir verwenden load, unabhängig davon, ob wir einen neuen Schlüsselspeicher erstellen oder einen vorhandenen öffnen.

Und wir weisen KeyStore an, einen neuen zu erstellen, indem wir null als ersten Parameter übergeben.

Wir stellen auch ein Passwort zur Verfügung, mit dem in Zukunft auf den Keystore zugegriffen werden kann. Wir können dies auch auf null setzen , obwohl dies unsere Geheimnisse öffnen würde.

3.3. Lager

Schließlich speichern wir unseren neuen Schlüsselspeicher im Dateisystem:

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

Beachten Sie, dass oben nicht die verschiedenen geprüften Ausnahmen aufgeführt sind, die jeden Wurf abrufen , laden und speichern .

4. Laden eines Keystores

Um einen Keystore zu laden, müssen wir zunächst wie zuvor eine KeyStore- Instanz erstellen .

Dieses Mal geben wir jedoch das Format an, da wir ein vorhandenes laden:

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

Wenn unsere JVM den von uns übergebenen Keystore-Typ nicht unterstützt oder nicht mit dem Typ des Keystores im Dateisystem übereinstimmt, das wir öffnen, wird eine KeyStoreException angezeigt :

java.security.KeyStoreException: KEYSTORE_TYPE not found

Wenn das Passwort falsch ist, erhalten wir eine UnrecoverableKeyException:

java.security.UnrecoverableKeyException: Password verification failed

5. Einträge speichern

Im Keystore können drei verschiedene Arten von Einträgen gespeichert werden, wobei jeder Eintrag unter seinem Alias ​​steht:

  • Symmetrische Schlüssel (im JCE als geheime Schlüssel bezeichnet),
  • Asymmetrische Schlüssel (im JCE als öffentliche und private Schlüssel bezeichnet) und
  • Vertrauenswürdige Zertifikate

Werfen wir einen Blick auf jeden einzelnen.

5.1. Speichern eines symmetrischen Schlüssels

Das Einfachste, was wir in einem Schlüsselspeicher speichern können, ist ein symmetrischer Schlüssel.

Um einen symmetrischen Schlüssel zu speichern, benötigen wir drei Dinge:

  1. ein Alias - dies ist einfach der Name, den wir in Zukunft verwenden werden, um auf den Eintrag zu verweisen
  2. ein Schlüssel - der in einen KeyStore.SecretKeyEntry eingeschlossen ist .
  3. ein Passwort - das in ein sogenanntes ProtectionParam eingeschlossen ist .
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 cannot be null, however, it can be an empty String.If we leave the password null for an entry, we'll get a 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.

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

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.

5.2. Saving a Private Key

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

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

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

  1. an alias, same as before
  2. a private key. Because we aren't using the generic method, the key won't get wrapped. Also, for our case, it should be an instance of PrivateKey
  3. a password for accessing the entry. This time, the password is mandatory
  4. a certificate chain that certifies the corresponding public key
X509Certificate[] certificateChain = new X509Certificate[2]; chain[0] = clientCert; chain[1] = caCert; ks.setKeyEntry("sso-signing-key", privateKey, pwdArray, certificateChain);

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

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

But, there's a really strange exception to be aware of, and that is if pwdArray is an empty array:

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.

Also, it might be valuable to do a quick refresher on how to generate a certificate chain.

5.3. Saving a Trusted Certificate

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

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

Usually, the certificate is one that we didn't generate, but that 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.

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

6. Reading Entries

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

6.1. Reading a Single Entry

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 is of a different type, then getKey simply returns 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

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

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

Or, KeyStore#entryInstanceOf is a bit more powerful.

It's like containsAlias, except it also checks the entry type:

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

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

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.

8. Deleting a Keystore

Wenn wir unseren Keystore löschen möchten, ist die API keine Hilfe für uns, aber wir können trotzdem Java verwenden, um dies zu tun:

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

Alternativ können wir den Schlüsselspeicher beibehalten und einfach Einträge entfernen:

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

9. Fazit

In diesem Artikel wurde über die Verwaltung von Zertifikaten und Schlüsseln mithilfe der KeyStore-API gesprochen . Wir haben erläutert, was ein Schlüsselspeicher ist, wie ein Schlüsselspeicher erstellt, geladen und gelöscht wird, wie ein Schlüssel oder ein Zertifikat im Schlüsselspeicher gespeichert wird und wie vorhandene Einträge mit neuen Werten geladen und aktualisiert werden.

Die vollständige Implementierung des Beispiels finden Sie auf Github.

Java unten

Ich habe gerade den neuen Learn Spring- Kurs angekündigt , der sich auf die Grundlagen von Spring 5 und Spring Boot 2 konzentriert:

>> Überprüfen Sie den Kurs