Compare commits

...

10 Commits

Author SHA1 Message Date
Cédric Seron
8253ea2d2e step 3 working 2025-11-12 17:14:14 +01:00
Cédric Seron
3c0e94cb35 Merge branch 'main' of https://gitlab.naaturel.be/Naaturel/masi-symetric-cipher 2025-11-12 16:15:50 +01:00
Cédric Seron
fa02d69063 temp push 2025-11-12 16:15:39 +01:00
Matthias Guillitte
05a25ba761 JAR files building
Will build JAR for each 3 step separately when executing `gradlew build` or `gradlew jarStepX` where X is the step you want to export as JAR.
The generated JARs are located in `build/libs`.
To run a JAR, use `java -jar build/libs/stepX-1.0-SNAPSHOT.jar` where `X` is the step number
2025-11-12 15:28:43 +01:00
Cédric Seron
c44f31a8ea Merge branch 'main' of https://gitlab.naaturel.be/Naaturel/masi-symetric-cipher 2025-11-12 13:41:14 +01:00
Cédric Seron
67115b9693 first pass step3 2025-11-12 13:41:11 +01:00
Laurent Crema
1f76926cbb Modifier README.md 2025-11-05 12:54:28 +00:00
Laurent Crema
0ba3aa76cf Modifier README.md 2025-11-05 12:53:43 +00:00
Matthias Guillitte
26e45da333 Step 2 2025-10-29 14:42:23 +01:00
Matthias Guillitte
573b5e48d2 Our code ☭ 2025-10-29 13:13:53 +01:00
8 changed files with 462 additions and 29 deletions

View File

@@ -1 +1,3 @@
Thanks, you read me
### Commands to launch autoGrader
- cd ~/autoGrader/
- java -jar <autoGrader_name>.jar

Binary file not shown.

View File

@@ -2,6 +2,55 @@ plugins {
id("java")
}
sourceSets {
create("common") {
java.srcDir("src/main/java/common")
}
// Step1-3 each depend on common
create("step1") {
java.srcDirs("src/main/java/common", "src/main/java/step1")
}
create("step2") {
java.srcDirs("src/main/java/common", "src/main/java/step2")
}
create("step3") {
java.srcDirs("src/main/java/common", "src/main/java/step3")
}
}
// === Create JAR tasks for each step ===
tasks.register<Jar>("jarStep1") {
manifest {
attributes["Main-Class"] = "step1.Main"
}
archiveBaseName.set("step1")
from(sourceSets["step1"].output)
dependsOn("classes")
}
tasks.register<Jar>("jarStep2") {
manifest {
attributes["Main-Class"] = "step2.Main"
}
archiveBaseName.set("step2")
from(sourceSets["step2"].output)
dependsOn("classes")
}
tasks.register<Jar>("jarStep3") {
manifest {
attributes["Main-Class"] = "step3.Main"
}
archiveBaseName.set("step3")
from(sourceSets["step3"].output)
dependsOn("classes")
}
tasks.named("build") {
dependsOn("jarStep1", "jarStep2", "jarStep3")
}
group = "be.naaturel"
version = "1.0-SNAPSHOT"

View File

@@ -0,0 +1,21 @@
package common;
public class Logger {
public static void displayInfo(String message){
final String BLUE = "\u001B[34m";
final String RESET = "\u001B[0m";
System.out.println(BLUE + "Info >>> " + message + RESET);
}
public static void displayReceived(String message){
String GREEN = "\u001B[32m";
String RESET = "\u001B[0m";
System.out.println(GREEN + "Received >>> " + message + RESET);
}
public static void displaySent(String message){
final String RED = "\u001B[31m";
final String RESET = "\u001B[0m";
System.out.println(RED + "Sent >>> " + message + RESET);
}
}

View File

@@ -0,0 +1,19 @@
package common;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class SocketManager {
public static void send(Socket socket, String data) throws IOException {
OutputStream output = socket.getOutputStream();
output.write(String.format("%s\r\n", data).getBytes(StandardCharsets.UTF_8));
output.flush();
}
public static String readResponse(BufferedReader reader) throws IOException {
return reader.readLine();
}
}

View File

@@ -13,6 +13,11 @@ import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.Base64;
import static common.Logger.displayInfo;
import static common.Logger.displayReceived;
import static common.Logger.displaySent;
import static common.SocketManager.send;
import static common.SocketManager.readResponse;
public class Main {
@@ -87,34 +92,6 @@ public class Main {
}
}
public static void displayInfo(String message){
final String BLUE = "\u001B[34m";
final String RESET = "\u001B[0m";
System.out.println(BLUE + "Info >>> " + message + RESET);
}
public static void displayReceived(String message){
String GREEN = "\u001B[32m";
String RESET = "\u001B[0m";
System.out.println(GREEN + "Received >>> " + message + RESET);
}
public static void displaySent(String message){
final String RED = "\u001B[31m";
final String RESET = "\u001B[0m";
System.out.println(RED + "Sent >>> " + message + RESET);
}
public static void send(Socket socket, String data) throws IOException {
OutputStream output = socket.getOutputStream();
output.write(String.format("%s\r\n", data).getBytes(StandardCharsets.UTF_8));
output.flush();
}
public static String readResponse(BufferedReader reader) throws IOException {
return reader.readLine();
}
public static SecretKey get3DESKey() throws NoSuchAlgorithmException, NoSuchProviderException {
KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM, PROVIDER);
keyGen.init(new SecureRandom());

View File

@@ -0,0 +1,108 @@
package step2;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import static common.Logger.*;
import static common.SocketManager.*;
public class Main {
private static final String PASSPHRASE = "laPassphrasePartagee";
private static final int PBKDF2_ITERATION = 600_000;
private static final int KEY_LEN_BITS = 256;
public static void main(String[] args){
int port = 8888;
if (args.length >= 2 && args[0].equals("--port")) {
port = Integer.parseInt(args[1]);
}
try (ServerSocket socket = new ServerSocket(port)) {
displayInfo("Server is running on port " + port + "\n waiting for the client to connect...");
while (true) {
try {
handleClient(socket.accept());
return;
} catch (IOException ignored) {}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void handleClient(Socket socket) throws IOException, GeneralSecurityException {
socket.setSoTimeout(30000);
displayInfo("Client connected");
// Welcome message
send(socket, "Welcome to the phase 2 server!");
BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
// Reçois le message chiffré et les éléments permettant de dériver la clé
String lineSalt = readResponse(reader);
String lineIv = readResponse(reader);
String lineCt = readResponse(reader);
displayReceived("Salt: " + lineSalt);
displayReceived("IV: " + lineIv);
displayReceived("Ciphered text: " + lineCt);
byte[] salt = Base64.getDecoder().decode(lineSalt.substring(5).trim());
byte[] iv = Base64.getDecoder().decode(lineIv.substring(3).trim());
byte[] ct = Base64.getDecoder().decode(lineCt.substring(3).trim());
// Dérivation clé PBKDF2-HMAC-SHA256 en 256 bits à partir des éléments reçus et de la passphrase commune
byte[] derivedKey = deriveKeyPBKDF2(PASSPHRASE.toCharArray(), salt, PBKDF2_ITERATION, KEY_LEN_BITS);
// Déchiffrement du message avec AES/CBC/PKCS5Padding
String message = decryptAesCbcPkcs5(derivedKey, iv, ct);
displayInfo("Decrypted message: " + message);
// Calcul du TAG : SHA3-256( key || message || "that's all folks" )
byte[] tag = computeSha3_256(derivedKey, message.getBytes(StandardCharsets.UTF_8), "that's all folks".getBytes(StandardCharsets.UTF_8));
String tagB64 = Base64.getEncoder().encodeToString(tag);
// Envoie le TAG au client
send(socket, "TAG:" + tagB64);
displaySent("Tag: " + tagB64);
// Client should OK/KO but doesn't ?
}
private static byte[] deriveKeyPBKDF2(char[] passphrase, byte[] salt, int iterations, int keyLenBits) throws NoSuchAlgorithmException, InvalidKeySpecException {
PBEKeySpec spec = new PBEKeySpec(passphrase, salt, iterations, keyLenBits);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
SecretKey key = skf.generateSecret(spec);
return key.getEncoded();
}
private static String decryptAesCbcPkcs5(byte[] keyBytes, byte[] ivBytes, byte[] ciphertext) throws GeneralSecurityException, UnsupportedEncodingException {
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] plain = cipher.doFinal(ciphertext);
return new String(plain, StandardCharsets.UTF_8);
}
private static byte[] computeSha3_256(byte[] keyPart, byte[] messageBytes, byte[] suffix) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA3-256");
md.update(keyPart);
md.update(messageBytes);
md.update(suffix);
return md.digest();
}
}

View File

@@ -0,0 +1,257 @@
package step3;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import static common.Logger.displayInfo;
import static common.Logger.displayReceived;
import static common.Logger.displaySent;
import static common.SocketManager.readResponse;
import static common.SocketManager.send;
public class Main {
private static final BigInteger P = new BigInteger("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16);
private static final BigInteger G = new BigInteger("2", 16);
public static void main(String[] args){
int port = 8888;
if (args.length >= 2 && args[0].equals("--port")) {
port = Integer.parseInt(args[1]);
}
try (ServerSocket socket = new ServerSocket(port)) {
displayInfo("Server is running on port " + port + "\n waiting for the client to connect...");
while (true) {
try {
handleClient(socket.accept());
return;
} catch (IOException ignored) {}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void handleClient(Socket socket) throws IOException, GeneralSecurityException {
socket.setSoTimeout(30000);
displayInfo("Client connected");
BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
String message = readResponse(reader);
displayReceived("Received A : " + message);
byte[] A_bytes = Base64.getDecoder().decode(message.substring(2));
BigInteger A = new BigInteger(1, A_bytes);
// Compute b and B
BigInteger b = new BigInteger(256, new java.security.SecureRandom());
BigInteger B = G.modPow(b, P);
byte[] B_bytes = toFixedLen(B, 256);
// Send B
message = "B:" + Base64.getEncoder().encodeToString(B_bytes);
send(socket, message);
displaySent("B sent : " + message);
// Compute shared Z
BigInteger Z = A.modPow(b, P);
byte[] Z_bytes = toFixedLen(Z, 256);
// compute PBKDF-HMAC key
byte[] key = PBKDF_HMAC_key(A, B, P, G);
byte[] nonce = new byte[12];
new java.security.SecureRandom().nextBytes(nonce);
// Generate RSA 2048-bit key pair
KeyPair rsaKP = generateRSAKeyPair();
// Encrypt the RSA public key (X.509 DER) with AES-GCM using the derived key
byte[] pubBytes = rsaKP.getPublic().getEncoded();
// Calculer l'AAD (Additional Authenticated Data)
byte[] aad = AES_GCM(A, B, P, G); // retourne SHA-256(g || p || A || B)
// Chiffrer avec AAD
SecretKeySpec aesKeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, nonce);
cipher.init(Cipher.ENCRYPT_MODE, aesKeySpec, gcmSpec);
cipher.updateAAD(aad); // <-- Ajouter l'AAD AVANT doFinal
byte[] ciphertext = cipher.doFinal(pubBytes);
// Assemble nonce || ciphertext and Base64-encode
byte[] out = new byte[nonce.length + ciphertext.length];
System.arraycopy(nonce, 0, out, 0, nonce.length);
System.arraycopy(ciphertext, 0, out, nonce.length, ciphertext.length);
String payloadB64 = Base64.getEncoder().encodeToString(out);
message = "PUBS:" + payloadB64;
send(socket, message);
displaySent("PUBS sent : " + message);
// Read client's encrypted public key (PUBC) and encrypted RSA ciphertext (CT_RSA)
String PUBC = readResponse(reader);
displayReceived("Received PUBC : " + PUBC);
String CT_RSA = readResponse(reader);
displayReceived("Received CT_RSA : " + CT_RSA);
// Decrypt PUBC: format PUBC:<base64(nonce||ciphertext)>
byte[] pubcDecoded = Base64.getDecoder().decode(PUBC.substring(5));
if (pubcDecoded.length < 12) throw new IOException("PUBC too short");
byte[] pubcNonce = Arrays.copyOfRange(pubcDecoded, 0, 12);
byte[] pubcCt = Arrays.copyOfRange(pubcDecoded, 12, pubcDecoded.length);
// Decrypt client's public key with AES-GCM
GCMParameterSpec pubcGcm = new GCMParameterSpec(128, pubcNonce);
Cipher aesDec = Cipher.getInstance("AES/GCM/NoPadding");
aesDec.init(Cipher.DECRYPT_MODE, aesKeySpec, pubcGcm);
aesDec.updateAAD(aad);
byte[] clientPubBytes = aesDec.doFinal(pubcCt);
// Recreate client's RSA PublicKey
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey clientPub = kf.generatePublic(new X509EncodedKeySpec(clientPubBytes));
// Decrypt CT_RSA which is RSA-OAEP encrypted with server's public key (so server uses private key)
byte[] ctRsaDecoded = Base64.getDecoder().decode(CT_RSA.substring(CT_RSA.indexOf(':')+1));
Cipher rsaDec = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
rsaDec.init(Cipher.DECRYPT_MODE, rsaKP.getPrivate());
byte[] Mbytes = rsaDec.doFinal(ctRsaDecoded);
// Sign M with RSASSA-PSS (SHA-256, MGF1(SHA-256), saltLen=32)
Signature signer = Signature.getInstance("RSASSA-PSS");
PSSParameterSpec pssSpec = new PSSParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), 32, 1);
signer.setParameter(pssSpec);
signer.initSign(rsaKP.getPrivate(), new SecureRandom());
signer.update(Mbytes);
byte[] signature = signer.sign();
// Split signature into 2 chunks of 128 bytes
byte[] sig1 = Arrays.copyOfRange(signature, 0, 128);
byte[] sig2 = Arrays.copyOfRange(signature, 128, 256);
// Encrypt each chunk with client's public key using RSA/OAEP (SHA-256)
Cipher rsaEnc = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
rsaEnc.init(Cipher.ENCRYPT_MODE, clientPub);
byte[] encSig1 = rsaEnc.doFinal(sig1);
byte[] encSig2 = rsaEnc.doFinal(sig2);
// Send two messages (labels SIG1 and SIG2)
String sig1B64 = Base64.getEncoder().encodeToString(encSig1);
String sig2B64 = Base64.getEncoder().encodeToString(encSig2);
send(socket, "SIG1:" + sig1B64);
displaySent("SIG1 sent");
send(socket, "SIG2:" + sig2B64);
displaySent("SIG2 sent");
}
private static byte[] toFixedLen(BigInteger x, int len) {
byte[] t = x.toByteArray();
if (t.length == len) return t;
if (t[0] == 0 && t.length == len + 1) { // strip leading zero
byte[] r = new byte[len];
System.arraycopy(t, 1, r, 0, len);
return r;
}
byte[] r = new byte[len];
System.arraycopy(t, Math.max(0, t.length - len), r, len - Math.min(len, t.length),
Math.min(len, t.length));
return r;
}
private static byte[] PBKDF_HMAC_key(BigInteger A, BigInteger B, BigInteger p, BigInteger g) throws GeneralSecurityException, IOException {
// Build the concatenation: g as 1 byte, p/A/B as 256 bytes each
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(toFixedLen(g, 1));
outputStream.write(toFixedLen(p, 256));
outputStream.write(toFixedLen(A, 256));
outputStream.write(toFixedLen(B, 256));
byte[] concat = outputStream.toByteArray();
// SHA-256 over the concatenation
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] digest = sha256.digest(concat);
// passphrase = Base64(SHA-256(...)) as a UTF-8 string (then used as password chars)
String passphraseB64 = Base64.getEncoder().encodeToString(digest);
char[] passphraseChars = passphraseB64.toCharArray();
// salt (UTF-8) with trailing space
byte[] salt = "phase3 aead key ".getBytes(StandardCharsets.UTF_8);
PBEKeySpec spec = new PBEKeySpec(passphraseChars, salt, 600000, 256);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] key = skf.generateSecret(spec).getEncoded();
// Cleanup sensitive data in memory where practical
Arrays.fill(passphraseChars, '\0');
spec.clearPassword();
return key;
}
private static byte[] AES_GCM(BigInteger A, BigInteger B, BigInteger p, BigInteger g) throws GeneralSecurityException, IOException{
// Build the concatenation: g as 1 byte, p/A/B as 256 bytes each
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(toFixedLen(g, 1));
outputStream.write(toFixedLen(p, 256));
outputStream.write(toFixedLen(A, 256));
outputStream.write(toFixedLen(B, 256));
byte[] concat = outputStream.toByteArray();
// SHA-256 over the concatenation
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] AAD = sha256.digest(concat);
return AAD;
}
private static KeyPair generateRSAKeyPair() throws GeneralSecurityException {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048, new SecureRandom());
return kpg.generateKeyPair();
}
private static String publicKeyToX509Base64(PublicKey pub) {
return Base64.getEncoder().encodeToString(pub.getEncoded());
}
}