-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0485728
commit 21b9e55
Showing
6 changed files
with
308 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
__pycache__/ | ||
testfile.* | ||
launch.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,28 @@ | ||
# PythonEncrypt | ||
AES256 file encryption/decryption solution with key derivation | ||
|
||
Python3 solution to perform AES256 file encryption/decryption with key derivation. | ||
|
||
## Installation | ||
|
||
1. Install [Cryptography library](https://cryptography.io/en/latest/) for python. | ||
- With pip: ```pip install cryptography``` | ||
|
||
2. Clone this repository or download the latest release of PythonEncrypt to your machine. | ||
3. Run ```PythonEncrypt.py``` | ||
|
||
## Usage | ||
|
||
PythonEnrypt supports AES256 file encryption/decryption with the following modes: ```CBC, CTR, OFB``` . You can select the mode with the ```--mode``` flag. | ||
Print program usage with the flag ```-h``` | ||
|
||
To encrypt a file with the default CBC mode, run command: | ||
```PythonEncrypt.py --file <File> --encrypt``` | ||
To decrypt a file, run command: | ||
```PythonEncrypt.py --file <File> --decrypt``` | ||
|
||
## Security | ||
|
||
<span style="color:red">This program is **ONLY** for low-security uses.</span> This tool is not NIST SP 800-57 or FIPS 140 compliant. There are a few reasons for this, most importantly the cryptographic keys are not held in tamper proof memory and there aren't adequate cryptographic boundaries. This tool is made to protect against your snoopy co-worker or that cousin you have. | ||
|
||
There won't be updates to address this issue. This is due to Python being a poor choice for creating high security cryptograhic modules. To find out why, you can read up [here](https://stackoverflow.com/questions/728164/securely-erasing-password-in-memory-python). | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes | ||
from cryptography.hazmat.backends import default_backend | ||
from cryptography.hazmat.primitives import padding | ||
import modules.keyDerivation, modules.auxiliary | ||
import os, hashlib | ||
|
||
def AesCBCencrypt(file): | ||
""" | ||
Performs AES CBC Encryption with 256 bit key to a file and outputs the ciphertext into a new file. | ||
Input: Filename (Absolute path recommended) | ||
Output: 1 on success. | ||
""" | ||
Blocksize_bytes, Blocksize_bits = 16, 128 | ||
ofile = file + ".enc" | ||
iv = os.urandom(Blocksize_bytes) | ||
hash = hashlib.sha256(b'AesCBC').digest() | ||
|
||
# Derive the key from password | ||
KeyandSalt = modules.keyDerivation.DeriveKey() | ||
|
||
# Read the plaintext | ||
with open(file, "rb") as reader: | ||
plaintext = reader.read() | ||
|
||
# Do the padding and encrypt data | ||
padder = padding.PKCS7(Blocksize_bits).padder() | ||
plaintext = padder.update(plaintext) + padder.finalize() | ||
aesCipher = Cipher(algorithms.AES256(KeyandSalt[0]), modes.CBC(iv), default_backend()) | ||
aesEncryptor = aesCipher.encryptor() | ||
ciphertext = aesEncryptor.update(plaintext) + aesEncryptor.finalize() | ||
|
||
# Create encrypted file | ||
with open(ofile, "wb+") as writer: | ||
writer.write(KeyandSalt[1] + iv + hash + ciphertext) # Variables salt + IV + hash are required for decryption | ||
|
||
print('Success! Encrypted file at: %s' % ofile) | ||
return 1 | ||
|
||
def AesCTRencrypt(file): | ||
""" | ||
Performs AES CTR Encryption with 256 bit key to a file and outputs the ciphertext into a new file. | ||
Input: Filename (Absolute path recommended) | ||
Output: 1 on success. | ||
""" | ||
Blocksize_bytes, Blocksize_bits = 16, 128 | ||
ofile = file + ".enc" | ||
nonce = os.urandom(Blocksize_bytes) | ||
hash = hashlib.sha256(b'AesCTR').digest() | ||
|
||
# Derive the key from password | ||
KeyandSalt = modules.keyDerivation.DeriveKey() | ||
|
||
# Read the plaintext | ||
with open(file, "rb") as reader: | ||
plaintext = reader.read() | ||
|
||
# Do the padding and encrypt data | ||
padder = padding.PKCS7(Blocksize_bits).padder() | ||
plaintext = padder.update(plaintext) + padder.finalize() | ||
aesCipher = Cipher(algorithms.AES256(KeyandSalt[0]), modes.CTR(nonce), default_backend()) | ||
aesEncryptor = aesCipher.encryptor() | ||
ciphertext = aesEncryptor.update(plaintext) + aesEncryptor.finalize() | ||
|
||
# Create encrypted file | ||
with open(ofile, "wb+") as writer: | ||
writer.write(KeyandSalt[1] + nonce + hash + ciphertext) # Variables salt + nonce + hash are required for decryption | ||
|
||
print('Success! Encrypted file at: %s' % ofile) | ||
return 1 | ||
|
||
def AesOFBencrypt(file): | ||
""" | ||
Performs AES OFB Encryption with 256 bit key to a file and outputs the ciphertext into a new file. | ||
Input: Filename (Absolute path recommended) | ||
Output: 1 on success. | ||
""" | ||
Blocksize_bytes, Blocksize_bits = 16, 128 | ||
ofile = file + ".enc" | ||
iv = os.urandom(Blocksize_bytes) | ||
hash = hashlib.sha256(b'AesOFB').digest() | ||
|
||
# Derive the key from password | ||
KeyandSalt = modules.keyDerivation.DeriveKey() | ||
|
||
# Read the plaintext | ||
with open(file, "rb") as reader: | ||
plaintext = reader.read() | ||
|
||
# Encrypt the data, no padding needed | ||
aesCipher = Cipher(algorithms.AES256(KeyandSalt[0]), modes.OFB(iv), default_backend()) | ||
aesEncryptor = aesCipher.encryptor() | ||
ciphertext = aesEncryptor.update(plaintext) + aesEncryptor.finalize() | ||
|
||
# Create encrypted file | ||
with open(ofile, "wb+") as writer: | ||
writer.write(KeyandSalt[1] + iv + hash + ciphertext) # Variables salt + iv + hash are required for decryption | ||
|
||
print('Success! Encrypted file at: %s' % ofile) | ||
return 1 | ||
|
||
def AesDecrypt(file): | ||
""" | ||
Decrypts AES256 encrypted file. | ||
Input: Filename (Absolute path recommended) | ||
Output: 1 on Success | ||
""" | ||
Blocksize_bits = 128 | ||
ofile = file[:-4] | ||
HeaderInfo = modules.auxiliary.ExtractHeader(file) | ||
KeyandSalt = modules.keyDerivation.DeriveKey(HeaderInfo[0]) | ||
|
||
# Get the ciphertext | ||
with open(file, 'rb') as f: | ||
f.seek(64) | ||
ciphertext = f.read() | ||
|
||
# CBC Mode | ||
if HeaderInfo[2] == 1: | ||
print("CBC Mode of operation detected...") | ||
aesCipher = Cipher(algorithms.AES256(KeyandSalt[0]), modes.CBC(HeaderInfo[1]), default_backend()) | ||
aesDecryptor = aesCipher.decryptor() | ||
paddedData = aesDecryptor.update(ciphertext) + aesDecryptor.finalize() | ||
|
||
unpadder = padding.PKCS7(Blocksize_bits).unpadder() | ||
plaintext = unpadder.update(paddedData) + unpadder.finalize() | ||
|
||
with open(ofile, "wb+") as writer: | ||
writer.write(plaintext) | ||
|
||
print("File decrypted succesfully!") | ||
|
||
# CTR Mode | ||
if HeaderInfo[2] == 2: | ||
print("CTR Mode of operation detected...") | ||
aesCipher = Cipher(algorithms.AES256(KeyandSalt[0]), modes.CTR(HeaderInfo[1]), default_backend()) | ||
aesDecryptor = aesCipher.decryptor() | ||
paddedData = aesDecryptor.update(ciphertext) + aesDecryptor.finalize() | ||
|
||
unpadder = padding.PKCS7(Blocksize_bits).unpadder() | ||
plaintext = unpadder.update(paddedData) + unpadder.finalize() | ||
|
||
with open(ofile, "wb+") as writer: | ||
writer.write(plaintext) | ||
|
||
print("File decrypted succesfully!") | ||
|
||
# OFB Mode | ||
if HeaderInfo[2] == 3: | ||
print("OFB Mode of operation detected...") | ||
aesCipher = Cipher(algorithms.AES256(KeyandSalt[0]), modes.OFB(HeaderInfo[1]), default_backend()) | ||
aesDecryptor = aesCipher.decryptor() | ||
plaintext = aesDecryptor.update(ciphertext) + aesDecryptor.finalize() | ||
|
||
with open(ofile, "wb+") as writer: | ||
writer.write(plaintext) | ||
|
||
print("File decrypted succesfully!") | ||
|
||
return 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import os | ||
from sys import exit | ||
|
||
def ExtractHeader(file): | ||
""" | ||
Extract the header information from an encrypted file. Parses the salt, IV and mode of operation. CBC = 1, CTR = 2, OFB = 3 | ||
Input: Filename (Absolute path recommended) | ||
Output: Array of salt, IV, mode of operation code | ||
""" | ||
CBChash = "03872ff57deb958f37678ed016fb340709bbeb4deba111ca518ddc8f064c0ddd" | ||
CTRhash = "2d676e12555bcd4e155538681b165af6b494cc9cbff16adb27ad4533592a51b4" | ||
OFBhash = "2e4146a86c183223f533d4ad39eed6d6280795e354279a65555944648d899112" | ||
|
||
with open(file, 'rb') as f: | ||
header = f.read(64) | ||
|
||
# Parse the header information | ||
salt = header[:16] | ||
iv = header[16:32] | ||
hash = header[32:64] | ||
|
||
if hash.hex() != CBChash and hash.hex() != CTRhash and hash.hex() != OFBhash: | ||
print("Error: Incorrect mode of operation in header") | ||
exit(2) | ||
elif hash.hex() == CBChash: | ||
return [salt, iv, 1] | ||
elif hash.hex() == CTRhash: | ||
return [salt, iv, 2] | ||
else: | ||
return [salt, iv, 3] | ||
|
||
def SecureDelete(path, passes=1): | ||
""" | ||
Secure delete original plaintext file function. | ||
Input: File path | ||
Output: 1 on success | ||
""" | ||
with open(path, "ba+") as delfile: | ||
length = delfile.tell() | ||
with open(path, "br+") as delfile: | ||
for i in range(passes): | ||
delfile.seek(0) | ||
delfile.write(os.urandom(length)) | ||
os.remove(path) | ||
return 1 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from os import urandom | ||
from getpass import getpass | ||
from cryptography.hazmat.primitives import hashes | ||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC | ||
|
||
def DeriveKey(salt=None): | ||
""" | ||
A Key derivation function using PBKDF2. Produces a cryptographically secure key from user inputted password. If salt is given, it will be used. | ||
Input: Salt (Optional) | ||
Output: An array of Encryption key, Salt | ||
""" | ||
if salt is None: | ||
salt = urandom(16) | ||
|
||
kdf = PBKDF2HMAC( | ||
algorithm = hashes.SHA256(), | ||
length = 32, | ||
salt = salt, | ||
iterations = 300000, | ||
) | ||
|
||
key = kdf.derive(getpass("Please enter your password: ").encode()) | ||
return [key, salt] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import os, sys, argparse | ||
import modules.AES256, modules.auxiliary | ||
|
||
def getArgs(argv=None): | ||
""" | ||
Argument parser function. Returns the namespace of the parsed arguments. | ||
""" | ||
parser = argparse.ArgumentParser( | ||
prog = "pythonEncrypt.py", | ||
description='Python utility to encrypt or decrypt files.' | ||
) | ||
parser.add_argument("-f", "--file", help="File to perform action on", required=True) | ||
parser.add_argument("-m", "--mode", help="Select mode for encryption, default CBC (CBC, CTR, OFB)", nargs="?", type=str, default="CBC") | ||
group = parser.add_mutually_exclusive_group(required=True) | ||
group.add_argument("-e", "--encrypt", help="Encrypt the file", action="store_true") | ||
group.add_argument("-d", "--decrypt", help="Decrypt the file", action="store_true") | ||
|
||
if len(sys.argv) < 2: | ||
parser.print_usage() | ||
sys.exit(2) | ||
|
||
args = parser.parse_args() | ||
if args.mode != "CBC" and args.mode != "CTR" and args.mode != "OFB": | ||
print("Error: Invalid mode of operation!") | ||
sys.exit(2) | ||
|
||
return parser.parse_args(argv) | ||
|
||
if __name__ == "__main__": | ||
args = getArgs() | ||
|
||
if os.path.isfile(args.file): | ||
# Valid file given | ||
if args.encrypt and args.mode == "CBC": | ||
print("Encrypting file: {}".format(args.file)) | ||
modules.AES256.AesCBCencrypt(os.path.abspath(args.file)) | ||
modules.auxiliary.SecureDelete(os.path.abspath(args.file)) | ||
elif args.encrypt and args.mode == "CTR": | ||
print("Encrypting file: {}".format(args.file)) | ||
modules.AES256.AesCTRencrypt(os.path.abspath(args.file)) | ||
modules.auxiliary.SecureDelete(os.path.abspath(args.file)) | ||
elif args.encrypt and args.mode == "OFB": | ||
modules.AES256.AesOFBencrypt(os.path.abspath(args.file)) | ||
modules.auxiliary.SecureDelete(os.path.abspath(args.file)) | ||
elif args.decrypt: | ||
print("Decrypting file: {}".format(args.file)) | ||
modules.AES256.AesDecrypt(os.path.abspath(args.file)) | ||
else: | ||
print("Error: File {} could not be found!".format(args.file)) | ||
sys.exit(2) |