<div class="page">
<div class="cover text-center">
<img class="mx-auto" src=/itb/images/logo_mislata.png alt="logo">
# Certificats en Java - KeyTool
<div class="text-end fit-content ms-auto my-3 mt-auto pt-3">
<p><strong>Autor:</strong> Joan Puigcerver Ibáñez</p>
<p><strong>Correu electrònic:</strong> j.puigcerveribanez@edu.gva.es</p>
<p><strong>Curs:</strong> 2024/2025</p>
</div>
<div>
<p class="fw-bold mb-0">Llicència: BY-NC-SA</p>
<p class="d-none d-md-block">(Reconeixement - No Comercial - Compartir Igual)</p>
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.ca" target="_blank">
<img class="mx-auto" src="/itb/images/license.png" alt="Licence"/>
</a>
</div><!--license-->
</div><!--cover-->
</div><!--page-->
{:toc}
## Introducció
Un __certificat__ és un __document digital__ que s'utilitza per demostrar la identitat d'una entitat,
com ara un lloc web o una persona.
Conté informació sobre la identitat de l'entitat,
així com la clau pública de l'entitat.
Els certificats els emet un tercer de confiança anomenat autoritat de certificació (CA),
que verifica la identitat de l'entitat abans d'emetre el certificat.
Encara que normalment els certificats els emet una CA, qualsevol persona pot generar
certificats i allotjar una CA.
## Eina KeyTool
- [IOC: Eina KeyTool](https://ioc.xtec.cat/materials/FP/Recursos/fp_dam_m09_/web/fp_dam_m09_htmlindex/WebContent/u3/a1/continguts.html#l_eina_keytool)
::: docs
L'eina proporciona una sèrie d'accions a realitzar.
Podeu consultar la llista completa a la documentació oficial: https://docs.oracle.com/en/java/javase/11/tools/keytool.html
:::
L'eina __KeyTool__ ve incorporada amb el JDK de Java. Per poder utilitzar-la, comproveu que la carpeta `<JDK Installation Folder>/bin` està
afegida a la variable `PATH` del sistema.
#2.1[Configuració del sistema on s'ha afegit la carpeta d'instal·lació de Java a la variable PATH](/itb/DAM-PSP/UD4/img/path.png)
::: info
Com configurar les variables d'entorn del sistema a Windows 11:
- https://www.c-sharpcorner.com/article/how-to-addedit-path-environment-variable-in-windows-11/
:::
### Generar un certificat
Per poder generar un certificat, podem utilitzar la següent comanda com example:
```bash
keytool -genkey -keyalg RSA \
-alias example \
-keystore files/ud4/example_keystore.jks \
-validity 360 \
-storepass 123456 -keysize 2048 \
-dname "CN=Example, O=CIPFP Mislata, OU=PSP-DAM2S, L=Mislata, ST=València, C=ES"
```
Aquesta comanda s'utilitza per generar un parell de claus RSA de 2048 bits i guardar-les en un magatzem de claus Java KeyStore amb el nom
`keystore.jks` i identificat amb el àlias `example` que serà vàlid durant 360 dies.
Les opcions utilitzades són:
- `keytool` es la comanda de la utilitat Java Keytool.
- `-genkey` es l'opció per indicar que es genere un nou parell de claus.
- `-keyalg RSA` especifica que les claus han d'utilitzar l'algorisme RSA.
- `-alias` especifica l'alias que identifica el certificat en el magatzem de claus KeyStore.
- `-keypass` especifica la contrasenya del certificat.
- `-keystore` especifica el nom del magatzem de claus on es guardarà el certificat.
- `-storepass` especifica la contrasenya que protegeix el magatzem de claus.
- `-validity 360` especifica el nombre de dies que el certificat serà vàlid.
- `-keysize 2048` especifica la mida en bits de el parell de claus.
- `-dname` permet especificar la informació del certificat.
- `CN` o _Common Name_: Identificació del certificat.
- `O` o _Organization_: Organització a qui pertany el certificat.
- `OU` o _Organizational Unit_: Unitat dins de la organització.
- `L` o _Localitat_
- `ST` o _State_: Estat o província.
- `C` o _Country_: País.
### Llistar els certificats
Per llistar els certificats d'un magatzem de claus, podem utilitzar la següent comanda:
```bash
keytool -list -keystore files/ud4/example_keystore.jks -storepass 123456
```
### Exportar certificats
Per exportar certificats, es pot utilitzar l'opció `-export` de l'eina KeyTool. Aquesta acció exportarà el certificat amb la clau pública.
En aquest exemple, exportem el certificat generat en l'apartat anterior:
```bash
keytool -export -alias example \
-keystore files/ud4/example_keystore.jks \
-file files/ud4/example_certificate.crt \
-storepass 123456
```
Aquesta comanda exporta el certificat identificat per l'àlias `example` de el magatzem
de claus ubicat a `example_keystore.jks` al fitxer `example_certificate.crt`.
Les opcions utilitzades són:
- `keytool` es la comanda de la utilitat Java Keytool.
- `-export` es l'opció per indicar que s'exporte un certificat.
- `-alias` especifica l'alias que identifica el certificat
en el magatzem de claus KeyStore que es dessitga exportar.
- `-keystore` especifica el nom del magatzem de claus des d'on s'exportarà el certificat.
- `-file` especifica el fitxer on el certificat serà exportat.
### Importar certificats
Per importar certificats, es pot utilitzar l'opció `-import` de l'eina KeyTool.
En aquest exemple, importarem un nou certificat al magatzem de claus generat en l'apartat anterior:
```bash
keytool -import -alias example \
-keystore files/ud4/example_truststore.jks \
-file files/ud4/example_certificate.crt \
-storepass 123456
```
Aquesta comanda importa el certificat del fitxer `example_certificate.crt` i el emmagatzemarà en el magatzem
de claus ubicat a `example_truststore.jks` i l'identificarà per l'àlias `example`.
Les opcions utilitzades són:
- `keytool` es la comanda de la utilitat Java Keytool.
- `-import` es l'opció per indicar que s'importe un certificat.
- `-alias` especifica l'alias que identifica el certificat
importat en el magatzem de claus KeyStore
- `-keystore` especifica el nom del magatzem de claus on s'importarà el certificat.
- `-file` especifica el fitxer on el certificat importat.
## Certificats JKS en Java
Per treballar amb els certificats generats amb l'eina KeyTool en Java,
cal treballar amb els magatzems de claus KeyStore.
### Carregar una KeyStore
Per carregar un magatzem de claus KeyStore des d'un fitxer, podem utilitzar el següent mètode.
```java
public KeyStore loadKeyStore(String ksFile, String ksPwd) throws Exception {
KeyStore ks = KeyStore.getInstance("JKS");
File f = new File (ksFile);
if (f.isFile()) {
FileInputStream in = new FileInputStream (f);
ks.load(in, ksPwd.toCharArray());
}
return ks;
}
```
Una vegada ja s'ha carregat el magatzem, nteractuar amb ell per poder accedir a les seves entrades,
indexades amb un àlies.
- __Documentació API Java__: [KeyStore](https://docs.oracle.com/javase/7/docs/api/java/security/KeyStore.html)
Entre els mètodes més significatius, podem trobar:
- `int size()`: consulta el nombre d’entrades al magatzem.
- `Key getKey(String alias, char[] password)`: obté la clau (simètrica o privada,
el que corresponga per l’entrada) associada a un àlies, si existeix.
Si la seva entrada està protegida amb contrasenya, cal proporcionar-la al segon paràmetre.
Una clau simètrica és de tipus SecretKey, mentre que una de privada és de tipus PrivateKey.
- `Certificate getCertificate(String alias)`: permet extreure un certificat guardat a un àlies concret.
## Encriptaició utilitzant un certificat
Els certificats inclouen una clau púbilca `PublicKey` i una clau privada `PrivateKey`,
que poden ser utilitzades per encriptar i desencriptar missatges mitjançant l'algorisme `RSA`.
## Signatura utilitzant un certificat
La clau pública i privada d'un certificat també serveixen per signar un missatge i verificar que la signatura és correcta.
El procés és:
- L'emissor signa un missatge amb la seua clau privada.
- La signatura és generada a partir de la clau privada i el contingut del missatge.
- L'emissor envia el missatge i la signatura al receptor.
- El receptor verifica la signatura a partir de la clau pública de l'emissor i el missatge rebut.
Si la signatura és vàlida, significa que el missatge rebut és el mateix que el missatge enviat per l'emissor.
Si la signatura és invàlida, significa que el missatge rebut no és el mateix missatge enviat per l'emissor i ha pogut ser modificat
per tercers.
Els següents mètodes permeten signar i comprovar la signatura d'un `String` fàcilment.
No obstant això, qualsevol `byte[]` pot ser signat i verificat.
```java
public static String signText(PrivateKey privateKey, String text) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(text.getBytes());
byte[] signatureBytes = signature.sign();
return Base64.getEncoder().encodeToString(signatureBytes);
}
public static boolean verifySignature(PublicKey publicKey, String text, String signed) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(text.getBytes());
byte[] signatureBytes = Base64.getDecoder().decode(signed);
return signature.verify(signatureBytes);
}
```
## Fitxer amb la configuració
En aquest exemple s'ha utilitzat un fitxer de configuració `.properties` per especificar
les dades sensibles o que poden canviar ens els diferents entorns (desenvolupament, test, producció).
Utilitzar fitxers de configuració es considera una bona pràctica, ja que permet
configurar diferents dades per diferents entorns i evita que es puguen extraure
dades sensibles del codi font o dels executables (utilitzant mètodes com la decompilació).
En el nostre entorn, hem creat el fitxer `application.properties`, on s'ha guardat
la informació:
```cfg
ud4.examples.keystore.passwd=123456
```
Després, hem definit la classe `Config` que permet llegir la configuració d'un fitxer.
```java
package ud4.examples;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class Config {
public static Properties getConfig(String path) throws IOException {
Properties properties = new Properties();
properties.load(new FileInputStream(path));
return properties;
}
}
```
## Llibreria d'utilitats en Java
Aquesta classe recopila els mètodes anteriors per poder ser utilitzats.
A més, inclou un exemple d'utilització.
```java
package ud4.examples;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
public class CertificateUtils {
public static KeyStore loadKeyStore(String ksFile, String ksPwd) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException {
KeyStore ks = KeyStore.getInstance("JKS");
File f = new File (ksFile);
if (f.isFile()) {
FileInputStream in = new FileInputStream (f);
ks.load(in, ksPwd.toCharArray());
}
return ks;
}
public static String getCertificateInfo(Certificate certificate){
X509Certificate cert = (X509Certificate) certificate;
return cert.getSubjectX500Principal().getName();
}
public static String signText(PrivateKey privateKey, String text) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(text.getBytes());
byte[] signatureBytes = signature.sign();
return Base64.getEncoder().encodeToString(signatureBytes);
}
public static boolean verifySignature(PublicKey publicKey, String text, String signed) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(text.getBytes());
byte[] signatureBytes = Base64.getDecoder().decode(signed);
return signature.verify(signatureBytes);
}
public static void main(String[] args) {
try {
/*
* Guardar contrasenyes en el codi NO ÉS UNA BONA PRÀCTICA,
* cal utilitzar variables d'entorn o un fitxer de configuració.
*/
Properties config = Config.getConfig("application.properties");
String keyStorePassword = config.getProperty("ud4.examples.keystore.passwd");
KeyStore keyStore = loadKeyStore("files/ud4/example_keystore.jks", keyStorePassword);
List<String> aliases = Collections.list(keyStore.aliases());
System.out.println("Certificats en el magatzem de claus.");
System.out.printf("Total: %d\n", keyStore.size());
for(String alias : aliases)
System.out.println("- " + alias);
/*
El certificat ha segut creat prèviament amb la comanda:
```
keytool -genkey -keyalg RSA \
-alias example \
-keystore files/ud4/example_keystore.jks \
-validiry 360 \
-storepass 123456 -keysize 2048 \
-dname "CN=Example, O=CIPFP Mislata, OU=PSP-DAM2S, L=Mislata, ST=València, C=ES"
```
Per poder llegir informació sobre el subjecte del certificat,
necessitem utilitzar l'objecte X509Certificate.
*/
for (String alias : aliases) {
Certificate certificate = keyStore.getCertificate(alias);
System.out.println(getCertificateInfo(certificate));
// Clau pública del certificat
PublicKey examplePublic = certificate.getPublicKey();
// Clau privada
PrivateKey examplePrivate = (PrivateKey) keyStore.getKey(alias, keyStorePassword.toCharArray());
String message = "This is a secret information.";
System.out.printf("Message: %s\n", message);
String encrypted = RSA.encrypt(examplePublic, message);
System.out.printf("Encrypted: %s\n", encrypted);
String decrypted = RSA.decrypt(examplePrivate, encrypted);
System.out.printf("Decrypted: %s\n", decrypted);
String signautre = signText(examplePrivate, message);
System.out.printf("Signed: %s\n", signautre);
System.out.println("Signature verification: " + verifySignature(examplePublic, message, signautre));
}
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}
```