From 326b83bec2140c0d402896fbd0dc96a0f98d3d64 Mon Sep 17 00:00:00 2001 From: securisec Date: Fri, 3 Jan 2025 20:56:48 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=97=93=20Jan=203,=202025=208:53:05?= =?UTF-8?q?=E2=80=AFPM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit โœจ out_as_any to output in the current format the state is in ๐Ÿ”ฅ bytes_to_ascii renamed to list_to_bytes to handle byte ints ๐Ÿ™ update str_to_hex ๐Ÿ”ฅ str_to_list -> to_list to handle bytes and bytearrays ๐Ÿ™ xor updated to include raw key_type ๐Ÿค– types added/updated ๐Ÿงช tests added/updated --- chepy/core.py | 9 ++++ chepy/core.pyi | 2 +- chepy/modules/dataformat.py | 74 +++++++++++++++++++++++++--- chepy/modules/dataformat.pyi | 9 ++-- chepy/modules/encryptionencoding.py | 24 ++++++--- chepy/modules/encryptionencoding.pyi | 2 +- tests/test_core.py | 3 +- tests/test_dataformat.py | 23 ++++++--- tests/test_encryptionencoding.py | 5 ++ 9 files changed, 124 insertions(+), 27 deletions(-) diff --git a/chepy/core.py b/chepy/core.py index 7e51a14..b6f326e 100644 --- a/chepy/core.py +++ b/chepy/core.py @@ -714,6 +714,15 @@ def o(self): return self.state.encode() return self.state + @property + def out_as_any(self): + """Get the final output + + Returns: + Any: Final output + """ + return self.state + @property def out(self) -> Any: """Get the final output diff --git a/chepy/core.pyi b/chepy/core.pyi index 3ecaa85..6c4d1f9 100644 --- a/chepy/core.pyi +++ b/chepy/core.pyi @@ -52,7 +52,7 @@ class ChepyCore: def o(self): ... @property def out(self: ChepyCoreT) -> ChepyCoreT: ... - def out_as_str(self: ChepyCoreT) -> str: ... + def out_as_any(self: ChepyCoreT) -> str: ... def get_by_index(self: ChepyCoreT, *indexes: int) -> ChepyCoreT: ... def get_by_key(self: ChepyCoreT, *keys: Union[str, bytes], py_style: bool=False, split_key: Union[str, None] = '.') -> ChepyCoreT: ... def copy_to_clipboard(self: ChepyCoreT) -> None: ... diff --git a/chepy/modules/dataformat.py b/chepy/modules/dataformat.py index dda12b6..487a577 100644 --- a/chepy/modules/dataformat.py +++ b/chepy/modules/dataformat.py @@ -63,14 +63,20 @@ def eval_state(self) -> DataFormatT: return self @ChepyDecorators.call_stack - def bytes_to_ascii(self) -> DataFormatT: + def list_to_bytes(self, ascii: bool = False) -> DataFormatT: """Convert bytes (array of bytes) to ascii + Args: + ascii (bool, optional): Output as ascii, by default False + Returns: Chepy: The Chepy object. """ assert isinstance(self.state, list), "Data in state is not a list" - self.state = bytearray(self.state).decode() + if ascii: + self.state = bytearray(self.state).decode() + else: + self.state = bytes(bytearray(self.state)) return self @ChepyDecorators.call_stack @@ -729,13 +735,19 @@ def hex_to_str(self, ignore: bool = False) -> DataFormatT: return self @ChepyDecorators.call_stack - def str_to_hex(self) -> DataFormatT: + def str_to_hex(self, delimiter: Union[str, bytes] = b"") -> DataFormatT: """Converts a string to a hex string + Args: + delimiter (Union[str, bytes], optional): Format delimiter. Defaults to b' + Returns: Chepy: The Chepy object. """ - self.state = binascii.hexlify(self._convert_to_bytes()) + data = self._convert_to_bytes() + delimiter = self._bytes_to_str(delimiter) + out = delimiter + delimiter.join(f"{byte:02x}" for byte in data) + self.state = out return self @ChepyDecorators.call_stack @@ -903,7 +915,7 @@ def bytearray_to_str( raise TypeError("State is not a bytearray") @ChepyDecorators.call_stack - def str_to_list(self) -> DataFormatT: + def to_list(self) -> DataFormatT: """Convert string to list Converts the string in state to an array of individual characyers @@ -912,10 +924,13 @@ def str_to_list(self) -> DataFormatT: Chepy: The Chepy object. Examples: - >>> Chepy("abc").str_to_list().o + >>> Chepy("abc").to_list().o ["a", "b", "c"] """ - self.state = list(self._convert_to_str()) + if isinstance(self.state, (bytearray, bytes, str)): + self.state = list(self.state) + else: # pragma: no cover + raise ValueError("State is not a valid data type") return self @ChepyDecorators.call_stack @@ -1365,6 +1380,51 @@ def from_nato( self.state = join_by.join([d.get(p, p) for p in data]) return self + @ChepyDecorators.call_stack + def swap_values( + self, indices1: Union[str, List[int]], indices2: Union[str, List[int]] + ): + """Swaps the values in the global bytes based on indices specified in two lists. + + Args: + indices1 (Union[str, List[int]]): The first indices. If string, it should be , delimited + indices2 (Union[str, List[int]]): The second indices. If string, it should be , delimited + + Returns: + Chepy: The Chepy object. + """ + if isinstance(indices1, str): + indices1 = [int(x.strip()) for x in indices1.split(",")] + if isinstance(indices2, str): + indices2 = [int(x.strip()) for x in indices2.split(",")] + + is_list = isinstance(self.state, list) + if is_list: + data = self.state + else: + data = bytearray(self._convert_to_bytes()) + + # Check if both lists are of the same length + if len(indices1) != len(indices2): # pragma: no cover + raise ValueError("The two argument lists must have the same length.") + + # Swap values in the byte list + for idx1, idx2 in zip(indices1, indices2): + # Validate indices are within bounds + if ( + idx1 < 0 or idx1 >= len(data) or idx2 < 0 or idx2 >= len(data) + ): # pragma: no cover + raise IndexError( + f"Index out of range. Lenth of state is {len(data)}, indices provided {idx1} {idx2}" + ) + + # Perform the swap + data[idx1], data[idx2] = data[idx2], data[idx1] + + # Convert the modified list back to bytes + self.state = data if is_list else bytes(data) + return self + @ChepyDecorators.call_stack def swap_strings(self, by: int) -> DataFormatT: """Swap characters in string diff --git a/chepy/modules/dataformat.pyi b/chepy/modules/dataformat.pyi index 08a655b..ed33796 100644 --- a/chepy/modules/dataformat.pyi +++ b/chepy/modules/dataformat.pyi @@ -1,5 +1,5 @@ from ..core import ChepyCore -from typing import Any, Literal, TypeVar, Union +from typing import Any, Literal, TypeVar, Union, List yaml: Any DataFormatT = TypeVar('DataFormatT', bound='DataFormat') @@ -8,7 +8,7 @@ class DataFormat(ChepyCore): def __init__(self, *data: Any) -> None: ... state: Any = ... def eval_state(self: DataFormatT) -> DataFormatT: ... - def bytes_to_ascii(self: DataFormatT) -> DataFormatT: ... + def list_to_bytes(self: DataFormatT, ascii: bool=False) -> DataFormatT: ... def list_to_str(self: DataFormatT, join_by: Union[str, bytes]=...) -> DataFormatT: ... def str_list_to_list(self: DataFormatT) -> DataFormatT: ... def join(self: DataFormatT, join_by: Union[str, bytes]=...) -> DataFormatT: ... @@ -36,7 +36,7 @@ class DataFormat(ChepyCore): def hex_to_int(self: DataFormatT) -> DataFormatT: ... def hex_to_bytes(self: DataFormatT) -> DataFormatT: ... def hex_to_str(self: DataFormatT, ignore: bool=...) -> DataFormatT: ... - def str_to_hex(self: DataFormatT) -> DataFormatT: ... + def str_to_hex(self: DataFormatT, delimiter: Union[str, bytes]=b'') -> DataFormatT: ... def int_to_hex(self: DataFormatT) -> DataFormatT: ... def int_to_str(self: DataFormatT) -> DataFormatT: ... def binary_to_hex(self: DataFormatT) -> DataFormatT: ... @@ -47,7 +47,7 @@ class DataFormat(ChepyCore): def from_url_encoding(self: DataFormatT) -> DataFormatT: ... def to_url_encoding(self: DataFormatT, safe: str=..., all_chars: bool=...) -> DataFormatT: ... def bytearray_to_str(self: DataFormatT, encoding: str=..., errors: str=...) -> DataFormatT: ... - def str_to_list(self: DataFormatT) -> DataFormatT: ... + def to_list(self: DataFormatT) -> DataFormatT: ... def str_to_dict(self: DataFormatT) -> DataFormatT: ... def to_charcode(self: DataFormatT, join_by: str=..., base: int=...) -> DataFormatT: ... def from_charcode(self: DataFormatT, delimiter: Union[str, None]=None, join_by: str=' ', base: int=10) -> DataFormatT: ... @@ -68,6 +68,7 @@ class DataFormat(ChepyCore): def trim(self: DataFormatT) -> DataFormatT: ... def to_nato(self: DataFormatT, join_by:str=...) -> DataFormatT: ... def from_nato(self: DataFormatT, delimiter: Union[str, None]=None, join_by: str=...) -> DataFormatT: ... + def swap_values(self: DataFormatT, indices1: Union[str, List[int]], indices2: Union[str, List[int]]) -> DataFormatT: ... def swap_strings(self: DataFormatT, by:int) -> DataFormatT: ... def to_string(self: DataFormatT) -> DataFormatT: ... def stringify(self: DataFormatT, compact:bool=...) -> DataFormatT: ... diff --git a/chepy/modules/encryptionencoding.py b/chepy/modules/encryptionencoding.py index 65ad02f..b0f4324 100644 --- a/chepy/modules/encryptionencoding.py +++ b/chepy/modules/encryptionencoding.py @@ -313,9 +313,10 @@ def rot_8000(self): def xor( self, key: str, - key_type: Literal["hex", "utf8", "base64", "decimal"] = "hex", + key_type: Literal["hex", "utf8", "base64", "decimal", "raw"] = "hex", ) -> EncryptionEncodingT: - """XOR state with a key + """XOR state with a key. In raw format, both the state and the key is converted + to a bytearray. Args: key (str): Required. The key to xor by @@ -330,8 +331,18 @@ def xor( """ x = bytearray(b"") + + if key_type == "raw": + _data_ba = bytearray() + _data = self._convert_to_str() + _data_ba.extend(map(ord, _data)) + raw_key = bytearray() + raw_key.extend(map(ord, key)) + for char, key_val in zip(_data_ba, itertools.cycle(raw_key)): + x.append(char ^ key_val) + # check if state is a list and keys are list - if isinstance(self.state, bytearray) and isinstance(key, bytearray): + elif isinstance(self.state, bytearray) and isinstance(key, bytearray): for char, key_val in zip(self.state, itertools.cycle(key)): x.append(char ^ key_val) @@ -1216,9 +1227,10 @@ def atbash(self) -> EncryptionEncodingT: >>> Chepy("secret").atbash_encode().o "HVXIVG" """ - key = 'ZYXWVUTSRQPONMLKJIHGFEDCBA' + # Reference https://github.com/jameslyons/pycipher/blob/master/pycipher/atbash.py + key = "ZYXWVUTSRQPONMLKJIHGFEDCBA" data = self._convert_to_str() - ret = '' + ret = "" arr = Ciphers.ATBASH for c in data: if c.isalpha(): @@ -1226,7 +1238,7 @@ def atbash(self) -> EncryptionEncodingT: ret += key[arr[c.upper()]].lower() else: ret += key[arr[c]] - else: + else: ret += c self.state = ret # self.state = pycipher.Atbash().encipher(self._convert_to_str(), keep_punct=True) diff --git a/chepy/modules/encryptionencoding.pyi b/chepy/modules/encryptionencoding.pyi index e7c2376..facfa63 100644 --- a/chepy/modules/encryptionencoding.pyi +++ b/chepy/modules/encryptionencoding.pyi @@ -20,7 +20,7 @@ class EncryptionEncoding(ChepyCore): def rot_47(self: EncryptionEncodingT, amount: int=47) -> EncryptionEncodingT: ... def rot_47_bruteforce(self: EncryptionEncodingT) -> EncryptionEncodingT: ... def rot_8000(self: EncryptionEncodingT) -> EncryptionEncodingT: ... - def xor(self: EncryptionEncodingT, key: Union[str, bytearray], key_type: Literal['hex', 'utf8', 'base64', 'decimal']='hex') -> EncryptionEncodingT: ... + def xor(self: EncryptionEncodingT, key: Union[str, bytearray], key_type: Literal['hex', 'utf8', 'base64', 'decimal', 'raw']='hex') -> EncryptionEncodingT: ... def xor_bruteforce(self: EncryptionEncodingT, length: int=..., crib: Union[str, bytes, None]=...) -> EncryptionEncodingT: ... def jwt_decode(self: EncryptionEncodingT) -> EncryptionEncodingT: ... def jwt_verify(self: EncryptionEncodingT, secret: str, algorithm: list=...) -> EncryptionEncodingT: ... diff --git a/tests/test_core.py b/tests/test_core.py index c34bac2..9e8793f 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -57,7 +57,8 @@ def test_fork(): def test_save_buffer(): c = Chepy("A").save_buffer(0).str_to_hex().save_buffer(1) assert c.buffers[0] == "A" - assert c.buffers[1] == b"41" + assert c.buffers[1] == "41" + assert Chepy('ffffd64c').from_hex().swap_endianness().str_to_hex('\\x').out_as_any == '\\x4c\\xd6\\xff\\xff' def test_load_buffer(): diff --git a/tests/test_dataformat.py b/tests/test_dataformat.py index d0de39c..809a56f 100644 --- a/tests/test_dataformat.py +++ b/tests/test_dataformat.py @@ -13,8 +13,9 @@ def test_base16_decode(): assert Chepy("74657374").from_base16().o == b"test" -def test_bytes_to_ascii(): - assert Chepy([116, 101, 115, 116]).bytes_to_ascii().o == b"test" +def test_list_to_bytes(): + assert Chepy([116, 101, 115, 116]).list_to_bytes().o == b"test" + assert Chepy([116, 101, 115, 116]).list_to_bytes(True).o == b"test" def test_dict_to_json(): @@ -225,7 +226,7 @@ def test_from_url_encoding(): ) -def test_to_list(): +def test_str_list_to_list(): assert Chepy("[1,2,'lol', true]").str_list_to_list().o == [1, 2, "lol", True] @@ -241,7 +242,7 @@ def test_join(): def test_to_int(): assert Chepy("1").to_int().o == 1 - assert Chepy('AQAB').from_base64().to_int().o == 65537 + assert Chepy("AQAB").from_base64().to_int().o == 65537 def test_normalize_hex(): @@ -270,8 +271,10 @@ def test_from_bytes(): ) -def test_str_to_list(): - assert Chepy("abc").str_to_list().o == ["a", "b", "c"] +def test_to_list(): + assert Chepy("abc").to_list().o == ["a", "b", "c"] + assert Chepy(bytearray(b"abc")).to_list().o == [97, 98, 99] + assert Chepy(b"abc").to_list().o == [97, 98, 99] def test_str_to_dict(): @@ -444,6 +447,12 @@ def test_nato_convert(): ) +def test_swap_values(): + assert Chepy("abcd").swap_values("0", "3").o == b"dbca" + assert Chepy("abcd").swap_values([0, 1], [3, 2]).o == b"dcba" + assert Chepy([1, 2, 3]).swap_values("0", "2").o == [3, 2, 1] + + def test_swap_strings(): assert Chepy("oY u").swap_strings(2).o == b"You " @@ -463,7 +472,7 @@ def test_stringify(): def test_select(): assert Chepy("abcd").select(0, 2).o == b"ab" assert Chepy("abcd").select(2).o == b"cd" - assert Chepy("abcd").select('(b|c)').o == b"bcd" + assert Chepy("abcd").select("(b|c)").o == b"bcd" def test_length(): diff --git a/tests/test_encryptionencoding.py b/tests/test_encryptionencoding.py index 4a31fa1..2063645 100644 --- a/tests/test_encryptionencoding.py +++ b/tests/test_encryptionencoding.py @@ -69,6 +69,10 @@ def test_xor_hex(): assert Chepy("some data").xor("5544", "hex").o == b"&+8!u 404" +def test_xor_raw(): + assert Chepy("ab\xffcd").xor("\x31\x03", "raw").o == b"Pa\xce`U" + + def test_xor_binary(): assert ( Chepy("./tests/files/hello") @@ -691,6 +695,7 @@ def test_affine_decode(): def test_atbash_encode(): assert Chepy("AbCd1.2").atbash().o == b"ZyXw1.2" + assert Chepy("zbCc;oo?|c;oAp9P%").atbash().atbash().o == b"zbCc;oo?|c;oAp9P%" def test_to_morse_code():