API KeyStore Java

API KeyStore Java

1. обзор

В этом руководстве мы рассмотрим управление криптографическими ключами и сертификатами в Java с помощьюKeyStore API.

2. Хранилища ключей

Если нам нужно управлять ключами и сертификатами в Java, нам понадобитсяkeystore, which is simply a secure collection of aliased entries of keys and certificates.

Обычно мы сохраняем хранилища ключей в файловой системе и можем защитить его паролем.

По умолчанию в Java есть файл хранилища ключей, расположенный вJAVA_HOME/jre_ / lib / security / cacerts_. Мы можем получить доступ к этому хранилищу ключей, используя пароль хранилища ключей по умолчаниюchangeit.

Теперь, с этим битом фона,let’s get to creating our first one.

3. Создание хранилища ключей

3.1. строительство

Мы можем легко создать хранилище ключей, используяkeytool, или сделать это программно, используя APIKeyStore:

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

Здесь мы используем тип по умолчанию, хотя естьa few keystore types, такие какjceks илиpcks12.

Мы можем переопределить тип «JKS» (проприетарный протокол хранилища ключей Oracle) по умолчанию, используя параметр-Dkeystore.type:

-Dkeystore.type=pkcs12

Или, конечно, мы можем перечислить один из поддерживаемых форматов вgetInstance:

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

3.2. инициализация

Первоначально нам нужноload хранилище ключей:

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

Мы используемload независимо от того, создаем ли мы новое хранилище ключей или открываем существующее.

И мы говоримKeyStore создать новый, передавnull в качестве первого параметра.

Мы также предоставляем пароль, который будет использоваться для доступа к хранилищу ключей в будущем. Мы также можем установить его наnull, хотя это сделает наши секреты открытыми.

3.3. Место хранения

Наконец, мы сохраняем наш новый склад ключей в файловой системе:

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

Обратите внимание, что выше не показаны несколько отмеченных исключений, которые каждый раз выбрасываютgetInstance,load, andstore.

4. Загрузка хранилища ключей

Чтобы загрузить хранилище ключей, нам сначала нужно создать экземплярKeyStore, как и раньше.

Однако на этот раз давайте укажем формат, поскольку мы загружаем уже существующий:

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

Если наша JVM не поддерживает тип хранилища ключей, который мы передали, или если он не соответствует типу хранилища ключей в файловой системе, которую мы открываем, мы получимKeyStoreException:

java.security.KeyStoreException: KEYSTORE_TYPE not found

Кроме того, если пароль неправильный, мы получимUnrecoverableKeyException:

java.security.UnrecoverableKeyException: Password verification failed

5. Хранение записей

В хранилище ключей мы можем хранить три разных типа записей, каждая запись под своим псевдонимом:

  • Симметричные ключи (называемые секретными ключами в JCE),

  • Асимметричные ключи (именуемые в JCE открытыми и частными ключами), и

  • Доверенные сертификаты

Давайте посмотрим на каждую из них.

5.1. Сохранение симметричного ключа

Самая простая вещь, которую мы можем хранить в хранилище ключей - это симметричный ключ.

Чтобы сохранить симметричный ключ, нам понадобятся три вещи:

  1. an alias - это просто имя, которое мы будем использовать в будущем для обозначения записи

  2. a key - завернутый вKeyStore.SecretKeyEntry.

  3. a password - который заключен в так называемый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.* Если мы оставим парольnull  для записи, мы получимKeyStoreException:

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

Может показаться немного странным, что нам нужно обернуть ключ и пароль в классах-оболочках.

Мы заключаем ключ в оболочку, потому чтоsetEntry - это общий метод, который также можно использовать для других типов записей. Тип записи позволяет APIKeyStore обрабатывать его по-разному.

Мы обертываем пароль, потому чтоKeyStore API поддерживает обратные вызовы к графическим интерфейсам и интерфейсам командной строки для сбора пароля от конечного пользователя. Посетитеthe KeyStore.CallbackHandlerProtection Javadoc для получения дополнительной информации.

Мы также можем использовать этот метод для обновления существующего ключа. Нам просто нужно вызвать его снова с тем же псевдонимом и паролем и нашим новымsecret.

5.2. Сохранение закрытого ключа

Хранить асимметричные ключи немного сложнее, так как нам нужно работать с цепочками сертификатов.

Кроме того,KeyStore API дает нам специальный метод под названиемsetKeyEntry w, который более удобен, чем общий методsetEntry .

Итак, чтобы сохранить асимметричный ключ, нам понадобятся четыре вещи:

  1. an alias, как раньше

  2. a private key. Поскольку мы не используем общий метод, ключ не будет заключен в оболочку. Также в нашем случае это должен быть экземплярPrivateKey

  3. a password для доступа к записи. На этот раз пароль обязателен

  4. a certificate chain, который удостоверяет соответствующий открытый ключ

X509Certificate[] certificateChain = new X509Certificate[2];
chain[0] = clientCert;
chain[1] = caCert;
ks.setKeyEntry("sso-signing-key", privateKey, pwdArray, certificateChain);

Конечно, здесь много может пойти не так, как если быpwdArray былоnull:

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

Но есть действительно странное исключение, о котором следует знать, и это еслиpwdArray - пустой массив:

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

Чтобы обновить, мы можем просто снова вызвать метод с тем же псевдонимом и новымиprivateKey иcertificateChain.

Кроме того, может быть полезно быстро освежить в памятиhow to generate a certificate chain.

5.3. Сохранение доверенного сертификата

Storing trusted certificates is quite simple. It only requires the alias and the certificate itself, который имеет типCertificate:

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

Обычно это сертификат, который не создавался нами, а был получен от третьей стороны.

В связи с этим важно отметить, чтоKeyStore на самом деле не проверяет этот сертификат. Мы должны проверить это самостоятельно, прежде чем хранить его.

Чтобы обновить, мы можем просто снова вызвать метод с тем же псевдонимом и новымtrustedCertificate.

6. Чтение записей

Теперь, когда мы написали несколько статей, мы обязательно захотим их прочитать.

6.1. Чтение одной записи

Во-первых, мы можем извлечь ключи и сертификаты по их псевдониму:

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

Если записи с таким именем нет или она другого типа, то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"));
}

Но, если пароль для ключа неправильный,we’ll get that same odd error we talked about earlier:

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

6.2. Проверка наличия псевдонима в хранилище ключей

Поскольку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. Проверка вида въезда

ИлиKeyStore#entryInstanceOf немного мощнее.

Это похоже на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. Удаление записей

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);
}

К счастью,deleteEntry  идемпотентен, поэтому метод реагирует одинаково, существует запись или нет.

8. Удаление хранилища ключей

Если мы хотим удалить наше хранилище ключей, API нам не поможет, но мы все равно можем использовать Java для этого:

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

Или, в качестве альтернативы, мы можем хранить хранилище ключей и просто удалять записи:

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

9. Заключение

В этой статье мы говорили об управлении сертификатами и ключами с помощьюKeyStore API. Мы обсудили, что такое хранилище ключей, как его создать, загрузить и удалить, как сохранить ключ или сертификат в хранилище ключей и как загрузить и обновить существующие записи новыми значениями.

Полную реализацию примера можно найти вover on Github.