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/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/TP04/cfich_nike.py b/TPs/TP04/cfich_nike.py
index beeafb9..d04e296 100644
--- a/TPs/TP04/cfich_nike.py
+++ b/TPs/TP04/cfich_nike.py
@@ -1,11 +1,11 @@
 import argparse
 import os
 
+import cryptography.hazmat.primitives.serialization as serialization
 from cryptography.hazmat.primitives import hashes
 from cryptography.hazmat.primitives.asymmetric import dh
 from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
 from cryptography.hazmat.primitives.kdf.hkdf import HKDF
-import cryptography.hazmat.primitives.serialization as serialization
 from cryptography.hazmat.primitives.serialization import (
     Encoding,
     NoEncryption,
@@ -13,106 +13,125 @@ from cryptography.hazmat.primitives.serialization import (
     PublicFormat,
 )
 
+p = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF
+g = 2
+params = dh.DHParameterNumbers(p, g).parameters()
+
 def setup(user):
-    parameters = dh.generate_parameters(generator=2, key_size=512)
-    private_key = parameters.generate_private_key()
+    private_key = params.generate_private_key()
     public_key = private_key.public_key()
 
-    skey = open(f"{user}.sk", 'wb')
-    pkey = open(f"{user}.pk", 'wb')
-    skey.write(private_key.private_bytes(encoding=Encoding.PEM, format=PrivateFormat.PKCS8, encryption_algorithm=NoEncryption()))
-    pkey.write(public_key.public_bytes(encoding=Encoding.PEM, format=PublicFormat.SubjectPublicKeyInfo))
+    skey = open(f"{user}.sk", "wb")
+    pkey = open(f"{user}.pk", "wb")
+    skey.write(
+        private_key.private_bytes(
+            encoding=Encoding.PEM,
+            format=PrivateFormat.PKCS8,
+            encryption_algorithm=NoEncryption(),
+        )
+    )
+    pkey.write(
+        public_key.public_bytes(
+            encoding=Encoding.PEM, format=PublicFormat.SubjectPublicKeyInfo
+        )
+    )
     skey.close()
     pkey.close()
 
 
-def encrypt(user,filename):
-    peer_pkey_file = open(f"{user}.pk", 'rb')
+def encrypt(user, filename):
+    peer_pkey_file = open(f"{user}.pk", "rb")
     if peer_pkey_file is None:
         print(f"No public key found for {user}")
         return
 
-    file_r = open(filename, 'rb')
+    file_r = open(filename, "rb")
     if file_r is None:
         print(f"File {filename} not found")
         return
 
-    # FIX: Add try
-    parameters = dh.generate_parameters(generator=2, key_size=512)
-    skey = parameters.generate_private_key()
-    peer_pkey= serialization.load_pem_public_key(peer_pkey_file.read())
-    derived_key = None
-    if isinstance(peer_pkey, dh.DHPublicKey):
-        shared_key = skey.exchange(peer_pkey)
-        derived_key = HKDF(algorithm=hashes.SHA256(), length=32, salt=None, info=b'handshake data').derive(shared_key)
-    else:
+
+    skey = params.generate_private_key()
+    peer_pkey = serialization.load_pem_public_key(peer_pkey_file.read())
+    if not isinstance(peer_pkey, dh.DHPublicKey):
         print("Error: Invalid key type")
         return
 
+    shared_key = skey.exchange(peer_pkey)
+    derived_key = HKDF(
+        algorithm=hashes.SHA256(), length=32, salt=None, info=None
+    ).derive(shared_key)
+
     message = file_r.read()
     chacha = ChaCha20Poly1305(derived_key)
     nonce = os.urandom(12)
     ciphertext = chacha.encrypt(nonce, message, None)
-    file_w = open(f"{filename}.enc", 'wb')
-    nonce_ciphertext = mkpair(nonce, ciphertext)
-    to_send = mkpair(peer_pkey.public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo), nonce_ciphertext)
+    file_w = open(f"{filename}.enc", "wb")
+    nonce_ciphertext = nonce + ciphertext
+    pkey_bytes = skey.public_key().public_bytes(
+        Encoding.DER, PublicFormat.SubjectPublicKeyInfo
+    )
+    to_send = mkpair(pkey_bytes, nonce_ciphertext)
     file_w.write(to_send)
 
     file_r.close()
     file_w.close()
-    skey_file.close()
     peer_pkey_file.close()
 
 
-def decrypt(user,filename):
-    skey_file = open(f"{user}.sk", 'rb')
+def decrypt(user, filename):
+    skey_file = open(f"{user}.sk", "rb")
     if skey_file is None:
         print(f"No private key found for {user}")
         return
 
-    file_r = open(filename, 'rb')
+    file_r = open(filename, "rb")
     if file_r is None:
         print(f"File {filename} not found")
         return
 
     message_bytes = file_r.read()
     peer_pkey_bytes, ciphertext = unpair(message_bytes)
-    # FIX: Add try
-    skey  = serialization.load_pem_private_key(skey_file.read(), None)
-    peer_pkey = serialization.load_pem_public_key(peer_pkey_bytes)
-    derived_key = None
-    if isinstance(skey, dh.DHPrivateKey) and isinstance(peer_pkey, dh.DHPublicKey):
-        shared_key = skey.exchange(peer_pkey)
-        derived_key = HKDF(algorithm=hashes.SHA256(), length=32, salt=None, info=b'handshake data').derive(shared_key)
-    else:
+
+    skey = serialization.load_pem_private_key(skey_file.read(), None)
+    peer_pkey = serialization.load_der_public_key(peer_pkey_bytes)
+    if not isinstance(skey, dh.DHPrivateKey) or not isinstance(
+        peer_pkey, dh.DHPublicKey
+    ):
         print("Error: Invalid key type")
         return
 
-    nonce, encrypted_message = unpair(ciphertext)
+    shared_key = skey.exchange(peer_pkey)
+    derived_key = HKDF(
+        algorithm=hashes.SHA256(), length=32, salt=None, info=None
+    ).derive(shared_key)
+
+    nonce = ciphertext[:12]
+    encrypted_message = ciphertext[12:]
     chacha = ChaCha20Poly1305(derived_key)
     message = chacha.decrypt(nonce, encrypted_message, None)
-    file_w = open(f"{filename}.decrypt", 'wb')
+    file_w = open(f"{filename}.dec", "wb")
     file_w.write(message)
     file_r.close()
     file_w.close()
     skey_file.close()
 
 
-
-
 def mkpair(x, y):
-    """ produz uma byte-string contendo o tuplo '(x,y)' ('x' e 'y' são byte-strings) """
+    """produz uma byte-string contendo o tuplo '(x,y)' ('x' e 'y' são byte-strings)"""
     len_x = len(x)
-    len_x_bytes = len_x.to_bytes(2, 'little')
+    len_x_bytes = len_x.to_bytes(2, "little")
     return len_x_bytes + x + y
 
+
 def unpair(xy):
-    """ extrai componentes de um par codificado com 'mkpair' """
-    len_x = int.from_bytes(xy[:2], 'little')
-    x = xy[2:len_x+2]
-    y = xy[len_x+2:]
+    """extrai componentes de um par codificado com 'mkpair'"""
+    len_x = int.from_bytes(xy[:2], "little")
+    x = xy[2 : len_x + 2]
+    y = xy[len_x + 2 :]
     return x, y
 
+
 def main():
     parser = argparse.ArgumentParser(
         description="Program to perform operations using ChaCha20 cipher on files",
@@ -121,7 +140,9 @@ def main():
     subparsers = parser.add_subparsers(dest="operation", help="Operation to perform")
 
     # Setup subcommand
-    setup_parser = subparsers.add_parser("setup", help="Setup diffie helman key pair for user")
+    setup_parser = subparsers.add_parser(
+        "setup", help="Setup diffie helman key pair for user"
+    )
     setup_parser.add_argument("user", help="User for which to setup the key pair")
 
     # Encrypt subcommand
@@ -142,11 +163,12 @@ def main():
         case "enc":
             user = args.user
             file = args.file
-            encrypt(user,file)
+            encrypt(user, file)
         case "dec":
             user = args.user
             file = args.file
-            decrypt(user,file)
+            decrypt(user, file)
+
 
 if __name__ == "__main__":
     main()
diff --git a/TPs/TP04/input b/TPs/TP04/input
new file mode 100644
index 0000000..badaeeb
--- /dev/null
+++ b/TPs/TP04/input
@@ -0,0 +1,8 @@
+As armas e os barões assinalados,
+Que da ocidental praia Lusitana,
+Por mares nunca de antes navegados,
+Passaram ainda além da Taprobana,
+Em perigos e guerras esforçados,
+Mais do que prometia a força humana,
+E entre gente remota edificaram
+Novo Reino, que tanto sublimaram;
diff --git a/TPs/TP05/ALICE.crt b/TPs/TP05/ALICE.crt
new file mode 100644
index 0000000..985abe4
--- /dev/null
+++ b/TPs/TP05/ALICE.crt
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDyzCCArOgAwIBAgIUCPhaX8nmoqG8snFrfNe+saiJ8CswDQYJKoZIhvcNAQEL
+BQAwgYoxCzAJBgNVBAYTAlBUMQ4wDAYDVQQIDAVNaW5obzEOMAwGA1UEBwwFQnJh
+Z2ExHjAcBgNVBAoMFVVuaXZlcnNpZGFkZSBkbyBNaW5obzENMAsGA1UECwwETVNH
+UzEfMB0GA1UEAwwWRW50aWRhZGUgQ2VydGlmaWNhZG9yYTELMAkGA1UEQQwCRUMw
+HhcNMjQwMzA4MjEzOTM4WhcNMjQwNjE2MjEzOTM4WjB9MQswCQYDVQQGEwJQVDEO
+MAwGA1UECAwFTWluaG8xDjAMBgNVBAcMBUJyYWdhMR4wHAYDVQQKDBVVbml2ZXJz
+aWRhZGUgZG8gTWluaG8xDTALBgNVBAsMBE1TR1MxDjAMBgNVBAMMBUFMSUNFMQ8w
+DQYDVQRBDAZDTElFTlQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6
+dLkrGhqif05lmhTWKbRbWRtMpAqTlPs1uNTg4h0eXhvGGpWQBYiQ2lekreTtXB4U
+vJ0EjDxcTQ68SodEvoLYlotPtnDlLwP5yAeoRy96lyFwbgWMjWQY/ILCY7CtLYmo
+9ZA2ySL/6K1agDTE5Y+eRqO2WKu1kYL1O66zUs3dA+EsbWWGg6/sRvUWE+SuZV7O
+OMXKtEszeqbeD0E10O7b+wBx5CvomZb4GYDQMiZkhcP7Gg4pLakgQ4EWvM6Jqmun
+4DdMiutKxD5L2Q+MNhkYh8+1CsVg9z/MAd6OheSYyMKQD4M0MLm2EY35g+dHijL0
+S+XhRGikr0xI9jSDmFMnAgMBAAGjNTAzMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/
+BAQDAgbAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQA8
+MHI0Ss76IPWq3fEODzLdfAQ86khHNZZ+KQEnOOb8h5UEVSciwiilISxTHltPmNPX
+7YRvBvY3LFfURPQwV0UPL7FErORXMny8epd3W4/D/sL6HOL8n/5uq442jW8rPnaA
+wFUcoLXBRxX+sTFXQisqGewpEzUpyf2MGJoneOcB3xQteQv7K0Sp3rop0LmlEV74
+/ZP6cMjW4MjxLn71J6y3tJ5FP4rTpbUmefnD1F8YVsuNNb0kj1CUsx5YMIFp4PjQ
+sruJXrAGTlqa8YuUwwjX7v6wvMv8Di95TIMgTm7bwonsjO7lmQsO261zGySfa0hF
+jMUsOA7r+H44aIyWAeNb
+-----END CERTIFICATE-----
diff --git a/TPs/TP05/ALICE.key b/TPs/TP05/ALICE.key
new file mode 100644
index 0000000..3a609e7
--- /dev/null
+++ b/TPs/TP05/ALICE.key
@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-256-CBC,5614AC7BE85EAABC3789637A31321B7F
+
+HxiSg2g4b4agu05VuCQdKdiAvh7fQaBsPVYvvuGvGa3szsol9RBwdEviaOE9hIWz
+vKIJghMVEz6C6fWwHoTv6qegTT0uMK6+Z7XN6Ma7GVABE9JDCc7AvAP8or1T0+NN
+xh3iztDQvPu+EfLD0jaZDP2pmz5ymq/ibeHIf8hutrdF9dkeNuwtRhKJ9vLv+S5c
+mJ4etKwDK1un2lt3wQpWSYQoqSKBz1B4tJT+NL6Nz0ZVW3nEcjNpUdLhBK+yBusM
+s7LwiEoqG/1VxDx8gPweOVHDIeURjaOR91NOSdjwwpwOuYOWdhSkfEa/fGSVol9u
+QMLEtNN1SRAWvakAmwhNrPNlSRRcFjn2nvO4AS1dRxZuwZEkYuiWeGONGFD6S4lK
+rG1rvwYlI28p9+VKVDi7XsW+HUFJ1tG2qYC5BgAOav3S9HIFhjhvYn3sOoM/0vxt
+cSr7r94v88UFhvKJZUSLHZn/yCiSX069yYEdY0grt8ZJRhRpN5G7o2T5ejQzGj8l
+U8fyDr89DlPYEQn/2eMgxIPcSghxAQqYF5MHvnZ6/CjNMKgQFc8ymGiq/HXveTE9
+91WtuH4NQZYsXanN6ZtUQm3a2h8lJD8x/H8LDQPFtwMfHMQP1WK8FlaiKiT7242Q
+Gu98ZwEyRBY1qO9rVYX0nbsXMxLAMefdtZlAgKovl1S5xj5r6LcIvFDsVqN5K98h
+IK2aoOG023wYJ4eNkjL4dBJiwZ5+RUg3sj4wkNzidcd0cucioj7mbTl4SWJilMI+
+fSXzvJADEI4qyjZbPb3KNGqeX0tvF7bkRHwZk91b1ihvvWsK1Lc6j++aDzOraqxS
+yVT5IuBy/kd/L+qps3UipneWuUA9NmqUgtIvp20W5R8G9aAfZFFaK0uVPbOYSLum
+WNIfBZDZN0SnuiiVoLCaMkFRzK/5GL7UTKKr5JQxS4KW+1VDrdUc8NqzMBhmFwmX
+fcGZNOj7bNkckooFIIEtUVL1r2/wX8t3hRY9M4S+MYuOR6Vr2yuajEJPodFD1Hom
+BMQDrkicVUJ9yP6QL/UO7a+QNZ2wdaIjfG6zOZkf0RQ/JxJtCN5jaziV6LNE/SfX
+UPeh/EdxVqU9hcdcZw6UsLCrVBpRJCwe5UhVHzrsShFW5DqdNBB0Blfxy/9X3IP4
+tyOeLqRj7jgHrVgd11rccOiB7vyydUL8J+ilmMbFDlk8lfJYvDked7OaQ5ca3/q9
+djkJfVBzGX4s7leJKg8286FBhceKgWhlyqrmXWe9WuBZNj8M94RvoZOCzrlw7WgF
+SFf2kSNmh0yXFtsYlNO5skmJo0pgZc5qDQnkHlfXq+Hf89a62L+igsgiy5F1cShx
+lCiFa/fkLUe3/+7kJg9XR35XqbLdFS0OLTMmLyghsL+p1Iab28xvajc2jyb7ftGe
+CBDD0KT3FvEYSFiE8o0JVYgiWav5NJOML7kW0fq6/ZUm1j9oXxWBpZx8rlVs1jTK
+16vkatrUENs9xaWmDsYvuvBmOnSFFzaCFcZ/mE6ixUUim7qKZWfGGwcTs6e1UmlW
+twsdWhQEVdZF9mF3NdXqUGqWOHV5RJCHyx3sGzps+pb3DiIgp1Xu6Y8R1057sGKY
+-----END RSA PRIVATE KEY-----
diff --git a/TPs/TP05/BOB.crt b/TPs/TP05/BOB.crt
new file mode 100644
index 0000000..497d736
--- /dev/null
+++ b/TPs/TP05/BOB.crt
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDyTCCArGgAwIBAgIUX4OZN6EvuWMWUD7EWgYwomRa2JswDQYJKoZIhvcNAQEL
+BQAwgYoxCzAJBgNVBAYTAlBUMQ4wDAYDVQQIDAVNaW5obzEOMAwGA1UEBwwFQnJh
+Z2ExHjAcBgNVBAoMFVVuaXZlcnNpZGFkZSBkbyBNaW5obzENMAsGA1UECwwETVNH
+UzEfMB0GA1UEAwwWRW50aWRhZGUgQ2VydGlmaWNhZG9yYTELMAkGA1UEQQwCRUMw
+HhcNMjQwMzA4MjEzOTM3WhcNMjQwNjE2MjEzOTM3WjB7MQswCQYDVQQGEwJQVDEO
+MAwGA1UECAwFTWluaG8xDjAMBgNVBAcMBUJyYWdhMR4wHAYDVQQKDBVVbml2ZXJz
+aWRhZGUgZG8gTWluaG8xDTALBgNVBAsMBE1TR1MxDDAKBgNVBAMMA0JPQjEPMA0G
+A1UEQQwGU0VSVkVSMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAljc+
+6jnrgaXhkNqTTCfi4i55Poxmj2AM3xVxlZwron1oAFFx7Uw1RoqE+bKLk3chZPTu
+I6Va4Ch9UF1lHl3YKf7+Z0I68JC/Z4mnZGnTYOZ2b9V5WqIyDWFcQlGhKfEQlPZg
+BJYZNlrnmePTJjUkniG3+t3u9rSvm/5copv+/Bp5jb9lXZAHVoYNulVY9Th5aRQZ
+8PB2bu+k3bsz/rGtF3TRpuMPu1oGoBUq6OVVWWZ6279DlKwycq+Yxl+0dd9dZD80
+X+yTyzTd8pkWwRTEtQ3cy4z+V5NsBlw7JBVOPVNz0FtRrUctZmgvW/S87yrUDOQ5
+eBXVsZwrZm6tmtHhVQIDAQABozUwMzAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQE
+AwIGwDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAQEAay7j
+May5ZrUh0O/XC/n/+NpxrxtUOWy+9ioGa/EJcuPRQJbyHTBkF7jhdY5WwgEMe2e/
+qJvM9sDfx0EL6fc6S3Dku6LINvsFKGOp3ljYt5JZZGXFiNshCEXjJ5l061jWWenn
+bAo2ef8OiO0LnfNqAZLqpRD0nI+AdEdqU/O5e792IMzPVNqDVxccYGvHron63WzB
+/32gqBBby5gU0USTXffwCLlauv51wKAXCwkp7uGvU0adKp2vL9rHo5OY67N4FzaA
+2a3/SMX5YMy0fvMB5Ao8mR2Y+jmQ8Lwb1xIMBx43NOc8exZtfPoHWH6WMKNyYx4i
+ydtkrMURnOOdXcBpAQ==
+-----END CERTIFICATE-----
diff --git a/TPs/TP05/BOB.key b/TPs/TP05/BOB.key
new file mode 100644
index 0000000..3df7657
--- /dev/null
+++ b/TPs/TP05/BOB.key
@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-256-CBC,003D12E5CDCB087CCE0B3C7E89D2D651
+
+lQirD96D/XjbbJOlZUzBQDqSmKeKICwrawMHddX6t34N414dcGuLaFPVDdvW5Aml
+W6gI43ovOLJQzF+thtHRflANehPE7ObHM+oRJn7+yyzHOv7zvFlm+8GBqRSb6H8m
+iVoupPPlFjcUfiy/6DNdRWU3iL2iIGFSwhbboZANrXMTBT+p7GGoaRTxdtwTd3hd
+kj2U73ehg9TeGSH/ftXr0dOGKIBZLXeQlIpiPTy86yn93fZ28E3EmjgOCWJXblTa
+Plv6xuLY/Y8vZUjjhoJto9IzeA5to4aI30gW90JkxaTnedk9ZL1skjMUKHCrl2Mi
+loKU0AI7wJrVlEl7ea3xZ52U+5Qe/KmkZL8GLo5MsB9Fc0jbpRhbsLrrocRW8qgy
+KYROLFb5l0s3N2WUN3zBmEO8vjm47mbZblGpPbHGpnVEvuqppNwBoWtUCiq9iptU
+0BWWcQ68QUdl0YDp+Mutl3/28ONJo9UYtBFo55PLTNG8tp9qz0Bh/i2hqoCIFoqD
+k0rVkGVaXU54IqQLZdF6yhWsKlUyehX8hy1SgukLzgpzwfHkW9DqlztKrmlcXPYl
+bHCAaQhimwufRrqSD7nq2Pn0Mq2qtdp6rn+jS9n6Spls1OPNZx4ev5zxga3v1NfA
+nIdXeevmP5VZlkIlZmkcoeMMPBKl1f4hzDL14kdA5kTBRbjasJQce4K6bqrueOxP
+qpsTGE9KF64NcD/8a2MQhnnpv7TTIFKy1eLrdrojhHUPnDWlVGJ0ihGpvn2AjmJ9
+YIFkxXhnegwbUYzabWFXk5fuX8Z2rXPXzpaKGfDTrEvpw0katlMZ/k9cfRJa0obb
+W0cwUwkgv7kH8DDRpnU2arNLtkQMviZKhNkVudWh7dD9wQwFLu46yyrocz9qbCHF
+vRA0IU5wwrHs0VxETEGm9qnfGJLHbSYaj57nybX2SI2tpwyqvdWEzCIuEvJ155Yc
+8u4HijBHI2ysEnoUycDJhZSfW/nl+R892Ppf5t29YVIgdM0yUV2nVA2D+e/buDnR
+B6ZKACBmNQIQIx9RsF3Q+EQipxUE82at48wWbTVl5iWbWJUbWK+Yoj9M1NDQZZjO
+7ytGzg9WkmuG/6HxuKJJl+0/zdn3+9BvgBGBqjEXNHTaG7OSMrIiwOCFmWzFKguh
+YLkh26+JuedrRmx1KoubR1q+gc+VDBD8owQaFz00O8virrqFC00zrC0Q0NtiEvCs
+24qkFHsvlvEwOwVAS0xL/rORmZ2GoW7hTVYKth6iOfLjJo9Df/UxNrdqFmmjtI3J
+G67ao4w93wiJnRwmgPyuvo0i5UKJqxkcwVkEzVuvYlLhgXj9IXp55atFNQ0h8NLr
+aoaHPGX/B/Fj7qisIa3925yXHeLBRDtUFsp8koY/GmY2BPuIrc+utwuJykb02puW
++Z+FcWNeCJFxvGJgH1mVUGpcbbO4VwpuT2PoxBIeTca5ujfwJyPEBiXVgB8rfKxV
+OjzrHdGIlz3aaLX1zGmiffyEQdWI1RHTk9VwF2K6Yxl+/ARiFqju89vzHXRX7B6s
+1CqApD0x5dHmuyqywAX3KE7qyvgQKabL4K33OcLyr1G5i6V0dLKfbseQWzVImpK9
+-----END RSA PRIVATE KEY-----
diff --git a/TPs/TP05/EC.crt b/TPs/TP05/EC.crt
new file mode 100644
index 0000000..90406d6
--- /dev/null
+++ b/TPs/TP05/EC.crt
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID3DCCAsSgAwIBAgIUTH2fOhbGuLcGi6Dy3NlUYspUr/IwDQYJKoZIhvcNAQEL
+BQAwgYoxCzAJBgNVBAYTAlBUMQ4wDAYDVQQIDAVNaW5obzEOMAwGA1UEBwwFQnJh
+Z2ExHjAcBgNVBAoMFVVuaXZlcnNpZGFkZSBkbyBNaW5obzENMAsGA1UECwwETVNH
+UzEfMB0GA1UEAwwWRW50aWRhZGUgQ2VydGlmaWNhZG9yYTELMAkGA1UEQQwCRUMw
+HhcNMjQwMzA4MjEzOTMxWhcNMjQwNjE2MjEzOTMxWjCBijELMAkGA1UEBhMCUFQx
+DjAMBgNVBAgMBU1pbmhvMQ4wDAYDVQQHDAVCcmFnYTEeMBwGA1UECgwVVW5pdmVy
+c2lkYWRlIGRvIE1pbmhvMQ0wCwYDVQQLDARNU0dTMR8wHQYDVQQDDBZFbnRpZGFk
+ZSBDZXJ0aWZpY2Fkb3JhMQswCQYDVQRBDAJFQzCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAKL4muNmXblWtpkGx7/Yfd9MnF++dwVlw835qnzCftMpVtAd
+QvyTryrGGNElGQmUNhihnz9zAHQPMFaUt4M3Xx8Wfilts1jzjEpqA3dikWQNkwC8
+ao8JA6v3n8rX0mWFA4Ggee6LpoiAu9YksAr8VYqSxH/cXg5SYybraxwW/GaFkfp9
+brMM7X1rGNeTsWpPpHNUTuMDqdBsaTRw9LAfAg8bDB3JKaIuX78MxH2C2UrCuf0s
+WyoEl6mJrDHXqxJ+mFntTLizHzEN3M8AZ/vrz/14avfcuih4i4LxFw/MlvTbWlLm
++gmgOBKmsx7JJ/O9VHg3AsBvG2XtmVc13XA3h4cCAwEAAaM4MDYwDwYDVR0TAQH/
+BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJ
+KoZIhvcNAQELBQADggEBAJCvNfm9zA8Dnk+j5qfzrMTbSbKyNSUR4Ic02Ji+Ew1h
+BsydcPMS62+yRH01I+AVLO1QX1dSJUoifQtHDbIOC2HkVaYq7mOyKPWVYkqRQT5u
+cwON88wpXqKq50ngT+0+yQMGNdT5+ON2DsL/I70O9WJ/ZE8bNPs+uYkZISvui+FW
+n0+iuuXIvB7wtdlVAB1RGvrkvi71UfXUPQXDX92rQj63oyrXuTtEDvsjWidMzPoN
+7H5VkAY4t9LTWJhosuNvKZbeHKNiZdUUiz53zTACc6WTN8k7nUyW/m1SgsYwAt1m
+AgfXmmTQqOeGFTkt5K3ElmCs3XD2ZMUnRf7w4jwb+ms=
+-----END CERTIFICATE-----
diff --git a/TPs/TP05/Images/Q1.png b/TPs/TP05/Images/Q1.png
new file mode 100644
index 0000000..f0eb896
Binary files /dev/null and b/TPs/TP05/Images/Q1.png differ
diff --git a/TPs/TP05/Readme.md b/TPs/TP05/Readme.md
new file mode 100644
index 0000000..bc0e4f5
--- /dev/null
+++ b/TPs/TP05/Readme.md
@@ -0,0 +1,56 @@
+# Respostas das Questões
+## Q1
+
+Para verificar que as chaves fornecidas constituem um par RSA valido, podemos extrair a chave publica do certificado e da chave privada (separadamente) e verificar se a chave publica é igual à chave publica extraída da chave privada.
+
+![Verificação da chave publica](https://github.com/uminho-mei-es/2324-G05/blob/main/TPs/TP05/Images/Q1.png)
+
+## Q2   
+Utilizando o comando `openssl x509 -text -noout -in xxx.cert` podemos visualizar o conteúdo do certificado. Correndo o comando para o certificado `ALICE.crt` obtemos o seguinte resultado:
+
+```bash
+
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            08:f8:5a:5f:c9:e6:a2:a1:bc:b2:71:6b:7c:d7:be:b1:a8:89:f0:2b
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: C = PT, ST = Minho, L = Braga, O = Universidade do Minho, OU = MSGS, CN = Entidade Certificadora, pseudonym = EC
+        Validity
+            Not Before: Mar  8 21:39:38 2024 GMT
+            Not After : Jun 16 21:39:38 2024 GMT
+        Subject: C = PT, ST = Minho, L = Braga, O = Universidade do Minho, OU = MSGS, CN = ALICE, pseudonym = CLIENT
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                ...
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:FALSE
+            X509v3 Key Usage: critical
+                Digital Signature, Non Repudiation
+            X509v3 Extended Key Usage:
+                TLS Web Client Authentication
+    Signature Algorithm: sha256WithRSAEncryption
+    Signature Value:
+        ...
+```
+Neste certificado, os campos importantes incluem:
+- **Serial Number**: Identificador único do certificado.
+- **Validity**: Indica o período durante o qual o certificado é válido.
+- **Subject**: Informações sobre a entidade a quem o certificado é emitido.
+- **Subject Public Key Info**: Detalhes sobre a chave pública, incluindo o algoritmo e o tamanho.
+- **X509v3 extensions**: Contém informações adicionais, como restrições básicas, uso de chaves e uso de chaves estendido.
+- **Signature Algorithm**: Algoritmo usado para assinar o certificado.
+- **Signature Value**: O valor de assinatura real que garante a integridade do certificado.
+
+
+## Q3
+
+Para provocar erros do tipo de validação do certificado, podemos alterar o certificado de várias maneiras, como:
+- Alterar a data de validade para uma data anterior à data atual.
+- Alterar o nome do emissor ou do sujeito.
+- Alterar a chave pública ou privada.
+
+Para provocar erros na validação da assinatura, podemos alterar a assinatura do certificado, o que invalidará a assinatura e resultará em um erro de validação.
diff --git a/TPs/TP05/sig_fich.py b/TPs/TP05/sig_fich.py
new file mode 100644
index 0000000..ee23477
--- /dev/null
+++ b/TPs/TP05/sig_fich.py
@@ -0,0 +1,165 @@
+import os
+import sys
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric import padding
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.backends import default_backend
+from cryptography import x509
+from cryptography.x509.oid import NameOID
+from cryptography.exceptions import InvalidSignature
+import argparse
+import datetime
+
+def mkpair(x, y):
+    """ produz uma byte-string contendo o tuplo '(x,y)' ('x' e 'y' são byte-strings) """
+    len_x = len(x)
+    len_x_bytes = len_x.to_bytes(2, 'little')
+    return len_x_bytes + x + y
+
+def unpair(xy):
+    """ extrai componentes de um par codificado com 'mkpair' """
+    len_x = int.from_bytes(xy[:2], 'little')
+    x = xy[2:len_x+2]
+    y = xy[len_x+2:]
+    return x, y
+
+def cert_load(fname):
+   """ lê certificado de ficheiro """
+   with open(fname, "rb") as fcert:
+        cert = x509.load_pem_x509_certificate(fcert.read())
+   return cert
+
+def cert_validtime(cert, now=None):
+    """ valida que 'now' se encontra no período
+    de validade do certificado. """
+    if now is None:
+        now = datetime.datetime.now(tz=datetime.timezone.utc)
+    if now < cert.not_valid_before_utc or now > cert.not_valid_after_utc:
+        raise x509.verification.VerificationError("Certificate is not valid at this time")
+
+def cert_validsubject(cert, attrs=[]):
+    """ verifica atributos do campo 'subject'. 'attrs'
+    é uma lista de pares '(attr,value)' que condiciona
+    os valores de 'attr' a 'value'. """
+    print(cert.subject)
+    for attr in attrs:
+        if cert.subject.get_attributes_for_oid(attr[0])[0].value != attr[1]:
+            raise x509.verification.VerificationError("Certificate subject does not match expected value")    
+
+def cert_validexts(cert, policy=[]):
+    """ valida extensões do certificado. 'policy' é uma lista de pares '(ext,pred)' onde 'ext' é o OID de uma extensão e 'pred'
+    o predicado responsável por verificar o conteúdo dessa extensão. """
+    for check in policy:
+        ext = cert.extensions.get_extension_for_oid(check[0]).value
+        if not check[1](ext):
+            raise x509.verification.VerificationError("Certificate extensions does not match expected value")
+
+def valida_cert(cert, ca_cert, user):
+    try:
+        ca_cert.public_key().verify(
+            cert.signature,
+            cert.tbs_certificate_bytes,
+            padding.PKCS1v15(), 
+            cert.signature_hash_algorithm
+        )
+    except InvalidSignature:
+        raise x509.verification.VerificationError("Certificate signature is invalid")
+    
+    cert_validtime(cert)
+
+    cert_validsubject(cert, [(NameOID.COMMON_NAME, user)])
+
+    
+
+"""
+sign <user> <fich> -- em que assina o conteúdo de <fich> usando a chave privada armazenada em <user>.key. 
+Deve produzir o ficheiro <fich>.sig contendo o par composto pela assinatura e certificado do assinante;
+"""
+def sign(user, filename):
+
+    with open(user + ".key", "rb") as key_file:
+        private_key = serialization.load_pem_private_key(key_file.read(), password=b'1234', backend=default_backend())
+
+    user_cert = cert_load(user + ".crt")
+
+    with open(filename, "rb") as file:
+        data_to_sign = file.read()
+
+
+
+    signature = private_key.sign(data_to_sign,
+                                 padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
+                                    salt_length=padding.PSS.MAX_LENGTH), 
+                                 hashes.SHA256())
+
+     
+    signature_and_cert = mkpair(signature, user_cert.public_bytes(serialization.Encoding.PEM))
+    
+    with open(filename + ".sig", "wb") as sig_file:
+        sig_file.write(signature_and_cert)
+
+
+
+"""
+verify <fich>-- verifica a assinatura contida em <fich>.sig usando a 
+informação do signatário contida no certificado (também incluído em <fich>.sig). 
+Deve apresentar o status de validade da assinatura (Válida/Inválida) e, 
+no caso de ser válida, ainda os dados do signatário.
+"""
+def verify(filename, user):
+
+    with open(filename + ".sig", "rb") as sig_file:
+        signature_and_cert = sig_file.read()
+
+    signature, cert_bytes = unpair(signature_and_cert)
+
+    cert = x509.load_pem_x509_certificate(cert_bytes, default_backend())
+
+    ca_cert = cert_load("EC.crt")
+
+    try:
+        valida_cert(cert, ca_cert, user)
+
+        with open(filename, "rb") as file:
+            data_to_verify = file.read()
+
+        cert.public_key().verify(signature, data_to_verify,
+                                    padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
+                                        salt_length=padding.PSS.MAX_LENGTH), 
+                                    hashes.SHA256())
+        
+        print("Valid signature")
+        print(cert.subject)
+
+    except x509.verification.VerificationError as e:
+        print("Invalid signature: " + str(e))
+        return
+
+def main():
+    parser = argparse.ArgumentParser(description="Sign or verify files using X.509 certificates.")
+    parser.add_argument("command", choices=["sign", "verify"], help="Command to execute: 'sign' or 'verify'")
+    parser.add_argument("user", help="Username")
+    parser.add_argument("filename", help="File name")
+    parser.add_argument("--test", action="store_true", help="Perform testing (simulate errors)")
+
+    args = parser.parse_args()
+
+    match args.command:
+        case "sign" if os.path.exists(args.user + ".crt") and os.path.exists(args.user + ".key"):
+            sign(args.user, args.filename)
+            print("File signed successfully.")
+        case "sign":
+            print("User certificate or private key not found.")
+        case "verify" if os.path.exists(args.filename + ".sig"):
+            verify(args.filename, args.user)
+        case "verify":
+            print("Signature file not found.")
+        case _:
+            parser.print_help()
+            sys.exit(1)
+
+
+
+if __name__ == "__main__":
+    main()
+
