Skip to content

env encryption script #308

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions ENCRYPT-ENV-SCRIPT/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# ENCRYPTING ENVIRONMENT VARIABLE FILE SCRIPT

## A commandline software script to encrypt or decrypt an env file, specifically to share secret encrypted environment variables using a password via git
The encryption uses the cryptography library using symmetric encryption, which means the same key used to encrypt data, is also usable for decryption.


**To run the software:**
```
pip3 install -r requirements.txt
python3 envvars_script.py --help
```

**Output:**
```
usage: ennvars_script.py [-h] (-e | -d) file

File Encryption Script with a Password.

positional arguments:
file File to encrypt/decrypt.

optional arguments:
-h, --help show this help message and exit
-e, --encrypt To encrypt the file, only -e or --encrypt can be specified.
-d, --decrypt To decrypt the file, only -d or --decrypt can be specified.
```

### Encrypt a file
* Encrypting a file for example ".env". Create the file in the project root directory and save it with the env content. Run the following example command:
```
python3 envvars_script.py <.env> --encrypt
OR
python3 envvars_script.py <.env> -e
```
* replace <.env> with the right filename.
* Enter password to encrypt file. Note: password must be atleast 8 characters

* Check file content has been encrypted with the following files created:
```
.env.envs
.env.salt

```

### Decrypt file
* To decrypt file, (use same password used in encryting the file otherwise decrypting won't work). Run the following example command:
```
python3 envvars_script.py <.env.envs> --decrypt
OR
python3 envvars_script.py <.env.envs> -d
```
* replace <.env.envs> with the right filename.
* Enter password to decrypt file. This must be the same password used in encrypting the file.

* Check file has been decrypted and updated with the original content(before encryption) if .env existed otherwise new .env file created with content.

**To run unit test:**
```
python3 test_script.py
```
Empty file added ENCRYPT-ENV-SCRIPT/__init__.py
Empty file.
208 changes: 208 additions & 0 deletions ENCRYPT-ENV-SCRIPT/envars_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import os
import base64
import secrets

from cryptography.fernet import Fernet
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
import cryptography


class EncryptionHelper:
"""
A class to represent Encryption.

Methods
-------
load_salt(self, filename):
A method to read and return a generated salt saved in file.
derive_key(self, salt, password):
A method to derive key.
generate_key(self, password, filename, load_existing_salt=False, save_salt=False):
A method to generate key.
encrypt(self, filename, key):
A method to encrypt file.
decrypt(self, filename, key):
A method to decrypt file.
"""

@staticmethod
def generate_salt(size: int):
"""
A method to generate a salt.

Parameters
----------
size : int
The size of the bytes strings to be generated.

Returns
-------
bytes
The method returns bytes strings containing the secret token.
"""

return secrets.token_bytes(size)

@staticmethod
def load_salt(filename: str):
"""
A method to read and return a save salt file.

Parameters
----------
filename : str
The file name to read from.

Returns
-------
bytes
The method returns bytes containing the salt.
"""

# load salt from salt file
return open(filename.replace(".envs", ".salt"), "rb").read()

@staticmethod
def derive_key(salt: bytes, password: str):
"""
A method to derive a key using password and salt token.

Parameters
----------
salt : bytes
The bytes strings containing the salt token.
password : str
The strings of characters containing the password.

Returns
-------
bytes
The method returns bytes string containing the derived key.
"""

# derive key using salt and password
key = Scrypt(salt=salt, length=32, n=2**14, r=8, p=1)
return key.derive(password.encode())

@staticmethod
def generate_key(password: str, filename: str, load_existing_salt=False, save_salt=False):
"""
A method to generate key.

Parameters
----------
password : str
The strings of characters containing the password.
filename : str
The strings of characters containing file name.
load_existing_salt : bool, optional
A boolean value determining existing salt exists.
save_salt : bool, optional
A boolean value determining save salt exists.

Returns
-------
bytes
The method returns bytes string containing the generated key.
"""

# check existing salt file
if load_existing_salt:
try:
with open(filename.replace(".envs", ".salt"), "rb") as salt_file:
salt_file.readline()
except FileNotFoundError:
return base64.urlsafe_b64encode(os.urandom(32))
# load existing salt
salt = EncryptionHelper.load_salt(filename)
if save_salt:
# generate new salt/token and save it to file
salt = EncryptionHelper.generate_salt(16)
with open(f"{filename}.salt", "wb") as salt_file:
salt_file.write(salt)

# generate the key from the salt and the password
derived_key = EncryptionHelper.derive_key(salt, password)
# encode it using Base 64 and return it
return base64.urlsafe_b64encode(derived_key)

@staticmethod
def encrypt(filename: str, key: bytes):
"""
A method to encrypt file.

Parameters
----------
filename : str
The strings of characters containing file name.
key : bytes
A bytes of stings containing the encryption key.

Returns
-------
None
The method returns a none value.
"""

fernet = Fernet(key)

try:
with open(filename, "rb") as file:
file_data = file.read()
except FileNotFoundError:
print("File not found")
return

# encrypting file_data
encrypted_data = fernet.encrypt(file_data)

# writing to a new file with the encrypted data
with open(f"{filename}.envs", "wb") as file:
file.write(encrypted_data)

print("File encrypted successfully...")
return "File encrypted successfully..."

@staticmethod
def decrypt(filename: str, key: bytes):
"""
A method to decrypt file.

Parameters
----------
filename : str
The strings of characters containing file name.
key : bytes
A bytes of stings containing the encryption key.

Returns
-------
None
The method returns a none value.
"""

fernet = Fernet(key)
try:
with open(filename, "rb") as file:
encrypted_data = file.read()
except FileNotFoundError:
print("File not found.")
return
# decrypt data using the Fernet object
try:
decrypted_data = fernet.decrypt(encrypted_data)
except cryptography.fernet.InvalidToken:
print("Invalid token, likely the password is incorrect.")
return
# write the original file with decrypted content
with open(filename.replace(".envs", ""), "wb") as file:
file.write(decrypted_data)
# delete salt file after decrypting file
f = open(filename.replace(".envs", ".salt"), 'w')
f.close()
os.remove(f.name)
# delete decrypted file
os.remove(filename)
print("File decrypted successfully...")

return "File decrypted successfully..."
54 changes: 54 additions & 0 deletions ENCRYPT-ENV-SCRIPT/envars_script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import sys
import getpass

from envars_helper import EncryptionHelper

if __name__ == "__main__":
import argparse

encryption_helper = EncryptionHelper()

parser = argparse.ArgumentParser(description="File Encryption Script with a Password.",
allow_abbrev=False)
parser.add_argument("file", help="File to encrypt/decrypt.")
group_args = parser.add_mutually_exclusive_group(required=True)
group_args.add_argument("-e", "--encrypt", action="store_true",
help="To encrypt the file, only -e or --encrypt can be specified.")
group_args.add_argument("-d", "--decrypt", action="store_true",
help="To decrypt the file, only -d or --decrypt can be specified.")

args = parser.parse_args()
filename = args.file
encrypt_arg = args.encrypt
decrypt_arg = args.decrypt

try:
with open(filename, "r") as f:
file_data = f.readline()
except FileNotFoundError:
print("File not found.")
sys.exit(1)

password = None
if encrypt_arg:
ext = filename.split(".").pop()
if ext == "envs":
print("File already encrypted.")
sys.exit(1)
password = getpass.getpass("Enter the password for encryption: ")
while len(password) < 8:
print("Password must be 8 characters or above. \n")
password = getpass.getpass("Enter the password for encryption: ")
elif decrypt_arg:
ext = filename.split(".").pop()
if ext != "envs":
print("File was not encrypted. Encrypted file has a .envs extension")
sys.exit(1)
password = getpass.getpass("Enter the password used for encryption: ")

if encrypt_arg:
key = encryption_helper.generate_key(password, filename, save_salt=True)
encryption_helper.encrypt(filename, key)
else:
key = encryption_helper.generate_key(password, filename, load_existing_salt=True)
encryption_helper.decrypt(filename, key)
3 changes: 3 additions & 0 deletions ENCRYPT-ENV-SCRIPT/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
cffi==1.15.1
cryptography==37.0.4
pycparser==2.21
Loading