Java KeyStore API

Java Top

Saya baru saja mengumumkan kursus Learn Spring baru , yang berfokus pada dasar-dasar Spring 5 dan Spring Boot 2:

>> LIHAT KURSUSnya

1. Ikhtisar

Dalam tutorial ini, kami melihat mengelola kunci kriptografi dan sertifikat di Java menggunakan API KeyStore .

2. Penyimpanan kunci

Jika kita perlu mengelola kunci dan sertifikat di Java, kita memerlukan keystore , yang merupakan kumpulan aman dari entri kunci dan sertifikat alias .

Kami biasanya menyimpan keystore ke sistem file, dan kami dapat melindunginya dengan kata sandi.

Secara default, Java memiliki file keystore yang terletak di JAVA_HOME / jre / lib / security / cacerts . Kita dapat mengakses keystore ini menggunakan default password yang keystore changeit .

Sekarang, dengan sedikit latar belakang itu, mari kita membuat yang pertama.

3. Membuat Keystore

3.1. Konstruksi

Kita dapat dengan mudah membuat keystore menggunakan keytool, atau kita dapat melakukannya secara terprogram menggunakan KeyStore API:

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

Di sini, kami menggunakan tipe default, meskipun ada beberapa tipe keystore yang tersedia seperti jceks atau pcks12 .

Kita dapat mengganti jenis "JKS" default (protokol keystore berpemilik Oracle) menggunakan parameter -Dkeystore.type :

-Dkeystore.type=pkcs12

Atau, tentu saja, kami dapat mencantumkan salah satu format yang didukung di getInstance :

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

3.2. Inisialisasi

Awalnya, kita perlu memuat keystore:

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

Kami menggunakan beban apakah kami membuat keystore baru atau membuka yang sudah ada.

Dan, kami memberi tahu KeyStore untuk membuat yang baru dengan meneruskan null sebagai parameter pertama.

Kami juga menyediakan kata sandi, yang akan digunakan untuk mengakses keystore di masa mendatang. Kami juga dapat menyetel ini ke nol , meskipun itu akan membuat rahasia kami terbuka.

3.3. Penyimpanan

Terakhir, kami menyimpan keystore baru kami ke sistem file:

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

Perhatikan bahwa yang tidak ditampilkan di atas adalah beberapa pengecualian yang diperiksa getInstance , memuat, dan menyimpan setiap lemparan.

4. Memuat Keystore

Untuk memuat keystore, pertama kita perlu membuat instance KeyStore , seperti sebelumnya.

Kali ini, mari kita tentukan formatnya karena kita memuat yang sudah ada:

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

Jika JVM kita tidak mendukung jenis keystore yang kita teruskan, atau jika tidak cocok dengan jenis keystore pada sistem file yang kita buka, kita akan mendapatkan KeyStoreException :

java.security.KeyStoreException: KEYSTORE_TYPE not found

Selain itu, jika sandi salah, kita akan mendapatkan UnrecoverableKeyException:

java.security.UnrecoverableKeyException: Password verification failed

5. Menyimpan Entri

Di keystore, kita dapat menyimpan tiga jenis entri, setiap entri di bawah aliasnya:

  • Kunci Simetris (disebut sebagai Kunci Rahasia di JCE),
  • Kunci Asimetris (disebut sebagai Kunci Publik dan Pribadi di JCE), dan
  • Sertifikat Tepercaya

Mari kita lihat satu per satu.

5.1. Menyimpan Kunci Simetris

Hal paling sederhana yang bisa kita simpan di keystore adalah Kunci Symmetric.

Untuk menyimpan kunci simetris, kita membutuhkan tiga hal:

  1. sebuah alias - ini hanyalah nama yang akan kita gunakan di masa depan untuk merujuk ke entri tersebut
  2. sebuah kunci - yang dibungkus dalam KeyStore.SecretKeyEntry .
  3. kata sandi - yang dibungkus dengan apa yang disebut 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 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

Jika kami ingin menghapus keystore kami, API tidak membantu kami, tetapi kami masih dapat menggunakan Java untuk melakukannya:

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

Atau, sebagai alternatif, kita dapat menyimpan keystore, dan hanya menghapus entri:

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

9. Kesimpulan

Di artikel ini, kami berbicara tentang mengelola sertifikat dan kunci menggunakan KeyStore API . Kita membahas apa itu keystore, cara membuat, memuat dan menghapus satu, cara menyimpan kunci atau sertifikat di keystore dan cara memuat dan memperbarui entri yang ada dengan nilai baru.

Implementasi lengkap dari contoh ini dapat ditemukan di Github.

Jawa bawah

Saya baru saja mengumumkan kursus Learn Spring baru , yang berfokus pada dasar-dasar Spring 5 dan Spring Boot 2:

>> LIHAT KURSUSnya