Skip to content

Commit 6d1c0f3

Browse files
committed
Turn it into a Python module
1 parent 725846e commit 6d1c0f3

File tree

6 files changed

+183
-63
lines changed

6 files changed

+183
-63
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
dist/
2+
build/
3+
.vscode/
4+
.DS_Store
5+
*.pyc
6+
*.egg-info

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
# padding_oracle.py
22

3-
Extremely fast threaded [padding oracle](http://server.maojui.me/Crypto/Padding_oracle_attack/) automation script for Python >= 3.7.
3+
Extremely fast threaded [padding oracle](http://server.maojui.me/Crypto/Padding_oracle_attack/) automation script for Python 3.
4+
5+
## Install
6+
7+
Installing from PyPI:
8+
9+
```shell
10+
pip3 install -U padding_oracle
11+
```
12+
13+
Or, installing from GitHub:
14+
15+
```shell
16+
pip3 install -U git+https://github.com/djosix/padding_oracle.py.git
17+
```
418

519
## Performance
620

padding_oracle/__init__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'''
2+
Copyright (c) 2020 Yuankui Lee
3+
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the "Software"), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is
9+
furnished to do so, subject to the following conditions:
10+
11+
The above copyright notice and this permission notice shall be included in all
12+
copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
SOFTWARE.
21+
'''
22+
23+
from .padding_oracle import *
24+
from .encoding import *

padding_oracle/encoding.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
'''
2+
Copyright (c) 2020 Yuankui Lee
3+
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the "Software"), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is
9+
furnished to do so, subject to the following conditions:
10+
11+
The above copyright notice and this permission notice shall be included in all
12+
copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
SOFTWARE.
21+
'''
22+
23+
import base64
24+
import urllib.parse
25+
from typing import Union
26+
27+
__all__ = [
28+
'base64_encode', 'base64_decode',
29+
'urlencode', 'urldecode'
30+
]
31+
32+
33+
def _to_bytes(data: Union[str, bytes]):
34+
if isinstance(data, str):
35+
data = data.encode()
36+
assert isinstance(data, bytes)
37+
return data
38+
39+
40+
def _to_str(data):
41+
if isinstance(data, bytes):
42+
data = data.decode()
43+
elif isinstance(data, str):
44+
pass
45+
else:
46+
data = str(data)
47+
return data
48+
49+
50+
def base64_decode(data: Union[str, bytes]) -> bytes:
51+
data = _to_bytes(data)
52+
return base64.b64decode(data)
53+
54+
55+
def base64_encode(data: Union[str, bytes]) -> str:
56+
data = _to_bytes(data)
57+
return base64.b64encode(data).decode()
58+
59+
60+
def urlencode(data: Union[str, bytes]) -> str:
61+
data = _to_bytes(data)
62+
return urllib.parse.quote(data)
63+
64+
65+
def urldecode(data: str) -> bytes:
66+
data = _to_str(data)
67+
return urllib.parse.unquote_plus(data)
Lines changed: 48 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,57 @@
1-
import logging
2-
import base64
3-
import urllib.parse
1+
'''
2+
Copyright (c) 2020 Yuankui Lee
3+
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the "Software"), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is
9+
furnished to do so, subject to the following conditions:
10+
11+
The above copyright notice and this permission notice shall be included in all
12+
copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
SOFTWARE.
21+
'''
422

23+
import logging
524
from typing import Union, Callable
625
from concurrent.futures import ThreadPoolExecutor
726

27+
from .encoding import *
828

929
__all__ = [
10-
'base64_encode', 'base64_decode',
11-
'urlencode', 'urldecode',
1230
'padding_oracle',
1331
'remove_padding'
1432
]
1533

1634

17-
def _to_bytes(data: Union[str, bytes]):
18-
if isinstance(data, str):
19-
data = data.encode()
20-
assert isinstance(data, bytes)
21-
return data
22-
23-
def _to_str(data):
24-
if isinstance(data, bytes):
25-
data = data.decode()
26-
elif isinstance(data, str):
27-
pass
28-
else:
29-
data = str(data)
30-
return data
31-
32-
33-
def base64_decode(data: Union[str, bytes]) -> bytes:
34-
data = _to_bytes(data)
35-
return base64.b64decode(data)
36-
37-
def base64_encode(data: Union[str, bytes]) -> str:
38-
data = _to_bytes(data)
39-
return base64.b64encode(data).decode()
40-
41-
def urlencode(data: Union[str, bytes]) -> str:
42-
data = _to_bytes(data)
43-
return urllib.parse.quote(data)
44-
45-
def urldecode(data: str) -> bytes:
46-
data = _to_str(data)
47-
return urllib.parse.unquote_plus(data)
48-
4935
def remove_padding(data: Union[str, bytes]):
5036
data = _to_bytes(data)
5137
return data[:-data[-1]]
5238

5339

54-
55-
5640
def _dummy_oracle(cipher: bytes) -> bool:
5741
raise NotImplementedError('You must implement the oracle function')
5842

5943

6044
def padding_oracle(cipher: bytes,
6145
block_size: int,
62-
oracle: Callable[[bytes], bool]=_dummy_oracle,
63-
num_threads: int=1,
64-
log_level: int=logging.INFO,
65-
null: bytes=b' ') -> bytes:
46+
oracle: Callable[[bytes], bool] = _dummy_oracle,
47+
num_threads: int = 1,
48+
log_level: int = logging.INFO,
49+
null: bytes = b' ') -> bytes:
6650
# Check the oracle function
6751
assert callable(oracle), 'the oracle function should be callable'
6852
assert oracle.__code__.co_argcount == 1, 'expect oracle function with only 1 argument'
6953
assert len(cipher) % block_size == 0, 'cipher length should be multiple of block size'
70-
54+
7155
logger = logging.getLogger('padding_oracle')
7256
logger.setLevel(log_level)
7357
formatter = logging.Formatter('[%(asctime)s][%(levelname)s] %(message)s')
@@ -84,52 +68,54 @@ def _update_plaintext(i: int, c: bytes):
8468
logger.info('plaintext: {}'.format(b''.join(plaintext)))
8569

8670
oracle_executor = ThreadPoolExecutor(max_workers=num_threads)
87-
71+
8872
def _block_decrypt_task(i, prev: bytes, block: bytes):
8973
logger.debug('task={} prev={} block={}'.format(i, prev, block))
9074
guess_list = list(prev)
91-
75+
9276
for j in range(1, block_size + 1):
9377
oracle_hits = []
9478
oracle_futures = {}
95-
79+
9680
for k in range(256):
9781
if i == len(blocks) - 1 and j == 1 and k == prev[-j]:
9882
# skip the last padding byte if it is identical to the original cipher
9983
continue
100-
84+
10185
test_list = guess_list.copy()
10286
test_list[-j] = k
103-
oracle_futures[k] = oracle_executor.submit(oracle, bytes(test_list) + block)
104-
87+
oracle_futures[k] = oracle_executor.submit(
88+
oracle, bytes(test_list) + block)
89+
10590
for k, future in oracle_futures.items():
10691
if future.result():
10792
oracle_hits.append(k)
108-
109-
logger.debug('oracles at block[{}][{}] -> {}'.format(i, block_size - j, oracle_hits))
110-
93+
94+
logger.debug(
95+
'oracles at block[{}][{}] -> {}'.format(i, block_size - j, oracle_hits))
96+
11197
if len(oracle_hits) != 1:
11298
logfmt = 'at block[{}][{}]: expect only one positive result, got {}. (skipped)'
11399
logger.error(logfmt.format(i, block_size-j, len(oracle_hits)))
114100
return
115-
101+
116102
guess_list[-j] = oracle_hits[0]
117-
103+
118104
p = guess_list[-j] ^ j ^ prev[-j]
119105
_update_plaintext(i * block_size - j, bytes([p]))
120-
106+
121107
for n in range(j):
122108
guess_list[-n-1] ^= j
123109
guess_list[-n-1] ^= j + 1
124-
110+
125111
blocks = []
126-
112+
127113
for i in range(0, len(cipher), block_size):
128114
j = i + block_size
129115
blocks.append(cipher[i:j])
130-
116+
131117
logger.debug('blocks: {}'.format(blocks))
132-
118+
133119
with ThreadPoolExecutor() as executor:
134120
futures = []
135121
for i in reversed(range(1, len(blocks))):
@@ -138,7 +124,7 @@ def _block_decrypt_task(i, prev: bytes, block: bytes):
138124
futures.append(executor.submit(_block_decrypt_task, i, prev, block))
139125
for future in futures:
140126
future.result()
141-
127+
142128
oracle_executor.shutdown()
143-
129+
144130
return b''.join(plaintext)

setup.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import setuptools
2+
3+
with open('README.md', 'r') as fh:
4+
long_description = fh.read()
5+
6+
setuptools.setup(
7+
name='padding_oracle',
8+
version='0.1.1',
9+
author='Yuankui Lee',
10+
author_email='[email protected]',
11+
description='Threaded padding oracle automation.',
12+
long_description=long_description,
13+
long_description_content_type='text/markdown',
14+
url='https://github.com/djosix/padding_oracle.py',
15+
packages=setuptools.find_packages(),
16+
classifiers=[
17+
'Programming Language :: Python :: 3',
18+
'License :: OSI Approved :: MIT License',
19+
'Operating System :: OS Independent',
20+
'Topic :: Security :: Cryptography'
21+
],
22+
python_requires='>=3.5',
23+
)

0 commit comments

Comments
 (0)