Need to encrypt some text with a password or private key in Python? You certainly came to the right place. AES-256 is a solid symmetric cipher that is commonly used to encrypt data for oneself. In other words, the same person who is encrypting the data is typically decrypting it as well (think ). password manager Dependencies For this tutorial, we will be using Python 3, so make sure you install , which will give us access to an implementation of AES-256: pycryptodome pip3 install pycryptodomex Padding - Handled by GCM AES-256 typically requires that the data to be encrypted is supplied in 16-byte blocks, and you may have seen that on other sites or tutorials. AES-256 in GCM mode, however, doesn't require any special padding to be done by us manually. Encrypting Now we create a simple function. This function uses the password to encrypt the plain text. Therefore, anyone with access to the encrypted text and the password will be able to decrypt it. encrypt(plain_text, password) salt = get_random_bytes(AES.block_size) private_key = hashlib.scrypt( password.encode(), salt=salt, n= ** , r= , p= , dklen= ) cipher_config = AES.new(private_key, AES.MODE_GCM) cipher_text, tag = cipher_config.encrypt_and_digest(bytes(plain_text, )) { : b64encode(cipher_text).decode( ), : b64encode(salt).decode( ), : b64encode(cipher_config.nonce).decode( ), : b64encode(tag).decode( ) } : def encrypt (plain_text, password) # generate a random salt # use the Scrypt KDF to get a private key from the password 2 14 8 1 32 # create cipher config # return a dictionary with the encrypted text 'utf-8' return 'cipher_text' 'utf-8' 'salt' 'utf-8' 'nonce' 'utf-8' 'tag' 'utf-8' Notes on encrypt() function : A random nonce (arbitrary value) must be a random and unique value for each time our encryption function is used with the same key. Think of it as a random salt for a cipher. The library supplies us with a secure nonce. Nonce : Scrypt is used to generate a secure private key from the password. This will make it harder for an attacker to brute-force our encryption. Scrypt : A new random salt is used for each run of our encryption. This makes it impossible for an attacker to use precomputed hashes in an attempt to crack the cipher. (see ) Salt rainbow table Scrypt : parameters N is the cost factor. It must be a power of two, and the higher it is the more secure the key, but the more resources it requires to run. R is the block size. P is the parallelization factor, useful for running on multiple cores. : We encode all of our bytes-type data into base64 a convenient string representation Base64 : The tag is used to authenticate the data when using AES in GCM mode. This ensures no one can change our data without us knowing about it when we decrypt. Tag Decrypting salt = b64decode(enc_dict[ ]) cipher_text = b64decode(enc_dict[ ]) nonce = b64decode(enc_dict[ ]) tag = b64decode(enc_dict[ ]) private_key = hashlib.scrypt( password.encode(), salt=salt, n= ** , r= , p= , dklen= ) cipher = AES.new(private_key, AES.MODE_GCM, nonce=nonce) decrypted = cipher.decrypt_and_verify(cipher_text, tag) decrypted : def decrypt (enc_dict, password) # decode the dictionary entries from base64 'salt' 'cipher_text' 'nonce' 'tag' # generate the private key from the password and salt 2 14 8 1 32 # create the cipher config # decrypt the cipher text return Notes on decrypt() function The decrypt() function needs the same salt, nonce, and tag that we used for encryption. We used a dictionary for convenience in parsing, but if we instead wanted one string of ciphertext we could have used a scheme like The configuration parameters on the Scrypt and AES functions need to be the same as the encrypt function. salt.nonce.tag.cipher_text Give Me The Full Code! You probably want to see it all work in an example script. Look no further! base64 b64encode, b64decode hashlib Cryptodome.Cipher AES os Cryptodome.Random get_random_bytes salt = get_random_bytes(AES.block_size) private_key = hashlib.scrypt( password.encode(), salt=salt, n= ** , r= , p= , dklen= ) cipher_config = AES.new(private_key, AES.MODE_GCM) cipher_text, tag = cipher_config.encrypt_and_digest(bytes(plain_text, )) { : b64encode(cipher_text).decode( ), : b64encode(salt).decode( ), : b64encode(cipher_config.nonce).decode( ), : b64encode(tag).decode( ) } salt = b64decode(enc_dict[ ]) cipher_text = b64decode(enc_dict[ ]) nonce = b64decode(enc_dict[ ]) tag = b64decode(enc_dict[ ]) private_key = hashlib.scrypt( password.encode(), salt=salt, n= ** , r= , p= , dklen= ) cipher = AES.new(private_key, AES.MODE_GCM, nonce=nonce) decrypted = cipher.decrypt_and_verify(cipher_text, tag) decrypted password = input( ) encrypted = encrypt( , password) print(encrypted) decrypted = decrypt(encrypted, password) print(bytes.decode(decrypted)) main() # AES 256 encryption/decryption using pycryptodome library from import import from import import from import : def encrypt (plain_text, password) # generate a random salt # use the Scrypt KDF to get a private key from the password 2 14 8 1 32 # create cipher config # return a dictionary with the encrypted text 'utf-8' return 'cipher_text' 'utf-8' 'salt' 'utf-8' 'nonce' 'utf-8' 'tag' 'utf-8' : def decrypt (enc_dict, password) # decode the dictionary entries from base64 'salt' 'cipher_text' 'nonce' 'tag' # generate the private key from the password and salt 2 14 8 1 32 # create the cipher config # decrypt the cipher text return : def main () "Password: " # First let us encrypt secret message "The secretest message here" # Let us decrypt using our original password Thanks For Reading Lane on Twitter: @wagslane Lane on Dev.to: wagslane Download Qvault: https://qvault.io By Lane Wagner