API KeyStore Java

Java Teratas

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

>> SEMAK KURSUS

1. Gambaran keseluruhan

Dalam tutorial ini, kita melihat mengurus kunci dan sijil kriptografi di Java menggunakan KeyStore API.

2. Kedai kunci

Jika kita perlu menguruskan kunci dan sijil di Jawa, kami memerlukan stor kunci , yang hanya koleksi selamat dialiaskan penyertaan kunci dan sijil.

Kami biasanya menyimpan stor kunci ke sistem fail, dan kami dapat melindunginya dengan kata laluan.

Secara lalai, Java mempunyai fail keystore yang terletak di JAVA_HOME / jre / lib / security / cacerts . Kita boleh mengakses kedai kunci ini dengan menggunakan kata laluan perubahan kata kunci kunci .

Sekarang, dengan sedikit latar belakang, mari kita buat yang pertama.

3. Membuat Kedai Kunci

3.1. Pembinaan

Kita boleh membuat keystore dengan mudah menggunakan keytool, atau kita boleh melakukannya secara programatik menggunakan KeyStore API:

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

Di sini, kami menggunakan jenis lalai, walaupun terdapat beberapa jenis kedai kunci yang tersedia seperti jceks atau pcks12 .

Kita boleh mengatasi jenis "JKS" lalai (protokol kedai kunci milik Oracle) menggunakan parameter -Dkeystore.type :

-Dkeystore.type=pkcs12

Atau, tentu saja, kita boleh menyenaraikan salah satu format yang disokong di getInstance :

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

3.2. Permulaan

Pada mulanya, kita perlu memuatkan kedai kunci:

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

Kami menggunakan beban sama ada kami membuat kedai kunci baru atau membuka yang sudah ada.

Dan, kami memberitahu KeyStore untuk membuat yang baru dengan memberikan null sebagai parameter pertama.

Kami juga menyediakan kata laluan, yang akan digunakan untuk mengakses kedai kunci pada masa akan datang. Kita juga dapat menetapkan ini menjadi batal , walaupun itu akan membuka rahsia kita.

3.3. Penyimpanan

Akhirnya, kami menyimpan kedai kunci baru kami ke sistem fail:

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

Nota yang tidak ditunjukkan di atas adalah beberapa pengecualian diperiksa bahawa getInstance , beban, dan menyimpan setiap lontaran.

4. Memuatkan Kedai Kunci

Untuk memuatkan keystore, pertama-tama kita perlu membuat instance KeyStore , seperti sebelumnya.

Namun, kali ini, tentukan formatnya kerana kita memuatkan format yang ada:

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

Sekiranya JVM kami tidak menyokong jenis keystore yang kami lalui, atau jika tidak sesuai dengan jenis keystore pada sistem fail yang kami buka, kami akan mendapat KeyStoreException :

java.security.KeyStoreException: KEYSTORE_TYPE not found

Sekiranya kata laluan salah, kami akan mendapat UnrecoverableKeyException:

java.security.UnrecoverableKeyException: Password verification failed

5. Menyimpan Penyertaan

Di kedai kunci, kami boleh menyimpan tiga jenis entri yang berbeza, setiap entri di bawah aliasnya:

  • Kekunci Simetri (disebut sebagai Kunci Rahsia di JCE),
  • Kunci Asimetri (disebut sebagai Kunci Awam dan Swasta di JCE), dan
  • Sijil yang Dipercayai

Mari kita perhatikan setiap satu.

5.1. Menyimpan Kekunci Simetri

Perkara paling mudah yang boleh kita simpan di kedai kunci adalah Kunci Simetri.

Untuk menyimpan kunci simetri, kami memerlukan tiga perkara:

  1. alias - dia hanyalah nama yang akan kita gunakan pada masa akan datang untuk merujuk kepada entri
  2. kunci - yang dibungkus dalam KeyStore.SecretKeyEntry .
  3. kata laluan - yang dibungkus dalam apa yang dipanggil ProtectionParam .
KeyStore.SecretKeyEntry secret = new KeyStore.SecretKeyEntry(secretKey); KeyStore.ProtectionParameter password = new KeyStore.PasswordProtection(pwdArray); ks.setEntry("db-encryption-secret", secret, password);

Perlu diingat bahawa kata laluan tidak boleh kosong, namun, itu boleh menjadi String kosong . Jika kita meninggalkan kata laluan null untuk entri, kita akan mendapat KeyStoreException:

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

Nampaknya agak pelik bahawa kita perlu membungkus kunci dan kata laluan di kelas pembungkus.

Kami membungkus kunci kerana setEntry adalah kaedah generik yang boleh digunakan untuk jenis entri lain juga. Jenis kemasukan membolehkan KeyStore API memperlakukannya secara berbeza.

Kami membungkus kata laluan kerana KeyStore API menyokong panggilan balik ke GUI dan CLI untuk mengumpulkan kata laluan dari pengguna akhir. Lihat Javadoc KeyStore .CallbackHandlerProtection untuk maklumat lebih lanjut.

Kami juga boleh menggunakan kaedah ini untuk mengemas kini kunci yang ada. Kita hanya perlu memanggilnya semula dengan alias dan kata laluan yang sama dan rahsia baru kita .

5.2. Menyimpan Kunci Peribadi

Menyimpan kunci asimetri sedikit lebih rumit kerana kita perlu berurusan dengan rantai sijil.

Juga, stor kunci API memberikan kita satu kaedah khusus dipanggil setKeyEntry yang lebih mudah daripada generik setEntry kaedah.

Oleh itu, untuk menyimpan kunci tidak simetri, kita memerlukan empat perkara:

  1. alias , sama seperti sebelumnya
  2. kunci peribadi . Kerana kami tidak menggunakan kaedah generik, kuncinya tidak akan dibungkus. Juga, untuk kes kami, ini harus menjadi contoh 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

Sekiranya kami ingin menghapus kedai kunci kami, API tidak membantu kami, tetapi kami masih boleh menggunakan Java untuk melakukannya:

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

Atau, sebagai alternatif, kita boleh menyimpan kedai kunci, dan hanya membuang entri:

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

9. Kesimpulannya

Dalam artikel ini, kami membincangkan tentang menguruskan sijil dan kunci menggunakan KeyStore API . Kami membincangkan apa itu kunci kedai, bagaimana membuat, memuat dan memadamnya, bagaimana menyimpan kunci atau sijil di kedai kunci dan bagaimana memuat dan mengemas kini entri yang ada dengan nilai baru.

Pelaksanaan penuh contoh boleh didapati di Github.

Bahagian bawah Java

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

>> SEMAK KURSUS