Skip to content

Commit

Permalink
πŸ—“ Jul 4, 2023 1:06:13 PM
Browse files Browse the repository at this point in the history
✨ crypto extras generate_rsa_keypair
✨ jwt_token_genereate_embedded_jwk
✨ rsa_private_pem_to_jwk
✨ rsa_public_key_from_jwk
πŸ”₯ rename extract_zero_with_chars
✨ decode_zero_width
πŸ§ͺ tests added/updated
  • Loading branch information
securisec committed Jul 4, 2023
1 parent 81b3de2 commit 7b6314f
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 14 deletions.
3 changes: 2 additions & 1 deletion TODO
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ New ideas:
☐ random from state
☐ ascii shift cipher kqfl ? snyjHYK"8fwymdxf~xdm8qq5$ = niteCTF{3arth_says_h3ll0} somewhat
☐ ✨ affine bruteforce
☐ ✨ zero-width encode/decode
☐ ✨ zero-width encode
☐ ✨ 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
☐ rsa enc/dec with key or from pem directly
☐ ✨ jwt hmac confusion

Bug:

Expand Down
28 changes: 27 additions & 1 deletion chepy/extras/crypto.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import json
import lazy_import
from typing import Iterator, Dict, List, Union
from binascii import hexlify, unhexlify
from itertools import cycle
from urllib.request import urlopen
from Crypto.PublicKey import RSA
RSA = lazy_import.lazy_module("Crypto.PublicKey.RSA")

from .combinatons import generate_combo, hex_chars
from chepy import Chepy
Expand Down Expand Up @@ -152,3 +153,28 @@ def one_time_pad_crib(
)
hold.append(piece)
return hold


def generate_rsa_keypair(
bits: int = 1024, passphrase: str = None
) -> Dict[str, dict]: # pragma: no cover
"""Generates an RSA keypair with the specified number of bits.
Args:
bits: The number of bits for the RSA keypair.
Returns:
A tuple of the RSA public key and RSA private key, both in PEM format.
"""

keypair = RSA.generate(bits)
return {
"pem": {
"public": keypair.publickey().exportKey("PEM"),
"private": keypair.exportKey("PEM", passphrase=passphrase),
},
"der": {
"public": keypair.publickey().exportKey("DER"),
"private": keypair.exportKey("DER", passphrase=passphrase),
},
}
3 changes: 2 additions & 1 deletion chepy/extras/crypto.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Dict, Iterator, Union
from typing import Dict, Iterator, Union, Any

def factordb(n: int) -> dict: ...
def construct_private_key(n: int, e: int, d: int, format: str=..., passphrase: str=...) -> str: ...
def xor_bruteforce_multi(data: str, min: int=..., max: int=..., errors: str=...) -> Iterator[Dict[str, str]]: ...
def xor_repeating_key(data1:bytes, data2: bytes, min: int =..., max: int = ...) -> Union[bytes, None]: ...
def generate_rsa_keypair(bits: int=..., passphrase: str=...) -> Dict[str, Dict[str, Any]] : ...
162 changes: 161 additions & 1 deletion chepy/modules/encryptionencoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,9 @@ def jwt_bruteforce(
return self

@ChepyDecorators.call_stack
def jwt_token_none_alg(self, headers: Dict[str, Any] = {}) -> EncryptionEncodingT:
def jwt_token_generate_none_alg(
self, headers: Dict[str, Any] = {}
) -> EncryptionEncodingT:
"""Generate a jwt token with none algorithem
Args:
Expand All @@ -453,6 +455,66 @@ def jwt_token_none_alg(self, headers: Dict[str, Any] = {}) -> EncryptionEncoding
self.state = encoded_headers + b"." + encoded_payload + b"."
return self

@ChepyDecorators.call_stack
def jwt_token_generate_embedded_jwk(
self,
private_key_pem: str,
private_key_passphrase: str = None,
headers: dict = {},
alg: str = "RS256",
) -> EncryptionEncodingT:
"""Generate a JWT token with an embedded JWK
Args:
private_key_pem (str): Private key to sign token
private_key_passphrase (str, optional): Private key passphrase. Defaults to None.
headers (dict, optional): Token headers. Defaults to {}.
alg (str, optional): Token algorithem. Defaults to "RS256".
Returns:
Chepy: The Chepy object.
"""
payload = self.state
assert isinstance(payload, dict), "State should be a dictionary"
private_key = RSA.import_key(private_key_pem, private_key_passphrase)

n = private_key.n
e = private_key.e

jwk_header = {
"kty": "RSA",
"e": base64.urlsafe_b64encode(e.to_bytes((e.bit_length() + 7) // 8, "big"))
.decode("utf-8")
.rstrip("="),
"n": base64.urlsafe_b64encode(n.to_bytes((n.bit_length() + 7) // 8, "big"))
.decode("utf-8")
.rstrip("="),
}
if headers.get("kid"):
jwk_header["kid"] = headers.get("kid")
headers["jwk"] = jwk_header
headers["alg"] = alg

encoded_header = (
base64.urlsafe_b64encode(bytes(json.dumps(headers), "utf-8"))
.decode("utf-8")
.rstrip("=")
)
encoded_payload = (
base64.urlsafe_b64encode(bytes(json.dumps(payload), "utf-8"))
.decode("utf-8")
.rstrip("=")
)

signature_input = f"{encoded_header}.{encoded_payload}".encode("utf-8")
hashed_input = Hash.SHA256.new(signature_input)
signature = PKCS1_15.new(private_key).sign(hashed_input)

token = f"{encoded_header}.{encoded_payload}.{base64.urlsafe_b64encode(signature).decode('utf-8').replace('=', '')}"

self.state = token
return self

@ChepyDecorators.call_stack
def rc4_encrypt(self, key: str, key_format: str = "hex") -> EncryptionEncodingT:
"""Encrypt raw state with RC4
Expand Down Expand Up @@ -1269,6 +1331,104 @@ def rsa_verify(
self.state = PKCS1_15.new(key).verify(h, signature)
return self

@ChepyDecorators.call_stack
def rsa_private_pem_to_jwk(self) -> EncryptionEncodingT:
"""Convert RSA PEM private key to jwk format
Returns:
Chepy: The Chepy object.
"""
# Load the PEM private key
private_key = RSA.import_key(self._convert_to_str())

n = private_key.n
e = private_key.e
d = private_key.d
p = private_key.p
q = private_key.q
dp = private_key.d % (p - 1)
dq = private_key.d % (q - 1)
qi = pow(q, -1, p)

n_base64url = (
base64.urlsafe_b64encode(n.to_bytes((n.bit_length() + 7) // 8, "big"))
.decode("utf-8")
.rstrip("=")
)
e_base64url = (
base64.urlsafe_b64encode(e.to_bytes((e.bit_length() + 7) // 8, "big"))
.decode("utf-8")
.rstrip("=")
)
d_base64url = (
base64.urlsafe_b64encode(d.to_bytes((d.bit_length() + 7) // 8, "big"))
.decode("utf-8")
.rstrip("=")
)
p_base64url = (
base64.urlsafe_b64encode(p.to_bytes((p.bit_length() + 7) // 8, "big"))
.decode("utf-8")
.rstrip("=")
)
q_base64url = (
base64.urlsafe_b64encode(q.to_bytes((q.bit_length() + 7) // 8, "big"))
.decode("utf-8")
.rstrip("=")
)
dp_base64url = (
base64.urlsafe_b64encode(dp.to_bytes((dp.bit_length() + 7) // 8, "big"))
.decode("utf-8")
.rstrip("=")
)
dq_base64url = (
base64.urlsafe_b64encode(dq.to_bytes((dq.bit_length() + 7) // 8, "big"))
.decode("utf-8")
.rstrip("=")
)
qi_base64url = (
base64.urlsafe_b64encode(qi.to_bytes((qi.bit_length() + 7) // 8, "big"))
.decode("utf-8")
.rstrip("=")
)

private = {
"p": p_base64url,
"kty": "RSA",
"q": q_base64url,
"d": d_base64url,
"e": e_base64url,
"qi": qi_base64url,
"dp": dp_base64url,
"dq": dq_base64url,
"n": n_base64url,
}

public = {"kty": "RSA", "e": e_base64url, "n": n_base64url}
self.state = {"private": private, "public": public}
return self

@ChepyDecorators.call_stack
def rsa_public_key_from_jwk(self) -> EncryptionEncodingT:
"""Genereate RSA public key in PEM format from JWK
Raises:
AssertionError: If n or e not found
Returns:
Chepy: The Chepy object.
"""
assert isinstance(self.state, dict), "State should be a dict"
jwk_key = self.state
if not "e" in jwk_key or "n" not in jwk_key:
raise AssertionError("e or n not found") # pragma: no cover
e = int.from_bytes(base64.urlsafe_b64decode(jwk_key["e"] + "=="), "big")
n = int.from_bytes(base64.urlsafe_b64decode(jwk_key["n"] + "=="), "big")

public_key = RSA.construct((n, e))

self.state = public_key.export_key().decode("utf-8")
return self

@ChepyDecorators.call_stack
def monoalphabetic_substitution(
self, mapping: Dict[str, str] = {}
Expand Down
7 changes: 5 additions & 2 deletions chepy/modules/encryptionencoding.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from ..core import ChepyCore
from typing import Any, TypeVar, Literal, Dict
from typing import Any, TypeVar, Literal, Dict, Union

jwt: Any
AES: Any
Expand All @@ -26,7 +26,8 @@ class EncryptionEncoding(ChepyCore):
def jwt_verify(self: EncryptionEncodingT, secret: str, algorithm: list=...) -> EncryptionEncodingT: ...
def jwt_sign(self: EncryptionEncodingT, secret: str, algorithms: str=...) -> EncryptionEncodingT: ...
def jwt_bruteforce(self: EncryptionEncodingT, wordlist: str, b64_encode: bool=..., algorithm: list=...) -> EncryptionEncodingT: ...
def jwt_token_none_alg(self: EncryptionEncodingT, headers: Dict[str, Any]=...) -> EncryptionEncodingT: ...
def jwt_token_generate_none_alg(self: EncryptionEncodingT, headers: Dict[str, Any]=...) -> EncryptionEncodingT: ...
def jwt_token_generate_embedded_jwk(self: EncryptionEncodingT, private_key_pem: str, private_key_passphrase: str = ..., headers: dict = ..., alg: str = Union["RS256", "RS512"]) -> EncryptionEncodingT: ...
def rc4_encrypt(self: EncryptionEncodingT, key: str, key_format: RC4_FORMAT=...) -> EncryptionEncodingT: ...
def rc4_decrypt(self: EncryptionEncodingT, key: str, key_format: RC4_FORMAT=...) -> EncryptionEncodingT: ...
def des_encrypt(self: EncryptionEncodingT, key: str, iv: str=..., mode: Literal["CBC", "OFB", "CTR", "ECB"]=..., key_format: FORMAT=..., iv_format: FORMAT=...) -> EncryptionEncodingT: ...
Expand All @@ -51,4 +52,6 @@ class EncryptionEncoding(ChepyCore):
def rsa_decrypt(self: EncryptionEncodingT, priv_key_path: str) -> EncryptionEncodingT: ...
def rsa_sign(self: EncryptionEncodingT, priv_key_path: str) -> EncryptionEncodingT: ...
def rsa_verify(self: EncryptionEncodingT, signature: bytes, public_key_path: str) -> EncryptionEncodingT: ...
def rsa_private_pem_to_jwk(self: EncryptionEncodingT) -> EncryptionEncodingT: ...
def rsa_public_key_from_jwk(self: EncryptionEncodingT) -> EncryptionEncodingT: ...
def monoalphabetic_substitution(self: EncryptionEncodingT, mapping: Dict[str, str]=...): ...
61 changes: 59 additions & 2 deletions chepy/modules/extractors.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import math
from binascii import unhexlify
from typing import TypeVar, Union
from urllib.parse import urlparse as _pyurlparse

import regex as re
import re as old_re

from ..core import ChepyCore, ChepyDecorators

ExtractorsT = TypeVar("ExtractorsT", bound="Extractors")

_zw_chars = []
_zw_codelengthText = 0
_zw_radix = 0


class Extractors(ChepyCore):
def __init__(self, *data):
Expand Down Expand Up @@ -503,8 +509,9 @@ def find_longest_continious_pattern(self, str2: str) -> ExtractorsT:
return self

@ChepyDecorators.call_stack
def extract_zero_width_chars(self) -> ExtractorsT:
"""Extract zero width characters between U+E0000 to U+E007F
def extract_zero_width_chars_tags(self) -> ExtractorsT:
"""Extract zero width characters between U+E0000 to U+E007F. Implements
https://www.irongeek.com/i.php?page=security/unicode-steganography-homoglyph-encoder
Returns:
Chepy: The Chepy object.
Expand All @@ -522,3 +529,53 @@ def extract_zero_width_chars(self) -> ExtractorsT:
)
)
return self

@ChepyDecorators.call_stack
def decode_zero_width(
self, _zw_chars: str = "\u200c\u200d\u202c\ufeff"
) -> ExtractorsT:
"""Extract zero with characters. Decode implementation of
https://330k.github.io/misc_tools/unicode_steganography.html
Args:
chars (str, optional): Characters for stego. Defaults to '\u200c\u200d\u202c\ufeff'.
Returns:
Chepy: The Chepy object.
"""

def set_use_chars(newchars):
global _zw_chars, _zw_radix, _zw_codelengthText
if len(newchars) >= 2:
_zw_chars = list(newchars)
_zw_radix = len(_zw_chars)
_zw_codelengthText = math.ceil(math.log(65536) / math.log(_zw_radix))
return None

def split_zerowidth_characters(str1):
result = {}
result["originalText"] = old_re.sub("[" + "".join(_zw_chars) + "]", "", str1)
result["hiddenText"] = old_re.sub("[^" + "".join(_zw_chars) + "]", "", str1)
return result

def decode_from_zero_width_characters_text(str1):
r = str1
result = []
for i in range(_zw_radix):
r = r.replace(_zw_chars[i], str(i))
for i in range(0, len(r), _zw_codelengthText):
result.append(chr(int(r[i : i + _zw_codelengthText], _zw_radix)))
return "".join(result)

def decodeText(text):
splitted = split_zerowidth_characters(text)
return {
# 'originalText': splitted['originalText'],
"hidden": decode_from_zero_width_characters_text(
splitted["hiddenText"]
) # , codelengthText)
}

set_use_chars(_zw_chars)
self.state = decodeText(self._convert_to_str())
return self
3 changes: 2 additions & 1 deletion chepy/modules/extractors.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ 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: ...
def extract_zero_width_chars_tags(self: ExtractorsT) -> ExtractorsT: ...
def decode_zero_width(self: ExtractorsT, chars: str=...) -> ExtractorsT: ...
Loading

0 comments on commit 7b6314f

Please sign in to comment.