├── .gitignore ├── .images └── RedCipher.gif ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs └── usage.md ├── red_cipher ├── __init__.py ├── __main__.py ├── actions.py ├── aes_encryptor.py ├── banner.py ├── handle_json.py └── rsa_encryptor.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__/ 3 | test.py 4 | notes.txt 5 | notes.md 6 | .DS_Store 7 | .vscode 8 | Keys/ 9 | testing/ 10 | test/ 11 | venv/ -------------------------------------------------------------------------------- /.images/RedCipher.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedMads/RedCipher/bf6cfb852be21eaa356a90c969ded2460a87b683/.images/RedCipher.gif -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # RedCipher v1.0.5 (2022-May-13) 2 | ## Features 3 | - windows support 4 | - commented code 5 | - less lines of code 6 | - keys and settings files are generated inside folder `.RedCipher/` in user home directory 7 | - the project become library now and hosted on [pypi](https://pypi.org/project/redcipher/) 8 | 9 | ## Fixed 10 | - reverse RSA key for encryption and decryption 11 | - encrypt with private key issue 12 | - decrypt with public key issue 13 | - order of function calls in check_all() function 14 | 15 | ## Removed 16 | - seprator between AES key and message in RSA encryption 17 | - random encryption in shreding data function 18 | - RSA load functions 19 | - unneeded functions 20 | 21 |
22 |
23 |
24 | 25 | # RedCipher v1.0.4 (2021-Sep-10) 26 | ## Features 27 | - Encrypt File name option 28 | - Command `-g` Back ! 29 | - Salting password for more security 30 | 31 | ## Fixed 32 | - File permission error 33 | 34 | ## Added 35 | we add some objects to `settings.json` file 36 | 37 | - `salt` object store the salt ( you can change the salt ) 38 | - `use_salt` object is boolean `false` will not use salt `true` it will use salt 39 | - `encrypt_filename` is boolean `false` will not encrypt file name `true` will encrypt file name 40 | 41 | 42 | 43 |
44 |
45 |
46 | 47 | # RedCipher v1.0.3 (2021-Aug-21) 48 | ## Features 49 | - Shred the original file after encryption 50 | - The encrypted file has less space than before 51 | 52 | ## Changes 53 | - Replace Cryptography module with Pycryptodome 54 | - Change object `key_size` value to `2048` in `settings.json` 55 | 56 | ## Removed 57 | - `-g` command was removed temporary 58 | - Remove `salt` object form `settings.json` 59 | 60 |
61 |
62 |
63 | 64 | # RedCipher v1.0.2 (2021-Aug-10) 65 | ## Fixed 66 | - Fix execute the program from other directory BUG 67 | 68 | ## Added 69 | - Add overwrite question to the user ! 70 | - Check if the password is empty string and print error message to the user 71 | 72 |
73 |
74 |
75 | 76 | # RedCipher v1.0.1 (2021-Aug-03) 77 | ## Fixed 78 | - Windows subsystem issue 79 | 80 |
81 |
82 |
83 | 84 | 85 | # RedCipher v1.0.0 (2021-Aug-02) 86 | 87 | - First release of RedCipher project 88 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 RedMads 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | global-include requirements.txt *.py -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is RedCipher ? 2 | 3 | command line utility help users encrypt messages or files with simple commands 4 | 5 | 6 | ## Overview: 7 | 8 | ![RedCipher](https://github.com/RedMads/RedCipher/blob/main/.images/RedCipher.gif) 9 | 10 | 11 | 12 | ## Guide: 13 | 14 | - [Usage](docs/usage.md) 15 | - [Settings](#settings) 16 | - [Installation](#installation) 17 | - [Contributors](#special-thanks-to) 18 | 19 | 20 | 21 | ## Installation 22 | make sure python is installed on your system 23 | ``` 24 | pip3 install redcipher 25 | ``` 26 | 27 | ## Settings: 28 | 29 | settings file are created once the program run for the frist time. 30 | 31 | if the settings file not found the program will load the default settings in `src/handle_json.py` file and rewrite the settings in this path `{user-home}/.RedCipher/settings.json`. 32 | 33 | let's say if you have already installed the program and have settings in the path the program will load the settings and won't rewrite it again. 34 | 35 | ```json 36 | 37 | { 38 | "settings": { 39 | 40 | "extension": ".redc", 41 | "keySize": 3072, 42 | "salt": "s%piyAc7MhDN*qAS)}YrrXb.A9_&t!", 43 | "useSalt": true, 44 | "encryptFileName": false 45 | 46 | } 47 | } 48 | 49 | 50 | ``` 51 | - `extension` object store the encrypted file extension 52 | 53 | - `keySize` default size of bits for RSA keys generation 54 | 55 | - `salt` stores the salt for salting AES key and secure it ( you can change it ! ) 56 | 57 | - `useSalt` stores boolean value to decide if the program use salt or no 58 | - `false` tells the program don't use salt 59 | - `true` use the salt 60 | 61 | - `encryptFileName` object store boolean value to decide if program encrypt file name or not 62 | - `false` will not encrypt file name 63 | - `true` it will encrypt it 64 | 65 | 66 | ## special thanks to: 67 | 68 | - [Zaky202](https://github.com/Zaky202) - for fixing color issue on windows 69 | - [greedalbadi](https://github.com/greedalbadi) - resort classes and file imports -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | ## Flags: 2 | 3 | ``` 4 | usage: redc [-h] [-e] [-d] [-f] [-m] [-g] [-l] 5 | 6 | optional arguments: 7 | -h, --help show this help message and exit 8 | -e , --encrypt -e < AES, RSA > : to encrypt 9 | -d , --decrypt -d < AES, RSA > : to decrypt 10 | -f , --file -f < filePath > : specify file path 11 | -m , --message -m < message > : specify message 12 | -g , --generate -g < byteSize > : generate rsa keys 13 | -l , --load -l < keyFilePath > : load key file to encrypt or decrypt 14 | ``` 15 | 16 | ## Usage AES: 17 | 18 | encrypt message: 19 | ``` 20 | redc -e aes -m "Your Message" 21 | ``` 22 | 23 | decrypt message: 24 | ``` 25 | redc -d aes -m "Encrypted Messsge" 26 | ``` 27 | 28 | encrypt file: 29 | ``` 30 | redc -e aes -f fileName 31 | ``` 32 | 33 | decrypt file: 34 | ``` 35 | redc -d aes -f encFileName.redc 36 | ``` 37 | 38 | ## Usage RSA: 39 | 40 | encrypt message: 41 | ``` 42 | redc -e rsa -m "Your Message" 43 | ``` 44 | decrypt message: 45 | ``` 46 | redc -d rsa -m "Encrypted Message" 47 | ``` 48 | 49 | encrypt file: 50 | ``` 51 | redc -e rsa -f fileName 52 | ``` 53 | 54 | decrypt file: 55 | ``` 56 | redc -d rsa -f encFileName.redc 57 | ``` 58 | 59 | specify costum key to encrypt or decrypt: 60 | ``` 61 | redc -l keyFileName.pem 62 | ``` -------------------------------------------------------------------------------- /red_cipher/__init__.py: -------------------------------------------------------------------------------- 1 | from .actions import Action 2 | from .aes_encryptor import AesEncryptor 3 | from .banner import * 4 | from .handle_json import HandleJson 5 | from .rsa_encryptor import RsaEncryptor 6 | -------------------------------------------------------------------------------- /red_cipher/__main__.py: -------------------------------------------------------------------------------- 1 | from .actions import Action 2 | from .banner import * 3 | from .handle_json import HandleJson 4 | from .rsa_encryptor import RsaEncryptor 5 | import argparse, platform, logging, coloredlogs 6 | import os 7 | 8 | 9 | class Main: 10 | 11 | 12 | def __init__(self): 13 | 14 | self.e_obj = RsaEncryptor() 15 | self.a_obj = Action() 16 | self.h_obj = HandleJson() 17 | 18 | self.h_obj.loadJson() 19 | 20 | self.keySize = self.h_obj.getKeySize() 21 | 22 | self.programPath = os.path.join(os.path.expanduser("~"), ".RedCipher/") 23 | 24 | self.mode = ["encryption", "decryption"] 25 | self.algrothims = ["rsa","aes"] 26 | 27 | self.enc_mode = None 28 | self.msg = "" 29 | self.algo = None 30 | self.file_mode = False 31 | self.file_path = None 32 | self.load_mode = False 33 | self.load_path = "" 34 | 35 | self.show_help = True 36 | self.help = None 37 | 38 | 39 | def check_args(self): 40 | 41 | parser = argparse.ArgumentParser() 42 | 43 | 44 | parser.add_argument("-e", "--encrypt", required=False, type=str, metavar="", help="-e < AES, RSA > : to encrypt") 45 | parser.add_argument("-d", "--decrypt", required=False, type=str, metavar="", help="-d < AES, RSA > : to decrypt") 46 | parser.add_argument("-f","--file", required=False, type= str, metavar="", help="-f < filePath > : specify file path") 47 | parser.add_argument("-m", "--message", required=False, type=str, metavar="", help="-m < message > : specify message") 48 | parser.add_argument("-g", "--generate", required=False, type=int, metavar="", help="-g < byteSize > : generate rsa keys") 49 | parser.add_argument("-l", "--load", required=False, type=str, metavar="", help="-l < keyFilePath > : load key file to encrypt or decrypt") 50 | 51 | 52 | args = parser.parse_args() 53 | 54 | 55 | if args.encrypt: 56 | 57 | self.enc_mode = True 58 | self.algo = args.encrypt 59 | self.show_help = False 60 | 61 | 62 | if str(self.algo).lower() in self.algrothims: 63 | 64 | pass 65 | 66 | else: 67 | parser.print_help() 68 | 69 | 70 | elif args.decrypt: 71 | 72 | self.enc_mode = False 73 | self.algo = args.decrypt 74 | self.show_help = False 75 | 76 | 77 | if str(self.algo).lower() in self.algrothims: 78 | 79 | pass 80 | 81 | else: 82 | 83 | parser.print_help() 84 | 85 | 86 | if args.message: 87 | 88 | self.msg = args.message 89 | self.show_help = False 90 | 91 | 92 | elif args.file: 93 | 94 | self.file_mode = True 95 | self.file_path = args.file 96 | self.show_help = False 97 | 98 | if args.generate: 99 | 100 | self.show_help = False 101 | self.a_obj.rsaKeyMinAction(args.generate) 102 | self.a_obj.overwriteKeysAction(args.generate) 103 | 104 | 105 | 106 | if args.load: 107 | 108 | self.load_mode = True 109 | self.load_path = args.load 110 | self.show_help = False 111 | 112 | if self.load_path != "": 113 | self.a_obj.checkFile(self.load_path, "Key file not found") 114 | 115 | if self.show_help: 116 | 117 | parser.print_help() 118 | 119 | 120 | 121 | def action(self): 122 | 123 | if str(self.algo).lower() == "aes": 124 | 125 | # check if user specify file 126 | if self.file_mode: 127 | 128 | self.a_obj.aesFileAction(self.file_path, self.enc_mode) 129 | 130 | # user don't specify file 131 | elif not self.file_mode: 132 | 133 | self.a_obj.aesAction(self.msg, self.enc_mode) 134 | 135 | 136 | elif str(self.algo).lower() == "rsa": 137 | 138 | if self.file_mode: 139 | 140 | self.a_obj.checkAll(self.file_path) 141 | self.a_obj.rsaFileAction(self.file_path, self.load_path, self.enc_mode) 142 | 143 | 144 | else: 145 | 146 | self.a_obj.rsaAction(self.msg, self.load_path, self.enc_mode) 147 | 148 | 149 | 150 | # function check if main directory of program is exsits or no 151 | def checkProgramPaths(self): 152 | 153 | if not os.path.exists(self.programPath): 154 | os.mkdir(self.programPath) 155 | 156 | filesInMainPath = os.listdir(self.programPath) 157 | 158 | if "settings.json" not in filesInMainPath: 159 | self.h_obj.writeSettings() 160 | 161 | if "Keys" not in filesInMainPath: 162 | os.mkdir(self.e_obj.keys_dir) 163 | self.e_obj.generateRsaKeys(self.keySize) 164 | 165 | # check if Keys directory is empty then we will generate keys 166 | if os.listdir(self.e_obj.keys_dir) == []: 167 | self.e_obj.generateRsaKeys() 168 | 169 | 170 | def main(): 171 | 172 | # Install ColorLogger if the system is windows 173 | if platform.system().lower() == "windows": 174 | logger = logging.getLogger(f"Logger") 175 | coloredlogs.install(logger=logger) 176 | 177 | outputBanner() 178 | m_obj = Main() 179 | m_obj.checkProgramPaths() 180 | m_obj.check_args() 181 | m_obj.action() 182 | 183 | # Reset Terminal Color 184 | print(reset) 185 | 186 | if __name__ == "__main__": 187 | 188 | main() -------------------------------------------------------------------------------- /red_cipher/actions.py: -------------------------------------------------------------------------------- 1 | from .rsa_encryptor import RsaEncryptor 2 | from .banner import * 3 | from .aes_encryptor import AesEncryptor 4 | from base64 import b64encode, b64decode 5 | import getpass 6 | import os 7 | import shutil 8 | import sys 9 | 10 | class Action: 11 | 12 | def __init__(self): 13 | 14 | self.e_obj = RsaEncryptor() 15 | self.a_obj = AesEncryptor() 16 | 17 | 18 | 19 | # This Function ask the user to input password ! 20 | def getPassword(self, retype=True): 21 | 22 | if retype: 23 | 24 | password = getpass.getpass(f"{aqua}[{red}${aqua}] {red}Enter Password{aqua}: {reset}") 25 | 26 | retype_password = getpass.getpass(f"{aqua}[{red}${aqua}] {red}Retype Password{aqua}: {reset}") 27 | 28 | 29 | if password == "": 30 | 31 | print(f"{aqua}[{red}!{aqua}] {red}Please Enter vaild password{reset}") 32 | sys.exit(1) 33 | 34 | if password == retype_password: 35 | 36 | key = self.a_obj.password2AesKey(password) 37 | 38 | return key 39 | 40 | else: 41 | 42 | print(f"\n{aqua}[{red}!{aqua}] {red}Password Dont Match{aqua}!{reset}") 43 | sys.exit(1) 44 | 45 | elif not retype: 46 | 47 | password = getpass.getpass(f"{aqua}[{red}${aqua}] {red}Enter Password{aqua}: {reset}") 48 | 49 | key = self.a_obj.password2AesKey(password) 50 | 51 | return key 52 | 53 | # This function check if file is exists or not 54 | def checkFile(self, filepath, message="File not found"): 55 | 56 | try: 57 | open(filepath,"rb") 58 | 59 | except FileNotFoundError: 60 | 61 | print(f"{aqua}[{red}!{aqua}] {red}{message}{aqua} !{reset}") 62 | sys.exit(1) 63 | 64 | 65 | 66 | # This function check if the path is directory or file 67 | def checkDir(self, path): 68 | 69 | if not os.path.isdir(path): 70 | 71 | pass 72 | 73 | elif os.path.isdir(path): 74 | 75 | print(f"{aqua}[{red}!{aqua}] {red}This is not file is a directory {aqua}!{reset}") 76 | sys.exit(1) 77 | 78 | 79 | # simple function to check what platform program run at 80 | def checkOs(self): 81 | 82 | if os.name == "nt": return "windows", "\\" 83 | 84 | else: return "linux", "/" 85 | 86 | 87 | # This function copy files 88 | def copyFile(self, filepath): 89 | 90 | filename = os.path.basename(filepath) 91 | 92 | slash = self.checkOs()[1] 93 | 94 | if slash in filepath: 95 | 96 | c_filepath = os.path.dirname(filepath) + slash + "C_" + filename 97 | 98 | else: 99 | 100 | c_filepath = "C_" + filename 101 | 102 | 103 | shutil.copy(filepath, c_filepath) 104 | 105 | return c_filepath 106 | 107 | 108 | # simple function check file permssion ! 109 | def checkPermission(self, filepath): 110 | 111 | try: 112 | open(filepath, "rb+").close() 113 | 114 | except PermissionError: 115 | 116 | print(f"{aqua}[{red}!{aqua}]{red} {filepath} Permission denied {aqua}!{reset}") 117 | sys.exit(1) 118 | 119 | 120 | 121 | # simple function for check multi checks in one call ! 122 | def checkAll(self, filepath): 123 | 124 | self.checkDir(filepath) 125 | self.checkFile(filepath) 126 | self.checkPermission(filepath) 127 | 128 | 129 | 130 | 131 | # This function if he want overwrite file with encryption or not 132 | def overwriteAction(self): 133 | 134 | # Just give the user some spcae LOL ! 135 | 136 | answers_y = ["y", "yes", "ye","yep", "yah", "ya", "yeah"] 137 | 138 | answers_n = ["n", "no", "nope", "nah"] 139 | 140 | 141 | while True: 142 | 143 | inp = input(f"{aqua}[{red}?{aqua}]{red} Do you want overwrite it {aqua}({red}y{aqua}/{red}n{aqua}): {red}").lower().strip() 144 | 145 | if inp in answers_y: return "y"; break 146 | 147 | elif inp in answers_n: return "n"; break 148 | 149 | else: continue 150 | 151 | 152 | # useful function for warning the user if he trys 153 | # to generate new keys and he have keys before 154 | def overwriteKeysAction(self, keySize:int): 155 | 156 | while True: 157 | 158 | inp = input(f"{aqua}[{red}?{aqua}]{red} you have keys in {self.e_obj.keys_dir} do you want overwrite it {aqua}({red}y{aqua}/{red}n{aqua}): {red} {reset}") 159 | 160 | if inp == "y": 161 | 162 | self.e_obj.generateRsaKeys(True, keySize) 163 | print(f"{aqua}[{red}${aqua}]{red} keys successfully generated {self.e_obj.keys_dir}{reset}") 164 | break 165 | 166 | elif inp == "n": sys.exit(1) 167 | 168 | else: continue 169 | 170 | 171 | # function output message to user if he try generate key less than 1024 bit 172 | def rsaKeyMinAction(self, keySize:int): 173 | 174 | if keySize < 1024: 175 | 176 | print(f"{aqua}[{red}!{aqua}] {red}Key size is less than 1024 bits{aqua}!{reset}") 177 | sys.exit(1) 178 | 179 | 180 | # function to check if user trys to decrypt with public key 181 | def checkPubKeyDec (self, keyPath:str) -> None: 182 | 183 | if "public" in keyPath: 184 | print(f"{aqua}[{red}!{aqua}] {red}Cannot decrypt with public key{aqua}!{reset}") 185 | sys.exit(1) 186 | 187 | 188 | # function to check if user trys to encrypt with private key 189 | def checkPrivkeyEnc(self, keyPath:str) -> None: 190 | 191 | if "private" in keyPath: 192 | print(f"{aqua}[{red}!{aqua}] {red}Cannot encrypt with private key{aqua}!{reset}") 193 | sys.exit(1) 194 | 195 | 196 | 197 | 198 | # This function handle AES encryption or decrption 199 | def aesAction(self, msg, encryption=True): 200 | 201 | if encryption: 202 | 203 | encrypted_msg = self.a_obj.aesEncrypt(msg.encode(), self.getPassword()) 204 | 205 | print(f"{aqua}[{red}${aqua}] {red}Encrypted MSG{aqua}:{red} {b64encode(encrypted_msg).decode()}{reset}") 206 | 207 | 208 | elif not encryption: 209 | 210 | try: 211 | decrypted_msg = self.a_obj.aesDecrypt(b64decode(msg.encode()),self.getPassword(False)) 212 | 213 | print(f"{aqua}[{red}${aqua}] {red}Decrypted MSG{aqua}:{red} {decrypted_msg.decode()}{reset}") 214 | 215 | except ValueError: 216 | 217 | print(f"{aqua}[{red}!{aqua}] {red}Password is incorrect{aqua}!{reset}") 218 | sys.exit(1) 219 | 220 | 221 | # This function handle AES file Encryption 222 | def aesFileAction(self, path, encryption=True): 223 | 224 | self.checkAll(path) 225 | 226 | 227 | if encryption: 228 | 229 | overwrite_answer = self.overwriteAction() 230 | 231 | if overwrite_answer == "y": 232 | 233 | self.a_obj.aesEncryptFile(path, self.getPassword()) 234 | print(f"{aqua}[{red}${aqua}] {red}{path} Encrypted successfully {aqua}!{reset}") 235 | 236 | elif overwrite_answer == "n": 237 | 238 | c_filepath = self.copyFile(path) 239 | 240 | self.a_obj.aesEncryptFile(c_filepath, self.getPassword()) 241 | print(f"{aqua}[{red}${aqua}] {red}{c_filepath} Encrypted successfully {aqua}!{reset}") 242 | 243 | 244 | 245 | elif not encryption: 246 | 247 | try: 248 | self.a_obj.aesDecryptFile(path, self.getPassword(False)) 249 | 250 | print(f"{aqua}[{red}${aqua}] {red}{path} Decrypted successfully {aqua}!{reset}") 251 | 252 | except ValueError: 253 | print(f"\n{aqua}[{red}!{aqua}] {red}Password is incorrect{aqua}!{reset}") 254 | sys.exit(1) 255 | 256 | 257 | # This function handle RSA encryption and decryption! 258 | def rsaAction(self, msg:str, keyPath:str, encryption=True): 259 | 260 | if encryption: 261 | 262 | self.checkPrivkeyEnc(keyPath) 263 | 264 | encrypted_msg = self.e_obj.rsaEncrypt(msg.encode("utf-8"), keyPath)[1] 265 | print(f"{aqua}[{red}${aqua}] {red}Encrypted MSG{aqua}:{red} {b64encode(encrypted_msg).decode()}{reset}") 266 | 267 | 268 | elif not encryption: 269 | 270 | self.checkPubKeyDec(keyPath) 271 | 272 | try: 273 | decrypted_msg = self.e_obj.rsaDecrypt(b64decode(msg.encode("utf-8")), keyPath)[1] 274 | print(f"{aqua}[{red}${aqua}] {red}Decrypted MSG{aqua}:{red} {decrypted_msg.decode()}{reset}") 275 | 276 | 277 | except ValueError: 278 | 279 | print(f"{aqua}[{red}!{aqua}] {red}Wrong Decryption Key {aqua}!{reset}") 280 | sys.exit(1) 281 | 282 | 283 | 284 | # This function handle RSA file Encryption and Decryption 285 | def rsaFileAction(self, path:str, keyPath:str, encryption=True): 286 | 287 | self.checkAll(path) 288 | 289 | 290 | if encryption: 291 | 292 | self.checkPrivkeyEnc(keyPath) 293 | 294 | overwrite_answer = self.overwriteAction() 295 | 296 | if overwrite_answer == "y": 297 | 298 | self.e_obj.rsaEncryptFile(path, keyPath) 299 | print(f"{aqua}[{red}${aqua}] {red}{path} Encrypted successfully {aqua}!{reset}") 300 | 301 | elif overwrite_answer == "n": 302 | 303 | c_filepath = self.copyFile(path) 304 | 305 | self.e_obj.rsaEncryptFile(c_filepath, keyPath) 306 | print(f"{aqua}[{red}${aqua}] {red}{c_filepath} Encrypted successfully {aqua}!{reset}") 307 | 308 | 309 | elif not encryption: 310 | 311 | self.checkPubKeyDec(keyPath) 312 | 313 | try: 314 | self.e_obj.rsaDecryptFile(path, keyPath) 315 | print(f"{aqua}[{red}${aqua}] {red}{path} Decrypted successfully {aqua}!{reset}") 316 | 317 | except ValueError: 318 | 319 | print(f"{aqua}[{red}!{aqua}] {red}Wrong Decryption Key {aqua}!{reset}") 320 | sys.exit(1) 321 | -------------------------------------------------------------------------------- /red_cipher/aes_encryptor.py: -------------------------------------------------------------------------------- 1 | from Crypto.Cipher import AES 2 | from Crypto.Util.Padding import pad, unpad 3 | from binascii import hexlify, unhexlify 4 | from base64 import b64encode 5 | from .handle_json import HandleJson 6 | from pathlib import Path 7 | import hashlib 8 | import os 9 | import secrets 10 | 11 | class AesEncryptor: 12 | 13 | def __init__(self): 14 | 15 | self.key_length = 32 # 256 bits key length 16 | self.iv_length = 16 # 128 bits IV length 17 | 18 | self.h_obj = HandleJson() 19 | self.h_obj.loadJson() 20 | 21 | # get the encrypted extention from the json file ! 22 | self.ext = self.h_obj.getExt() 23 | 24 | self.iv = secrets.token_bytes(self.iv_length) 25 | 26 | 27 | 28 | # Simple function for generating random 256 bit key and encode it 29 | def generateKey(self, enc="base64"): 30 | 31 | if enc.lower() == "base64": 32 | 33 | return b64encode(secrets.token_bytes(self.key_length)) 34 | 35 | elif enc.lower() == "hex": 36 | 37 | return hexlify(secrets.token_bytes(self.key_length)) 38 | 39 | # if the arg sommethig else we will retrun 40 | # key as byte formated 41 | else: return secrets.token_bytes(self.key_length) 42 | 43 | 44 | # this function convert plaintext to SHA-256 hashed text ( 32 bytes ) 45 | def password2AesKey(self, password): 46 | 47 | # check if the user want to use salt 48 | if self.h_obj.getUseSalt() == True: 49 | 50 | return hashlib.sha256(self.saltPassword(password).encode()).digest() 51 | 52 | else: 53 | return hashlib.sha256(password.encode()).digest() 54 | 55 | 56 | # simple function for salting passwords ! 57 | def saltPassword(self, password): 58 | 59 | # get the salt from settings file 60 | salt = self.h_obj.getSalt() 61 | 62 | # devide the length of the salt by 2 to get the middle 63 | mid_salt = len(salt) // 2 64 | 65 | # check if length of salt is even ! 66 | if (len(salt) % 2) == 0: 67 | 68 | return salt[:mid_salt] + password + salt[mid_salt:] 69 | 70 | # odd length 71 | else: 72 | 73 | return password + salt 74 | 75 | 76 | 77 | 78 | # simple function to shred data and delete it 4Ever ! 79 | def shredingData(self, path, passes=3): 80 | 81 | # open the file and overwrite encrypted data with random data 82 | with open(path, "br+") as delfile: 83 | 84 | fileSize = delfile.tell() 85 | 86 | for _ in range(passes): 87 | 88 | # return to the begin of the file 89 | delfile.seek(0) 90 | 91 | # write to the file random data 92 | delfile.write(secrets.token_bytes(fileSize)) 93 | 94 | delfile.seek(0) 95 | 96 | # write zeros to the file 97 | delfile.write(b"\0" * fileSize) 98 | 99 | # finally delete the file 100 | os.remove(path) 101 | 102 | 103 | 104 | 105 | # Encrypt function with AES-256 CBC mode ! 106 | def aesEncrypt(self, data, key): 107 | 108 | # initialize AES cipher object 109 | cipher = AES.new(key, AES.MODE_CBC, self.iv) 110 | 111 | # encrypt and pad the data with size of block (16) 112 | encrypted_data = cipher.encrypt(pad(data, AES.block_size)) 113 | 114 | # combine IV with encrpted data 115 | return self.iv + encrypted_data 116 | 117 | 118 | # Decrypt function with AES-256 CBC mode ! 119 | def aesDecrypt(self, enc_data, key): 120 | 121 | # extract frist 16 byte (IV) 122 | iv = enc_data[:16] 123 | 124 | cipher = AES.new(key, AES.MODE_CBC, iv) 125 | 126 | # skipping frist 16 bye and decrypt the data 127 | decrypted_data = cipher.decrypt(enc_data[16:]) 128 | 129 | # remove the padding zeros and return the data 130 | return unpad(decrypted_data, AES.block_size) 131 | 132 | 133 | # Encrypt file with AES-256 CBC mode 134 | def aesEncryptFile(self, filename, key): 135 | 136 | enc_filename = self.encryptFileName(filename, key) 137 | 138 | with open(filename, "rb") as file: 139 | 140 | data = file.read() 141 | 142 | 143 | file.close() 144 | 145 | if enc_filename != False: 146 | 147 | with open(enc_filename, "wb") as enc_file: 148 | 149 | encrypted_data = self.aesEncrypt(data, key) 150 | 151 | enc_file.write(encrypted_data) 152 | 153 | 154 | enc_file.close() 155 | 156 | else: 157 | 158 | with open(filename + self.ext, "wb") as enc_file: 159 | 160 | encrypted_data = self.aesEncrypt(data, key) 161 | 162 | enc_file.write(encrypted_data) 163 | 164 | 165 | enc_file.close() 166 | 167 | 168 | self.shredingData(filename) 169 | 170 | 171 | # Decrypt file with AES-256 CBC mode ! 172 | def aesDecryptFile(self, enc_filename, key): 173 | 174 | dec_filename = self.encryptFileName(enc_filename, key, False) 175 | 176 | 177 | 178 | with open(enc_filename, "rb") as enc_file: 179 | 180 | enc_data = enc_file.read() 181 | 182 | enc_file.close() 183 | 184 | if dec_filename != False: 185 | 186 | with open(dec_filename, "wb") as dec_file: 187 | 188 | decrypted_data = self.aesDecrypt(enc_data, key) 189 | 190 | dec_file.write(decrypted_data) 191 | 192 | 193 | dec_file.close() 194 | 195 | else: 196 | 197 | # split path to get filename ! 198 | filename = os.path.splitext(enc_filename) 199 | 200 | with open(filename[0], "wb") as dec_file: 201 | 202 | decrypted_data = self.aesDecrypt(enc_data, key) 203 | 204 | dec_file.write(decrypted_data) 205 | 206 | dec_file.close() 207 | 208 | self.shredingData(enc_filename) 209 | 210 | 211 | # simple function take a path extract filename 212 | # and return it encrypted / decrypted ! 213 | def encryptFileName(self, filepath, key, encryption=True): 214 | 215 | dirname = os.path.dirname(filepath) 216 | basename = os.path.basename(filepath) 217 | 218 | if filepath in os.listdir("."): 219 | dirname = "." + dirname 220 | 221 | 222 | if self.h_obj.getEncryptFileName() == True: 223 | 224 | if encryption: 225 | 226 | return str(Path(dirname + "/" + hexlify(self.aesEncrypt(basename.encode(), key)).decode() + self.ext)) 227 | 228 | elif not encryption: 229 | 230 | return str(Path(dirname + "/" + self.aesDecrypt(unhexlify(basename.replace(self.ext, "").encode()), key).decode())) 231 | 232 | else: return False 233 | -------------------------------------------------------------------------------- /red_cipher/banner.py: -------------------------------------------------------------------------------- 1 | import random 2 | from colorama import Fore 3 | 4 | 5 | red = Fore.RED 6 | aqua = Fore.CYAN 7 | blink = "\033[5m" 8 | reset = Fore.RESET 9 | 10 | 11 | sepChars = "=-*~+._" 12 | version = f"{aqua}v{red}1{aqua}.{red}0{aqua}.{red}5" 13 | 14 | 15 | def pickRandSepChar(): 16 | 17 | return random.choice(sepChars) 18 | 19 | 20 | banner = f""" 21 | {red} ____ _ ____ _ _ 22 | | _ \ ___ __| |/ ___(_)_ __ | |__ ___ _ __ 23 | | |_) / _ \/ _` | | | | '_ \| '_ \ / _ \ '__| 24 | | _ < __/ (_| | |___| | |_) | | | | __/ | 25 | |_| \_\___|\__,_|\____|_| .__/|_| |_|\___|_| 26 | |_| {version} 27 | 28 | {red}Professional Encryption {aqua}/{red} Decryption program {aqua}! 29 | 30 | {" " * 5}{aqua}{pickRandSepChar() * 35}{red} 31 | 32 | """ 33 | 34 | 35 | def outputBanner(): 36 | 37 | print(banner) 38 | 39 | 40 | 41 | 42 | if __name__ == "__main__": 43 | 44 | outputBanner() 45 | -------------------------------------------------------------------------------- /red_cipher/handle_json.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | class HandleJson: 5 | 6 | def __init__(self): 7 | 8 | self.settings = {} 9 | 10 | # get the settings filepath 11 | self.programPath = os.path.join(os.path.expanduser("~"), ".RedCipher/") 12 | self.settingsPath = os.path.join(self.programPath, "settings.json") 13 | 14 | # This function load the settings file ! 15 | def loadJson(self): 16 | 17 | try: 18 | 19 | # Try to read the < settings.json > file ! 20 | with open(self.settingsPath, "r") as json_file: 21 | 22 | self.settings = json.load(json_file) 23 | 24 | json_file.close() 25 | 26 | except FileNotFoundError: 27 | 28 | # if < settings.json > is missing load the default settings ! 29 | self.settings = { 30 | "settings": { 31 | 32 | "extension": ".redc", 33 | "keySize": 3072, 34 | "salt": "s%piyAc7MhDN*qAS)}YrrXb.A9_&t!", 35 | "useSalt": True, 36 | "encryptFileName": False 37 | 38 | } 39 | } 40 | 41 | 42 | 43 | # function write default settings to a file 44 | def writeSettings(self): 45 | 46 | self.loadJson() 47 | 48 | with open(self.settingsPath, "w") as settingsFile: 49 | settingsFile.write(json.dumps(self.settings, indent=4)) 50 | 51 | 52 | 53 | def getExt(self): 54 | 55 | return self.settings["settings"]["extension"] 56 | 57 | def getKeySize(self): 58 | 59 | return self.settings["settings"]["keySize"] 60 | 61 | def getSalt(self): 62 | 63 | return self.settings["settings"]["salt"] 64 | 65 | def getUseSalt(self): 66 | 67 | return self.settings["settings"]["useSalt"] 68 | 69 | def getEncryptFileName(self): 70 | 71 | return self.settings["settings"]["encryptFileName"] 72 | 73 | 74 | 75 | if __name__ == '__main__': 76 | 77 | h_obj = HandleJson() 78 | 79 | h_obj.loadJson() 80 | 81 | # test lines 82 | print(f"Salt: {h_obj.getSalt()}") 83 | print(f"Extension: {h_obj.getExt()}") 84 | print(f"KeySize: {h_obj.getKeySize()}") 85 | -------------------------------------------------------------------------------- /red_cipher/rsa_encryptor.py: -------------------------------------------------------------------------------- 1 | from Crypto.PublicKey import RSA 2 | from Crypto.Cipher import PKCS1_OAEP 3 | from os import path 4 | from .handle_json import HandleJson 5 | from .aes_encryptor import AesEncryptor 6 | 7 | # RSA Encryptor class 8 | class RsaEncryptor: 9 | 10 | def __init__(self): 11 | 12 | # initialize some objects 13 | self.h_obj = HandleJson() 14 | self.a_obj = AesEncryptor() 15 | 16 | # load json file (settings.json) 17 | self.h_obj.loadJson() 18 | 19 | self.ext = self.h_obj.getExt() 20 | 21 | # get key-size from settings file 22 | self.keys_size = self.h_obj.getKeySize() 23 | 24 | 25 | # Path convert paths to system default format 26 | # path.dirname extract the dirname of a __file__ 27 | # str(self.key_dir) convert the return value to string 28 | self.keys_dir = path.join(self.h_obj.programPath, "Keys") 29 | self.public_key_file = path.join(self.keys_dir, "public.pem") 30 | self.private_key_file = path.join(self.keys_dir, "private.pem") 31 | 32 | 33 | 34 | # a function to check if user specify coustum key 35 | def checkCostumKey(self, keyPath:str, privKey:bool=False) -> open: 36 | 37 | # check if the user specify path for key or not 38 | if keyPath != "": 39 | return open(keyPath) 40 | 41 | # if the user don't specify any path we gonna load default key path 42 | else: 43 | # check if key is private key or not then we load the default key 44 | if privKey: return open(self.private_key_file) 45 | 46 | else: return open(self.public_key_file) 47 | 48 | 49 | 50 | # This Function Generate RSA keys and write them in files ! 51 | # Args < KeySize: int / default: 2048 > 52 | # Notice: the keys size was taken from constructor function up! 53 | 54 | def generateRsaKeys(self, cmd=False, size=2048): 55 | 56 | # check if the user don't use tag -g to generate keys 57 | if not cmd: 58 | 59 | # generate RSA private key with default size of bytes 60 | private = RSA.generate(self.keys_size) 61 | 62 | # open private key file and write the private key 63 | with open(self.private_key_file, "wb") as private_file: 64 | 65 | private_file.write(private.export_key()) 66 | 67 | # open public key file and write the public key 68 | with open(self.public_key_file, "wb") as public_file: 69 | 70 | # extract public key from private key 71 | # and export it to pem to write it in file 72 | public_file.write(private.publickey().export_key()) 73 | 74 | # if user use the tag -g to generate keys 75 | elif cmd: 76 | 77 | # we do the same here 78 | 79 | # take the size of bytes that user has specify 80 | private = RSA.generate(size) 81 | 82 | with open(self.private_key_file, "wb") as private_file: 83 | 84 | private_file.write(private.export_key()) 85 | 86 | with open(self.public_key_file, "wb") as public_file: 87 | 88 | public_file.write(private.publickey().export_key()) 89 | 90 | 91 | 92 | # This Function Encrypt a string with tha RSA key ! 93 | # Args < message: String > 94 | # Return Encrypted Message ! 95 | 96 | def rsaEncrypt(self, message:bytes, pubkPath=""): 97 | 98 | pubFile = self.checkCostumKey(pubkPath).read() 99 | 100 | # import the public key to RSA object 101 | pubKey = RSA.import_key(pubFile); 102 | 103 | # initialize RSA cipher with the pubKey 104 | rsa_cipher = PKCS1_OAEP.new(pubKey) 105 | 106 | # generate random AES key 107 | aes_key = self.a_obj.generateKey("None") 108 | 109 | # encrypt the message with AES key 110 | encrypted_data = self.a_obj.aesEncrypt(message, aes_key) 111 | 112 | # encrypt AES key with RSA key 113 | encrypted_aes_key = rsa_cipher.encrypt(aes_key) 114 | 115 | # combine encrypted AES key with encrypted data 116 | full_encrypted = encrypted_aes_key + encrypted_data 117 | 118 | # finally return AES key and encrypted data as bytes 119 | return aes_key ,full_encrypted 120 | 121 | 122 | 123 | 124 | # This Function Decrypt a encrypted string with RSA key ! 125 | # Args < enc_message: String > 126 | # Return Decrypted Message ! 127 | 128 | def rsaDecrypt(self, enc_message:bytes, privkPath=""): 129 | 130 | privFile = self.checkCostumKey(privkPath, True).read() 131 | 132 | # import private key to RSA object 133 | key = RSA.import_key(privFile) 134 | 135 | # create RSA cipher with private key 136 | rsa_cipher = PKCS1_OAEP.new(key) 137 | 138 | # extraxt the frist 256 bits (Encrypted AES key) 139 | enc_aes_key = enc_message[:256] 140 | 141 | # decrypt aes key with RSA private key 142 | aes_key = rsa_cipher.decrypt(enc_aes_key) 143 | 144 | # skip 256 bit and extraxt the rest (Encrypted data) 145 | dec_data = enc_message[256:] 146 | 147 | # finally return AES key and decrypted data 148 | return aes_key, self.a_obj.aesDecrypt(dec_data, aes_key) 149 | 150 | # This Function Encrypt a File with RSA public key ! 151 | # Args < filepath: String > 152 | # Return Encrypted File ! 153 | def rsaEncryptFile(self, filepath, keyPath:str): 154 | 155 | # open the file that user wants encrypt 156 | with open(filepath, "rb") as file: 157 | 158 | # read all data of the file 159 | data = file.read() 160 | 161 | # encrypt the data with rsa_encrypt function 162 | # take the AES key and store it into variable 163 | # as well encrypted data 164 | key, encrypted_data = self.rsaEncrypt(data, keyPath) 165 | 166 | # close the file 167 | file.close() 168 | 169 | # check if the use specify encrypt file feature or not 170 | # if it so we will store the encrypted name of file to variable 171 | # otherwise we will retrun False to this variable 172 | enc_filename = self.a_obj.encryptFileName(filepath, key) 173 | 174 | # check if the encrypted file name don't match False 175 | if enc_filename != False: 176 | 177 | # open new file with the encrypted file name 178 | with open(enc_filename, "wb") as enc_file: 179 | 180 | # write encrypted data 181 | enc_file.write(encrypted_data) 182 | 183 | # close the file 184 | enc_file.close() 185 | 186 | # the use don't want to encrypt file name 187 | else: 188 | 189 | # open new file with orignal name and we add 190 | # the encryption extension to the file 191 | # example: test.txt 192 | # will be: test.txt.redc 193 | with open(filepath + self.ext, "wb") as enc_file: 194 | 195 | # write the encrypted data 196 | enc_file.write(encrypted_data) 197 | 198 | # close the file 199 | enc_file.close() 200 | 201 | # destroy data from the orignal file and delete it 202 | self.a_obj.shredingData(filepath) 203 | 204 | 205 | 206 | # This Function Decrypt a Encrypted File with RSA private key ! 207 | # Args < filepath: String > 208 | # Return Decrypted File ! 209 | def rsaDecryptFile(self, filepath, keyPath:str): 210 | 211 | # seprate the filename and the extesion 212 | filename = path.splitext(filepath) 213 | 214 | # open the encrypted file as read binary mode 215 | with open(filepath, "rb") as enc_file: 216 | 217 | # extraxt and read the encrypted data 218 | encrypted_data = enc_file.read() 219 | 220 | # decrypt the data with rsa_decrypt function 221 | # take the AES key and store it into variable 222 | # as well decrypted data 223 | key, decrypted_data = self.rsaDecrypt(encrypted_data, keyPath) 224 | 225 | # close the encrypted file 226 | enc_file.close() 227 | 228 | # check if the user specify encrypt file feature or not 229 | # if it so we will store the decrypted name of file to variable 230 | # otherwise we will retrun False to this variable 231 | dec_filename = self.a_obj.encryptFileName(filepath, key, False) 232 | 233 | # check if the decrypted file name don't match False 234 | if dec_filename != False: 235 | 236 | # open new file 237 | with open(dec_filename, "wb") as file: 238 | 239 | # write the decrypted data into the file 240 | file.write(decrypted_data) 241 | 242 | # close the decrypted file 243 | file.close() 244 | 245 | else: 246 | 247 | # open the orignal filename 248 | with open(filename[0], "wb") as file: 249 | 250 | file.write(decrypted_data) 251 | 252 | file.close() 253 | 254 | self.a_obj.shredingData(filepath) 255 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pycryptodome==3.14.1 2 | colorama==0.4.4 3 | cryptography==3.3.2 4 | argparse==1.4.0 5 | coloredlogs==15.0.1 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | __name__ = "redcipher" 4 | __version__ = "v1.5" 5 | __author__ = "RedMads" 6 | __author_email__ = "redmads@protonmail.com" 7 | __date__ = "2022-5-12" 8 | 9 | 10 | with open("README.md", "r", encoding="utf-8") as readme: 11 | long_description = readme.read() 12 | 13 | with open("requirements.txt", "r") as reqFile: 14 | 15 | requirements = [] 16 | 17 | for line in reqFile.readlines(): 18 | requirements.append(line[:-1]) 19 | 20 | readme.close() 21 | reqFile.close() 22 | 23 | 24 | 25 | setup( 26 | name=__name__, 27 | version=__version__, 28 | description='Professional Encryption / Decryption program !', 29 | long_description=long_description, 30 | long_description_content_type='text/markdown', 31 | url='https://github.com/RedMads/RedCipher/', 32 | author=__author__, 33 | author_email=__author_email__, 34 | 35 | project_urls={ 36 | 'Source': 'https://github.com/RedMads/RedCipher', 37 | 'Report Bugs': 'https://github.com/RedMads/RedCipher/issues', 38 | 'Download': 'https://pypi.org/project/redcipher/#files', 39 | 'Documentation': 'https://github.com/RedMads/RedCipher/blob/master/README.md' 40 | }, 41 | 42 | license='MIT', 43 | 44 | keywords=[ 45 | "python", "encrypt", "decrypt", 46 | "cipher", "cryptography", "crypto", 47 | "AES", "RSA", "decryption", "encryption" 48 | ], 49 | 50 | packages=find_packages(), 51 | 52 | install_requires=['pycryptodome==3.14.1', 53 | 'colorama==0.4.4', 'cryptography==3.3.2', 'argparse==1.4.0', 'coloredlogs==15.0.1'], 54 | 55 | entry_points={ 56 | 'console_scripts': [ 57 | "redc=red_cipher.__main__:main", 58 | ] 59 | } 60 | ) --------------------------------------------------------------------------------