<div class="page"> <div class="cover text-center"> <img class="mx-auto" src=/itb/images/logo_mislata.png alt="logo"> # Encriptació simètrica - Algorisme AES <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ó AES (Advanced Encryption Standard) és un algorisme de xifratge de clau simètrica que s'utilitza per xifrar i desxifrar informació sensible. Funciona prenent text pla com a entrada i aplicant una sèrie de transformacions matemàtiques, o "rounds", mitjançant una clau secreta. El resultat és un text xifrat, que és il·legible sense la clau. El nombre de rondes i les operacions matemàtiques específiques utilitzades depenen de la mida de la clau; AES pot utilitzar claus de 128 bits, 192 bits o 256 bits. L'objectiu d'AES és protegir la informació sensible, com ara números de targeta de crèdit i credencials d'inici de sessió, de l'accés no autoritzat. S'utilitza àmpliament en una varietat d'aplicacions, com ara compres en línia, banca en línia i comunicacions segures. ## Generar claus ### Generar claus aleatòriament En Java podem generar una `SecretKey` utilitzant la classe `KeyGenerator`, indicant l'algorisme que volem utilitzar. ```java public static SecretKey keygenKeyGeneration(int keySize) { SecretKey sKey = null; if ((keySize == 128)||(keySize == 192)||(keySize == 256)) { try { KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(keySize); sKey = kgen.generateKey(); } catch (NoSuchAlgorithmException ex) { System.err.println("Generador no disponible."); } } return sKey; } ``` També es pot utilitzar l'eina __openssl__ per generar la clau AES: ``` openssl genpkey -algorithm AES-128-CBC -out key.pem ``` ### Generar claus a partir d'una contrasenya El següent mètode genera una `SecretKey` a partir del hash _SHA-256_ d'una contrasenya. ```java public static SecretKey passwordKeyGeneration(String password, int keySize) { SecretKey sKey = null; if ((keySize == 128)||(keySize == 192)||(keySize == 256)) { try { byte[] data = password.getBytes(StandardCharsets.UTF_8); MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] hash = md.digest(data); byte[] key = Arrays.copyOf(hash, keySize/8); sKey = new SecretKeySpec(key, "AES"); } catch (Exception ex) { System.err.println("Error generant la clau:" + ex); } } return sKey; } ``` ## Operacions amb AES L'algorsme __AES__ 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[] aes(SecretKey key, byte[] data, int opmode) throws Exception { Cipher cipher = Cipher.getInstance("AES/ECB/PKCS4Padding"); cipher.init(opmode, key); return cipher.doFinal(data); } ``` ### Encriptar Per encriptar dades en format `byte[]`, podem utilitzar el mètode `aes` amb l'opció `Cipher.ENCRYPT_MODE`. Podem crear un mètode "wraper", que rebrà la clau i xifrarà les dades proporcionades: ```java public static byte[] encrypt(SecretKey key, byte[] data) throws Exception { return aes(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(SecretKey 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 `aes` 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(SecretKey key, byte[] data) throws Exception { return aes(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(SecretKey key, String str){ try { // Decode the base64 encrypted string to a byte[] byte[] data = Base64.getDecoder().decode(str); // Decrypyt the byte[] data byte[] decrypted = decrypt(key, data); // Encode the decrypted data in 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 Per guardar una clau privada en un fitxer, sols cal codificar-la en base64 i guarda-la en el fitxer. ```java public static void saveSecretKeyToFile(SecretKey key, String path) throws IOException { // Base64 encode the key String encodedKey = Base64.getEncoder().encodeToString(key.getEncoded()); // Write the key to a file Files.write(Paths.get(path), encodedKey.getBytes()); } ``` ### Carregar una clau Per carregar una clau privada des d'un fitxer, sols cal llegir els continguts i crear l'objecte `SecretKey`. ```java public static SecretKey loadSecretKeyFromFile(String path) throws IOException { // Read the key from file String fileContent = new String(Files.readAllBytes(Paths.get(path))); // Decode the base64 key into byte[] byte[] keyBytes = Base64.getDecoder().decode(fileContent); // Create the SecretKey object return new SecretKeySpec(keyBytes, "AES"); } ``` ## Codi font Tot el codi font anteriorment proporcionat s'ha compilat en la següent llibreria: ```java package ud4.examples; import javax.crypto.*; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; public class AES { public static SecretKey keygenKeyGeneration(int keySize) { SecretKey sKey = null; if ((keySize == 128)||(keySize == 192)||(keySize == 256)) { try { KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(keySize); sKey = kgen.generateKey(); } catch (NoSuchAlgorithmException ex) { System.err.println("Generador no disponible."); } } return sKey; } public static SecretKey passwordKeyGeneration(String password, int keySize) { SecretKey sKey = null; if ((keySize == 128)||(keySize == 192)||(keySize == 256)) { try { byte[] data = password.getBytes(StandardCharsets.UTF_8); MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] hash = md.digest(data); byte[] key = Arrays.copyOf(hash, keySize/8); sKey = new SecretKeySpec(key, "AES"); } catch (Exception ex) { System.err.println("Error generant la clau:" + ex); } } return sKey; } public static String encrypt(SecretKey key, String str) throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { // 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); } public static String decrypt(SecretKey key, String str) throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { // Decode the base64 encrypted string to a byte[] byte[] data = Base64.getDecoder().decode(str); // Decrypyt the byte[] data byte[] decrypted = decrypt(key, data); // Encode the decrypted data in a String return new String(decrypted); } public static byte[] encrypt(SecretKey key, byte[] data) throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { return aes(key, data, Cipher.ENCRYPT_MODE); } public static byte[] decrypt(SecretKey key, byte[] data) throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { return aes(key, data, Cipher.DECRYPT_MODE); } private static byte[] aes(SecretKey key, byte[] data, int opmode) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { Cipher cipher = Cipher.getInstance("AES"); cipher.init(opmode, key); return cipher.doFinal(data); } public static void saveSecretKeyToFile(SecretKey key, String path) throws IOException { // Base64 encode the key String encodedKey = Base64.getEncoder().encodeToString(key.getEncoded()); // Write the key to a file Files.write(Paths.get(path), encodedKey.getBytes()); } public static SecretKey loadSecretKeyFromFile(String path) throws IOException { // Read the key from file String fileContent = new String(Files.readAllBytes(Paths.get(path))); // Decode the base64 key into byte[] byte[] keyBytes = Base64.getDecoder().decode(fileContent); // Create the SecretKey object return new SecretKeySpec(keyBytes, "AES"); } public static void main(String[] args) throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { String message = "Aquest és un missatge super secret"; ArrayList<SecretKey> keys = new ArrayList<>(); String password = "veryDifficultPassword"; int keySize = 256; keys.add(passwordKeyGeneration(password, keySize)); System.out.printf("Key generated with password: %s\n", password); keys.add(keygenKeyGeneration(keySize)); for (SecretKey key : keys) { System.out.printf("Key: %s\n", Base64.getEncoder().encodeToString(key.getEncoded())); System.out.printf("Message: %s\n", message); String encrypted = encrypt(key, message); System.out.printf("Encrypted message: %s\n", encrypted); String decrypted = decrypt(key, encrypted); System.out.printf("Decrypted message: %s\n", decrypted); System.out.println(); } } } ```