Salta el contingut
 

RSA – Encriptació asimètrica

Autor: Joan Puigcerver Ibáñez

Correu electrònic: j.puigcerveribanez@edu.gva.es

Llicència: CC BY-NC-SA 4.0

(Reconeixement - NoComercial - CompartirIgual) 🅭

RSA – Encriptació asimètrica

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.

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'algorisme 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.

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 wrapper, que rebrà la clau pública del destinatari i xifrarà les dades proporcionades:

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 wrapper per fer-ho:

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 wrapper, que rebrà la clau privada del destinatari que desxifrarà les dades proporcionades:

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 wrapper per fer-ho:

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 mitjanç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 un format estàndard.

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 mitjanç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 un format estàndard.

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 en el moment de guardar-la.

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 en el moment de guardar-la.

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:

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

Comentaris