<div class="page">
<div class="cover text-center">
<img class="mx-auto" src=/itb/images/logo_mislata.png alt="logo">
# Encriptació asimètrica - Algorisme RSA
<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ó
RSA és un algorisme de xifratge asimètric de clau pública.
S'utilitza àmpliament per protegir dades sensibles, com ara números de targeta de crèdit,
números d'identificació personal (PIN) i contrasenyes.
L'algorisme va ser inventat l'any 1977 per Ron Rivest, Adi Shamir i Leonard Adleman,
i rep el nom de les inicials dels inventors.
L'algorisme RSA implica la generació de dos nombres primers grans i una clau pública i una privada.
La clau pública s'utilitza per xifrar les dades i la clau privada per desxifrar-les.
Les dades xifrades amb la clau pública només es poden desxifrar amb la clau privada, i viceversa.
El procés de xifratge i desxifrat mitjançant RSA és el següent:
1. El remitent genera una clau pública i una clau privada.
2. El remitent xifra les dades mitjançant la clau pública del destinatari.
3. Les dades xifrades s'envien al destinatari.
4. El destinatari utilitza la seva clau privada per desxifrar les dades.
L'objectiu principal de RSA és proporcionar transmissió de dades segura entre dues parts.
Com que el procés de xifratge i desxifrat requereix tant les claus públiques com les privades,
és extremadament difícil per a un tercer interceptar i llegir les dades transmeses.
## Generar claus
Per generar el parell de claus `KeyPair` a Java podem utilitzar
l'objecte `KeyPairGenerator`, indicant l'algorisme que volem
utilitzar.
```java
public static KeyPair generateKeyPair(int len) throws Exception {
if (!(len == 1024 || len == 2048 || len == 4096))
throw new Exception("La mida de la clau no és vàlida");
KeyPair keys = null;
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(len);
keys = keyGen.genKeyPair();
} catch (Exception ex) {
System.err.println("Generador no disponible.");
}
return keys;
}
```
També es pot utilitzar l'eina __openssl__:
- Generar la __clau privada__:
```
openssl genrsa -out private_key.pem 2048
```
- Generar la __clau pública__:
```
openssl rsa -in private_key.pem -pubout -out public_key.pem
```
## Operacions amb RSA
L'algorsme __RSA__ serveix per xifrar i desxifrar dades.
Aquestes operacions es poden dur a terme a Java mitjançant la classe `Cipher`.
En el següent mètode, s'utilitza la classe `Chiper` per portar a terme
l'acció d'encriptar o desencriptar, indicada mitjançant el paràmetre `int opmode`.
Aquest paràmetre pot rebre els valors `Cipher.ENCRYPT_MODE` o `Cipher.DECRYPT_MODE`.
```java
private static byte[] rsa(Key key, byte[] data, int opmode) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding","SunJCE");
cipher.init(opmode, key);
return cipher.doFinal(data);
}
```
### Encriptar
Per encriptar dades en format `byte[]`, podem utilitzar el mètode `rsa` amb
l'opció `Cipher.ENCRYPT_MODE`.
Podem crear un mètode "wraper", que rebrà la clau pública del destinatari
i xifrarà les dades proporcionades:
```java
public static byte[] encrypt(PublicKey key, byte[] data) throws Exception {
return rsa(key, data, Cipher.ENCRYPT_MODE);
}
```
Si volem encriptar dades de tipus `String`, primer caldrà convertir-les
a `byte[]`.
Podem crear un altre mètode "wraper" per fer-ho:
```java
public static String encrypt(PublicKey key, String str){
try {
// Decode the UTF-8 String into byte[] and encrypt it
byte[] data = encrypt(key, str.getBytes(StandardCharsets.UTF_8));
// Encode the encrypted data into base64
return Base64.getEncoder().encodeToString(data);
} catch (Exception ex){
System.err.println("Error xifrant les dades: " + ex);
}
return null;
}
```
### Desencriptar
Per desencriptar dades en format `byte[]`, podem utilitzar el mètode `rsa` amb
l'opció `Cipher.DECRYPT_MODE`.
Podem crear un mètode "wraper", que rebrà la clau privada del destinatari
que desxifrarà les dades proporcionades:
```java
public static byte[] decrypt(PrivateKey key, byte[] data) throws Exception {
return rsa(key, data, Cipher.DECRYPT_MODE);
}
```
Si volem desencriptar dades de tipus `String`, primer caldrà convertir-les
a `byte[]`.
Podem crear un altre mètode "wraper" per fer-ho:
```java
public static String decrypt(PrivateKey key, String str){
try {
// Decodifiquem les dades xifrades en base64 a byte[]
byte[] data = Base64.getDecoder().decode(str);
// Desencriptem les dades
byte[] decrypted = decrypt(key, data);
// Codifiquem les dades desencriptades a String
return new String(decrypted);
} catch (Exception ex){
System.err.println("Error desxifrant les dades: " + ex);
}
return null;
}
```
## Claus en fitxers
### Guardar una clau privada
Per guardar una clau privada en un fitxer, cal convertir la clau al format PEM mijançant la classe `PKCS8EncodedKeySpec`
i després escriure-la a un fitxer.
El format PEM és un format codificat en base64 per guardar claus criptogràfiques.
En el següent exemple, s'ha afegit un "header" i "footer" a la clau, perquè la clau es guarde en el mateix format
que el generat amb la comanda __rsa__.
```java
public static void savePrivateKeyToFile(PrivateKey key, String path) throws IOException {
// Get the encoded private key
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key.getEncoded());
// Base64 encode the key
String encodedKey = Base64.getEncoder().encodeToString(keySpec.getEncoded());
// Add the PEM headers and footers
encodedKey = "-----BEGIN PRIVATE KEY-----\n" + encodedKey + "-----END PRIVATE KEY-----";
// Write the key to a file
Files.write(Paths.get(path), encodedKey.getBytes());
}
```
### Guardar una clau pública
Per guardar una clau pública en un fitxer, cal convertir la clau al format PEM mijançant la classe `X509EncodedKeySpec`
i després escriure-la a un fitxer.
El format PEM és un format codificat en base64 per guardar claus criptogràfiques.
En el següent exemple, s'ha afegit un "header" i "footer" a la clau, perquè la clau es guarde en el mateix format
que el generat amb la comanda __rsa__.
```java
public static void savePublicKeyToFile(PublicKey key, String path) throws IOException {
// Get the encoded public key
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key.getEncoded());
// Base64 encode the key
String encodedKey = Base64.getEncoder().encodeToString(keySpec.getEncoded());
// Add the PEM headers and footers
encodedKey = "-----BEGIN PUBLIC KEY-----\n" + encodedKey + "-----END PUBLIC KEY-----";
// Write the key to a file
Files.write(Paths.get(path), encodedKey.getBytes());
}
```
### Carregar una clau privada
Per carregar una clau privada d'un fitxer, cal llegir les dades codificades en el format PEM
mitjançant la classe `PKCS8EncodedKeySpec` i convertir-la al objecte Java corresponent.
i després escriure-la a un fitxer.
En el següent exemple, s'ha eliminat el "header" i "footer" de la clau, que es pot haver afegit
si s'ha generat amb la comanda __rsa__.
```java
public static PrivateKey loadPrivateKeyFromFile(String path) throws InvalidKeySpecException, IOException, NoSuchAlgorithmException {
// Read the private key from the PEM file
String privateKeyPem = new String(Files.readAllBytes(Paths.get(path)));
privateKeyPem = privateKeyPem.replace("-----BEGIN PRIVATE KEY-----\n", "");
privateKeyPem = privateKeyPem.replace("-----END PRIVATE KEY-----", "");
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyPem);
// Create the private key
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
```
### Carregar una clau pública
Per carregar una clau pública d'un fitxer, cal llegir les dades codificades en el format PEM
mitjançant la classe `X509EncodedKeySpec` i convertir-la al objecte Java corresponent.
i després escriure-la a un fitxer.
En el següent exemple, s'ha eliminat el "header" i "footer" de la clau, que es pot haver afegit
si s'ha generat amb la comanda __rsa__.
```java
public static PublicKey loadPublicKeyFromFile(String path) throws InvalidKeySpecException, IOException, NoSuchAlgorithmException {
// Read the public key from the PEM file
String publicKeyPem = new String(Files.readAllBytes(Paths.get(path)));
publicKeyPem = publicKeyPem.replace("-----BEGIN PUBLIC KEY-----\n", "");
publicKeyPem = publicKeyPem.replace("-----END PUBLIC KEY-----", "");
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyPem);
// Create the public key
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
}
```
## Codi font
Tot el codi font anteriorment proporcionat s'ha compilat
en la següent llibreria:
```java
package ud4.examples;
import javax.crypto.Cipher;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class RSA {
public static KeyPair generateKeyPair(int len) throws Exception {
if (!(len == 1024 || len == 2048 || len == 4096))
throw new Exception("La mida de la clau no és vàlida");
KeyPair keys = null;
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(len);
keys = keyGen.genKeyPair();
} catch (Exception ex) {
System.err.println("Generador no disponible.");
}
return keys;
}
public static String encrypt(PublicKey key, String str){
try {
// Decode the UTF-8 String into byte[] and encrypt it
byte[] data = encrypt(key, str.getBytes(StandardCharsets.UTF_8));
// Encode the encrypted data into base64
return Base64.getEncoder().encodeToString(data);
} catch (Exception ex){
System.err.println("Error xifrant les dades: " + ex);
}
return null;
}
public static String decrypt(PrivateKey key, String str){
try {
// Decodifiquem les dades xifrades en base64 a byte[]
byte[] data = Base64.getDecoder().decode(str);
// Desencriptem les dades
byte[] decrypted = decrypt(key, data);
// Codifiquem les dades desencriptades a String
return new String(decrypted);
} catch (Exception ex){
System.err.println("Error desxifrant les dades: " + ex);
}
return null;
}
public static byte[] encrypt(PublicKey key, byte[] data) throws Exception {
return rsa(key, data, Cipher.ENCRYPT_MODE);
}
public static byte[] decrypt(PrivateKey key, byte[] data) throws Exception {
return rsa(key, data, Cipher.DECRYPT_MODE);
}
private static byte[] rsa(Key key, byte[] data, int opmode) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(opmode, key);
return cipher.doFinal(data);
}
public static PrivateKey loadPrivateKeyFromFile(String path) throws InvalidKeySpecException, IOException, NoSuchAlgorithmException {
// Read the private key from the PEM file
String privateKeyPem = new String(Files.readAllBytes(Paths.get(path)));
privateKeyPem = privateKeyPem.replace("-----BEGIN PRIVATE KEY-----\n", "");
privateKeyPem = privateKeyPem.replace("-----END PRIVATE KEY-----", "");
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyPem);
// Create the private key
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
public static void savePrivateKeyToFile(PrivateKey key, String path) throws IOException {
// Get the encoded private key
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key.getEncoded());
// Base64 encode the key
String encodedKey = Base64.getEncoder().encodeToString(keySpec.getEncoded());
// Add the PEM headers and footers
encodedKey = "-----BEGIN PRIVATE KEY-----\n" + encodedKey + "-----END PRIVATE KEY-----";
// Write the key to a file
Files.write(Paths.get(path), encodedKey.getBytes());
}
public static PublicKey loadPublicKeyFromFile(String path) throws InvalidKeySpecException, IOException, NoSuchAlgorithmException {
// Read the public key from the PEM file
String publicKeyPem = new String(Files.readAllBytes(Paths.get(path)));
publicKeyPem = publicKeyPem.replace("-----BEGIN PUBLIC KEY-----\n", "");
publicKeyPem = publicKeyPem.replace("-----END PUBLIC KEY-----", "");
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyPem);
// Create the public key
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
}
public static void savePublicKeyToFile(PublicKey key, String path) throws IOException {
// Get the encoded public key
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key.getEncoded());
// Base64 encode the key
String encodedKey = Base64.getEncoder().encodeToString(keySpec.getEncoded());
// Add the PEM headers and footers
encodedKey = "-----BEGIN PUBLIC KEY-----\n" + encodedKey + "-----END PUBLIC KEY-----";
// Write the key to a file
Files.write(Paths.get(path), encodedKey.getBytes());
}
public static void main(String[] args) {
String message = "Aquest és un missatge super secret";
KeyPair keys = null;
try {
keys = generateKeyPair(1024);
System.out.printf("Public key: %s\n", Base64.getEncoder().encodeToString(keys.getPublic().getEncoded()));
System.out.printf("Private key: %s\n", Base64.getEncoder().encodeToString(keys.getPrivate().getEncoded()));
System.out.printf("Message: %s\n", message);
String encrypted = encrypt(keys.getPublic(), message);
System.out.printf("Encrypted message: %s\n", encrypted);
String decrypted = decrypt(keys.getPrivate(), encrypted);
System.out.printf("Decrypted message: %s\n", decrypted);
System.out.println();
} catch (Exception e) {
System.err.println("Error creant el parell de claus: " + e);
}
}
}
```