Come leggere il file PEM per ottenere chiavi pubbliche e private

Java Top

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO

1. Panoramica

Nella crittografia a chiave pubblica (nota anche come crittografia asimmetrica), il meccanismo di crittografia si basa su due chiavi correlate, una chiave pubblica e una chiave privata. La chiave pubblica viene utilizzata per crittografare il messaggio mentre solo il proprietario della chiave privata può decrittografare il messaggio.

In questo tutorial, vedremo come leggere le chiavi pubbliche e private da un file PEM.

In primo luogo, studieremo alcuni concetti importanti sulla crittografia a chiave pubblica. Quindi, impareremo come leggere i file PEM usando Java puro.

Infine, esploreremo la libreria BouncyCastle come approccio alternativo.

2. Concetti

Prima di iniziare, comprendiamo alcuni concetti chiave.

X.509 è uno standard che definisce il formato dei certificati a chiave pubblica. Quindi, questo formato descrive una chiave pubblica tra le altre informazioni.

DER è il formato di codifica più popolare per memorizzare dati come certificati X.509, chiavi private PKCS8 nei file. È una codifica binaria e il contenuto risultante non può essere visualizzato con un editor di testo.

PKCS8 è una sintassi standard per la memorizzazione delle informazioni sulla chiave privata. La chiave privata può essere facoltativamente crittografata utilizzando un algoritmo simmetrico.

Non solo le chiavi private RSA possono essere gestite da questo standard, ma anche altri algoritmi. Le chiavi private PKCS8 vengono in genere scambiate tramite il formato di codifica PEM.

PEM è un meccanismo di codifica in base 64 di un certificato DER. PEM può anche codificare altri tipi di dati come chiavi pubbliche / private e richieste di certificati.

Un file PEM contiene anche un'intestazione e un piè di pagina che descrivono il tipo di dati codificati:

-----BEGIN PUBLIC KEY----- ...Base64 encoding of the DER encoded certificate... -----END PUBLIC KEY-----

3. Utilizzo di Pure Java

3.1. Leggi i dati PEM da un file

Iniziamo leggendo il file PEM e memorizzandone il contenuto in una stringa:

String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());

3.2. Ottieni la chiave pubblica dalla stringa PEM

Costruiremo un metodo di utilità che ottiene la chiave pubblica dalla stringa codificata PEM:

-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsjtGIk8SxD+OEiBpP2/T JUAF0upwuKGMk6wH8Rwov88VvzJrVm2NCticTk5FUg+UG5r8JArrV4tJPRHQyvqK wF4NiksuvOjv3HyIf4oaOhZjT8hDne1Bfv+cFqZJ61Gk0MjANh/T5q9vxER/7TdU NHKpoRV+NVlKN5bEU/NQ5FQjVXicfswxh6Y6fl2PIFqT2CfjD+FkBPU1iT9qyJYH A38IRvwNtcitFgCeZwdGPoxiPPh1WHY8VxpUVBv/2JsUtrB/rAIbGqZoxAIWvijJ Pe9o1TY3VlOzk9ASZ1AeatvOir+iDVJ5OpKmLnzc46QgGPUsjIyo6Sje9dxpGtoG QQIDAQAB -----END PUBLIC KEY-----

Supponiamo di ricevere un file come parametro:

public static RSAPublicKey readPublicKey(File file) throws Exception { String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset()); String publicKeyPEM = key .replace("-----BEGIN PUBLIC KEY-----", "") .replaceAll(System.lineSeparator(), "") .replace("-----END PUBLIC KEY-----", ""); byte[] encoded = Base64.decodeBase64(publicKeyPEM); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded); return (RSAPublicKey) keyFactory.generatePublic(keySpec); }

Come possiamo vedere, prima dobbiamo rimuovere l'intestazione, il piè di pagina e anche le nuove righe. Quindi, dobbiamo decodificare la stringa con codifica Base64 nel formato binario corrispondente.

Successivamente, dobbiamo caricare il risultato in una classe di specifica della chiave in grado di gestire un materiale di chiave pubblica. Nel nostro caso, useremo la classe X509EncodedKeySpec .

Infine, possiamo generare un oggetto chiave pubblica dalla specifica utilizzando la classe KeyFactory .

3.3. Ottieni chiave privata dalla stringa PEM

Ora che sappiamo come leggere una chiave pubblica, l'algoritmo per leggere una chiave privata è molto simile.

Utilizzeremo una chiave privata codificata PEM in formato PKCS8. Vediamo come sono l'intestazione e il piè di pagina:

-----BEGIN PRIVATE KEY----- ...Base64 encoded key... -----END PRIVATE KEY-----

Come abbiamo appreso in precedenza, abbiamo bisogno di una classe in grado di gestire il materiale delle chiavi PKCS8. La classe PKCS8EncodedKeySpec ricopre questo ruolo.

Quindi, vediamo l'algoritmo:

public RSAPrivateKey readPrivateKey(File file) throws Exception { String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset()); String privateKeyPEM = key .replace("-----BEGIN PRIVATE KEY-----", "") .replaceAll(System.lineSeparator(), "") .replace("-----END PRIVATE KEY-----", ""); byte[] encoded = Base64.decodeBase64(privateKeyPEM); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); return (RSAPrivateKey) keyFactory.generatePrivate(keySpec); }

4. Utilizzo della libreria BouncyCastle

4.1. Leggi la chiave pubblica

Esploreremo la libreria BouncyCastle e vedremo come può essere utilizzata come alternativa alla pura implementazione Java.

Otteniamo la chiave pubblica:

public RSAPublicKey readPublicKey(File file) throws Exception { KeyFactory factory = KeyFactory.getInstance("RSA"); try (FileReader keyReader = new FileReader(file); PemReader pemReader = new PemReader(keyReader)) { PemObject pemObject = pemReader.readPemObject(); byte[] content = pemObject.getContent(); X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(content); return (RSAPublicKey) factory.generatePublic(pubKeySpec); } }

Ci sono alcune classi importanti di cui dobbiamo essere consapevoli quando usiamo BouncyCastle:

  • PemReader : prende un Reader come parametro e ne analizza il contenuto. Rimuove le intestazioni non necessarie e decodifica i dati PEM Base64 sottostanti in un formato binario.
  • PemObject : memorizza il risultato generato dal PemReader .

Inoltre, vediamo un altro approccio che avvolge le classi Java ( X509EncodedKeySpec, KeyFactory ) nella stessa classe di BouncyCastle ( JcaPEMKeyConverter ):

public RSAPublicKey readPublicKeySecondApproach(File file) throws IOException { try (FileReader keyReader = new FileReader(file)) { PEMParser pemParser = new PEMParser(keyReader); JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(pemParser.readObject()); return (RSAPublicKey) converter.getPublicKey(publicKeyInfo); } }

4.2. Leggi la chiave privata

We're going to see two examples that are very similar to the ones showed above.

In the first example, we just need to replace the X509EncodedKeySpec class with the PKCS8EncodedKeySpec class and return an RSAPrivateKey object instead of an RSAPublicKey:

public RSAPrivateKey readPrivateKey(File file) throws Exception { KeyFactory factory = KeyFactory.getInstance("RSA"); try (FileReader keyReader = new FileReader(file); PemReader pemReader = new PemReader(keyReader)) { PemObject pemObject = pemReader.readPemObject(); byte[] content = pemObject.getContent(); PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content); return (RSAPrivateKey) factory.generatePrivate(privKeySpec); } }

Now, let's rework a bit the second approach from the previous section in order to read a private key:

public RSAPrivateKey readPrivateKeySecondApproach(File file) throws IOException { try (FileReader keyReader = new FileReader(file)) { PEMParser pemParser = new PEMParser(keyReader); JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); PrivateKeyInfo privateKeyInfo = PrivateKeyInfo.getInstance(pemParser.readObject()); return (RSAPrivateKey) converter.getPrivateKey(privateKeyInfo); } }

As we can see, we just replaced SubjectPublicKeyInfo with PrivateKeyInfo and RSAPublicKey with RSAPrivateKey.

4.3. Advantages

There are a couple of advantages provided by the BouncyCastle library.

One advantage is that we don’t need to manually skip or remove the header and the footer. Another one is that we’re not responsible for the Base64 decoding either. Therefore, we can write less error-prone code with BouncyCastle.

Moreover, the BouncyCastle library supports the PKCS1 format as well. Despite the fact that PKCS1 is also a popular format used to store cryptographic keys (only RSA keys), Java doesn't support it on its own.

5. Conclusion

In this article, we learned how to read public and private keys from PEM files.

First, we studied a few key concepts around public-key cryptography. Then, we saw how to read public and private keys using pure Java.

Infine, abbiamo esplorato la libreria BouncyCastle e abbiamo scoperto che è una buona alternativa poiché offre alcuni vantaggi rispetto alla pura implementazione Java.

Il codice sorgente completo per entrambi gli approcci Java e BouncyCastle è disponibile su GitHub.

Fondo Java

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO