Skip to content

Commit

Permalink
🗓 Jun 29, 2023 7:04:54 PM
Browse files Browse the repository at this point in the history
✨ extract_zero_with_chars
➕ deps added/updated
✨ pretty exception handling
🔥 moved to python 3.10
💚 build steps added/updated
🚀 updated swap_endianness to reflect cyberchef
✨ rot_8000
✨ encode_us_ascii_7_bit
🚀 update vigener encode and decode
  • Loading branch information
securisec committed Jun 29, 2023
1 parent 56d7154 commit a589ff7
Show file tree
Hide file tree
Showing 17 changed files with 238 additions and 63 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/tests_multi_os.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
python-version:
- "3.8"
- "3.10"
# - "3.9"

steps:
Expand Down Expand Up @@ -40,7 +40,7 @@ jobs:
- name: Install test requirements
run: |
pip install sphinx recommonmark pytest==6.2.1 pytest-cov bandit pyperclip
pip install sphinx recommonmark pytest==7.4.0 pytest-cov bandit pyperclip
- name: Test with pytest
run: |
Expand Down
24 changes: 5 additions & 19 deletions TODO
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,11 @@ New ideas:
☐ append method for core to add data to the state
☐ qr create
☐ random from state
☐ rot8000
☐ us-ascii 7bit 20127 https://gchq.github.io/CyberChef/#recipe=Encode_text('US-ASCII%20(7-bit)%20(20127)') 걳걵걮걻걢갴걳갳걟갱갲갸걟갱갵걟걢갱건걟걲갳걭갴거거갱걮걧걽
☐ vigenere make aware of all cases/numbers/specials. i.e. npcdhzaon{a4Rmp!_K1N5q0p_4vQfKkT1uA3R} key victory shaktictf{y4Yyy!_M1S5i0n_4cCoMpL1sH3D}
☐ ascii shift cipher kqfl ? snyjHYK"8fwymdxf~xdm8qq5$ = niteCTF{3arth_says_h3ll0} somewhat
☐ ✨ affine bruteforce
☐ ✨ zero-width encode/decode
☐ ✨ hill cipher encode/decode/brute
☐ 💡 maybe a decorator function to convert all inputs into bytes when possible? this will allow for a consistant bytes approach to all functions

Bug:

Expand Down Expand Up @@ -55,26 +53,14 @@ Github Actions:
Distribution:
☐ request to add to kali
☐ request to add to brew
☐ request to add to apt
☐ request to add to aur

Misc:
☐ cyberchef recipe to chepy recipe converter

Archive:
✔ ✨ extractor partially done
✔ vigenere make aware of all cases/numbers/specials. i.e. npcdhzaon{a4Rmp!_K1N5q0p_4vQfKkT1uA3R} key victory shaktictf{y4Yyy!_M1S5i0n_4cCoMpL1sH3D}
✔ us-ascii 7bit 20127 https://gchq.github.io/CyberChef/#recipe=Encode_text('US-ASCII%20(7-bit)%20(20127)') 걳걵걮걻걢갴걳갳걟갱갲갸걟갱갵걟걢갱건걟걲갳걭갴거거갱걮걧걽
✔ rot8000
✔ base decode all bases
✔ rename base64 function to from_basexx
✔ ✨ cha cha encode, decode
✔ ✨ monoalphabetic substitution
✔ ✨ from nato
✔ for join_by handle int
✔ ip from int and vice versa @project(New ideas)
✔ subsection. regex select from state and run all subsequent operations on the selected data @project(New ideas)
✔ base 91 @project(Methods)
✔ remove unprintable method @project(New ideas)
✔ qr plugin with aztec, barcodes, qr etc @project(New ideas)
✔ 🔥 combine all encoding/decoding methods @project(New ideas)
✔ translate function that takes a string the converts the state @project(New ideas)
✔ split by newline @project(New ideas)
✔ string to leetcode @project(New ideas)

1 change: 1 addition & 0 deletions chepy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import pretty_errors
from docstring_parser import parse as _doc_parse

from .modules.aritmeticlogic import AritmeticLogic
Expand Down
25 changes: 17 additions & 8 deletions chepy/modules/dataformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -1342,18 +1342,27 @@ def remove_nonprintable(self, replace_with: bytes = b"") -> DataFormatT:
return self

@ChepyDecorators.call_stack
def swap_endianness(self) -> DataFormatT:
# TODO make this better. this is not inline with cyberchef
"""Swap endianness of a hex string.
def swap_endianness(self, word_length: int = 4) -> DataFormatT:
"""Swap endianness.
Args:
word_length (int, optional): Word length. Use 8 for big endian. Defaults to 4.
Returns:
Chepy: The Chepy object.
"""
data = self._convert_to_bytes()
# check if hex
if not re.match(b"^[0-9a-fA-F]+$", data): # pragma: no cover
raise ValueError("Data is not hex")
self.state = hex(struct.unpack("<I", struct.pack(">I", int(data, 16)))[0])[2:]
num_bytes = len(data)
padding_length = (word_length - num_bytes) % word_length
padded_data = data + b"\x00" * padding_length

swapped_data = b""
for i in range(0, len(padded_data), word_length):
word = padded_data[i : i + word_length]
swapped_word = struct.unpack("<" + "B" * word_length, word)[::-1]
swapped_data += struct.pack("B" * word_length, *swapped_word)

self.state = swapped_data
return self

@ChepyDecorators.call_stack
Expand Down Expand Up @@ -1401,4 +1410,4 @@ def bytes_to_long(self) -> DataFormatT:
"""
d = self._convert_to_bytes()
self.state = crypto_number.bytes_to_long(d)
return self
return self
2 changes: 1 addition & 1 deletion chepy/modules/dataformat.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class DataFormat(ChepyCore):
def remove_nonprintable(self: DataFormatT, replace_with: bytes = ...): ...
def to_base91(self: DataFormatT) -> DataFormatT: ...
def from_base91(self: DataFormatT) -> DataFormatT: ...
def swap_endianness(self: DataFormatT) -> DataFormatT: ...
def swap_endianness(self: DataFormatT, word_length: int=...) -> DataFormatT: ...
def bruteforce_from_base_xx(self: DataFormatT) -> DataFormatT: ...
def long_to_bytes(self: DataFormatT) -> DataFormatT: ...
def bytes_to_long(self: DataFormatT) -> DataFormatT: ...
157 changes: 139 additions & 18 deletions chepy/modules/encryptionencoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,76 @@ def rot_47_bruteforce(self) -> EncryptionEncodingT:
self.state = hold
return self

@ChepyDecorators.call_stack
def rot_8000(self):
"""Rot8000
Returns:
Chepy: The Chepy object.
"""
data = self._convert_to_str()
valid_code_points = {
33: True,
127: False,
161: True,
5760: False,
5761: True,
8192: False,
8203: True,
8232: False,
8234: True,
8239: False,
8240: True,
8287: False,
8288: True,
12288: False,
12289: True,
55296: False,
57344: True,
}

BMP_SIZE = 0x10000

rotlist = {} # the mapping of char to rotated char
hiddenblocks = []
startblock = 0

for key, value in valid_code_points.items():
if value:
hiddenblocks.append({"start": startblock, "end": key - 1})
else:
startblock = key

validintlist = [] # list of all valid chars
currvalid = False

for i in range(BMP_SIZE):
if i in valid_code_points:
currvalid = valid_code_points[i]
if currvalid:
validintlist.append(i)

rotatenum = len(validintlist) // 2

# go through every valid char and find its match
for i in range(len(validintlist)):
rotlist[chr(validintlist[i])] = chr(
validintlist[(i + rotatenum) % (rotatenum * 2)]
)

outstring = ""

for char in data:
# if it is not in the mappings list, just add it directly (no rotation)
if char not in rotlist:
outstring += char # pragma: no cover
continue # pragma: no cover

# otherwise, rotate it and add it to the string
outstring += rotlist[char]

return outstring

@ChepyDecorators.call_stack
def xor(
self,
Expand Down Expand Up @@ -893,36 +963,87 @@ def blowfish_decrypt(

@ChepyDecorators.call_stack
def vigenere_encode(self, key: str) -> EncryptionEncodingT:
"""Encode with Vigenere ciper
"""Vigenere encode
Args:
key (str): Required. The secret key
key (str): Key
Returns:
Chepy: The Chepy oject.
Raises:
ValueError: Key is not alpha
ValueError: Key is not provided
Examples:
>>> Chepy("secret").vigenere_encode("secret").o
"KIEIIM"
Returns:
Chepy: The Chepy object.
"""
self.state = pycipher.Vigenere(key=key).encipher(self._convert_to_str())
input_str = self._convert_to_str()
alphabet = "abcdefghijklmnopqrstuvwxyz"
key = key.lower()
output = ""
fail = 0

if not key:
raise ValueError("No key entered") # pragma: no cover
if not key.isalpha():
raise ValueError("The key must consist only of letters") # pragma: no cover

for i in range(len(input_str)):
if input_str[i].isalpha():
is_upper = input_str[i].isupper()
input_char = input_str[i].lower()
key_char = key[(i - fail) % len(key)]
key_index = alphabet.index(key_char)
input_index = alphabet.index(input_char)
encoded_index = (key_index + input_index) % 26
encoded_char = alphabet[encoded_index]
output += encoded_char.upper() if is_upper else encoded_char
else:
output += input_str[i]
fail += 1

self.state = output
return self

@ChepyDecorators.call_stack
def vigenere_decode(self, key: str) -> EncryptionEncodingT:
"""Decode Vigenere ciper
"""Vigenere decode
Args:
key (str): Required. The secret key
key (str): Key
Returns:
Chepy: The Chepy oject.
Raises:
ValueError: Key is not alpha
ValueError: Key is not provided
Examples:
>>> Chepy("KIEIIM").vigenere_decode("secret").o
"SECRET"
Returns:
Chepy: The Chepy object.
"""
self.state = pycipher.Vigenere(key=key).decipher(self._convert_to_str())
input_str = self._convert_to_str()
alphabet = "abcdefghijklmnopqrstuvwxyz"
output = ""
fail = 0

if not key:
raise ValueError("No key entered") # pragma: no cover
if not key.isalpha():
raise ValueError("The key must consist only of letters") # pragma: no cover

for i in range(len(input_str)):
if input_str[i].isalpha():
is_upper = input_str[i].isupper()
input_char = input_str[i].lower()
key_char = key[(i - fail) % len(key)]
key_index = alphabet.index(key_char)
input_index = alphabet.index(input_char)
encoded_index = (input_index - key_index + len(alphabet)) % len(
alphabet
)
encoded_char = alphabet[encoded_index]
output += encoded_char.upper() if is_upper else encoded_char
else:
output += input_str[i]
fail += 1

self.state = output
return self

@ChepyDecorators.call_stack
Expand Down Expand Up @@ -1048,11 +1169,11 @@ def from_morse_code(
if word_delim in chars:
chars = re.sub(word_delim, "", chars, re.I)
if morse_code_dict.get(chars) is not None:
decode += " " + morse_code_dict.get(chars, '')
decode += " " + morse_code_dict.get(chars, "")
else: # pragma: no cover
decode += " " + chars
else:
decode += morse_code_dict.get(chars, '')
decode += morse_code_dict.get(chars, "")
self.state = decode
return self

Expand Down
1 change: 1 addition & 0 deletions chepy/modules/encryptionencoding.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class EncryptionEncoding(ChepyCore):
def rot_13(self: EncryptionEncodingT) -> EncryptionEncodingT: ...
def rot_47(self: EncryptionEncodingT, amount: int=...) -> EncryptionEncodingT: ...
def rot_47_bruteforce(self: EncryptionEncodingT) -> EncryptionEncodingT: ...
def rot_8000(self: EncryptionEncodingT) -> EncryptionEncodingT: ...
def xor(self: EncryptionEncodingT, key: str, key_type: Literal['hex', 'utf', 'base64']=..., ascii: bool=...) -> EncryptionEncodingT: ...
def xor_bruteforce(self: EncryptionEncodingT, length: int=...) -> EncryptionEncodingT: ...
def jwt_decode(self: EncryptionEncodingT) -> EncryptionEncodingT: ...
Expand Down
28 changes: 26 additions & 2 deletions chepy/modules/extractors.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from binascii import unhexlify
from typing import TypeVar, Union
from urllib.parse import urlparse as _pyurlparse

Expand Down Expand Up @@ -441,7 +442,9 @@ def extract_base64(self, min: int = 20) -> ExtractorsT:
return self

@ChepyDecorators.call_stack
def find_continuous_patterns(self, str2: Union[str, bytes], min_value: int = 10):
def find_continuous_patterns(
self, str2: Union[str, bytes], min_value: int = 10
) -> ExtractorsT:
"""Find continius patterns between the state as a string and the provided str2
Args:
Expand Down Expand Up @@ -469,7 +472,7 @@ def find_continuous_patterns(self, str2: Union[str, bytes], min_value: int = 10)
return self

@ChepyDecorators.call_stack
def find_longest_continious_pattern(self, str2: str):
def find_longest_continious_pattern(self, str2: str) -> ExtractorsT:
"""Find longest continious pattern
Args:
Expand Down Expand Up @@ -498,3 +501,24 @@ def find_longest_continious_pattern(self, str2: str):

self.state = max(matches, key=len) if matches else ""
return self

@ChepyDecorators.call_stack
def extract_zero_width_chars(self) -> ExtractorsT:
"""Extract zero width characters between U+E0000 to U+E007F
Returns:
Chepy: The Chepy object.
"""
input_string = self._convert_to_str()
extracted_characters = []

for char in input_string:
if "\U000e0000" <= char <= "\U000e007f":
extracted_characters.append(char)

self.state = unhexlify(
b"".join(
[bytes(x.encode("unicode_escape"))[-2:] for x in extracted_characters]
)
)
return self
1 change: 1 addition & 0 deletions chepy/modules/extractors.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ class Extractors(ChepyCore):
def extract_base64(self: ExtractorsT, min: int=...) -> ExtractorsT: ...
def find_continuous_patterns(self: ExtractorsT, str2: Union[str, bytes], min_value: int=...) -> ExtractorsT: ...
def find_longest_continious_pattern(self: ExtractorsT, str2: Union[str, bytes]) -> ExtractorsT: ...
def extract_zero_width_chars(self: ExtractorsT) -> ExtractorsT: ...
11 changes: 11 additions & 0 deletions chepy/modules/language.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ def encode(self, encoding: str, errors: str = "backslashreplace") -> LanguageT:
self.state = self._convert_to_str().encode(encoding, errors=errors)
return self

@ChepyDecorators.call_stack
def encode_us_ascii_7_bit(self) -> LanguageT:
"""Encode state using US ascii 7 bit
Returns:
Chepy: The Chepy object.
"""
data = self._convert_to_str()
self.state = "".join(chr(ord(c) & 127) for c in data)
return self

@ChepyDecorators.call_stack
def decode(self, encoding: str, errors: str = "backslashreplace") -> LanguageT:
"""Decode the string using the given encoding.
Expand Down
Loading

0 comments on commit a589ff7

Please sign in to comment.