Рубрики
Без рубрики

Java KeyStore API

Узнайте, как взаимодействовать с Java keystores программно.

Автор оригинала: Adrian Precub.

1. Обзор

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

2. Ключевые магазины

Если нам нужно управлять ключами и сертификатами на Java, нам нужна keystore , который является просто безопасной коллекцией псевдонимом записи ключей и сертификатов.

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

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

Теперь, с этим немного фона, Давайте вернемся к созданию нашего первого.

3. Создание keystore

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

Мы можем легко создать keystore с помощью ключевые , или мы можем сделать это программно с помощью KeyStore API:

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

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

Мы можем переопределить по умолчанию “JKS” (протокол, собственный Oracle) с помощью -Dkeystore.тип параметр:

-Dkeystore.type=pkcs12

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

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

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

Сначала нам нужно загрузка keystore:

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

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

И мы расскажем KeyStore создать новый, передавая нулевой в качестве первого параметра.

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

3.3. Хранение

Наконец, мы экономим наш новый клавиатурный магазин для файловой системы:

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

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

4. Загрузка Keystore

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

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

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

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

java.security.KeyStoreException: KEYSTORE_TYPE not found

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

java.security.UnrecoverableKeyException: Password verification failed

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

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

  • Симметричные ключи (именуемые Секретными Ключами в JCE),
  • Асимметричные ключи (именуемые государственными и частными ключами в JCE) и
  • Доверенные сертификаты

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

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

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

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

  1. псевдоним – его просто имя, которое мы будем использовать в будущем для обозначения вступления
  2. ключевой – который завернут в KeyStore.SecretKeyEntry .
  3. пароль – который завернут в то, что называется ЗащитаПарам .
KeyStore.SecretKeyEntry secret
 = new KeyStore.SecretKeyEntry(secretKey);
KeyStore.ProtectionParameter password
 = new KeyStore.PasswordProtection(pwdArray);
ks.setEntry("db-encryption-secret", secret, password);

Имейте в виду, что пароль не может быть нулевой, однако, это может быть пустой струна. Если мы оставим пароль нулевой для входа, мы получим KeyStoreException:

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

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

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

Мы заворачиваем пароль, потому что KeyStore API поддерживает обратные вызовы в ГУИ и ЦРУ для сбора пароля от конечному пользователю. Отъезд KeyStore . CallbackHandlerПротекторная Джавадок для получения более подробной информации.

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

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

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

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

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

  1. псевдоним , как и раньше
  2. частный ключевой . Поскольку мы не используем общий метод, ключ не будет обернут. Кроме того, для нашего случая, это должен быть пример ПриватНаяКей
  3. пароль для доступа к записи. На этот раз пароль является обязательным
  4. цепочка сертификатов который удостоверяет соответствующий общественный ключ
X509Certificate[] certificateChain = new X509Certificate[2];
chain[0] = clientCert;
chain[1] = caCert;
ks.setKeyEntry("sso-signing-key", privateKey, pwdArray, certificateChain);

Теперь, много может пойти не так здесь, конечно, как если бы pwdArray это нулевой :

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

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

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

Чтобы обновить, мы можем просто позвонить в метод снова с тем же псевдонимом и новым частныйКей и сертификатChain.

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

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

Хранение доверенных сертификатов довольно просто. Для этого требуется только псевдоним и сертификат сама , который имеет тип Сертификат :

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

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

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

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

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

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

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

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

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

Если нет записи под этим именем или она другого типа, то getKey просто возвращает нулевой :

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

Но, если пароль для ключа не так, Мы получим ту же странную ошибку, о которой мы говорили ранее:

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

6.2. Проверка того, содержит ли Keystore псевдоним

С KeyStore просто хранит записи с помощью Карта , он предоставляет возможность проверить существование без получения записи:

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 немного мощнее.

Это как содержитАлиас , за исключением того, что он также проверяет тип входа:

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

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

8. Удаление Keystore

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

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

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

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

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

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

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