Codice sorgente Java.

DocumentazioneApp Cryptea

La nostra applicazione è basata su un server Java, che si occupa delle diverse funzioni legate alla crittografia e ai certificati.

Trasmette le proprie risposte, attraverso un Socket TCP, al sito web, il quale ha il ruolo di una vera e propria API (Application Programming Interface) fra Server e Utente, e permette di gestire le diverse operazioni.

L'interazione avviene attraverso payload in formato JSON, sia lato backend e sia lato frontend. Per gestire gli oggetti JSON è stata utilizzata la libreria jackson-3.0.3.

Per approfondire Deploy, configurazione e avvio del server su Docker, leggi questo articolo.

Struttura del server Java

/
|   Main.java
|
+---algorithms
|       RSA.java
|       RSA_didattico.java
|
+---cert
|       Certificate.java
|       ManageKeys.java
|       SHADigest.java
|
+---keys
|       FileCryptea.java
|       FilePEM.java
|
+---modes
|       UseCBC.java
|       UseCBCforFile.java
|       UseECB.java
|
+---server
|       API.java
|       ServerWorker.java
|       Test.java
|
\---utils
        Base64Helper.java
        CipherText.java
        EspMod.java
        FileHelper.java
        HexHelper.java
        JsonHelper.java
        Prime.java
  • Main.java: Contiene la classe principale per avviare il server.

  • /algorithms: Contiene gli algoritmi di crittografia attualmente disponibili su Cryptea.

  • /cert: Contiene le classi necessarie per gestire la Certification Authority (verifica dei certificati, gestione delle chiavi pubbliche, calcolo del digest)

  • /keys: Contiene tutto il necessario per generare le chiavi nel nostro formato Cryptea.

  • /modes: La cifratura RSA può lavorare con dati inferiori al valore del Modulo n. Ho superato questo limite applicando 2 principali metodi di gestione dei blocchi più noti, ECB e CBC.

  • /utils: Contiene strumenti utili per la conversione nei diversi formati e tipi, e classi utili per RSA come Prime.java e EspMod.java.

  • /server: Contiene tutto il necessario per il funzionamento del server, realizzato in modalità MultiThread con supporto nativo dei socket. Contiene inoltre la classe Test.java, che viene avviata prima dell'apertura del socket server per verificare che tutti gli algoritmi e i processi di cifratura e decifratura funzionino correttamente.

Contenuto delle classi principali

Main.java

java
package app.cryptea;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import app.cryptea.server.ServerWorker;
import app.cryptea.server.Test;

public class Main {
    public static void main(String[] args) {
        System.out.println("");
        System.out.println("> cryptea-backend@1.0.0");
        System.out.println("> built on java 25.0.1 2025-10-21 LTS");
        System.out.println("");
        System.out.println(" Java(TM) Server Edition (jdk-23.0.2+7)");
        System.out.println("  - Local:      localhost:8080");
        System.out.println("  - Network:    java:8080");
        System.out.println("");
        System.out.println(">> Running local test...");

        try {
            if (Test.runMe(2048, false)) {
                System.out.println(">> Test succeded!");
                System.out.println(">> Ready for connections...");
                System.out.println("\n");
            } else {
                System.out.println(">> Test failed!");
                System.out.println(">> Abort server...");
                System.out.println("\n");

                // Chiusura main
                return;
            }
        } catch (Exception e) {
            System.out.println(">> Test failed!");
            System.out.println(">> Abort server...");
            System.out.println("\n");
            e.printStackTrace();

            // Chiusura main
            return;
        }

        ServerSocket server = null;

        try {

            // Uso di un server con comunicazione Socket multi-thread
            server = new ServerSocket(8080);

            while (true) {
                Socket client = server.accept();
                new ServerWorker(client).start();
            }

        } catch (Exception e) {
            System.out.println(">> Errore nell'esecuzione del serverThread Main");
            System.out.println(">> Interrompo server...");
            System.out.println("\n");
            e.printStackTrace();
        } finally {
            try {
                if (server != null) {
                    server.close();
                }
            } catch (IOException e) {
                System.out.println(">> Errore nella chiusura del SocketServer");
                System.out.println(">> Interrompo server...");
                System.out.println("\n");
                e.printStackTrace();
            }
        }

    }
}

Test.java

java
package app.cryptea.server;

import app.cryptea.algorithms.RSA;
import app.cryptea.cert.Certificate;
import app.cryptea.cert.ManageKeys;
import app.cryptea.utils.CipherText;

public class Test {
    public static boolean runMe(int bitSize, boolean verbose) throws Exception {
        RSA keys = new RSA(bitSize);
        RSA dest = new RSA(bitSize);

        if (verbose) {
            keys.showResult();

            System.out.println("\n" + ManageKeys.publicToFile("cryptea", keys.getPublicKey()[0], keys.getPublicKey()[1]));
            System.out.println();
            System.out.println(ManageKeys.privateToFile("cryptea", keys.getPrivateKey()[0], keys.getPrivateKey()[1],
                    keys.getP(), keys.getQ()));
            System.out.println();
            System.out.println(Certificate.create("Test", "CRT-000000", keys.getPublicKey()[0], keys.getPublicKey()[1]));
        }

        String text = "Ciao mondo! Sono un testo scritto a mano casualmente per simulare un blocco grande, ma non molto, che verrà cifrato e decifrato usando RSA prima in modalità ECB classica, ma vulnerabile, e poi in modalità CBC con protezione del contenuto attraverso un calcolo di HMAC";

        String ecb = CipherText.encrypt("ecb", "server", text, keys.getPublicKey(), keys.getPrivateKey());
        String plainecb = CipherText.decrypt("ecb", "server", ecb, keys.getPrivateKey(), keys.getPublicKey());

        if (verbose) {
            System.out.println("\n\n\n" + ecb);
            System.out.println("\n" + plainecb);
            System.out.println("\n");            
        }

        String cbc = CipherText.encrypt("cbc", "server", text, keys.getPublicKey(), keys.getPrivateKey());
        String plaincbc = CipherText.decrypt("cbc", "server", cbc, keys.getPrivateKey(), keys.getPublicKey());

        if (verbose) {
            System.out.println("\n" + cbc);
            System.out.println("\n" + plaincbc);
            System.out.println("\n");
        }

        // Cifratura del contenuto con la chiave pubblica del destinatario, firma con la chiave privata
        String cbcpeer = CipherText.encrypt("cbc", "user", text, dest.getPublicKey(), keys.getPrivateKey());
        String plaincbcpeer = CipherText.decrypt("cbc", "user", cbcpeer, dest.getPrivateKey(), keys.getPublicKey());

        if (verbose) {
            System.out.println("\n\n\n" + cbcpeer);
            System.out.println("\n" + plaincbcpeer);
        }

        String cbcselfpeer = CipherText.encrypt("cbc", "user", text, keys.getPublicKey(), keys.getPrivateKey());
        String plainselfcbcpeer = CipherText.decrypt("cbc", "user", cbcselfpeer, keys.getPrivateKey(), keys.getPublicKey());

        if (verbose) {
            System.out.println("\n\n\n" + cbcselfpeer);
            System.out.println("\n" + plainselfcbcpeer);
        }

        String cbcnosign = CipherText.encrypt("cbc", "none", text, dest.getPublicKey(), keys.getPrivateKey());
        String plaincbcnosign = CipherText.decrypt("cbc", "none", cbcnosign, dest.getPrivateKey(), keys.getPublicKey());

        if (verbose) {
            System.out.println("\n\n\n" + cbcnosign);
            System.out.println("\n" + plaincbcnosign);
            System.out.println("\n");
        }

        if (verbose) {
            System.out.println("\n");
            System.out.println(API.sendCertificate(Certificate.create("Test", "CRT-000000", keys.getPublicKey()[0], keys.getPublicKey()[1])));
            System.out.println();
            System.out.println(API.sendKeysRSA(ManageKeys.publicToFile("cryptea", keys.getPublicKey()[0], keys.getPublicKey()[1]), ManageKeys.privateToFile("cryptea", keys.getPrivateKey()[0], keys.getPrivateKey()[1], keys.getP(), keys.getQ())));
            System.out.println();
            System.out.println(API.sendResultRSA(keys.getP(), keys.getQ(), keys.getPublicKey()[0], keys.getPrivateKey()[0], keys.getPublicKey()[1], keys.getM(), ManageKeys.publicToFile("cryptea", keys.getPublicKey()[0], keys.getPublicKey()[1]), ManageKeys.privateToFile("cryptea", keys.getPrivateKey()[0], keys.getPrivateKey()[1], keys.getP(), keys.getQ())));
            System.out.println("\n");
        }

        // Lancerà prima un'eccezione se qualcosa va storto...
        if (plainecb.equals(text) && plaincbc.equals(text) && plaincbcpeer.equals(text) && plaincbcnosign.equals(text) && plainselfcbcpeer.equals(text)) {
            // ECB, CBC & RSA funzionano!
            return true;
        }

        return false;
    }
}

RSA.java

java
package app.cryptea.algorithms;

import java.math.BigInteger;

import app.cryptea.utils.EspMod;
import app.cryptea.utils.Prime;

public class RSA {
    // numero primo p
    BigInteger p;

    // numero primo q
    BigInteger q;

    // prodotto p * q
    BigInteger n;

    // funzione di Eulero
    BigInteger m;

    // numero e
    BigInteger e;

    // valore k
    BigInteger k;

    // coppia di chiavi finale
    BigInteger[] kPub;
    BigInteger[] kPr;

    public RSA(int bitSize) {
        // 0. Inizializzazione variabili
        kPub = new BigInteger[2];
        kPr = new BigInteger[2];

        do {
            genKeys(bitSize);
        } while (!testCrypto(17));
    }

    private void genKeys(int bitSize) {
        // 1. Scelgo 2 numeri primi p e q
        // Devono essere diversi fra loro
        // Li genero ciascuno con 1024 bit, per
        // ottenere una crittografia a 2048 bit

        // Input bitsize variabile...
        while (true) {
            this.p = Prime.getRandomPrime(bitSize / 2);
            while (true) {
                this.q = Prime.getRandomPrime(bitSize / 2);
                if (!p.equals(q)) {
                    break;
                }
            }

            // 2. Determino n come il prodotto di p e q
            // Da n è impossibile risalire a p e q "generatori"
            // Operazione intrattabile con i computer attuali
            n = p.multiply(q);

            if (n.bitLength() == bitSize) {
                break;
            }
        }

        // 3. Calcolo la funzione di Eulero m
        // m = (p - 1) * (q - 1)
        m = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE));

        // 4. Calcolo il numero e
        // "e" + valido t.c. MCD(e, m) = 1
        // "e" e "m" devono essere primi fra loro
        // Itero utilizzando il numero primo di "Fermat" per velocità
        // numero primo di "Fermat" = 65537
        // parto da nFermat, standard de-facto per i vantaggi di efficienza
        // (Hamming-weight)
        e = BigInteger.valueOf(65537);
        while (e.compareTo(p.shiftRight(1)) == -1) {
            if (e.gcd(m).equals(BigInteger.ONE)) {
                // se l'MCD (Greatest Common Divisor) è = 1
                // i due numeri sono primi fra loro
                break;
            }
            e = e.add(BigInteger.TWO);
            if (e.compareTo(BigInteger.valueOf(165537)) == 1) {
                // evito loop infinito, e rigenero p e q
                return;
            }
        }

        // 5. Calcolo k
        // Qui uso la funzione consigliata da Java,
        // adatta per numeri molto grandi
        k = e.modInverse(m);

        // 6. Genero la chiave pubblica Kpub
        // Formata da "e" e "n"
        kPub[0] = e;
        kPub[1] = n;

        // 7. Genero la chiave privata Kpr
        // Formata da "k" e "n"
        kPr[0] = k;
        kPr[1] = n;
    }

    public BigInteger[] getPrivateKey() {
        return kPr;
    }

    public BigInteger[] getPublicKey() {
        return kPub;
    }

    public BigInteger getP() {
        return p;
    }

    public BigInteger getQ() {
        return q;
    }

    public BigInteger getM() {
        return m;
    }

    public void showResult() {
        System.out.println("\np: " + p);
        System.out.println("\nq: " + q);
        System.out.println("\nn: " + n);
        System.out.println("\nm: " + m);
        System.out.println("\ne: " + e);
        System.out.println("\nk: " + k);
        System.out.println("\n\nChiave pubblica: " + kPub[0] + "\n" + kPub[1]);
        System.out.println("\n\nChiave privata: " + kPr[0] + "\n" + kPr[1]);
    }

    public BigInteger encrypt(BigInteger input) {
        BigInteger output = BigInteger.ZERO;

        if (n != null && e != null) {
            output = EspMod.doIt(input, e, n);
        } else {
            System.out.println("[ERRORE] Genera prima le chiavi!");
        }

        return output;
    }

    public BigInteger decrypt(BigInteger input) {
        BigInteger output = BigInteger.ZERO;

        if (n != null && k != null) {
            output = EspMod.doIt(input, k, n);
        } else {
            System.out.println("[ERRORE] Genera prima le chiavi!");
        }

        return output;
    }

    public boolean testCrypto(int input) {
        if (kPr != null && kPub != null) {
            BigInteger in = BigInteger.valueOf(input);
            BigInteger cipher = encrypt(in);
            BigInteger decrypted = decrypt(cipher);

            return decrypted.equals(in);
        } else {
            return false;
        }
    }

    // Metodi statici per CA
    public static BigInteger encryptWithPublic(BigInteger input, BigInteger e, BigInteger n) {
        BigInteger output = BigInteger.ZERO;

        if (n != null && e != null) {
            output = EspMod.doIt(input, e, n);
        } else {
            System.out.println("[ERRORE] Chiavi mancanti!");
        }

        return output;
    }

    public static BigInteger encryptWithPrivate(BigInteger input, BigInteger k, BigInteger n) {
        BigInteger output = BigInteger.ZERO;

        if (n != null && k != null) {
            output = EspMod.doIt(input, k, n);
        } else {
            System.out.println("[ERRORE] Chiavi mancanti!");
        }

        return output;
    }

    public static BigInteger decryptWithPrivate(BigInteger input, BigInteger k, BigInteger n) {
        BigInteger output = BigInteger.ZERO;

        if (n != null && k != null) {
            output = EspMod.doIt(input, k, n);
        } else {
            System.out.println("[ERRORE] Chiavi mancanti!");
        }

        return output;
    }

    public static BigInteger decryptWithPublic(BigInteger input, BigInteger e, BigInteger n) {
        BigInteger output = BigInteger.ZERO;

        if (n != null && e != null) {
            output = EspMod.doIt(input, e, n);
        } else {
            System.out.println("[ERRORE] Chiavi mancanti!");
        }

        return output;
    }
}

EspMod.java

java
package app.cryptea.utils;

import java.math.BigInteger;

public class EspMod {
    
    public static BigInteger doIt(BigInteger a, BigInteger b, BigInteger n) {
        BigInteger result = BigInteger.valueOf(1);
        
        // 1. Calcolo il primo modulo della produttoria
        a = a.mod(n);

        // 2. Avvio il ciclo while per effettuare tutti i prodotti
        // mentre b > 0 (true = 1)
        while (b.compareTo(BigInteger.ZERO) == 1) {
            if (b.getLowestSetBit() == 0) {
                // se è dispari (ultimo bit uguale a 1)
                result = (result.multiply(a)).mod(n);
            }

            // 3. Calcolo la successiva potenza
            a = (a.multiply(a)).mod(n);

            // 4. Divido per 2 b
            b = b.shiftRight(1);
        }

        return result;
    }
}

Prime.java

java
package app.cryptea.utils;
import java.math.BigInteger;
import java.security.SecureRandom;

public class Prime {

    public static BigInteger getRandomPrime(int bitSize) {
        SecureRandom numeroRandom = new SecureRandom();
        BigInteger prime;
        
        do {
            prime = new BigInteger(bitSize, numeroRandom).setBit(bitSize - 1).setBit(0);
        } while (!prime.isProbablePrime(100));

        // probabilità di errore < 2 ^ -100 con .isProbablePrime(da 0 a 128)
        
        // imposto il bit più significativo a 1, e il bit meno significativo ad 1
        // garantisco di avere numeri molto grandi, e dispari

        return prime;
    }

}

CipherText.java

java
package app.cryptea.utils;

import java.math.BigInteger;

import app.cryptea.modes.UseCBC;
import app.cryptea.modes.UseCBCforFile;
import app.cryptea.modes.UseECB;

public class CipherText {

    public static String encrypt(String type, String signature, String testo, BigInteger[] kpub, BigInteger[] kpr)
            throws Exception {

        if (type.toLowerCase().equals("ecb")) {
            return UseECB.encrypt(testo, kpub);
        } else if (type.toLowerCase().equals("cbc")) {
            byte[] iv = UseCBC.generateIV(kpub[1]); // passo n come lunghezza
            String iv64 = Base64Helper.fromBytes(iv);
            String cipher = UseCBC.encrypt(testo, kpub, iv); // torna un base64
            String hmac = "";

            if (signature.equals("server")) {
                hmac = UseCBC.getServerHMAC(iv64, cipher); // input base64
            } else if (signature.equals("user")) {
                hmac = UseCBC.getHMAC(iv64, cipher, kpr[0], kpr[1]);
            } else if (signature.equals("none")) {
                return iv64 + "." + cipher;
            } else {
                throw new IllegalArgumentException("Modalità di firma errata!");
            }

            // Struttura payload
            // iv + cipher + hmac

            return iv64 + "." + cipher + "." + hmac;
        } else {
            throw new IllegalArgumentException("Modalità di cifratura errata!");
        }
    }

    public static String encryptFile(String metadata64, String content64, BigInteger[] kpub, BigInteger[] kpr)
            throws Exception {
        byte[] iv = UseCBCforFile.generateIV(kpub[1]); // passo n come lunghezza
        String iv64 = Base64Helper.fromBytes(iv);
        String cipher = UseCBCforFile.encrypt(content64, kpub, iv); // torna un base64
        String hmac = UseCBCforFile.getHMAC(metadata64, iv64, cipher, kpr[0], kpr[1]);

        return metadata64 + "." + iv64 + "." + cipher + "." + hmac;
    }

    public static String decrypt(String type, String signature, String payload, BigInteger[] kpr, BigInteger[] kpub)
            throws Exception {

        signature = signature.trim().toLowerCase();
        type = type.trim().toLowerCase();

        if (type.equals("ecb")) {
            // input base64
            return UseECB.decrypt(payload, kpr);
        } else if (type.equals("cbc")) {
            // 1. Verificare HMAC

            // Struttura payload
            // iv + cipher + hmac
            String[] parts = payload.split("\\.");
            String iv64 = "";
            String cipher64 = "";
            String hmac64 = "";

            if (parts.length < 2) {
                throw new Exception("Formato messaggio CBC incompleto");
            }

            iv64 = parts[0];
            cipher64 = parts[1];

            if (!signature.equals("none")) {
                if (parts.length < 3) {
                    throw new Exception("Formato messaggio CBC incompleto");
                }

                hmac64 = parts[2];
            }

            if (signature.equals("server")) {
                if (UseCBC.verifyServerHMAC(iv64, cipher64, hmac64)) {
                    return UseCBC.decrypt(cipher64, kpr, Base64Helper.toBytes(iv64));
                } else {
                    throw new Exception("Firma HMAC non valida! Tentativo di compromissione...");
                }

            } else if (signature.equals("user")) {
                if (UseCBC.verifyHMAC(iv64, cipher64, hmac64, kpub[0], kpub[1])) {
                    return UseCBC.decrypt(cipher64, kpr, Base64Helper.toBytes(iv64));
                } else {
                    throw new Exception("Firma HMAC non valida! Tentativo di compromissione...");
                }

            } else if (signature.equals("none")) {
                return UseCBC.decrypt(cipher64, kpr, Base64Helper.toBytes(iv64));
            } else {
                throw new IllegalArgumentException("Modalità di firma errata!");
            }

        } else {
            throw new IllegalArgumentException("Modalità di cifratura errata!");
        }
    }

    public static String decryptFile(String payload, BigInteger[] kpr, BigInteger[] kpub) throws Exception {
        // 1. Verificare HMAC
        // Struttura payload
        // metadata + iv + cipher + hmac
        String[] parts = payload.split("\\.");
        String metadata64 = "";
        String iv64 = "";
        String cipher64 = "";
        String hmac64 = "";

        if (parts.length < 4) {
            throw new Exception("Formato messaggio CBC incompleto");
        }

        metadata64 = parts[0];
        iv64 = parts[1];
        cipher64 = parts[2];
        hmac64 = parts[3];

        if (UseCBCforFile.verifyHMAC(metadata64, iv64, cipher64, hmac64, kpub[0], kpub[1])) {
            return UseCBCforFile.decrypt(cipher64, kpr, Base64Helper.toBytes(iv64));
        } else {
            throw new Exception("Firma HMAC non valida! Tentativo di compromissione...");
        }
    }
}
Codice sorgente Java | App Cryptea | Documentazione | Cryptea: Encryption served hot.