├── .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 | 
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 | )
--------------------------------------------------------------------------------