Skip to content

Commit 5c8581d

Browse files
committed
Add Hacky Encrypt mode
Added a hacky encryption mode. Duplicated the wrapper and callback functions so they can be modified for encryption mode if needed.
1 parent 3977769 commit 5c8581d

File tree

2 files changed

+62
-12
lines changed

2 files changed

+62
-12
lines changed

src/padding_oracle/legacy.py

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,21 @@
3333
'padding_oracle',
3434
]
3535

36-
37-
def padding_oracle(ciphertext: Union[bytes, str],
36+
def padding_oracle(payload: Union[bytes, str],
3837
block_size: int,
3938
oracle: OracleFunc,
4039
num_threads: int = 1,
4140
log_level: int = logging.INFO,
4241
null_byte: bytes = b' ',
4342
return_raw: bool = False,
43+
mode: Union[bool, str] = 'encrypt',
4444
) -> Union[bytes, List[int]]:
4545
'''
4646
Run padding oracle attack to decrypt ciphertext given a function to check
4747
wether the ciphertext can be decrypted successfully.
4848
4949
Args:
50-
ciphertext (bytes|str) the ciphertext you want to decrypt
50+
payload (bytes|str) the payload you want to encrypt/decrypt
5151
block_size (int) block size (the ciphertext length should be
5252
multiple of this)
5353
oracle (function) a function: oracle(ciphertext: bytes) -> bool
@@ -58,33 +58,48 @@ def padding_oracle(ciphertext: Union[bytes, str],
5858
set (default: None)
5959
return_raw (bool) do not convert plaintext into bytes and
6060
unpad (default: False)
61+
mode (bool|str) encrypt the payload (defaut: False/'decrypt')
62+
6163
6264
Returns:
63-
plaintext (bytes|List[int]) the decrypted plaintext
65+
result (bytes|List[int]) the processed payload
6466
'''
6567

6668
# Check args
6769
if not callable(oracle):
6870
raise TypeError('the oracle function should be callable')
69-
if not isinstance(ciphertext, (bytes, str)):
70-
raise TypeError('ciphertext should have type bytes')
71+
if not isinstance(payload, (bytes, str)):
72+
raise TypeError('payload should have type bytes')
7173
if not isinstance(block_size, int):
7274
raise TypeError('block_size should have type int')
73-
if not len(ciphertext) % block_size == 0:
74-
raise ValueError('ciphertext length should be multiple of block size')
75+
if not len(payload) % block_size == 0:
76+
raise ValueError('payload length should be multiple of block size')
7577
if not 1 <= num_threads <= 1000:
7678
raise ValueError('num_threads should be in [1, 1000]')
7779
if not isinstance(null_byte, (bytes, str)):
7880
raise TypeError('expect null with type bytes or str')
7981
if not len(null_byte) == 1:
8082
raise ValueError('null byte should have length of 1')
83+
if not isinstance(mode, (bool, str)):
84+
raise TypeError('expect mode with type bool or str')
85+
if isinstance(mode, str) and mode not in ('encrypt', 'decrypt'):
86+
raise ValueError('mode must be either encrypt or decrypt')
8187

8288
logger = get_logger()
8389
logger.setLevel(log_level)
8490

85-
ciphertext = to_bytes(ciphertext)
91+
payload = to_bytes(payload)
8692
null_byte = to_bytes(null_byte)
8793

94+
95+
# encryption routine
96+
if mode == 'encrypt' or mode:
97+
return encrypt(payload, block_size, oracle, num_threads, null_byte, return_raw, logger)
98+
99+
# otherwise continue with decryption as normal
100+
return decrypt(payload, block_size, oracle, num_threads, null_byte, return_raw, logger):
101+
102+
def encrypt(payload, block_size, oracle, num_threads, null_byte, return_raw, logger):
88103
# Wrapper to handle exceptions from the oracle function
89104
def wrapped_oracle(ciphertext: bytes):
90105
try:
@@ -105,15 +120,51 @@ def plaintext_callback(plaintext: bytes):
105120
plaintext = convert_to_bytes(plaintext, null_byte)
106121
logger.info(f'plaintext: {plaintext}')
107122

108-
plaintext = solve(ciphertext, block_size, wrapped_oracle, num_threads,
123+
plaintext = solve(payload, block_size, wrapped_oracle, num_threads,
109124
result_callback, plaintext_callback)
110125

111126
if not return_raw:
112127
plaintext = convert_to_bytes(plaintext, null_byte)
113128
plaintext = remove_padding(plaintext)
114129

115-
return plaintext
116130

131+
def decrypt(payload, block_size, oracle, num_threads, null_byte, return_raw, logger):
132+
# Wrapper to handle exceptions from the oracle function
133+
def wrapped_oracle(ciphertext: bytes):
134+
try:
135+
return oracle(ciphertext)
136+
except Exception as e:
137+
logger.error(f'error in oracle with {ciphertext!r}, {e}')
138+
logger.debug('error details: {}'.format(traceback.format_exc()))
139+
return False
140+
141+
def result_callback(result: ResultType):
142+
if isinstance(result, Fail):
143+
if result.is_critical:
144+
logger.critical(result.message)
145+
else:
146+
logger.error(result.message)
147+
148+
def plaintext_callback(plaintext: bytes):
149+
plaintext = convert_to_bytes(plaintext, null_byte)
150+
logger.info(f'plaintext: {plaintext}')
151+
152+
def blocks(data: bytes):
153+
return [data[i:(i+block_size)] for i in range(0, len(data), block_size)]
154+
155+
def bytes_xor(byte_string_1: bytes, byte_string_2: bytes):
156+
return bytes([_a ^ _b for _a, _b in zip(byte_string_1, byte_string_2)])
157+
158+
plaintext_blocks = blocks(payload)
159+
ciphertext_blocks = [null_byte * block_size for i in range(len(plaintext_blocks)+1)]
160+
161+
for index in range(len(plaintext_blocks)-1, -1, -1):
162+
plaintext = solve(b'\x00' * block_size + ciphertext_blocks[index+1], block_size, wrapped_oracle,
163+
num_threads, result_callback, plaintext_callback)
164+
ciphertext_blocks[i] = bytes_xor(plaintext_blocks[index], plaintext)
165+
166+
ciphertext = b''.join(ciphertext_blocks)
167+
return ciphertext
117168

118169
def get_logger():
119170
logger = logging.getLogger('padding_oracle')

src/padding_oracle/solve.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
'remove_padding',
3939
]
4040

41-
4241
class Pass(NamedTuple):
4342
block_index: int
4443
solved: List[int]

0 commit comments

Comments
 (0)