├── MANIFEST.in ├── setup.py ├── .github └── workflows │ └── test.yml ├── pyproject.toml ├── LICENSE ├── .gitignore ├── README.md └── owiener.py /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Python test 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Set up Python ${{ matrix.python-version }} 21 | uses: actions/setup-python@v5 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | - name: doctest 25 | run: | 26 | python -m doctest owiener.py 27 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "owiener" 7 | version = "1.0.10" 8 | authors = [ 9 | {name = "Nao Yonashiro", email = "owan.orisano@gmail.com"} 10 | ] 11 | description = "A Python3 implementation of the Wiener attack on RSA" 12 | readme = "README.md" 13 | requires-python = ">=3.5" 14 | license-files = ["LICENSE"] 15 | classifiers = [ 16 | "Development Status :: 5 - Production/Stable", 17 | "Topic :: Security :: Cryptography", 18 | "Programming Language :: Python :: 3.7", 19 | "Programming Language :: Python :: 3.8", 20 | "Programming Language :: Python :: 3.9", 21 | "Programming Language :: Python :: 3.10", 22 | "Programming Language :: Python :: 3.11", 23 | "Programming Language :: Python :: 3.12", 24 | "Programming Language :: Python :: 3.13", 25 | "Programming Language :: Python :: 3.14", 26 | "Programming Language :: Python :: 3 :: Only", 27 | ] 28 | 29 | [project.urls] 30 | Repository = "https://github.com/orisano/owiener" 31 | 32 | [tool.setuptools] 33 | py-modules = ["owiener"] 34 | include-package-data = true 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Nao Yonashiro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # oWiener 2 | [![Test](https://github.com/orisano/owiener/actions/workflows/test.yml/badge.svg)](https://github.com/orisano/owiener/actions/workflows/test.yml) 3 | [![PyPI version](https://badge.fury.io/py/owiener.svg)](https://badge.fury.io/py/owiener) 4 | [![Python Versions](https://img.shields.io/pypi/pyversions/owiener.svg)](https://pypi.org/project/owiener/) 5 | 6 | A Python3 implementation of the Wiener attack on RSA. 7 | 8 | ## Installation 9 | ```bash 10 | python3 -m pip install owiener 11 | ``` 12 | or 13 | ```bash 14 | curl -O https://raw.githubusercontent.com/orisano/owiener/master/owiener.py 15 | ``` 16 | 17 | ## Example 18 | ```python 19 | import owiener 20 | 21 | e = 30749686305802061816334591167284030734478031427751495527922388099381921172620569310945418007467306454160014597828390709770861577479329793948103408489494025272834473555854835044153374978554414416305012267643957838998648651100705446875979573675767605387333733876537528353237076626094553367977134079292593746416875606876735717905892280664538346000950343671655257046364067221469807138232820446015769882472160551840052921930357988334306659120253114790638496480092361951536576427295789429197483597859657977832368912534761100269065509351345050758943674651053419982561094432258103614830448382949765459939698951824447818497599 22 | n = 109966163992903243770643456296093759130737510333736483352345488643432614201030629970207047930115652268531222079508230987041869779760776072105738457123387124961036111210544028669181361694095594938869077306417325203381820822917059651429857093388618818437282624857927551285811542685269229705594166370426152128895901914709902037365652575730201897361139518816164746228733410283595236405985958414491372301878718635708605256444921222945267625853091126691358833453283744166617463257821375566155675868452032401961727814314481343467702299949407935602389342183536222842556906657001984320973035314726867840698884052182976760066141 23 | d = owiener.attack(e, n) 24 | 25 | if d is None: 26 | print("Failed") 27 | else: 28 | print("Hacked d={}".format(d)) 29 | 30 | # Hacked d=4221909016509078129201801236879446760697885220928506696150646938237440992746683409881141451831939190609743447676525325543963362353923989076199470515758399 31 | ``` 32 | 33 | ## References 34 | Cryptanalysis of Short RSA Secret Exponents: 35 | https://www.cits.ruhr-uni-bochum.de/imperia/md/content/may/krypto2ss08/shortsecretexponents.pdf 36 | pablocelayes/rsa-wiener-attack: 37 | https://github.com/pablocelayes/rsa-wiener-attack 38 | wihoho/Wiener-s-Attack: 39 | https://github.com/wihoho/Wiener-s-Attack 40 | 41 | ## Author 42 | Nao Yonashiro (@orisano) 43 | 44 | ## License 45 | MIT 46 | -------------------------------------------------------------------------------- /owiener.py: -------------------------------------------------------------------------------- 1 | """ 2 | The MIT License (MIT) 3 | Copyright (c) 2019 Nao Yonashiro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 21 | OR OTHER DEALINGS IN THE SOFTWARE. 22 | """ 23 | from typing import Tuple, Iterator, Iterable, Optional 24 | 25 | 26 | def isqrt(n: int) -> int: 27 | """ 28 | ref: https://en.wikipedia.org/wiki/Integer_square_root 29 | 30 | >>> isqrt(289) 31 | 17 32 | >>> isqrt(2) 33 | 1 34 | >>> isqrt(1000000 ** 2) 35 | 1000000 36 | """ 37 | if n == 0: 38 | return 0 39 | 40 | # ref: https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Rough_estimation 41 | x = 2 ** ((n.bit_length() + 1) // 2) 42 | while True: 43 | y = (x + n // x) // 2 44 | if y >= x: 45 | return x 46 | x = y 47 | 48 | 49 | def is_perfect_square(n: int) -> bool: 50 | """ 51 | ref: https://hnw.hatenablog.com/entry/20140503 52 | 53 | >>> is_perfect_square(100) 54 | True 55 | 56 | >>> is_perfect_square(2000000000000000000000000000 ** 2) 57 | True 58 | 59 | >>> is_perfect_square(2000000000000000000000000000 ** 2 + 1) 60 | False 61 | """ 62 | sq_mod256 = (1,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0) 63 | if sq_mod256[n & 0xff] == 0: 64 | return False 65 | 66 | mt = ( 67 | (9, (1,1,0,0,1,0,0,1,0)), 68 | (5, (1,1,0,0,1)), 69 | (7, (1,1,1,0,1,0,0)), 70 | (13, (1,1,0,1,1,0,0,0,0,1,1,0,1)), 71 | (17, (1,1,1,0,1,0,0,0,1,1,0,0,0,1,0,1,1)) 72 | ) 73 | a = n % (9 * 5 * 7 * 13 * 17) 74 | if any(t[a % m] == 0 for m, t in mt): 75 | return False 76 | 77 | return isqrt(n) ** 2 == n 78 | 79 | 80 | def rational_to_contfrac(x: int, y: int) -> Iterator[int]: 81 | """ 82 | ref: https://en.wikipedia.org/wiki/Euclidean_algorithm#Continued_fractions 83 | 84 | >>> list(rational_to_contfrac(4, 11)) 85 | [0, 2, 1, 3] 86 | """ 87 | while y: 88 | a = x // y 89 | yield a 90 | x, y = y, x - a * y 91 | 92 | 93 | def contfrac_to_rational_iter(contfrac: Iterable[int]) -> Iterator[Tuple[int, int]]: 94 | """ 95 | ref: https://www.cits.ruhr-uni-bochum.de/imperia/md/content/may/krypto2ss08/shortsecretexponents.pdf (6) 96 | """ 97 | n0, d0 = 0, 1 98 | n1, d1 = 1, 0 99 | for q in contfrac: 100 | n = q * n1 + n0 101 | d = q * d1 + d0 102 | yield n, d 103 | n0, d0 = n1, d1 104 | n1, d1 = n, d 105 | 106 | 107 | def convergents_from_contfrac(contfrac: Iterable[int]) -> Iterator[Tuple[int, int]]: 108 | """ 109 | ref: https://www.cits.ruhr-uni-bochum.de/imperia/md/content/may/krypto2ss08/shortsecretexponents.pdf Section.3 110 | """ 111 | n_, d_ = 1, 0 112 | for i, (n, d) in enumerate(contfrac_to_rational_iter(contfrac)): 113 | if i % 2 == 0: 114 | yield n + n_, d + d_ 115 | else: 116 | yield n, d 117 | n_, d_ = n, d 118 | 119 | 120 | def attack(e: int, n: int) -> Optional[int]: 121 | """ 122 | ref: https://www.cits.ruhr-uni-bochum.de/imperia/md/content/may/krypto2ss08/shortsecretexponents.pdf Section.4 123 | 124 | >>> attack(2621, 8927) 125 | 5 126 | >>> attack(6792605526025, 9449868410449) 127 | 569 128 | >>> attack(30749686305802061816334591167284030734478031427751495527922388099381921172620569310945418007467306454160014597828390709770861577479329793948103408489494025272834473555854835044153374978554414416305012267643957838998648651100705446875979573675767605387333733876537528353237076626094553367977134079292593746416875606876735717905892280664538346000950343671655257046364067221469807138232820446015769882472160551840052921930357988334306659120253114790638496480092361951536576427295789429197483597859657977832368912534761100269065509351345050758943674651053419982561094432258103614830448382949765459939698951824447818497599, 109966163992903243770643456296093759130737510333736483352345488643432614201030629970207047930115652268531222079508230987041869779760776072105738457123387124961036111210544028669181361694095594938869077306417325203381820822917059651429857093388618818437282624857927551285811542685269229705594166370426152128895901914709902037365652575730201897361139518816164746228733410283595236405985958414491372301878718635708605256444921222945267625853091126691358833453283744166617463257821375566155675868452032401961727814314481343467702299949407935602389342183536222842556906657001984320973035314726867840698884052182976760066141) 129 | 4221909016509078129201801236879446760697885220928506696150646938237440992746683409881141451831939190609743447676525325543963362353923989076199470515758399 130 | """ 131 | f_ = rational_to_contfrac(e, n) 132 | for k, dg in convergents_from_contfrac(f_): 133 | edg = e * dg 134 | phi = edg // k 135 | 136 | x = n - phi + 1 137 | if x % 2 == 0 and is_perfect_square((x // 2) ** 2 - n): 138 | g = edg - phi * k 139 | return dg // g 140 | return None 141 | 142 | --------------------------------------------------------------------------------