Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
carlospuenteg authored Oct 1, 2022
1 parent 1878538 commit 89d5ea4
Show file tree
Hide file tree
Showing 15 changed files with 436 additions and 2 deletions.
99 changes: 98 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
* [Enter the input](#enter-the-input)
* [Run](#run)
* [Configuration](#configuration)
* [Examples](#examples)
* [Encryption](#encryption-example)
* [Text Decryption](#text-decryption-example)
* [Image Decryption](#image-decryption-example)

## Description

Expand Down Expand Up @@ -50,4 +54,97 @@ You can change the `POS_LEN` in the `config.py` file, which changes the input te

Default is `POS_LEN = 4`, so the maximum length the text can have by default is **32,446**

The more the `POS_LEN` is, the longer the text can be, but the longer it will take to encrypt and decrypt it.
The more the `POS_LEN` is, the longer the text can be, but the longer it will take to encrypt and decrypt it.

| POS_LEN | Max text lenght |
|-|-|
| 1 | 2 |
| 2 | 107 |
| 3 | 2,026 |
| 4 | 32,446 |
| 5 | 524,169 |
| 6 | 8,386,749 |
| 7 | 134,188,028 |
| 8 | 2,147,329,544 |
| ... | ... |




## Examples

### Encryption example

<img width=400 src=readme-assets/example_encryption.png>

##### `config.py`

```python
POS_LEN = 2
```

##### Input text (`input/test.txt`)
```txt
This is a test 123!!!
It can have every UTF-8 character! ✔️ ❤️ ☆
```

##### Password
```txt
test#@–{}password123¿?
```

##### Encrypted text (`output/encrypted_text.txt`)
```txt
3e8b00b4bcbb4ff246fdb3bc9afd63cf080e1cbefdc4b4bb2b5f1400f3fd4e6cb10d40825d0ab41e080e4e751a1ebbb8e7c4448fc14434d5c84d7fb3cc68e2c66033d5cfeece84bd256888b5e3dbb5bdc7fd47845be373e44bc8defbabb92e544f5eb0b4c43403084344d663
```

##### Encrypted image (`output/encrypted_image.png`)
<img width=70 src=readme-assets/encrypted_image.png>



### Text Decryption example

<img width=400 src=readme-assets/example_text_decryption.png>

##### Input text (`input/encrypted_text.txt`)
```txt
This is a test 123!!!
It can have every UTF-8 character! ✔️ ❤️ ☆
```

##### Password
```txt
test#@–{}password123¿?
```

##### Decrypted text (`output/decrypted_text.txt`)
```txt
This is a test 123!!!
The text can have every UTF-8 character! ✔️ ❤️ ☆
```



### Image Decryption example

<img width=400 src=readme-assets/example_image_decryption.png>

##### Input image (`input/encrypted_image.png`)
<img width=70 src=readme-assets/encrypted_image.png>

##### Password
```txt
test#@–{}password123¿?
```

##### Decrypted image (`output/decrypted_image.txt`)
```txt
This is a test 123!!!
The text can have every UTF-8 character! ✔️ ❤️ ☆
```
38 changes: 38 additions & 0 deletions classes/Decryptor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import random

import sys; sys.path.append("..")
from constants.constants import *
from utils.cryptography import *

class Decryptor:
def __init__(self, ciphertext:str, pwd:str):
self.ciphertext = ciphertext
self.pwd = pwd
self.hash = get_pwd_hash(pwd)


def decrypt(self) -> str:
cipher_len = len(self.ciphertext) # Same as CIPHER_LEN

if not all(c in HEX_SYMB for c in self.ciphertext) or len(self.ciphertext) != cipher_len:
raise Exception("Encrypted text is invalid")

# Seed the random generator with the hash
seed = int(self.hash,16)
random.seed(seed)

# Get the encryption code
encryption_code = get_encryption_code(self.hash)

# Decrypt the text length
textE_len_digits = len(format(cipher_len,"x")) # If the position length is 5, the length of the encrypted text will fit in 5 digits
textE_len_idxs = get_textE_len_idxs(seed, cipher_len, textE_len_digits)
textE_lenE = "".join([self.ciphertext[idx] for idx in textE_len_idxs])
textE_len = int(dec(textE_lenE, encryption_code), 16)

# Decrypt the text
textE_idxs = get_textE_idxs(seed, cipher_len, textE_len, textE_len_idxs)
textE = "".join([self.ciphertext[idx] for idx in textE_idxs])
text = h2t(dec(textE, encryption_code))

return text
47 changes: 47 additions & 0 deletions classes/Encryptor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import random

import sys; sys.path.append("..")
from constants.constants import *
from utils.cryptography import *

class Encryptor:
def __init__(self, text:str, pwd:str):
self.text = text
self.pwd = pwd
self.hash = get_pwd_hash(self.pwd)


def encrypt(self) -> str: # Returns a ciphertext
if len(self.text)>TXT_MAX_LEN or len(self.text)==0:
raise Exception("Text length is invalid")

# Seed the random generator with the hash
seed = int(self.hash,16)
random.seed(seed)

# Get the encryption code
encryption_code = get_encryption_code(self.hash)

# Fill the ciphertext with random characters
ciphertext = [random.choice(HEX_SYMB) for _ in range(CIPHER_LEN)]

# INFO: Encrypt the text
textE = enc(t2h(self.text), encryption_code)

# INFO: Encrypt the text length
textE_len = len(textE)
textE_len_max_digits = len(format(CIPHER_LEN,"x")) # If the position length is 5, the length of the encrypted text will fit in 5 digits
textE_len_fixed_len = format(textE_len,"x").rjust(textE_len_max_digits,"0")
textE_lenE = enc(textE_len_fixed_len, encryption_code) # Encrypted text length (fixed length)

# Get the indexes where the INFO will be stored
textE_len_idxs = get_textE_len_idxs(seed, CIPHER_LEN, textE_len_max_digits)
textE_idxs = get_textE_idxs(seed, CIPHER_LEN, len(textE), textE_len_idxs)

# Save the text and text length in toret
for i,idx in enumerate(textE_len_idxs):
ciphertext[idx] = textE_lenE[i]
for i,idx in enumerate(textE_idxs):
ciphertext[idx] = textE[i]

return "".join(ciphertext)
18 changes: 18 additions & 0 deletions classes/ImageCreator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import numpy as np

import sys; sys.path.append("..")
from constants.constants import *

class ImageCreator:
def __init__(self, text:str):
self.text = text


def get_img_arr(self) -> np.ndarray:
img_arr = np.array(
[[int(self.text[i:i+2], 16),
int(self.text[i+2:i+4], 16),
int(self.text[i+4:i+6], 16)]
for i in range(0, len(self.text), 6)],
dtype=np.uint8)
return img_arr.reshape(IMG_SIZE, IMG_SIZE, 3)
117 changes: 117 additions & 0 deletions classes/Menu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import os
from colorama import Fore, init; init()
from PIL import Image

from .Text import Text
from .Options import Options
from .Encryptor import Encryptor
from .Decryptor import Decryptor
from .ImageCreator import ImageCreator

import sys; sys.path.append("..")
from constants.constants import *

class Menu():
def __init__(self):
self.create_folders()
print(f'\n{Text("Max length of the text",Fore.CYAN)}: {Text(f"{TXT_MAX_LEN:,}",Fore.GREEN)} (you can change it in {Text("config.py",Fore.LIGHTYELLOW_EX)}')
option = Options(["EXIT", "Encrypt", "Decrypt text", "Decrypt image"]).get_choice()

if option == 0:
exit()

elif option == 1:
self.encrypt()

elif option == 2:
self.decrypt_text()

elif option == 3:
self.decrypt_image()


def create_folders(self):
if not os.path.exists(INPUT_DIR): os.mkdir(INPUT_DIR)
if not os.path.exists(OUTPUT_DIR): os.mkdir(OUTPUT_DIR)


def encrypt(self):
text = open(self.get_input_file("Text filename: ", "txt")).read()
pwd = input("Password: ")
enc_text_file = self.get_file("New text filename: ", "txt")
enc_img_file = self.get_file("New image filename: ", "png") if self.yes_no("Save image? [y/n]: ") else None

try:
encrypted_text = Encryptor(text, pwd).encrypt()
self.save_text(encrypted_text, enc_text_file)
print(Text("\nText encrypted succesfully\n", Fore.GREEN))
if enc_img_file:
self.save_img(encrypted_text, enc_img_file)
print(Text("Image saved", Fore.MAGENTA))
except Exception as e:
print(Text(f"Error: {e}", Fore.RED))
return


def decrypt(self, ciphertext:str) -> str:
pwd = input("Password: ")
dec_text_file = self.get_file("New filename: ", "txt")

try:
decypted_text = Decryptor(ciphertext, pwd).decrypt()
self.save_text(decypted_text, dec_text_file)
print(Text("\nText decrypted succesfully\n", Fore.GREEN))
except Exception as e:
print(Text(f"Error: {e}", Fore.RED))
return


def decrypt_text(self):
ciphertext = open(self.get_input_file("Ciphertext filename: ", "txt")).read()
self.decrypt(ciphertext)


def decrypt_image(self):
ciphertext = self.img_to_text(self.get_input_file("Cipher image filename: ", "png"))
self.decrypt(ciphertext)


def img_to_text(self, img_file:str) -> str:
img_arr = np.array(Image.open(img_file)).flatten()
img_str = "".join([f'{n:02x}' for n in img_arr])
return img_str


def yes_no(self, msg:str="Save? [y/n]: ") -> bool:
inp = input(msg)
if inp.lower() == "y": return True
elif inp.lower() == "n": return False
else: return self.yes_no(msg)


def save_text(self, text:str, file:str):
with open(f"{OUTPUT_DIR}/{file}", "w") as f:
f.write(text)


def save_img(self, text:str, file:str):
img_arr = ImageCreator(text).get_img_arr()
Image.fromarray(img_arr).save(f"{OUTPUT_DIR}/{file}")


def get_file(self, msg:str, ext:str) -> str:
while True:
filename = input(msg)
if "." in filename:
if filename.endswith(f".{ext}"): return filename
print(Text("Invalid extension", Fore.RED))
continue
return f"{filename}.{ext}"


def get_input_file(self, msg:str, ext:str) -> str:
while True:
file = self.get_file(msg, ext)
path = f"{INPUT_DIR}/{file}"
if os.path.exists(path): return path
print(Text("File not found",Fore.RED))
28 changes: 28 additions & 0 deletions classes/Options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from colorama import Fore, init; init()
from .Text import Text

class Options:
def __init__(self, options:list, first_idx=0):
self.options = options
self.first_idx = first_idx


def get_choice(self) -> int:
self.__str__()
choice = input("Option: ")

if self.check_input(choice): return int(choice)

print(Text("\nInvalid choice.\n", Fore.RED))
return self.get_choice()


def check_input(self, choice:str) -> bool:
return choice.strip().isdigit() and \
int(choice) in range(self.first_idx, self.first_idx + len(self.options))


def __str__(self) -> str:
print("".join(
[f"[{self.first_idx+i}] {opt}\n"
for i,opt in enumerate(self.options)]))
9 changes: 9 additions & 0 deletions classes/Text.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from colorama import Fore, init; init()

class Text:
def __init__(self, text:str, color:str):
self.text = text
self.color = color

def __str__(self) -> str:
return f"{self.color}{self.text}{Fore.RESET}"
2 changes: 1 addition & 1 deletion config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
POS_LEN = 4
POS_LEN = 2
"""
The length that the positions in the encryption will have (in hex)
Expand Down
Loading

0 comments on commit 89d5ea4

Please sign in to comment.