API Java KeyStore

API Java KeyStore

1. Visão geral

Neste tutorial, estamos examinando o gerenciamento de chaves criptográficas e certificados em Java usandoKeyStore API.

2. Keystores

Se precisarmos gerenciar chaves e certificados em Java, precisamos de umkeystore, which is simply a secure collection of aliased entries of keys and certificates.

Normalmente, salvamos keystores em um sistema de arquivos e podemos protegê-lo com uma senha.

Por padrão, Java possui um arquivo keystore localizado emJAVA_HOME/jre_ / lib / security / cacerts_. Podemos acessar este armazenamento de chaves usando a senha de armazenamento de chaves padrãochangeit.

Agora, com aquele pedaço de fundo,let’s get to creating our first one.

3. Criação de um Keystore

3.1. Construção

Podemos criar facilmente um keystore usandokeytool ou podemos fazê-lo programaticamente usando a APIKeyStore:

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

Aqui, usamos o tipo padrão, embora hajaa few keystore types disponíveis comojceks oupcks12.

Podemos substituir o tipo padrão “JKS” (um protocolo de armazenamento de chaves proprietário da Oracle) usando um parâmetro-Dkeystore.type:

-Dkeystore.type=pkcs12

Ou podemos, é claro, listar um dos formatos suportados emgetInstance:

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

3.2. Inicialização

Inicialmente, precisamosload o armazenamento de chaves:

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

Usamosload quer estejamos criando um novo keystore ou abrindo um existente.

E dizemos aKeyStore para criar um novo passandonull como o primeiro parâmetro.

Também fornecemos uma senha, que será usada para acessar o keystore no futuro. Também podemos definir isso comonull, embora isso tornasse nossos segredos abertos.

3.3. Armazenamento

Por fim, salvamos nosso novo keystore no sistema de arquivos:

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

Observe que não são mostradas acima as várias exceções verificadas quegetInstance,load, areiastore a cada lance.

4. Carregando um Keystore

Para carregar um keystore, primeiro precisamos criar uma instânciaKeyStore, como antes.

Desta vez, porém, vamos especificar o formato, já que estamos carregando um existente:

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

Se nossa JVM não suportar o tipo de keystore que passamos ou se não corresponder ao tipo de keystore no sistema de arquivos que estamos abrindo, obteremos umKeyStoreException:

java.security.KeyStoreException: KEYSTORE_TYPE not found

Além disso, se a senha estiver errada, obteremos umUnrecoverableKeyException:

java.security.UnrecoverableKeyException: Password verification failed

5. Armazenando entradas

No keystore, podemos armazenar três tipos diferentes de entradas, cada entrada com seu alias:

  • Chaves simétricas (chamadas de chaves secretas no JCE),

  • Chaves assimétricas (chamadas de chaves públicas e privadas no JCE) e

  • Certificados confiáveis

Vamos dar uma olhada em cada um.

5.1. Salvando uma chave simétrica

A coisa mais simples que podemos armazenar em um keystore é uma Chave Simétrica.

Para salvar uma chave simétrica, precisamos de três coisas:

  1. an alias - este é simplesmente o nome que usaremos no futuro para nos referir à entrada

  2. a key - que está envolvido em umKeyStore.SecretKeyEntry.

  3. a password - que é envolvido no que é chamado deProtectionParam.

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.* Se deixarmos a senhanull  para uma entrada, obteremos umKeyStoreException:

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

Pode parecer um pouco estranho que precisamos agrupar a chave e a senha nas classes de wrapper.

Envolvemos a chave porquesetEntry é um método genérico que também pode ser usado para outros tipos de entrada. O tipo de entrada permite que a APIKeyStore trate-o de maneira diferente.

Encapsulamos a senha porque oKeyStore API oferece suporte a retornos de chamada para GUIs e CLIs para coletar a senha do usuário final. Confirathe KeyStore.CallbackHandlerProtection Javadoc para mais detalhes.

Também podemos usar esse método para atualizar uma chave existente. Só precisamos chamá-lo novamente com o mesmo alias e senha e nosso novosecret.

5.2. Salvar uma chave privada

Armazenar chaves assimétricas é um pouco mais complexo, pois precisamos lidar com cadeias de certificados.

Além disso, oKeyStore API nos fornece um método dedicado chamadosetKeyEntry que é mais conveniente do que o métodosetEntry genérico.

Portanto, para salvar uma chave assimétrica, precisaremos de quatro coisas:

  1. an alias, o mesmo que antes

  2. a private key. Como não estamos usando o método genérico, a chave não será agrupada. Além disso, para o nosso caso, deve ser uma instância dePrivateKey

  3. a password para acessar a entrada. Desta vez, a senha é obrigatória

  4. a certificate chain que certifica a chave pública correspondente

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

Agora, muitos podem dar errado aqui, é claro, como sepwdArray fornull:

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

Mas, há uma exceção realmente estranha para se estar ciente, e é sepwdArray for uma matriz vazia:

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

Para atualizar, podemos simplesmente chamar o método novamente com o mesmo alias e um novoprivateKey ecertificateChain.

Além disso, pode ser útil fazer uma atualização rápida emhow to generate a certificate chain.

5.3. Salvar um certificado confiável

Storing trusted certificates is quite simple. It only requires the alias and the certificate itself, que é do tipoCertificate:

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

Normalmente, o certificado é aquele que não geramos, mas veio de um terceiro.

Por isso, é importante observar aqui queKeyStore não verifica realmente este certificado. Devemos verificar por conta própria antes de armazená-lo.

Para atualizar, podemos simplesmente chamar o método novamente com o mesmo alias e um novotrustedCertificate.

6. Lendo inscrições

Agora que escrevemos algumas entradas, certamente vamos querer lê-las.

6.1. Ler uma única entrada

Primeiro, podemos extrair chaves e certificados pelo alias:

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

Se não houver entrada com esse nome ou se for de um tipo diferente,getKey simplesmente retornanull:

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

Mas, se a senha da chave estiver errada,we’ll get that same odd error we talked about earlier:

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

6.2. Verificando se um Keystore contém um Alias

ComoKeyStore apenas armazena entradas usando umMap, ele expõe a capacidade de verificar a existência sem recuperar a entrada:

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. Verificando o tipo de entrada

OuKeyStore#entryInstanceOf é um pouco mais poderoso.

É comocontainsAlias, exceto que também verifica o tipo de entrada:

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. Excluindo entradas

KeyStore, é claro, suporta a exclusão das entradas que adicionamos:

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

Felizmente,deleteEntry  é idempotente, então o método reage da mesma forma, quer a entrada exista ou não.

8. Excluindo um Keystore

Se queremos excluir nosso keystore, a API não ajuda em nada, mas ainda podemos usar o Java para fazer isso:

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

Ou, como alternativa, podemos manter o keystore por perto e remover as entradas:

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

9. Conclusão

Neste artigo, falamos sobre o gerenciamento de certificados e chaves usandoKeyStore API. Discutimos o que é um keystore, como criar, carregar e excluir um, como armazenar uma chave ou certificado no keystore e como carregar e atualizar entradas existentes com novos valores.

A implementação completa do exemplo pode ser encontradaover on Github.