diff --git a/TPs/TP02/py/input.txt.dec b/TPs/TP02/py/input.txt.dec
index afe74b8..e69de29 100644
--- a/TPs/TP02/py/input.txt.dec
+++ b/TPs/TP02/py/input.txt.dec
@@ -1 +0,0 @@
-panda é fixe!!!
diff --git a/TPs/TP02/py/input.txt.enc b/TPs/TP02/py/input.txt.enc
index 7517ad9..ce7432a 100644
--- a/TPs/TP02/py/input.txt.enc
+++ b/TPs/TP02/py/input.txt.enc
@@ -1 +1 @@
-�[n�"[v�շ�4Z�![n{V��#��쁻
\ No newline at end of file
+�US9�hM�(#c��b���oe@]�.J�?K.�k�|Da�vz>Z:���"]
\ No newline at end of file
diff --git a/TPs/TP02/py/input.txt.enc.dec b/TPs/TP02/py/input.txt.enc.dec
new file mode 100644
index 0000000..afe74b8
--- /dev/null
+++ b/TPs/TP02/py/input.txt.enc.dec
@@ -0,0 +1 @@
+panda é fixe!!!
diff --git a/TPs/TP02/py/pbenc_aes_ctr.py b/TPs/TP02/py/pbenc_aes_ctr.py
new file mode 100644
index 0000000..b494cf7
--- /dev/null
+++ b/TPs/TP02/py/pbenc_aes_ctr.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python3
+
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
+from cryptography.hazmat.primitives import hashes
+import os
+import argparse
+
+
+
+def encrypt(input_file, password):
+    inp = open(input_file,"rb")
+    out = open(f"{input_file}.enc","wb")
+
+    plaintext = inp.read()
+    print(f"plaintext len : {len(plaintext)}")
+
+
+    # Derive the key from the password using PBKDF2
+    salt = os.urandom(16)
+    kdf = PBKDF2HMAC(
+        algorithm=hashes.SHA256(),
+        length=32,
+        salt=salt,
+        iterations=100000
+    )
+
+    key = kdf.derive(password.encode('utf-8'))
+    iv = os.urandom(16)
+
+    cipher = Cipher(algorithms.AES(key),modes.CTR(iv))
+    encryptor = cipher.encryptor()
+    ciphertext = encryptor.update(plaintext)
+    
+    ciphertext = salt + iv + ciphertext
+
+    print(f"plaintext len : {len(plaintext)}")
+    print(f"ciphertext len : {len(ciphertext)}")
+    print(f"iv len : {len(iv)}")    
+
+    
+    out.write(ciphertext)
+
+    inp.close()
+    out.close()
+
+def decrypt(input_file,password):
+    inp = open(f"{input_file}","rb")
+    out = open(f"{input_file}.dec","wb")
+
+    input_bytes = inp.read()
+    salt = input_bytes[:16]
+    iv = input_bytes[16:32]
+    ciphertext = input_bytes[32:]
+
+    kdf = PBKDF2HMAC(
+        algorithm=hashes.SHA256(),
+        length=32,
+        salt=salt,
+        iterations=100000
+    )
+
+    print(f"plaintext len : {len(ciphertext)}")
+    print(f"iv len : {len(iv)}")
+    print(f"salt len : {len(salt)}")
+
+    key = kdf.derive(password.encode('utf-8'))
+
+    # FIX: block size for aes must be 16 bytes
+    # plaintext needs padding
+    cipher = Cipher(algorithms.AES(key),modes.CTR(iv))
+    decryptor = cipher.decryptor()
+    plaintext = decryptor.update(ciphertext)
+
+    out.write(plaintext)
+
+    inp.close()
+    out.close()
+
+def main():
+    parser = argparse.ArgumentParser(
+        description="Program to perform operations using AES cipher on files",
+    )
+
+    subparsers = parser.add_subparsers(dest="operation", help="Operation to perform")
+
+    # Encrypt subcommand
+    enc_parser = subparsers.add_parser("enc", help="Encrypt a file")
+    enc_parser.add_argument("fich", help="File to be encrypted")
+    enc_parser.add_argument("password", help="Pass-phrase to derive the key")
+
+    # Decrypt subcommand
+    dec_parser = subparsers.add_parser("dec", help="Decrypt a file")
+    dec_parser.add_argument("fich", help="File to be decrypted")
+    dec_parser.add_argument("password", help="Pass-phrase to derive the key")
+
+    args = parser.parse_args()
+    match args.operation:
+        case "enc":
+            input_file = args.fich
+            password = args.password
+            encrypt(input_file,password)
+        case "dec":
+            input_file = args.fich
+            password = args.password
+            decrypt(input_file,password)
+
+if __name__ == "__main__":
+    main()
diff --git a/TPs/TP03/Readme.md b/TPs/TP03/Readme.md
new file mode 100644
index 0000000..45c94ca
--- /dev/null
+++ b/TPs/TP03/Readme.md
@@ -0,0 +1,8 @@
+# Questao 1
+
+Ao utilizar o programa 'chacha20_int_attck.py' sobre um criptograma produzido por 'pbenc_chacha20_poly1305', the decrpyt function will raise the execption : 
+- 'cryptography.exceptions.InvalidTag' – If the authentication tag doesn’t validate this exception will be raised. This will occur when the ciphertext has been changed, but will also occur when the key, nonce, or associated data are wrong.
+
+We can try this by encrypting a message and then changing the ciphertext with the attack program:
+
+![Failed Attack](https://github.com/uminho-mei-es/2324-G05/blob/main/TPs/TP03/attack_fail.png)
\ No newline at end of file
diff --git a/TPs/TP03/attack_fail.png b/TPs/TP03/attack_fail.png
new file mode 100644
index 0000000..f489c7a
Binary files /dev/null and b/TPs/TP03/attack_fail.png differ
diff --git a/TPs/TP03/compile_commands.json b/TPs/TP03/compile_commands.json
new file mode 100644
index 0000000..469619e
--- /dev/null
+++ b/TPs/TP03/compile_commands.json
@@ -0,0 +1,17 @@
+[
+  {
+    "arguments": [
+      "/usr/bin/gcc",
+      "-c",
+      "-Wall",
+      "-O2",
+      "-I/opt/homebrew/opt/openssl@3.2/include",
+      "-o",
+      "pbenc_aes_ctr_hmac",
+      "pbenc_aes_ctr_hmac.c"
+    ],
+    "directory": "/Users/afonso/projects/University/4ano/2sem/ES/2324-G05/TPs/TP03",
+    "file": "/Users/afonso/projects/University/4ano/2sem/ES/2324-G05/TPs/TP03/pbenc_aes_ctr_hmac.c",
+    "output": "/Users/afonso/projects/University/4ano/2sem/ES/2324-G05/TPs/TP03/pbenc_aes_ctr_hmac"
+  }
+]
diff --git a/TPs/TP03/pbenc_aes_ctr_hmac b/TPs/TP03/pbenc_aes_ctr_hmac
new file mode 100755
index 0000000..cb20297
Binary files /dev/null and b/TPs/TP03/pbenc_aes_ctr_hmac differ
diff --git a/TPs/TP03/pbenc_aes_ctr_hmac.c b/TPs/TP03/pbenc_aes_ctr_hmac.c
index dc03ee6..79e823e 100644
--- a/TPs/TP03/pbenc_aes_ctr_hmac.c
+++ b/TPs/TP03/pbenc_aes_ctr_hmac.c
@@ -19,7 +19,7 @@ int aes_ctr(const char *input_file, const char *output_file, const unsigned char
 
 	int input_size = BUF_SIZE;
 	int output_size = input_size + (cipher_block_size - 1);
-	int u_len = 0, f_len = 0;
+	int f_len = 0;
 
 	unsigned char input_buf[input_size], output_buf[output_size];
 
@@ -45,30 +45,44 @@ int aes_ctr(const char *input_file, const char *output_file, const unsigned char
 		return 1;
 	}
 
-	// EVP_MAC *mac = EVP_MAC_fetch(NULL, "HMAC", NULL);
-	// if (!mac) {
-	// 	fprintf(stderr, "Error creating HMAC\n");
-	// 	fclose(finput);
-	// 	fclose(foutput);
-	// 	return 1;
-	// }
-	//
-	// EVP_MAC_CTX *hctx = NULL;
-	// if (!(hctx = EVP_MAC_CTX_new(mac))) {
-	// 	fprintf(stderr, "Error creating HMAC context\n");
-	// 	fclose(finput);
-	// 	fclose(foutput);
-	// 	return 1;
-	// }
-	// const OSSL_PARAM params[] = {OSSL_PARAM_UTF8_STRING(OSSL_MAC, "SHA256", 0), OSSL_PARAM_END};
+	EVP_MAC *mac = EVP_MAC_fetch(NULL, "HMAC", NULL);
+	if (!mac) {
+		fprintf(stderr, "Error creating HMAC\n");
+		fclose(finput);
+		fclose(foutput);
+		return 1;
+	}
 
-	// Set the digest type to SHA256
-	// if (EVP_MAC_CTX_set_params(hctx, EVP_sha256()) != 1) {
-	// 	fprintf(stderr, "Error setting HMAC digest type\n");
-	// 	fclose(finput);
-	// 	fclose(foutput);
-	// 	return 1;
-	// }
+	EVP_MAC_CTX *hctx = NULL;
+	if (!(hctx = EVP_MAC_CTX_new(mac))) {
+		fprintf(stderr, "Error creating HMAC context\n");
+		fclose(finput);
+		fclose(foutput);
+		return 1;
+	}
+
+	// Set parameters for HMAC algorithm
+	OSSL_PARAM params[2], *p = params;
+	const EVP_MD *md = EVP_sha256();
+	*p++ = OSSL_PARAM_construct_utf8_string("digest", (char *)EVP_MD_name(md), 0);
+	*p = OSSL_PARAM_construct_end();
+
+	unsigned char *hmac = malloc(32);
+	unsigned char *mac_input;
+	if (enc) {
+		mac_input = output_buf;
+	} else {
+		mac_input = input_buf;
+	}
+
+	fseek(finput, 0, SEEK_END);
+	long file_size = ftell(finput);
+	rewind(finput);
+	if (!enc) {
+		// Remove the size of the SALT, IV and HMAC from the file size
+		fseek(finput, 32, SEEK_SET);
+		file_size -= 64;
+	}
 
 	// If enc is 1, then we are encrypting, else we are decrypting
 	// If we are encrypting, we need to generate an IV
@@ -86,6 +100,7 @@ int aes_ctr(const char *input_file, const char *output_file, const unsigned char
 	} else {
 		// Seek forward by 16 bytes to ignore the salt
 		if (fseek(finput, 16, SEEK_SET) != 0) {
+
 			fprintf(stderr, "Error seeking to IV position in input file\n");
 			return 1;
 		}
@@ -102,7 +117,7 @@ int aes_ctr(const char *input_file, const char *output_file, const unsigned char
 		return 1;
 	}
 
-	if (EVP_MAC_init(hctx, key, 32, NULL) != 1) {
+	if (EVP_MAC_init(hctx, key, 32, params) != 1) {
 		fprintf(stderr, "ERROR: EVP_MAC_init failed. OpenSSL error: %s\n",
 				ERR_error_string(ERR_get_error(), NULL));
 		fclose(finput);
@@ -110,10 +125,15 @@ int aes_ctr(const char *input_file, const char *output_file, const unsigned char
 		return 1;
 	}
 
-	int read_size, len;
-	unsigned char *hmac = (unsigned char *)malloc(32);
-	while ((read_size = fread(input_buf, 1, BUF_SIZE, finput)) > 0) {
-		printf("Read %d bytes, passing through CipherUpdate...\n", read_size);
+	int len;
+	while (file_size > 0) {
+		if (file_size < BUF_SIZE) {
+			BUF_SIZE = file_size;
+		}
+		size_t read_size = fread(input_buf, 1, BUF_SIZE, finput);
+		if (read_size == 0) {
+			break;
+		}
 		if (EVP_CipherUpdate(ctx, output_buf, &len, input_buf, read_size) != 1) {
 			fprintf(stderr, "ERROR: EVP_CipherUpdate failed. OpenSSL error: %s\n",
 					ERR_error_string(ERR_get_error(), NULL));
@@ -121,17 +141,18 @@ int aes_ctr(const char *input_file, const char *output_file, const unsigned char
 			fclose(foutput);
 			return 1;
 		}
-		printf("\tGot back %d bytes from CipherUpdate...\n", len);
-		printf("Writing %d bytes to %s...\n", len, output_file);
+		if (EVP_MAC_update(hctx, mac_input, read_size) != 1) {
+			fprintf(stderr, "ERROR: EVP_MAC_update failed. OpenSSL error: %s\n",
+					ERR_error_string(ERR_get_error(), NULL));
+			fclose(finput);
+			fclose(foutput);
+			return 1;
+		}
 		if (fwrite(output_buf, 1, len, foutput) != len) {
 			fprintf(stderr, "Error writing to output file\n");
 			return 1;
 		}
-		printf("\tWrote %d bytes\n", len);
-		u_len += len;
-	}
-	if (read_size == -1) {
-		fprintf(stderr, "ERROR: Reading from the file %s failed.\n", input_file);
+		file_size -= read_size;
 	}
 
 	if (EVP_CipherFinal_ex(ctx, output_buf, &f_len) != 1) {
@@ -141,10 +162,22 @@ int aes_ctr(const char *input_file, const char *output_file, const unsigned char
 		fclose(foutput);
 		return 1;
 	}
-	printf("u_len: %d, f_len: %d\n", u_len, f_len);
+	size_t m_len = 0;
+	if (EVP_MAC_final(hctx, hmac, &m_len, 32) != 1) {
+		fprintf(stderr, "ERROR: EVP_MAC_final failed. OpenSSL error: %s\n",
+				ERR_error_string(ERR_get_error(), NULL));
+		fclose(finput);
+		fclose(foutput);
+		return 1;
+	}
+	if (m_len != 32) {
+		fprintf(stderr, "ERROR: HMAC length is not 32\n");
+		fclose(finput);
+		fclose(foutput);
+		return 1;
+	}
 
 	if (f_len) {
-		printf("Writing final %d bytes to %s...\n", f_len, output_file);
 		if (fwrite(output_buf, 1, f_len, foutput) != f_len) {
 			fprintf(stderr, "Error writing to output file\n");
 			fclose(finput);
@@ -152,7 +185,32 @@ int aes_ctr(const char *input_file, const char *output_file, const unsigned char
 			return 1;
 		}
 	}
-	printf("\tWrote last %d bytes\n", f_len);
+	if (enc) {
+		if (fwrite(hmac, 1, m_len, foutput) != m_len) {
+			fprintf(stderr, "Error writing HMAC to file\n");
+			fclose(finput);
+			fclose(foutput);
+			return 1;
+		}
+	} else {
+		unsigned char *hmac2 = malloc(32);
+		if (fread(hmac2, 1, 32, finput) != 32) {
+			fprintf(stderr, "Error reading HMAC from file\n");
+			fclose(finput);
+			fclose(foutput);
+			return 1;
+		}
+		int cmp = memcmp(hmac, hmac2, 32);
+		if (cmp == 0) {
+			printf("HMACs match\n");
+		} else {
+			fprintf(stderr, "HMACs do not match\n");
+			fclose(finput);
+			fclose(foutput);
+			return 1;
+		}
+	}
+
 	fclose(finput);
 	fclose(foutput);
 
@@ -160,7 +218,7 @@ int aes_ctr(const char *input_file, const char *output_file, const unsigned char
 }
 
 int encrypt(char *input_file, const char *passphrase) {
-	unsigned char key[KEY_SIZE];
+	unsigned char key[KEY_SIZE * 2];
 	unsigned char salt[SALT_SIZE];
 	// Derive key from passphrase using PBKDF2
 	char *output_file = malloc(strlen(input_file) + 5);
@@ -184,8 +242,9 @@ int encrypt(char *input_file, const char *passphrase) {
 	}
 	fclose(foutput);
 
+	int pass_len = strlen(passphrase);
 	// Derive key from passphrase using PBKDF2
-	if (PKCS5_PBKDF2_HMAC(passphrase, strlen(passphrase), salt, SALT_SIZE, ITERATIONS, EVP_sha256(),
+	if (PKCS5_PBKDF2_HMAC(passphrase, pass_len, salt, SALT_SIZE, ITERATIONS, EVP_sha256(),
 						  KEY_SIZE * 2, key) != 1) {
 		fprintf(stderr, "Error deriving key from passphrase\n");
 		return 1;
@@ -268,6 +327,7 @@ int main(int argc, char *argv[]) {
 		return 1;
 	}
 	enableEcho();
+	putchar('\n');
 	passphrase[strcspn(passphrase, "\n")] = '\0'; // Remove trailing newline
 	int suc = 0;
 	if (strcmp(mode, "enc") == 0) {
diff --git a/TPs/TP03/plaintext b/TPs/TP03/plaintext
new file mode 100644
index 0000000..a9c64a9
--- /dev/null
+++ b/TPs/TP03/plaintext
@@ -0,0 +1,42 @@
+This was a triumph.
+I'm making a note here: HUGE SUCCESS.
+It's hard to overstate my satisfaction.
+Aperture Science
+We do what we must because we can.
+For the good of all of us
+Except the ones who are dead.
+But there's no sense crying over every mistake
+You just keep on trying till you run out of cake
+And the science gets done and you make a neat gun
+For the people who are still alive.
+I'm not even angry.
+I'm being so sincere right now.
+Even though you broke my heart and killed me.
+And tore me to pieces.
+And threw every piece into a fire.
+As they burned it hurt because
+I was so happy for you!
+Now these points of data make a beautiful line
+And we're out of beta, we're releasing on time.
+So I'm GLaD. I got burned.
+Think of all the things we learned
+For the people who are still alive.
+Go ahead and leave me.
+I think I prefer to stay inside.
+Maybe you'll find someone else to help you.
+Maybe Black Mesa
+THAT WAS A JOKE. Haha. FAT CHANCE.
+Anyway, this cake is great.
+It's so delicious and moist.
+Look at me still talking when there's science to do.
+When I look out there, it makes me GLaD I'm not you.
+I've experiments to run, there is research to be done
+On the people who are still alive
+And believe me I am still alive.
+I'm doing science and I'm still alive.
+I feel FANTASTIC and I'm still alive.
+While you're dying I'll be still alive.
+And when you're dead I will be still alive.
+Still alive
+Still alive
+Still alive
diff --git a/TPs/TP03/py/chacha20_int_attck.py b/TPs/TP03/py/chacha20_int_attck.py
new file mode 100644
index 0000000..60ad33c
--- /dev/null
+++ b/TPs/TP03/py/chacha20_int_attck.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+
+import sys
+
+def attack(fctxt, pos, plainAtPos, newPlainAtPos):
+    f = open(fctxt,"rb")
+    ciphertext = f.read()
+    f.close()
+
+    plainAtPos = plainAtPos.encode()
+    newPlainAtPos = newPlainAtPos.encode()
+    txt_len = len(plainAtPos)
+    diff = bytes([a ^ b for (a,b) in zip(plainAtPos,newPlainAtPos)])
+    cipher_diff = bytes([a ^ b for (a,b) in zip(diff,ciphertext[pos:pos+txt_len])])
+
+    new_ciphertext = ciphertext[:pos] + cipher_diff + ciphertext[pos+txt_len:]
+
+    with open(fctxt+".attck","wb") as f:
+        f.write(new_ciphertext)
+
+def main():
+    argv = sys.argv[1:]
+    argc = len(argv)
+    if argc < 3 or argc > 5:
+        sys.exit("Needs 4 arguments <fctxt> <pos> <ptxtAtPos> <newPtxtAtPos>")
+
+    attack(argv[0],int(argv[1]),argv[2],argv[3])
+
+if __name__ == "__main__":
+    main()
diff --git a/TPs/TP03/py/pbenc_aes_ctr_hmac.py b/TPs/TP03/py/pbenc_aes_ctr_hmac.py
new file mode 100644
index 0000000..76a0fbf
--- /dev/null
+++ b/TPs/TP03/py/pbenc_aes_ctr_hmac.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python3
+
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives import hmac
+import os
+import argparse
+
+
+
+def encrypt(input_file, password):
+    inp = open(input_file,"rb")
+    out = open(f"{input_file}.enc","wb")
+
+    plaintext = inp.read()
+    print(f"plaintext len : {len(plaintext)}")
+
+
+    # Derive the key from the password using PBKDF2
+    salt = os.urandom(16)
+    kdf = PBKDF2HMAC(
+        algorithm=hashes.SHA256(),
+        length=64,
+        salt=salt,
+        iterations=100000
+    )
+    # FIX: key length must be 32 bytes
+    derived_key = kdf.derive(password.encode('utf-8'))
+
+    key = derived_key[:32]
+    hmac_key = derived_key[32:]
+
+    iv = os.urandom(16)
+
+    cipher = Cipher(algorithms.AES(key),modes.CTR(iv))
+    encryptor = cipher.encryptor()
+    ciphertext = encryptor.update(plaintext)
+    
+    ciphertext = salt + iv + ciphertext
+
+    print(f"plaintext len : {len(plaintext)}")
+    print(f"ciphertext len : {len(ciphertext)}")
+    print(f"iv len : {len(iv)}")    
+
+    h = hmac.HMAC(hmac_key,hashes.SHA256())
+    h.update(ciphertext)
+    tag = h.finalize()
+    ciphertext = ciphertext + tag
+
+    out.write(ciphertext)
+
+    inp.close()
+    out.close()
+
+def decrypt(input_file,password):
+    inp = open(f"{input_file}","rb")
+    out = open(f"{input_file}.dec","wb")
+
+    input_bytes = inp.read()
+    salt = input_bytes[:16]
+    iv = input_bytes[16:32]
+    ciphertext = input_bytes[32:-32]
+    tag = input_bytes[-32:]
+
+    kdf = PBKDF2HMAC(
+        algorithm=hashes.SHA256(),
+        length=64,
+        salt=salt,
+        iterations=100000
+    )
+
+    print(f"plaintext len : {len(ciphertext)}")
+    print(f"iv len : {len(iv)}")
+    print(f"salt len : {len(salt)}")
+
+    derived_key = kdf.derive(password.encode('utf-8'))
+    hmac_key = derived_key[32:]
+    key = derived_key[:32]
+
+    # FIX: block size for aes must be 16 bytes
+    # plaintext needs padding
+    cipher = Cipher(algorithms.AES(key),modes.CTR(iv))
+    decryptor = cipher.decryptor()
+    plaintext = decryptor.update(ciphertext)
+
+    h = hmac.HMAC(hmac_key,hashes.SHA256())
+    h.update(salt + iv + ciphertext)
+    if (h.finalize() != tag):
+        print("Error: HMAC verification failed")
+        return
+
+
+    out.write(plaintext)
+
+    inp.close()
+    out.close()
+
+def main():
+    parser = argparse.ArgumentParser(
+        description="Program to perform operations using AES cipher on files",
+    )
+
+    subparsers = parser.add_subparsers(dest="operation", help="Operation to perform")
+
+    # Encrypt subcommand
+    enc_parser = subparsers.add_parser("enc", help="Encrypt a file")
+    enc_parser.add_argument("fich", help="File to be encrypted")
+    enc_parser.add_argument("password", help="Pass-phrase to derive the key")
+
+    # Decrypt subcommand
+    dec_parser = subparsers.add_parser("dec", help="Decrypt a file")
+    dec_parser.add_argument("fich", help="File to be decrypted")
+    dec_parser.add_argument("password", help="Pass-phrase to derive the key")
+
+    args = parser.parse_args()
+    match args.operation:
+        case "enc":
+            input_file = args.fich
+            password = args.password
+            encrypt(input_file,password)
+        case "dec":
+            input_file = args.fich
+            password = args.password
+            decrypt(input_file,password)
+
+if __name__ == "__main__":
+    main()
diff --git a/TPs/TP03/py/pbenc_aes_gcm_hmac.py b/TPs/TP03/py/pbenc_aes_gcm_hmac.py
new file mode 100644
index 0000000..b21287e
--- /dev/null
+++ b/TPs/TP03/py/pbenc_aes_gcm_hmac.py
@@ -0,0 +1,76 @@
+import os
+from cryptography.hazmat.primitives.ciphers.aead import AESGCM
+import argparse
+
+def setup(key_file):
+    key = AESGCM.generate_key(bit_length=128)
+    with open(key_file, "wb") as f:
+        f.write(key)
+
+
+def encrypt(input_file, key_file):
+    with open(input_file, "rb") as f:
+        plaintext = f.read()
+
+    with open(key_file, "rb") as f:
+        key = f.read()
+
+    aad = b"authenticated but unencrypted data"
+    aesgcm = AESGCM(key)
+    nonce = os.urandom(12)
+
+    ct = aesgcm.encrypt(nonce, plaintext, aad)
+
+    with open(f"{input_file}.enc", "wb") as f:
+        f.write(nonce)
+        f.write(ct)
+
+
+def decrypt(input_file, key_file):
+    with open(input_file, "rb") as f:
+        nonce = f.read(12)
+        ct = f.read()
+
+    with open(key_file, "rb") as f:
+        key = f.read()
+
+    aad = b"authenticated but unencrypted data"
+    aesgcm = AESGCM(key)
+    pt = aesgcm.decrypt(nonce, ct, aad)
+
+    with open(f"{input_file}.dec", "wb") as f:
+        f.write(pt)
+
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description="Program to perform operations using AES-GCM cipher on files",
+    )
+
+    subparsers = parser.add_subparsers(dest="operation", help="Operation to perform")
+
+    # Encrypt subcommand
+    enc_parser = subparsers.add_parser("enc", help="Encrypt a file")
+    enc_parser.add_argument("fich", help="File to be encrypted")
+    enc_parser.add_argument("password", help="Pass-phrase to derive the key")
+
+    # Decrypt subcommand
+    dec_parser = subparsers.add_parser("dec", help="Decrypt a file")
+    dec_parser.add_argument("fich", help="File to be decrypted")
+    dec_parser.add_argument("password", help="Pass-phrase to derive the key")
+
+    args = parser.parse_args()
+    match args.operation:
+        case "enc":
+            input_file = args.fich
+            password = args.password
+            encrypt(input_file,password)
+        case "dec":
+            input_file = args.fich
+            password = args.password
+            decrypt(input_file,password)
+
+if __name__ == "__main__":
+    main()
+
diff --git a/TPs/TP03/py/pbenc_chacha20_poly1305.py b/TPs/TP03/py/pbenc_chacha20_poly1305.py
new file mode 100644
index 0000000..714793c
--- /dev/null
+++ b/TPs/TP03/py/pbenc_chacha20_poly1305.py
@@ -0,0 +1,93 @@
+from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
+import argparse
+import os
+
+
+def setup(key_file):
+    key = ChaCha20Poly1305.generate_key()
+    with open(key_file, "wb") as f:
+        f.write(key)
+
+
+def encrypt(input_file, key_file):
+    with open(key_file, "rb") as f:
+        key = f.read(32)
+
+    with open(input_file, "rb") as f:
+        plaintext = f.read()
+
+    aad = b"authenticated but unencrypted data"
+    nonce = os.urandom(12)
+
+    cipher = ChaCha20Poly1305(key)
+    ciphertext = cipher.encrypt(nonce, plaintext, aad)
+
+    with open(f"{input_file}.enc", "wb") as f:
+        f.write(nonce + ciphertext)
+
+
+
+def decrypt(input_file, key_file):
+    with open(key_file, "rb") as f:
+        key = f.read(32)
+    
+    with open(input_file, "rb") as f:
+        input_bytes = f.read()
+
+    aad = b"authenticated but unencrypted data"
+    nonce = input_bytes[:12]
+    ciphertext = input_bytes[12:]
+
+
+    cipher = ChaCha20Poly1305(key)
+    try:
+        plaintext = cipher.decrypt(nonce, ciphertext, aad)
+    except Exception as e:
+        print(f"Could not validate the authentication: {e}")
+        return
+    
+    
+
+    with open(f"{input_file}.dec", "wb") as f:
+        f.write(plaintext)
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description="Program to perform operations using Authenticated ChaCha20 cipher on files",
+    )
+
+    subparsers = parser.add_subparsers(dest="operation", help="Operation to perform")
+
+    # Setup subcommand
+    setup_parser = subparsers.add_parser("setup", help="Setup a key file")
+    setup_parser.add_argument("fkey", help="File to contain the appropriate key for the ChaCha20 cipher")
+
+    # Encrypt subcommand
+    enc_parser = subparsers.add_parser("enc", help="Encrypt a file")
+    enc_parser.add_argument("fich", help="File to be encrypted")
+    enc_parser.add_argument("fkey", help="File containing the key for the ChaCha20 cipher")
+
+    # Decrypt subcommand
+    dec_parser = subparsers.add_parser("dec", help="Decrypt a file")
+    dec_parser.add_argument("fich", help="File to be decrypted")
+    dec_parser.add_argument("fkey", help="File containing the key for the ChaCha20 cipher")
+
+    args = parser.parse_args()
+    match args.operation:
+        case "setup":
+            key_file = args.fkey
+            setup(key_file)
+        case "enc":
+            input_file = args.fich
+            key_file = args.fkey
+            encrypt(input_file,key_file)
+        case "dec":
+            input_file = args.fich
+            key_file = args.fkey
+            decrypt(input_file,key_file)
+        case "help":
+            parser.print_help()
+
+if __name__ == "__main__":
+    main()
diff --git a/TPs/TP03/py/plaintext.txt b/TPs/TP03/py/plaintext.txt
new file mode 100644
index 0000000..549938f
--- /dev/null
+++ b/TPs/TP03/py/plaintext.txt
@@ -0,0 +1 @@
+not very safe
\ No newline at end of file
diff --git a/TPs/TP03/py/plaintext.txt.enc b/TPs/TP03/py/plaintext.txt.enc
new file mode 100644
index 0000000..dfd60eb
Binary files /dev/null and b/TPs/TP03/py/plaintext.txt.enc differ
diff --git a/TPs/TP03/py/plaintext.txt.enc.dec b/TPs/TP03/py/plaintext.txt.enc.dec
new file mode 100644
index 0000000..549938f
--- /dev/null
+++ b/TPs/TP03/py/plaintext.txt.enc.dec
@@ -0,0 +1 @@
+not very safe
\ No newline at end of file
diff --git a/TPs/TP03/tokefile.toml b/TPs/TP03/tokefile.toml
new file mode 100644
index 0000000..dfcb7ed
--- /dev/null
+++ b/TPs/TP03/tokefile.toml
@@ -0,0 +1,15 @@
+[vars]
+CC="!which gcc"
+CFLAGS="-Wall -O2 -I/opt/homebrew/opt/openssl@3.2/include -L/opt/homebrew/opt/openssl@3.2/lib"
+LDFLAGS="-lssl -lcrypto"
+
+[targets.build]
+wildcards=["!ls -1 *.c","!ls -1 *.c | sed -e 's/\\.c$//'"]
+cmd="${CC} ${CFLAGS} ${LDFLAGS} @@ -o @@"
+
+[targets.debug]
+vars.CC="clang"
+vars.CFLAGS="-Wall -O0 -g -fsanitize=address -g -I/opt/homebrew/opt/openssl@3.2/include -L/opt/homebrew/opt/openssl@3.2/lib"
+wildcards=["!ls -1 *.c","!ls -1 *.c | sed -e 's/\\.c$//'"]
+cmd="${CC} ${CFLAGS} ${LDFLAGS} @@ -o @@"
+
