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