Salta el contingut
 

Examen ordinària

Joan Puigcerver Ibáñez

j.puigcerveribanez@edu.gva.es

Llicència: CC BY-NC-SA 4.0

(Reconeixement - NoComercial - CompartirIgual) 🅭

Entrega

Perill

Si l'entrega no compleix aquests criteris, no és qualificarà.

L'entrega ha de complir els següents requisits:

  • El codi ha d'estar en el paquet corresponent: ordinaria
  • El format de la eixida del programa ha de ser consistent amb el format demanat.
  • S'ha d'entregar un fitxer .zip amb el contingut del paquet.
  • El codi ha d'estar pujat a GitHub en el vostre repositori de l'assignatura.
  • Tag GitHub: Ordinaria (StackOverflow: Create a tag in a GitHub repository)

1a Avaluació

BarberShop

ordinaria.exam1.barbershop

Aquest programa simula una perruqueria.

La perruqueria té una sala d'espera amb 3 seients i pot atendre a un client cada vegada.

Quan un client arriba a l'establiment, següeix el següent procediment:

  • Mirar si té lloc a la sala d'espera:

    • Si té lloc, entra a la sala per esperar el seu torn.
    • Si no té lloc, s'espera fora de l'establiment.
  • Una vegada a la sala d'espera, mira si pot ser atés:

    • Si pot ser atés, deixa la sala d'espera i passa per poder ser atés.
    • Si no pot ser atés, contínua esperant el seu torn.
  • Una vegada s'ha segut atés, acaba i deixa l'establiment.

Implementa:

  • (1 punt) Inicia tots els clients i espera a que acaben.
  • (1 punt) Els mecanismes de gestió de la cua de la sala d'espera en BarberShop.
  • (1 punt) Els mecanismes de gestió de la cua del barber en BarberShop.
  • (2 punts) El procediment que ha de seguir cada client, tinguent en compte els mecanismes de sincronització.

Codi font

BarberShop.java
package ordinaria.exam1.barbershop;

import java.util.ArrayList;
import java.util.List;

public class BarberShop {

    /**
     * Nombre de seiens de la sala d'espera
     */
    private final int seients;
    // TODO: Crea els mecanismes necessaris per gestionar les cues (sala d'espera i atenció del client)

    public BarberShop(int seients) {
        this.seients = seients;
        // TODO: Inicialitza els mecanismes necessaris per gestionar les cues
    }

    // TODO: Crea mètodes per accedir als mecanismes de sincronització

    public static void main(String[] args) {
        BarberShop barberShop = new BarberShop(3);
        List<BarberCustomer> barberCustomers = new ArrayList<>();

        for (int i = 0; i < 20; i++) {
            barberCustomers.add(new BarberCustomer(barberShop, i));
        }

        // TODO: Inicia tots els clients

        // TODO: Espera a que tots els clients acaben
    }
}
BarberCustomer.java
package ordinaria.exam1.barbershop;

import java.util.concurrent.ThreadLocalRandom;

public class BarberCustomer {
    private final BarberShop shop;
    private final int id;

    public BarberCustomer(BarberShop shop, int id) {
        this.shop = shop;
        this.id = id;
    }

    // TODO: Utilitza els mecanismes de sincronització
    public void run(){
        try {
            System.out.printf("El client %d està esperant...\n", id);
            System.out.printf("El client %d està esperant en la sala d'espera\n", id);
            System.out.printf("El client %d s'està siguent atés...\n", id);
            int milis = ThreadLocalRandom.current().nextInt(1000, 5000);
            Thread.sleep(milis);
            System.out.printf("El client %d ja ha acabat!n", id);
        } catch (InterruptedException ignored) {}
    }
}

LapsRace

ordinaria.exam1.lapsrace

Aquest programa simula una carrera per etapes.

Cada carrera té un número d'etapes i un número de corredors. Quan comença la carrera, tots els corredors comencen a correr la primera etapa. Quan l'acaben, esperen a que tots els corredors acaben l'etapa per poder començar la següent etapa.

El corredor que guanya una etapa guanya 5 punts, el segon 2 punts i el tercer 1 punt.

Quan s'han completat totes les etapes, es mostra la classificació.

Implementa:

  • (1 punt) Crea els mecanismes corresponents perquè cada corredor s'execute en un fil independent.
  • (1 punt) Inicia tots els corredors en fils independents en startRace().
  • (1 punt) Espera a que tots els corredors acaben la carrera en endRace().
  • (2 punts) En el mètode endLap(Runner r) és llançat quan cada corredor acaba una etapa:
    • (1 punt) Si encara hi ha corredors en l'etapa, el corredor ha d'esperar.
    • (1 punt) Si és l'últim corredor, tots els corredors han de començar la següent etapa.

Codi font

Race.java
package ordinaria.exam1.lapsrace;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class Race {
    private final int numberLaps;
    private int currentLap;
    private final List<Runner> runners;
    private int runnersWaiting;

    public Race(int numberLaps) {
        this.numberLaps = numberLaps;
        this.currentLap = 0;
        this.runnersWaiting = 0;
        this.runners = new ArrayList<>();
    }

    public void addRunner(Runner r){
        runners.add(r);
    }

    public int getLap() {
        return currentLap;
    }

    public boolean isFinished(){
        return currentLap >= numberLaps;
    }

    public void createRunners(int numberRunners){
        for (int i = 0; i < numberRunners; i++) {
            addRunner(new Runner(this, i));
        }
    }

    /**
     * Actualitza els punts del corredor segons la seva posició
     * @param r Corredor
     */
    public void updatePoints(Runner r){
        String message = "";
        if(runnersWaiting == 0) { // És el primer en arribar
            message = String.format(" %d + 5 points", r.getPoints());
            r.addPoints(5);
        } else if (runnersWaiting == 1) { // És el segon en arribar
            r.addPoints(2);
            message = String.format(" %d + 2 points", r.getPoints());
        } else if (runnersWaiting == 2) { // És el tercer en arribar
            r.addPoints(1);
            message = String.format(" %d + 1 point", r.getPoints());
        }
        System.out.printf("%s has finished lap %d in position %d.%s\n", r.getName(), currentLap, runnersWaiting, message);
    }

    /**
     * TODO
     * El corredor ha acabat l'etapa.
     * - Si encara queden corredors en la carrera, s'espera a començar
     * - Si és l'últim en arribar, comença la següent etapa
     * @param r corredor
     */
    public void endLap(Runner r){
        runnersWaiting++;
        updatePoints(r);


        // Encara hi ha corredors en l'etapa actual
        if(runnersWaiting < runners.size()){
            // TODO: Espera a començar la següent etapa

        } else { // Tots els corredors han acabat l'etapa
            runnersWaiting = 0;
            currentLap++;
            if (!isFinished()) {
                System.out.printf("===== LAP %d =====\n", currentLap);
            }

            // TODO: Comença la següent etapa
        }
    }

    /**
     * Comença la carrera i inicia tots els corredors
     */
    public void startRace(){
        // TODO: Inicia tots els corredors
        System.out.println("===== LAP 0 =====");
    }

    /**
     * Acaba la carrera i mostra la classificació
     */
    public void endRace() {
        // TODO: Espera a que tots els corredors acaben

        System.out.println("===== END =====");
        runners.sort(Comparator.comparing(Runner::getPoints).reversed());
        for (int i = 0; i < runners.size(); i++) {
            Runner r = runners.get(i);
            System.out.printf("%d. %s - %d points\n", i+1, r.getName(), r.getPoints());
        }
    }

    public static void main(String[] args) {
        Race race = new Race(10);
        race.createRunners(5);
        race.startRace();
        race.endRace();
    }
}
Runner.java
package ordinaria.exam1.lapsrace;

public class Runner {
    private final int id;
    private int points;
    private final Race race;

    public Runner(Race race, int id) {
        this.race = race;
        this.id = id;
        this.points = 0;
    }

    public int getPoints() {
        return points;
    }

    public void addPoints(int points) {
        this.points += points;
    }

    public String getName(){
        return "Runner" + id;
    }

    public void run() {
        while(!race.isFinished()){
            try {
                System.out.printf("%s is running lap %d\n", this.getName(), race.getLap());
                Thread.sleep((long) (Math.random() * 1000));
                race.endLap(this);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

2a Avaluaació: Manual handshake

ordinaria.exam2.handshake

Aquest programa simula el handshake que realitza un client i un servidor quan es connecten mitjançant sockets SSL.

La idea del handshake és que les dues parts acordeu utilitzar una clau simètrica comú per xifrar els seus missatges. Per evitar que la clau siga interceptada, inicialment la comunicació es realitza amb el certificat del servidor.

Certificats

Abans que res, cal crear el certificat del servidor, que serà importat a una truststore utilitzada pel client:

  • Server KeyStore: files/ordinaria/exam2/handshake-keystore.jks
  • Client TrustStore: files/ordinaria/exam2/handshake-truststore.jks
  • Certificat:

    Important

    Quan inicie el servidor, ha de mostrar la informació del certificat per pantalla.

    Camp Valor
    Àlies handshake-server
    CN CognomNom-HandshakeServer
    OU PSP-DAM2S
    O CIPFP Mislata
    L Mislata
    ST València
    C ES

El servidor té el mètode getPrivateKey(), que haurà de retornar la clau privada del certificat del servidor generat anteriormanet.

El client té el mètode getPublicKey(), que haurà de retornar la clau pública del servidor del certificat importat en la TrustStore.

Comunicació encriptada

Tant el servidor com el client disposen dels mètodes sendEncryptedMessage(String message) i String readEncryptedMessage().

El mètode sendEncryptedMessage() encripta les dades mitjançant AES amb la clau simètrica acordada entre el servidor i el client (guardada com atribut en la classe) i les envia.

El mètode readEncryptedMessage() reb un missatge i desencripta les dades mitjançant AES amb la clau simètrica acordada entre el servidor i el client (guardada com atribut en la classe).

Handshake

El handshake es realitzarà de la següent manera:

  • Client:

    • Quan es connecta, l'usuari tria una contrasenya que serà utilitzada per generar una clau simètrica.
    • Encripta la contrasenya utilitzant RSA amb la clau pública del servidor.
    • Envia la contrasenya encriptada al servidor.
    • Crea una clau simètrica de 256 bits i la guarda.
    • Espera a rebre un missatge de confirmació.
  • Servidor:

    • Reb la contrasenya encriptada del client.
    • Desencripta la contrasenya utilitzant la clau privada del servidor.
    • Crea la clau simètrica de 256 bits i la guarda.
    • Envia la resposta "Handshake succesful" encriptat amb la clau simètrica.

Codi font

Servidor

HandshakeServer.java
package ordinaria.exam2.handshake.server;

import ud4.examples.CertificateUtils;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class HandshakeServer extends Thread {
    ServerSocket server;

    List<HandshakeServerHandler> clients;
    boolean running;

    private final String keyStorePath;
    private final String keyStorePassword;
    private final String alias;

    public HandshakeServer(int port, String keyStorePath, String keyStorePassword, String alias) throws IOException {
        server = new ServerSocket(port);
        clients = new ArrayList<>();
        running = true;

        this.keyStorePath = keyStorePath;
        this.keyStorePassword = keyStorePassword;
        this.alias = alias;
    }

    public String getKeyStorePath() {
        return keyStorePath;
    }

    public String getKeyStorePassword() {
        return keyStorePassword;
    }

    public String getAlias() {
        return alias;
    }

    private void showCertificate() throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException {
        KeyStore keyStore = CertificateUtils.loadKeyStore(this.keyStorePath, this.keyStorePassword);

        // TODO: Obté el certificat de la KeyStore amb el alies especificat
        Certificate serverCertificate = null;
        String certificateInfo = CertificateUtils.getCertificateInfo(serverCertificate);
        System.out.println(certificateInfo);
    }

    public void close(){
        running = false;
        this.interrupt();
    }

    public synchronized void removeClient(HandshakeServerHandler hc){
        clients.remove(hc);
    }

    @Override
    public void run() {
        while (running){
            try {
                Socket client = server.accept();
                HandshakeServerHandler handshakeServerHandler = new HandshakeServerHandler(client, this);
                clients.add(handshakeServerHandler);
                handshakeServerHandler.start();
                System.out.println("Nova connexió acceptada.");
            } catch (IOException e) {
                System.err.println("Error while accepting new connection");
                System.err.println(e.getMessage());
            }
        }
    }

    public static void main(String[] args) {
        try {
            /*
             TODO: llegeix la informació sensible des d'un fitxer de configuració
             */
            int port = 0;
            String keyStorePath = "";
            String keyStorePassword = "";
            String alias = "";
            Scanner scanner = new Scanner(System.in);

            HandshakeServer server = new HandshakeServer(port, keyStorePath, keyStorePassword, alias);
            server.showCertificate();
            server.start();

            // Tanca el servidor amb "ENTER"
            scanner.nextLine();
            server.close();
        } catch (IOException | CertificateException | KeyStoreException | NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }

    }
}
HandshakeServerHandler.java
package ordinaria.exam2.handshake.server;

import ordinaria.exam2.handshake.exceptions.CryptographyException;
import ordinaria.exam2.handshake.exceptions.HandshakeException;
import ud4.examples.CertificateUtils;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import java.io.*;
import java.net.Socket;
import java.security.*;
import java.security.cert.CertificateException;

public class HandshakeServerHandler extends Thread {
    private final HandshakeServer server;
    private final Socket socket;

    private final BufferedReader in;
    private final PrintWriter out;

    private SecretKey symetricKey;

    public HandshakeServerHandler(Socket socket, HandshakeServer server) throws IOException {
        this.server = server;
        this.socket = socket;
        in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        out = new PrintWriter(socket.getOutputStream(), true);
        symetricKey = null;
    }

    /**
     * TODO: Retorna la clau privada del certificat del servidor
     * @return Clau publica del servidor
     */
    private PrivateKey getPrivateKey() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException {
        String keyStorePath = server.getKeyStorePath();
        String keyStorePassword = server.getKeyStorePassword();
        String alias = server.getAlias();
        KeyStore keyStore = CertificateUtils.loadKeyStore(keyStorePath, keyStorePassword);

        // TODO: Obté la clau privada de la KeyStore amb el alies especificat
        PrivateKey privateKey = null; //TODO
        return privateKey;
    }
    /**
     * TODO: Realitza el handshake.
     * - Llegeix la contrasenya encriptada des del client
     * - Desencripta la contrasenya amb la clau privada del servidor
     * - Genera una clau simètrica utilitzant la contrasenya amb tamany 256 bits
     * - Envia la resposta "Handshake successful!"
     * @throws IOException
     */
    private void handshake() throws HandshakeException {
        try {
            // Llegeix la contrasenya encriptada
            String encryptedKeyPassword = in.readLine();

            // TODO: Desencripta la contrasenya amb la clau privada
            PrivateKey privateKey = getPrivateKey();
            String keyPasswd = "";

            // TODO: Genera una clau simètrica utilitzant la contrasenya amb tamany 256 bits
            this.symetricKey = null;

            // Envia la resposta
            sendEncryptedMessage("Handshake successful!");
            System.out.println("Handshake successful!");
        }  catch (UnrecoverableKeyException | NoSuchPaddingException | CertificateException |
                  IllegalBlockSizeException | IOException | KeyStoreException | NoSuchAlgorithmException |
                  BadPaddingException | InvalidKeyException e) {
            throw new HandshakeException(e.getMessage());
        }
    }

    /**
     * TODO: Encripta el missatge utilitzant la clau simètrica i envia'l al servidor
     * @param message Missatge a enviar
     */
    private void sendEncryptedMessage(String message) throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        if (symetricKey == null)
            throw new RuntimeException("Symetric key not initialized");

        // TODO: Encripta el missatge amb la clau simètrica
        String encrypted = ""; // TODO

        // Envia el missatge
        out.println(encrypted);
    }

    /**
     * TODO: Llig un missatge del servidor encriptat i el desencripta amb la clau simètrica
     * @return Missatge rebut
     */
    private String readEncryptedMessage() throws IOException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        if (symetricKey == null)
            throw new RuntimeException("Symetric key not initialized");

        // Llig el missatge
        String response = in.readLine();

        // Si la resposta és null, retornem null
        if(response == null)
            return null;

        // TODO: Desencripta'l mitjançant la clau simètrica
        return "";
    }

    private void echo() throws CryptographyException, IOException {
        try{
            String request = null;
            while((request = readEncryptedMessage()) != null){
                sendEncryptedMessage(request);
            }
        } catch (NoSuchPaddingException | IllegalBlockSizeException | IOException | NoSuchAlgorithmException |
                 BadPaddingException | InvalidKeyException e) {
            throw new CryptographyException(e.getMessage());
        }
        this.socket.close();
    }

    @Override
    public void run() {
        try {
            handshake();
            echo();
        } catch (HandshakeException e) {
            System.err.println("Error en el handshake: " + e.getMessage());
        } catch (CryptographyException e) {
            System.err.println("Error xifrant o desxifrant les dades: " + e.getMessage());
        } catch (IOException e) {
            System.err.println(e.getMessage());
        }
        server.removeClient(this);
    }
}

Client

HandshakeClient.java
package ordinaria.exam2.handshake.client;

import ordinaria.exam2.handshake.exceptions.CryptographyException;
import ordinaria.exam2.handshake.exceptions.HandshakeException;
import ud4.examples.CertificateUtils;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.Scanner;

public class HandshakeClient {
    private final Socket socket;
    private final Scanner scanner;
    private final PrintWriter out;
    private final BufferedReader in;
    private final String trustStorePath;
    private final String trustStorePassword;
    private final String alias;
    private SecretKey symetricKey;

    public HandshakeClient(String host, int port, String trustStorePath, String trustStorePassword, String alias) throws IOException {
        this.scanner = new Scanner(System.in);
        this.socket = new Socket(host, port);

        out = new PrintWriter(socket.getOutputStream(), true);
        in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

        this.trustStorePath = trustStorePath;
        this.trustStorePassword = trustStorePassword;
        this.alias = alias;
        this.symetricKey = null;
    }

    /**
     * TODO: Retorna la clau pública del certificat del servidor
     * @return Clau publica del servidor
     */
    private PublicKey getPublicKey() throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException {
        KeyStore trustStore = CertificateUtils.loadKeyStore(this.trustStorePath, this.trustStorePassword);

        // TODO: Obté el certificat de la KeyStore amb el alies especificat
        Certificate serverCertificate = null;
        String certificateInfo = CertificateUtils.getCertificateInfo(serverCertificate);
        System.out.println(certificateInfo);

        // TODO: Obté la clau pública del ceritificat
        PublicKey serverPublicKey = null;
        return serverPublicKey;
    }

    /**
     * TODO: Realitza el handshake.
     * - Llegeix la contrasenya a utilitzar
     * - Encripta la contrasenya amb la clau pública del servidor
     * - Envia la contrsaenya encriptada al servidor
     * - Genera una clau simètrica utilitzant la contrasenya amb tamany 256 bits
     * - Llegeix i mostra la resposta del servidor
     * @throws IOException
     */
    private void handshake() throws HandshakeException {
        try {
            // Llegeix la contrasenya
            System.out.print("Introdueix la contrasenya per generar la clau simètrica: ");
            String passwd = scanner.nextLine();

            // TODO: Encripta la contrasenya amb la clau pública del servidor
            PublicKey publicKey = getPublicKey();
            String encryptedPasswd = "";

            // Envia la contrasenya encriptada
            out.println(encryptedPasswd);

            // TODO: Genera una clau simètrica utilitzant la contrasenya amb tamany 256 bits
            symetricKey = null;

            // Llegeix i mostra la resposta del servidor
            String response = readEncryptedMessage();
            System.out.println(response);
        } catch (Exception e){
            throw new HandshakeException(e.getMessage());
        }
    }

    /**
     * TODO: Encripta el missatge utilitzant la clau simètrica i envia'l al servidor
     * @param message Missatge a enviar
     */
    private void sendEncryptedMessage(String message) throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        if (symetricKey == null)
            throw new RuntimeException("No s'ha inicialitzat la clau simètrica");

        // TODO: Encripta el missatge amb la clau simètrica
        String encrypted = "";

        // Envia el missatge al servidor
        out.println(encrypted);
    }

    /**
     * TODO: Llig un missatge del servidor encriptat i el desencripta amb la clau simètrica
     * @return Missatge rebut
     */
    private String readEncryptedMessage() throws IOException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        if (symetricKey == null)
            throw new RuntimeException("No s'ha inicialitzat la clau simètrica");

        // Llig el missatge
        String response = in.readLine();

        // Si la resposta és null, retornem null
        if(response == null)
            return null;

        // TODO: Desencripta'l mitjançant la clau simètrica
        return "";
    }

    public void chat() throws CryptographyException {
        try {
            System.out.println("Acabes d'entrar al chat.");
            String line;
            while ((line = scanner.nextLine()) != null && this.socket.isConnected()) {
                sendEncryptedMessage(line);
                String response = readEncryptedMessage();
                System.out.println("Response: " + response);
            }
        } catch (IOException | NoSuchPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException |
                 BadPaddingException | InvalidKeyException e){
            throw new CryptographyException(e.getMessage());
        }
    }

    public void close(){
        try {
            socket.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        System.out.println("Connectant-se amb el servidor...");
        try {
            /*
             TODO: llegeix la informació sensible des d'un fitxer de configuració
             */
            String host = "";
            int port = 0;
            String trustStorePath = "";
            String trustStorePassword = "";
            String alias = "";

            HandshakeClient chat = new HandshakeClient(host, port, trustStorePath, trustStorePassword, alias);
            chat.handshake();
            chat.chat();
        } catch (IOException e){
            System.err.println("Error connectant-se amb el servidor.");
        } catch (HandshakeException e) {
            System.err.println("Error realitzant el handshake: " + e.getMessage());
        } catch (CryptographyException e) {
            System.err.println("Error al xifrar o desxifrar les dades: " + e.getMessage());
        }
    }
}

Excepcions

HandshakeException.java
package ordinaria.exam2.handshake.exceptions;

public class HandshakeException extends Exception {
    public HandshakeException(String message) {
        super(message);
    }
}
CryptographyException.java
package ordinaria.exam2.handshake.exceptions;

public class CryptographyException extends Exception {
    public CryptographyException(String message) {
        super(message);
    }
}

Rúbrica

  • (2 punts) Genera els certificats.
  • (1.5 punt) Client: getPublicKey()
  • (1.5 punt) Servidor: getPrivateKey()
  • (1 punt) sendEncryptedMessage(String message)
  • (1 punt) String readEncryptedMessage()
  • Handshake:
    • Client:
      • (1 punt) Encripta la contrasenya amb la clau pública del servidor.
      • (0.5 punt) Genera una clau simètrica amb la contrasenya de 256 bits.
    • Servidor:
      • (1 punt) Desencripta la contrasenya amb la clau privada del servidor.
      • (0.5 punt) Genera una clau simètrica amb la contrasenya de 256 bits.
📌 Aquest document pot quedar desactualitzat després d’imprimir-lo. Pots consultar la versió més recent a la pàgina web.
🌿 Abans d’imprimir aquest document, considera si és realment necessari. Redueix el consum de paper i ajuda a protegir el nostre entorn.