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