My objective with this code was to create a class and call it from other .py files. The code itself is working, but I would like to know what could be improved or be better implemented.
I wrote another program just to call this class and its methods and it worked. I'm using functions that will only work on Windows because this is an exercise, not production code. My code has only one class (StudingClassFirstTime
) and 13 methods. It is using the external library PyNaCl.
import getpass # Allow user input a password without echo it on terminal
import os # To be able to use os.system('cls')
from nacl import pwhash, secret, utils, exceptions # Encrypt functions to be used
class StudingClassFirstTime:
# Constructor mode: Here I will define the KDF (argon2id) parameters that will be used by default
def __init__(self, kdf=pwhash.argon2id.kdf,
ops=pwhash.argon2id.OPSLIMIT_SENSITIVE,
mem=pwhash.argon2id.MEMLIMIT_SENSITIVE,
size=secret.SecretBox.KEY_SIZE):
self.kdf = pwhash.argon2id.kdf
self.ops = pwhash.argon2id.OPSLIMIT_SENSITIVE
self.mem = pwhash.argon2id.MEMLIMIT_SENSITIVE
self.size = secret.SecretBox.KEY_SIZE
# Load an encryption key stored on file
# This file should have only one encryption key inside it
# If you have one file.key, you can encrypt as many files as you want, but, all of them will use the very same key
# If you encrypt the same file twice with the same key, the output will not be equal thanks to NONCE
def load_key(self, key_file):
with open(key_file, 'rb') as my_key:
key_of_file = my_key.read()
return key_of_file
# Generate an encryption key and return it's value(key)
def generate_key(self):
self.salt = utils.random(pwhash.argon2id.SALTBYTES) # 16 bytes == 128 bits
password = self.password() # Ask a user to input a password
key = self.kdf(self.size, password, self.salt, opslimit=self.ops, memlimit=self.mem)
return key # Key have 32 bytes == 256 bits
# Generate a raw 256 bits (32 bytes) as a key
# Do not need any user password, or KDF function
def generate_key_file(self):
password = utils.random(32) # 32 bytes == 256 bits
file_key = secret.SecretBox(password).encode()
return file_key
# Generate a encryption key and write it to a file
def write_key_file(self, key_file):
key = self.generate_key_file()
with open(key_file, 'ab') as my_key:
if os.path.getsize(key_file) > 0: # Check if the file have more than 0 bytes
my_key.write(b'\n' + key_file) # If positive, first add a new line, then, write the key
else: # If negative, just write the key, without add new line
my_key.write(key_file)
# Encrypt a message, generating an encryption key based on password.
# The key will not be write to file.
def encrypt(self, message):
encrypt_key = self.generate_key()
encrypt_box = secret.SecretBox(encrypt_key)
encrypted = encrypt_box.encrypt(message)
assert len(encrypted) == len(message) + encrypt_box.NONCE_SIZE + encrypt_box.MACBYTES # NONCE_SIZE == 24 bytes // MACBYTES == 16 bytes
return self.salt + encrypted
# Encrypt a message with a key stored on file
# To use it, first you should use generate_key_file() or already have a key contains your key.
def encrypt_with_key(self, key_file, message):
encrypt_key = self.load_key(key_file) # Load the encryption key stored on file
encrypt_box = secret.SecretBox(encrypt_key)
encrypted = encrypt_box.encrypt(message)
assert len(encrypted) == len(message) + encrypt_box.NONCE_SIZE + encrypt_box.MACBYTES
return encrypted
# Decrypt a message, using a user supplied password
def decrypt(self, message):
try:
password = getpass.getpass(prompt="\nType password to DECRYPT: ", stream=None).encode() # Ask a user for the password to decrypt the message
salt = message[:16] # The first 16 bytes of message [0 included : 16 not included] is the salt value
decrypt_key = self.kdf(secret.SecretBox.KEY_SIZE, password, salt, opslimit=self.ops, memlimit=self.mem) # Derivate the enc. key from password
decrypt_box = secret.SecretBox(decrypt_key)
decrypted = decrypt_box.decrypt(message[16:])
assert len(decrypted) == len(message[16:]) - decrypt_box.NONCE_SIZE - decrypt_box.MACBYTES
return decrypted
except exceptions.CryptoError:
return 'error'
# Decrypt a message with a key stored on file
# To use it, first you should use encrypt_with_key() method.
def decrypt_with_key(self, key_file, message):
try:
decrypt_key = self.load_key(key_file)
decrypt_box = secret.SecretBox(decrypt_key)
decrypted = decrypt_box.decrypt(message)
assert len(decrypted) == len(message) - decrypt_box.NONCE_SIZE - decrypt_box.MACBYTES
return decrypted
except exceptions.CryptoError:
return 'error'
# Ask a user to type a password and verify it and return the password
def password(self):
while True:
password1 = getpass.getpass(prompt="\nType your password: ", stream=None).encode() # getpass do not echo the password on terminal
password2 = getpass.getpass(prompt="Type your password again: ", stream=None).encode()
if password1 != password2:
input("\nPassword does not match! Press any key to try again.")
os.system('cls')
continue
else:
return password1
# Similar as write_key_file(), but, will not write the key on file.
# This will be done by the function that call it.
# The objective here is allow the caller (function) to store multiple keys in one single file, instead of, one file.key per encrypted file
def key_db(self):
key = self.generate_key_file()
return key
# Similar to encrypt_with_key(), but the key will be supplied.
def encrypt_db(self, encrypt_key_db, message):
encrypt_box = secret.SecretBox(encrypt_key_db)
encrypted = encrypt_box.encrypt(message)
assert len(encrypted) == len(message) + encrypt_box.NONCE_SIZE + encrypt_box.MACBYTES
return salt + encrypted
# The inverse of encrypt_db()
def decrypt_db(self, key_db, message):
try:
decrypt_box = secret.SecretBox(bytes.fromhex(key_db))
decrypted = decrypt_box.decrypt(message)
assert len(decrypted) == len(message) - decrypt_box.NONCE_SIZE - decrypt_box.MACBYTES
return decrypted
except exceptions.CryptoError:
return 'error'
'''
The first 56 bytes of encrypted text are:
[0:16] = SALT (16) # Will be present only if the user supply a password
[16:40] = NONCE (24) # Always present
[40:56] = MACBYTES (16) # Always present
[56:] = Encrypted Text
'''