Skip to content

Instantly share code, notes, and snippets.

@Frontear
Last active October 26, 2021 17:42
Show Gist options
  • Save Frontear/45d2fa3da769841ae92d3fc2a0c29e3b to your computer and use it in GitHub Desktop.
Save Frontear/45d2fa3da769841ae92d3fc2a0c29e3b to your computer and use it in GitHub Desktop.
A simple string obfuscation/deobfuscation python program. This was used as a non-secure, educational use only "encryption" for a school assignment.
import zlib
import base64
import hashlib
SEPARATOR = "\0"
ENCODING = "UTF-8"
HASHING = hashlib.blake2b
def encrypt(data: str, key: str) -> bytes:
arr = []
data = list(data)
key_hash = HASHING(key.encode(ENCODING)).hexdigest()
arr.append(key_hash)
for i in range(len(data)):
char, shift = ord(data[i]), ord(key[i % len(key)])
enc = hex(char + shift)
arr.append(enc)
return base64.standard_b64encode(zlib.compress(SEPARATOR.join(arr).encode(ENCODING)))
def decrypt(data: bytes, key: str) -> str:
arr = []
data = zlib.decompress(base64.standard_b64decode(data)).decode(ENCODING).split(SEPARATOR)
key_hash = HASHING(key.encode(ENCODING)).hexdigest()
if key_hash == data[0]:
for i in range(len(data := data[1:])):
char, shift = int(data[i], 16), ord(key[i % len(key)])
dec = chr(char - shift)
arr.append(dec)
else:
raise InterruptedError("Given password was incorrect. Cannot decrypt")
return "".join(arr)
if __name__ == "__main__":
import string
data = string.printable
password = "ali4588"
with open("test.enc", "wb+") as f:
f.write(a := encrypt(data, password))
print(a)
f.seek(0)
print(d := decrypt(f.read(), password)); assert d == data
# len(b) > len(c)
print(a := encrypt(b := "a ", c := "a"), d := decrypt(a, c)); assert b == d
print(a := encrypt(b := "a ", c := "1"), d := decrypt(a, c)); assert b == d
print(a := encrypt(b := "1 ", c := "a"), d := decrypt(a, c)); assert b == d
print(a := encrypt(b := "1 ", c := "1"), d := decrypt(a, c)); assert b == d
print(a := encrypt(b := "a1", c := "a"), d := decrypt(a, c)); assert b == d
print(a := encrypt(b := "a1", c := "1"), d := decrypt(a, c)); assert b == d
#len(b) == len(c)
print(a := encrypt(b := "a ", c := "a1"), d := decrypt(a, c)); assert b == d
print(a := encrypt(b := "a ", c := "1a"), d := decrypt(a, c)); assert b == d
print(a := encrypt(b := "1 ", c := "a1"), d := decrypt(a, c)); assert b == d
print(a := encrypt(b := "1 ", c := "1a"), d := decrypt(a, c)); assert b == d
print(a := encrypt(b := "a1", c := "a1"), d := decrypt(a, c)); assert b == d
print(a := encrypt(b := "a1", c := "1a"), d := decrypt(a, c)); assert b == d
#len(b) < len(c)
print(a := encrypt(b := " ", c := "a1"), d := decrypt(a, c)); assert b == d
print(a := encrypt(b := " ", c := "1a"), d := decrypt(a, c)); assert b == d
print(a := encrypt(b := "a", c := "a1"), d := decrypt(a, c)); assert b == d
print(a := encrypt(b := "a", c := "1a"), d := decrypt(a, c)); assert b == d
print(a := encrypt(b := "1", c := "a1"), d := decrypt(a, c)); assert b == d
print(a := encrypt(b := "1", c := "1a"), d := decrypt(a, c)); assert b == d
@Frontear
Copy link
Author

Frontear commented Oct 5, 2021

The way this program works is fairly trivial. First, it requires data in the form of a string, and a password to secure said data. It hashes the password using the blake2b algo by default, storing its hexdigest() for later usage. It then makes use of a cipher pattern similar to the vigenère cipher, creating a variation of the character shift table, by first obtaining the ord of each character, obtaining the corresponding ord character of the password (examples for reference below), then add the ordinal values of the characters, hex them, and then append them to an array. The array also, at the 0th index, contains the hexdigest of the key, for comparision later on. Finally, the array is joined into a string separated by SEPARATOR, encoded via ENCODING, compressed through the zlib module, and then encoded via base64.

e.g.

data: HELLOWORLD1234
key: LIME14

pairings: H L, E I, L M, L E, O 1, W 4, O L, R I, L M, D E, 1 1, 2 4, 3 L, 4 I

The decryption process follows the exact reverse of the encryption process. It first decompresses the data, decodes it, and splits on the SEPARATOR. It first checks the hexdigest in the first element. If the comparison fails, it silent fails with an empty string as its return data. Otherwise, it obtains the ordinal number from the data, the corresponding key character ordinal, and subtracts to obtain the original character ordinal. It then converts that into an actual character, appending it to an array. Finally, the array is joined with no separator into the decrypted data.

The code above contains a large chunk of testing code used to validify the operation of the code via various input lengths. It also attempts to write and read from a file (largely due to the fact that this was a part of the assignment constraints.)

Please note that this is not secure in any capacity. It cannot cryptographically secure any data. The only reason I ended up realistically writing this code was a) to understand and experiment with obfuscating strings, and b) because the assignment constraints prohibited modules from outside the standard library. As such, I could not use any crypto libs, or otherwise this wouldn't have had to be written. Nonetheless, its good for future references and is actually not a terrible way to obfuscate data, as in the end, the data is only meant to not be legible by people, not be secured.

@Frontear
Copy link
Author

Decryption no longer silently fails, instead Errors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment