Search code examples
pythonencryptionaestxt

Padding issues in password manager


There is a padding issue with my code, at first time when password.txt (the file in which I save the decrypted passwords) but the issue is when I run the code for the second time I get this error:

`66lHmLVe~
The password is strong
Encrypted password: b'\xe1\x19\x04\x88</\xf4,\xd9\x10\xe9\xef\x8f4\x00\t'
Decrypted password: `66lHmLVe~
Traceback (most recent call last):
  File "c:\Users\basse\Project_3.py", line 78, in <module>
    decrypted_password = decrypt_data(bytes.fromhex(iv), bytes.fromhex(encrypted_password), key)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Users\basse\Project_3.py", line 55, in decrypt_data
    decrypted_data = unpad(cipher.decrypt(encrypted_data), block_size)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\basse\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\Crypto\Util\Padding.py", line 92, in unpad
    raise ValueError("Padding is incorrect.")
ValueError: Padding is incorrect.

here is the full code:

import re
import random
import string

def password_generator(size=10):
    if size <= 8:
        print("Size must be at least 4")
        return None
    password = []
    while len(password) < size:
        password.append(random.choice(string.ascii_lowercase))  # ensure at least one lowercase letter
        if len(password) < size:
            password.append(random.choice(string.ascii_uppercase))  # ensure at least one uppercase letter
        if len(password) < size:
            password.append(random.choice(string.digits))  # ensure at least one digit
        if len(password) < size:
            password.append(random.choice(string.punctuation))  # ensure at least one special character
    random.shuffle(password)  # shuffle to remove the predictability
    return ''.join(password)

def password_checker(password):
    if len(password) >= 8:
        if bool(re.match(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z\d])', password)):
            print("The password is strong")
        else:
            print("The password is weak")
    else:
        print("You have entered a short or invalid password.")

# Generate a password
generated_password = password_generator()
print(generated_password)

# Check the generated password
password_checker(generated_password)

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
import os

# AES requires that plaintexts be a multiple of 16, so we need to pad the data
block_size = 16

# Generate a random 128-bit IV for AES
iv = get_random_bytes(16)

def encrypt_data(iv,data, key):
    cipher = AES.new(key, AES.MODE_CBC, iv=iv)
    encrypted_data = cipher.encrypt(pad(data.encode('utf-8'), block_size))
    return cipher.iv, encrypted_data

def decrypt_data(iv, encrypted_data, key):
    cipher = AES.new(key, AES.MODE_CBC, iv=iv)
    decrypted_data = unpad(cipher.decrypt(encrypted_data), block_size)
    return decrypted_data.decode('utf-8')

# Generate a random 256-bit key for AES
key = get_random_bytes(32)

# Encrypt the generated password
iv, encrypted_password = encrypt_data(iv,generated_password, key)
print(f"Encrypted password: {encrypted_password}")

# Decrypt the encrypted password
decrypted_password = decrypt_data(iv, encrypted_password, key)
print(f"Decrypted password: {decrypted_password}")

# Save the encrypted password to a file
with open('C:/Users/foo/Project/Password.txt', 'a', encoding='utf-8') as f:
    f.write(f"{iv.hex()}:{encrypted_password.hex()}\n")
    
# Read the encrypted password from the file
with open('C:/Users/foo/Project/Password.txt', 'r', encoding='utf-8') as f:
    for line in f.readlines():
        line = line.strip()  # Remove the trailing newline character
        iv, encrypted_password = line.split(':')
        decrypted_password = decrypt_data(bytes.fromhex(iv), bytes.fromhex(encrypted_password), key)

I want to for the Passwords to be saved in Password.txt in encrypted form so when I call it, it decrypts the password and show in its original form for the Password Manager. so, if there any way I can save the passwords rather than text file it is okay, but I would love to use text file.


Solution

  • It may be better to not use the the padding package from cryptography instead use the code from this answer instead as I have ripped most of this from their answer.

    The text file wont affect anything the .txt file extension just says what application should be opening it and can still be used to write bytes into but I generally don't use a file extension for encrypted data as it's quicker to write but as shown in the documentation .bin is usually used. I more often use RSA encryption so I recommend you read through the pycrypto docs.

    (make sure you have hashlib installed since I added it)

    import re
    import random
    import string
    
    
    def password_generator(size=10):
        if size <= 8:
            print("Size must be at least 4")
            return None
        password = []
        while len(password) < size:
            password.append(random.choice(string.ascii_lowercase))  # ensure at least one lowercase letter
            if len(password) < size:
                password.append(random.choice(string.ascii_uppercase))  # ensure at least one uppercase letter
            if len(password) < size:
                password.append(random.choice(string.digits))  # ensure at least one digit
            if len(password) < size:
                password.append(random.choice(string.punctuation))  # ensure at least one special character
        random.shuffle(password)  # shuffle to remove the predictability
        return ''.join(password)
    
    
    def password_checker(password):
        if len(password) >= 8:
            if bool(re.match(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z\d])', password)):
                print("The password is strong")
            else:
                print("The password is weak")
        else:
            print("You have entered a short or invalid password.")
    
    
    # Generate a password
    generated_password = password_generator()
    print(generated_password)
    
    # Check the generated password
    password_checker(generated_password)
    
    from Crypto.Cipher import AES
    from Crypto.Random import get_random_bytes
    import base64
    import hashlib
    
    class AESCipher(object):
    
        def __init__(self, key):
            self.bs = AES.block_size
            self.key = hashlib.sha256(key).digest()
    
        def encrypt_data(self, iv, raw):
            raw = self._pad(raw)
            cipher = AES.new(self.key, AES.MODE_CBC, iv)
            return cipher.iv, iv + cipher.encrypt(raw.encode())
    
        def decrypt_data(self, iv, enc):
            cipher = AES.new(self.key, AES.MODE_CBC, iv)
            return AESCipher._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')
    
        def _pad(self, s):
            return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)
    
        @staticmethod
        def _unpad(s):
            return s[:-ord(s[len(s)-1:])]
    
    # Generate a random 128-bit IV for AES
    iv = get_random_bytes(16)
    
    # Generate a random 256-bit key for AES
    key = get_random_bytes(32)  # save this maybe?
    
    Cipher = AESCipher(key)
    
    # Encrypt the generated password
    iv, encrypted_password = Cipher.encrypt_data(iv, generated_password)
    print(f"Encrypted password: {encrypted_password}")
    
    # Decrypt the encrypted password
    decrypted_password = Cipher.decrypt_data(iv, encrypted_password)
    print(f"Decrypted password: {decrypted_password}")
    
    # Save the encrypted password to a file
    with open(
            'Password.txt',
            'a', encoding='utf-8') as f:
        f.write(f"{iv.hex()}:{encrypted_password.hex()}\n")
    
    # Read the encrypted password from the file
    with open(
            'Password.txt',
            'r', encoding='utf-8') as f:
        for line in f.readlines():
            line = line.strip()  # Remove the trailing newline character
            iv, encrypted_password = line.split(':')
            decrypted_password = Cipher.decrypt_data(bytes.fromhex(iv), bytes.fromhex(encrypted_password))
    
    

    Side notes for improvement:

    1. You didn't need to import os and I prefer to have all my imports at the top just for organisation
    2. Try to remove names and full paths of files so make 'C:/Users/basse/OneDrive - King Salman International University/Semester 5/Introduction to Cyber Security/Password.txt' this C:/Users/foo/Project/Password.txt' just in case of personal information gets leaked!

    Hope this helps