зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1840056 - update vendored python rsa library to 4.9 r=firefox-build-system-reviewers,mach-reviewers,ahochheiden
Differential Revision: https://phabricator.services.mozilla.com/D217759
This commit is contained in:
Родитель
1db34f7165
Коммит
8c8f2274c5
|
@ -1144,7 +1144,6 @@ files = [
|
|||
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
|
||||
|
@ -1246,12 +1245,13 @@ tests = ["coverage (>=3.7.1,<5.0.0)", "flake8", "pytest", "pytest-cov", "pytest-
|
|||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "3.1.4"
|
||||
version = "4.9"
|
||||
description = "Pure-Python RSA implementation"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
python-versions = ">=3.6,<4"
|
||||
files = [
|
||||
{file = "rsa-3.1.4.tar.gz", hash = "sha256:e2b0b05936c276b1edd2e1525553233b666df9e29b5c3ba223eed738277c82a0"},
|
||||
{file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"},
|
||||
{file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -1595,4 +1595,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "d82a43bd3bd110ce665d215a0965160e95de0c73e0ba2a5ff5991683aa6ae802"
|
||||
content-hash = "aa5c0bf1e3c1216446d407da3e27fa7b7ea44906e3279b7ab419008ea2d56241"
|
||||
|
|
|
@ -47,7 +47,7 @@ redo==2.0.3
|
|||
requests==2.31.0
|
||||
requests-unixsocket==0.2.0
|
||||
responses==0.10.6
|
||||
rsa==3.1.4
|
||||
rsa==4.9
|
||||
sentry-sdk==0.14.3
|
||||
setuptools==70.0.0
|
||||
six==1.16.0
|
||||
|
|
|
@ -498,7 +498,6 @@ pyyaml==6.0.1 ; python_version >= "3.8" and python_version < "4.0" \
|
|||
--hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \
|
||||
--hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \
|
||||
--hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \
|
||||
--hash=sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef \
|
||||
--hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \
|
||||
--hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \
|
||||
--hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \
|
||||
|
@ -532,8 +531,9 @@ requests==2.31.0 ; python_version >= "3.8" and python_version < "4.0" \
|
|||
responses==0.10.6 ; python_version >= "3.8" and python_version < "4.0" \
|
||||
--hash=sha256:502d9c0c8008439cfcdef7e251f507fcfdd503b56e8c0c87c3c3e3393953f790 \
|
||||
--hash=sha256:97193c0183d63fba8cd3a041c75464e4b09ea0aff6328800d1546598567dde0b
|
||||
rsa==3.1.4 ; python_version >= "3.8" and python_version < "4.0" \
|
||||
--hash=sha256:e2b0b05936c276b1edd2e1525553233b666df9e29b5c3ba223eed738277c82a0
|
||||
rsa==4.9 ; python_version >= "3.8" and python_version < "4" \
|
||||
--hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \
|
||||
--hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21
|
||||
sentry-sdk==0.14.3 ; python_version >= "3.8" and python_version < "4.0" \
|
||||
--hash=sha256:23808d571d2461a4ce3784ec12bbee5bdb8c026c143fe79d36cef8a6d653e71f \
|
||||
--hash=sha256:bb90a4e19c7233a580715fc986cc44be2c48fc10b31e71580a2037e1c94b6950
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
include README
|
||||
include LICENSE
|
||||
include *.py
|
||||
recursive-include rsa *.py
|
||||
recursive-include tests *.py
|
|
@ -1,18 +0,0 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: rsa
|
||||
Version: 3.1.4
|
||||
Summary: Pure-Python RSA implementation
|
||||
Home-page: http://stuvel.eu/rsa
|
||||
Author: Sybren A. Stuvel
|
||||
Author-email: sybren@stuvel.eu
|
||||
License: ASL 2
|
||||
Description: UNKNOWN
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Intended Audience :: Education
|
||||
Classifier: Intended Audience :: Information Technology
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Topic :: Security :: Cryptography
|
|
@ -1,31 +0,0 @@
|
|||
Pure Python RSA implementation
|
||||
==============================
|
||||
|
||||
`Python-RSA`_ is a pure-Python RSA implementation. It supports
|
||||
encryption and decryption, signing and verifying signatures, and key
|
||||
generation according to PKCS#1 version 1.5. It can be used as a Python
|
||||
library as well as on the commandline. The code was mostly written by
|
||||
Sybren A. Stüvel.
|
||||
|
||||
Documentation can be found at the Python-RSA homepage:
|
||||
http://stuvel.eu/rsa
|
||||
|
||||
Download and install using::
|
||||
|
||||
pip install rsa
|
||||
|
||||
or::
|
||||
|
||||
easy_install rsa
|
||||
|
||||
or download it from the `Python Package Index`_.
|
||||
|
||||
The source code is maintained in a `Mercurial repository`_ and is
|
||||
licensed under the `Apache License, version 2.0`_
|
||||
|
||||
|
||||
.. _`Python-RSA`: http://stuvel.eu/rsa
|
||||
.. _`Mercurial repository`: https://bitbucket.org/sybren/python-rsa
|
||||
.. _`Python Package Index`: http://pypi.python.org/pypi/rsa
|
||||
.. _`Apache License, version 2.0`: http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import time
|
||||
import rsa
|
||||
|
||||
poolsize = 8
|
||||
accurate = True
|
||||
|
||||
def run_speed_test(bitsize):
|
||||
|
||||
iterations = 0
|
||||
start = end = time.time()
|
||||
|
||||
# At least a number of iterations, and at least 2 seconds
|
||||
while iterations < 10 or end - start < 2:
|
||||
iterations += 1
|
||||
rsa.newkeys(bitsize, accurate=accurate, poolsize=poolsize)
|
||||
end = time.time()
|
||||
|
||||
duration = end - start
|
||||
dur_per_call = duration / iterations
|
||||
|
||||
print '%5i bit: %9.3f sec. (%i iterations over %.1f seconds)' % (bitsize,
|
||||
dur_per_call, iterations, duration)
|
||||
|
||||
for bitsize in (128, 256, 384, 512, 1024, 2048, 3072, 4096):
|
||||
run_speed_test(bitsize)
|
||||
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import re
|
||||
import rsa
|
||||
|
||||
def _logon( username, password ):
|
||||
# Retrive the public key
|
||||
# network stuff # req = urllib2.Request(AAA_GET_KEY, headers={'User-Agent': CLIENT_ID})
|
||||
# network stuff # response = urllib2.urlopen(req)
|
||||
# network stuff # html = response.read()
|
||||
# network stuff # print response.info() # DEBUG
|
||||
# network stuff # print html # DEBUG
|
||||
|
||||
# replacement for network stuff #
|
||||
html="<x509PublicKey>30820122300d06092a864886f70d01010105000382010f003082010a0282010100dad8e3c084137bab285e869ae99a5de9752a095753680e9128adbe981e8141225704e558b8ee437836ec8c5460514efae61550bfdd883549981458bae388c9490b5ab43475068b169b32da446b0aae2dfbb3a5f425c74b284ced3f57ed33b30ec7b4b95a8216f8b063e34af2c84fef58bab381f3b79b80d06b687e0b5fc7aaeb311a88389ab7aa1422ae0b58956bb9e91c5cbf2b98422b05e1eacb82e29938566f6f05274294a8c596677c950ce97dcd003709d008f1ae6418ce5bf55ad2bf921318c6e31b324bdda4b4f12ff1fd86b5b71e647d1fc175aea137ba0ff869d5fbcf9ed0289fe7da3619c1204fc42d616462ac1b6a4e6ca2655d44bce039db519d0203010001</x509PublicKey>"
|
||||
# end replacement for network stuff #
|
||||
|
||||
# This shall pick the key
|
||||
hexstring = re.compile('<x509PublicKey[^>]*>([0-9a-fA-F]+)</x509PublicKey>')
|
||||
|
||||
# pick the key and convert it to der format
|
||||
hex_pub_der = hexstring.search(html).group(1)
|
||||
pub_der = hex_pub_der.decode('hex')
|
||||
|
||||
# Convert it to a public key
|
||||
pub_key = rsa.PublicKey.load_pkcs1_openssl_der(pub_der)
|
||||
|
||||
# encode the password
|
||||
enc_pass = rsa.encrypt(password, pub_key)
|
||||
|
||||
# and hex-encode it
|
||||
hex_pass = enc_pass.encode('hex')
|
||||
|
||||
# _logon('me', 'MyPass')
|
||||
|
||||
import timeit
|
||||
timeit.timeit('_logon( "me", "MyPass" )',
|
||||
setup='from __main__ import _logon',
|
||||
number=1000)
|
||||
|
||||
|
|
@ -4,7 +4,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
|
|||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
@ -0,0 +1,106 @@
|
|||
Metadata-Version: 2.1
|
||||
Name: rsa
|
||||
Version: 4.9
|
||||
Summary: Pure-Python RSA implementation
|
||||
Home-page: https://stuvel.eu/rsa
|
||||
License: Apache-2.0
|
||||
Author: Sybren A. Stüvel
|
||||
Author-email: sybren@stuvel.eu
|
||||
Requires-Python: >=3.6,<4
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Intended Audience :: Education
|
||||
Classifier: Intended Audience :: Information Technology
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Security :: Cryptography
|
||||
Requires-Dist: pyasn1 (>=0.1.3)
|
||||
Project-URL: Repository, https://github.com/sybrenstuvel/python-rsa
|
||||
Description-Content-Type: text/markdown
|
||||
|
||||
# Pure Python RSA implementation
|
||||
|
||||
[![PyPI](https://img.shields.io/pypi/v/rsa.svg)](https://pypi.org/project/rsa/)
|
||||
[![Build Status](https://travis-ci.org/sybrenstuvel/python-rsa.svg?branch=master)](https://travis-ci.org/sybrenstuvel/python-rsa)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/sybrenstuvel/python-rsa/badge.svg?branch=master)](https://coveralls.io/github/sybrenstuvel/python-rsa?branch=master)
|
||||
[![Code Climate](https://api.codeclimate.com/v1/badges/a99a88d28ad37a79dbf6/maintainability)](https://codeclimate.com/github/codeclimate/codeclimate/maintainability)
|
||||
|
||||
[Python-RSA](https://stuvel.eu/rsa) is a pure-Python RSA implementation. It supports
|
||||
encryption and decryption, signing and verifying signatures, and key
|
||||
generation according to PKCS#1 version 1.5. It can be used as a Python
|
||||
library as well as on the commandline. The code was mostly written by
|
||||
Sybren A. Stüvel.
|
||||
|
||||
Documentation can be found at the [Python-RSA homepage](https://stuvel.eu/rsa). For all changes, check [the changelog](https://github.com/sybrenstuvel/python-rsa/blob/master/CHANGELOG.md).
|
||||
|
||||
Download and install using:
|
||||
|
||||
pip install rsa
|
||||
|
||||
or download it from the [Python Package Index](https://pypi.org/project/rsa/).
|
||||
|
||||
The source code is maintained at [GitHub](https://github.com/sybrenstuvel/python-rsa/) and is
|
||||
licensed under the [Apache License, version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
## Security
|
||||
|
||||
Because of how Python internally stores numbers, it is very hard (if not impossible) to make a pure-Python program secure against timing attacks. This library is no exception, so use it with care. See https://securitypitfalls.wordpress.com/2018/08/03/constant-time-compare-in-python/ for more info.
|
||||
|
||||
## Setup of Development Environment
|
||||
|
||||
```
|
||||
python3 -m venv .venv
|
||||
. ./.venv/bin/activate
|
||||
pip install poetry
|
||||
poetry install
|
||||
```
|
||||
|
||||
## Publishing a New Release
|
||||
|
||||
Since this project is considered critical on the Python Package Index,
|
||||
two-factor authentication is required. For uploading packages to PyPi, an API
|
||||
key is required; username+password will not work.
|
||||
|
||||
First, generate an API token at https://pypi.org/manage/account/token/. Then,
|
||||
use this token when publishing instead of your username and password.
|
||||
|
||||
As username, use `__token__`.
|
||||
As password, use the token itself, including the `pypi-` prefix.
|
||||
|
||||
See https://pypi.org/help/#apitoken for help using API tokens to publish. This
|
||||
is what I have in `~/.pypirc`:
|
||||
|
||||
```
|
||||
[distutils]
|
||||
index-servers =
|
||||
rsa
|
||||
|
||||
# Use `twine upload -r rsa` to upload with this token.
|
||||
[rsa]
|
||||
repository = https://upload.pypi.org/legacy/
|
||||
username = __token__
|
||||
password = pypi-token
|
||||
```
|
||||
|
||||
```
|
||||
. ./.venv/bin/activate
|
||||
pip install twine
|
||||
|
||||
poetry build
|
||||
twine check dist/rsa-4.9.tar.gz dist/rsa-4.9-*.whl
|
||||
twine upload -r rsa dist/rsa-4.9.tar.gz dist/rsa-4.9-*.whl
|
||||
```
|
||||
|
||||
The `pip install twine` is necessary as Python-RSA requires Python >= 3.6, and
|
||||
Twine requires at least version 3.7. This means Poetry refuses to add it as
|
||||
dependency.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
rsa/__init__.py,sha256=5bc5rkBB8vxWEtVYwoMQxM8df3O1Ak2_zEXqnkK9oes,1605
|
||||
rsa/asn1.py,sha256=WL2bhDg-q7riT8P8cBMpydsh020i6Ejl6vcQIuA0VXA,1792
|
||||
rsa/cli.py,sha256=DOE66cB0-0SjUhs-PX2gbxiSma5-CT1lEAdcCYrTXwE,10183
|
||||
rsa/common.py,sha256=DAWwAuOSv1X67CBHzBvH-1wOsRe9np6eVsL_ZLrBWcg,4863
|
||||
rsa/core.py,sha256=Rf33atg4-pI7U-mTdoosmn8gTeTyX5xP7yv0iqWyogc,1714
|
||||
rsa/key.py,sha256=3_xv7B-AZZ5jIIz-vpnpfJtStS415e8fNr2iTYOu5CM,28285
|
||||
rsa/parallel.py,sha256=NcL1QjNWJxH9zL2OAOYKgr-HbAeEEmdckdxC6KMhkmM,2405
|
||||
rsa/pem.py,sha256=lzFulzgLHyqhimeo3T4GeBXuGRClfkTMYYZbgmYYmQk,4123
|
||||
rsa/pkcs1.py,sha256=wN9SWn1_zFJvHDNLGPeGZxoDA5T7ipVy9DntNcCYBpU,16690
|
||||
rsa/pkcs1_v2.py,sha256=d5A27EcOgbgJeikuLZkzANOzBQh4nVX-Bom5DUXgXHw,3549
|
||||
rsa/prime.py,sha256=Kij81g-VneGw20Cq6LRaCVT3b9tX4gWIzkWV-3h4qMg,5304
|
||||
rsa/py.typed,sha256=TfYjsEjlfDcVNGFibSYzbCf81u37bSXWmv4oTYf0zY8,64
|
||||
rsa/randnum.py,sha256=AwhXEZAT6spbUUPjhwQXGXKOTlG8FPHOI3gmTAcQ0pk,2752
|
||||
rsa/transform.py,sha256=i-nVC7JcPZkYz1W-d-qg0n0PQS17kKeXhfd9IkDehj4,2272
|
||||
rsa/util.py,sha256=9PuWg2jQfV8FHdE9hpGHDCi2iGM8Z-r4tIQXRVFmqYY,3090
|
||||
rsa-4.9.dist-info/entry_points.txt,sha256=p0nVsezmPSjm5x4GDMD4a9Sshc9ukdfw1kkmOmpaAu0,201
|
||||
rsa-4.9.dist-info/LICENSE,sha256=Bz8ot9OJyP509gfhfCf4HqpazmntxDqITyP0G0HFxyY,577
|
||||
rsa-4.9.dist-info/WHEEL,sha256=y3eDiaFVSNTPbgzfNn0nYn5tEn1cX6WrdetDlQM4xWw,83
|
||||
rsa-4.9.dist-info/METADATA,sha256=-540qZBdoxQdUSuhxWlXTnY-oMNVz3EML49u9IfmmQ4,4173
|
||||
rsa-4.9.dist-info/RECORD,,
|
|
@ -0,0 +1,4 @@
|
|||
Wheel-Version: 1.0
|
||||
Generator: poetry 1.0.7
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
|
@ -0,0 +1,8 @@
|
|||
[console_scripts]
|
||||
pyrsa-decrypt=rsa.cli:decrypt
|
||||
pyrsa-encrypt=rsa.cli:encrypt
|
||||
pyrsa-keygen=rsa.cli:keygen
|
||||
pyrsa-priv2pub=rsa.util:private_to_public
|
||||
pyrsa-sign=rsa.cli:sign
|
||||
pyrsa-verify=rsa.cli:verify
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: rsa
|
||||
Version: 3.1.4
|
||||
Summary: Pure-Python RSA implementation
|
||||
Home-page: http://stuvel.eu/rsa
|
||||
Author: Sybren A. Stuvel
|
||||
Author-email: sybren@stuvel.eu
|
||||
License: ASL 2
|
||||
Description: UNKNOWN
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Intended Audience :: Education
|
||||
Classifier: Intended Audience :: Information Technology
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Topic :: Security :: Cryptography
|
|
@ -1,46 +0,0 @@
|
|||
LICENSE
|
||||
MANIFEST.in
|
||||
README.rst
|
||||
create_timing_table.py
|
||||
playstuff.py
|
||||
run_tests.py
|
||||
setup.cfg
|
||||
setup.py
|
||||
rsa/__init__.py
|
||||
rsa/_compat.py
|
||||
rsa/_version133.py
|
||||
rsa/_version200.py
|
||||
rsa/asn1.py
|
||||
rsa/bigfile.py
|
||||
rsa/cli.py
|
||||
rsa/common.py
|
||||
rsa/core.py
|
||||
rsa/key.py
|
||||
rsa/parallel.py
|
||||
rsa/pem.py
|
||||
rsa/pkcs1.py
|
||||
rsa/prime.py
|
||||
rsa/randnum.py
|
||||
rsa/transform.py
|
||||
rsa/util.py
|
||||
rsa/varblock.py
|
||||
rsa.egg-info/PKG-INFO
|
||||
rsa.egg-info/SOURCES.txt
|
||||
rsa.egg-info/dependency_links.txt
|
||||
rsa.egg-info/entry_points.txt
|
||||
rsa.egg-info/requires.txt
|
||||
rsa.egg-info/top_level.txt
|
||||
tests/__init__.py
|
||||
tests/constants.py
|
||||
tests/py2kconstants.py
|
||||
tests/py3kconstants.py
|
||||
tests/test_bigfile.py
|
||||
tests/test_common.py
|
||||
tests/test_compat.py
|
||||
tests/test_integers.py
|
||||
tests/test_load_save_keys.py
|
||||
tests/test_pem.py
|
||||
tests/test_pkcs1.py
|
||||
tests/test_strings.py
|
||||
tests/test_transform.py
|
||||
tests/test_varblock.py
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
[console_scripts]
|
||||
pyrsa-encrypt = rsa.cli:encrypt
|
||||
pyrsa-keygen = rsa.cli:keygen
|
||||
pyrsa-priv2pub = rsa.util:private_to_public
|
||||
pyrsa-sign = rsa.cli:sign
|
||||
pyrsa-verify = rsa.cli:verify
|
||||
pyrsa-encrypt-bigfile = rsa.cli:encrypt_bigfile
|
||||
pyrsa-decrypt-bigfile = rsa.cli:decrypt_bigfile
|
||||
pyrsa-decrypt = rsa.cli:decrypt
|
||||
|
|
@ -1 +0,0 @@
|
|||
pyasn1 >= 0.1.3
|
|
@ -1 +0,0 @@
|
|||
rsa
|
|
@ -1,12 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
@ -18,28 +16,45 @@
|
|||
Module for calculating large primes, and RSA encryption, decryption, signing
|
||||
and verification. Includes generating public and private keys.
|
||||
|
||||
WARNING: this implementation does not use random padding, compression of the
|
||||
cleartext input to prevent repetitions, or other common security improvements.
|
||||
Use with care.
|
||||
|
||||
If you want to have a more secure implementation, use the functions from the
|
||||
``rsa.pkcs1`` module.
|
||||
WARNING: this implementation does not use compression of the cleartext input to
|
||||
prevent repetitions, or other common security improvements. Use with care.
|
||||
|
||||
"""
|
||||
|
||||
__author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly"
|
||||
__date__ = "2014-02-22"
|
||||
__version__ = '3.1.4'
|
||||
|
||||
from rsa.key import newkeys, PrivateKey, PublicKey
|
||||
from rsa.pkcs1 import encrypt, decrypt, sign, verify, DecryptionError, \
|
||||
VerificationError
|
||||
from rsa.pkcs1 import (
|
||||
encrypt,
|
||||
decrypt,
|
||||
sign,
|
||||
verify,
|
||||
DecryptionError,
|
||||
VerificationError,
|
||||
find_signature_hash,
|
||||
sign_hash,
|
||||
compute_hash,
|
||||
)
|
||||
|
||||
__author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly"
|
||||
__date__ = "2022-07-20"
|
||||
__version__ = "4.9"
|
||||
|
||||
# Do doctest if we're run directly
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
||||
__all__ = ["newkeys", "encrypt", "decrypt", "sign", "verify", 'PublicKey',
|
||||
'PrivateKey', 'DecryptionError', 'VerificationError']
|
||||
|
||||
__all__ = [
|
||||
"newkeys",
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
"sign",
|
||||
"verify",
|
||||
"PublicKey",
|
||||
"PrivateKey",
|
||||
"DecryptionError",
|
||||
"VerificationError",
|
||||
"find_signature_hash",
|
||||
"compute_hash",
|
||||
"sign_hash",
|
||||
]
|
||||
|
|
|
@ -1,160 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Python compatibility wrappers."""
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
from struct import pack
|
||||
|
||||
try:
|
||||
MAX_INT = sys.maxsize
|
||||
except AttributeError:
|
||||
MAX_INT = sys.maxint
|
||||
|
||||
MAX_INT64 = (1 << 63) - 1
|
||||
MAX_INT32 = (1 << 31) - 1
|
||||
MAX_INT16 = (1 << 15) - 1
|
||||
|
||||
# Determine the word size of the processor.
|
||||
if MAX_INT == MAX_INT64:
|
||||
# 64-bit processor.
|
||||
MACHINE_WORD_SIZE = 64
|
||||
elif MAX_INT == MAX_INT32:
|
||||
# 32-bit processor.
|
||||
MACHINE_WORD_SIZE = 32
|
||||
else:
|
||||
# Else we just assume 64-bit processor keeping up with modern times.
|
||||
MACHINE_WORD_SIZE = 64
|
||||
|
||||
|
||||
try:
|
||||
# < Python3
|
||||
unicode_type = unicode
|
||||
have_python3 = False
|
||||
except NameError:
|
||||
# Python3.
|
||||
unicode_type = str
|
||||
have_python3 = True
|
||||
|
||||
# Fake byte literals.
|
||||
if str is unicode_type:
|
||||
def byte_literal(s):
|
||||
return s.encode('latin1')
|
||||
else:
|
||||
def byte_literal(s):
|
||||
return s
|
||||
|
||||
# ``long`` is no more. Do type detection using this instead.
|
||||
try:
|
||||
integer_types = (int, long)
|
||||
except NameError:
|
||||
integer_types = (int,)
|
||||
|
||||
b = byte_literal
|
||||
|
||||
try:
|
||||
# Python 2.6 or higher.
|
||||
bytes_type = bytes
|
||||
except NameError:
|
||||
# Python 2.5
|
||||
bytes_type = str
|
||||
|
||||
|
||||
# To avoid calling b() multiple times in tight loops.
|
||||
ZERO_BYTE = b('\x00')
|
||||
EMPTY_BYTE = b('')
|
||||
|
||||
|
||||
def is_bytes(obj):
|
||||
"""
|
||||
Determines whether the given value is a byte string.
|
||||
|
||||
:param obj:
|
||||
The value to test.
|
||||
:returns:
|
||||
``True`` if ``value`` is a byte string; ``False`` otherwise.
|
||||
"""
|
||||
return isinstance(obj, bytes_type)
|
||||
|
||||
|
||||
def is_integer(obj):
|
||||
"""
|
||||
Determines whether the given value is an integer.
|
||||
|
||||
:param obj:
|
||||
The value to test.
|
||||
:returns:
|
||||
``True`` if ``value`` is an integer; ``False`` otherwise.
|
||||
"""
|
||||
return isinstance(obj, integer_types)
|
||||
|
||||
|
||||
def byte(num):
|
||||
"""
|
||||
Converts a number between 0 and 255 (both inclusive) to a base-256 (byte)
|
||||
representation.
|
||||
|
||||
Use it as a replacement for ``chr`` where you are expecting a byte
|
||||
because this will work on all current versions of Python::
|
||||
|
||||
:param num:
|
||||
An unsigned integer between 0 and 255 (both inclusive).
|
||||
:returns:
|
||||
A single byte.
|
||||
"""
|
||||
return pack("B", num)
|
||||
|
||||
|
||||
def get_word_alignment(num, force_arch=64,
|
||||
_machine_word_size=MACHINE_WORD_SIZE):
|
||||
"""
|
||||
Returns alignment details for the given number based on the platform
|
||||
Python is running on.
|
||||
|
||||
:param num:
|
||||
Unsigned integral number.
|
||||
:param force_arch:
|
||||
If you don't want to use 64-bit unsigned chunks, set this to
|
||||
anything other than 64. 32-bit chunks will be preferred then.
|
||||
Default 64 will be used when on a 64-bit machine.
|
||||
:param _machine_word_size:
|
||||
(Internal) The machine word size used for alignment.
|
||||
:returns:
|
||||
4-tuple::
|
||||
|
||||
(word_bits, word_bytes,
|
||||
max_uint, packing_format_type)
|
||||
"""
|
||||
max_uint64 = 0xffffffffffffffff
|
||||
max_uint32 = 0xffffffff
|
||||
max_uint16 = 0xffff
|
||||
max_uint8 = 0xff
|
||||
|
||||
if force_arch == 64 and _machine_word_size >= 64 and num > max_uint32:
|
||||
# 64-bit unsigned integer.
|
||||
return 64, 8, max_uint64, "Q"
|
||||
elif num > max_uint16:
|
||||
# 32-bit unsigned integer
|
||||
return 32, 4, max_uint32, "L"
|
||||
elif num > max_uint8:
|
||||
# 16-bit unsigned integer.
|
||||
return 16, 2, max_uint16, "H"
|
||||
else:
|
||||
# 8-bit unsigned integer.
|
||||
return 8, 1, max_uint8, "B"
|
|
@ -1,442 +0,0 @@
|
|||
"""RSA module
|
||||
pri = k[1] //Private part of keys d,p,q
|
||||
|
||||
Module for calculating large primes, and RSA encryption, decryption,
|
||||
signing and verification. Includes generating public and private keys.
|
||||
|
||||
WARNING: this code implements the mathematics of RSA. It is not suitable for
|
||||
real-world secure cryptography purposes. It has not been reviewed by a security
|
||||
expert. It does not include padding of data. There are many ways in which the
|
||||
output of this module, when used without any modification, can be sucessfully
|
||||
attacked.
|
||||
"""
|
||||
|
||||
__author__ = "Sybren Stuvel, Marloes de Boer and Ivo Tamboer"
|
||||
__date__ = "2010-02-05"
|
||||
__version__ = '1.3.3'
|
||||
|
||||
# NOTE: Python's modulo can return negative numbers. We compensate for
|
||||
# this behaviour using the abs() function
|
||||
|
||||
from cPickle import dumps, loads
|
||||
import base64
|
||||
import math
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
import types
|
||||
import zlib
|
||||
|
||||
from rsa._compat import byte
|
||||
|
||||
# Display a warning that this insecure version is imported.
|
||||
import warnings
|
||||
warnings.warn('Insecure version of the RSA module is imported as %s, be careful'
|
||||
% __name__)
|
||||
|
||||
def gcd(p, q):
|
||||
"""Returns the greatest common divisor of p and q
|
||||
|
||||
|
||||
>>> gcd(42, 6)
|
||||
6
|
||||
"""
|
||||
if p<q: return gcd(q, p)
|
||||
if q == 0: return p
|
||||
return gcd(q, abs(p%q))
|
||||
|
||||
def bytes2int(bytes):
|
||||
"""Converts a list of bytes or a string to an integer
|
||||
|
||||
>>> (128*256 + 64)*256 + + 15
|
||||
8405007
|
||||
>>> l = [128, 64, 15]
|
||||
>>> bytes2int(l)
|
||||
8405007
|
||||
"""
|
||||
|
||||
if not (type(bytes) is types.ListType or type(bytes) is types.StringType):
|
||||
raise TypeError("You must pass a string or a list")
|
||||
|
||||
# Convert byte stream to integer
|
||||
integer = 0
|
||||
for byte in bytes:
|
||||
integer *= 256
|
||||
if type(byte) is types.StringType: byte = ord(byte)
|
||||
integer += byte
|
||||
|
||||
return integer
|
||||
|
||||
def int2bytes(number):
|
||||
"""Converts a number to a string of bytes
|
||||
|
||||
>>> bytes2int(int2bytes(123456789))
|
||||
123456789
|
||||
"""
|
||||
|
||||
if not (type(number) is types.LongType or type(number) is types.IntType):
|
||||
raise TypeError("You must pass a long or an int")
|
||||
|
||||
string = ""
|
||||
|
||||
while number > 0:
|
||||
string = "%s%s" % (byte(number & 0xFF), string)
|
||||
number /= 256
|
||||
|
||||
return string
|
||||
|
||||
def fast_exponentiation(a, p, n):
|
||||
"""Calculates r = a^p mod n
|
||||
"""
|
||||
result = a % n
|
||||
remainders = []
|
||||
while p != 1:
|
||||
remainders.append(p & 1)
|
||||
p = p >> 1
|
||||
while remainders:
|
||||
rem = remainders.pop()
|
||||
result = ((a ** rem) * result ** 2) % n
|
||||
return result
|
||||
|
||||
def read_random_int(nbits):
|
||||
"""Reads a random integer of approximately nbits bits rounded up
|
||||
to whole bytes"""
|
||||
|
||||
nbytes = ceil(nbits/8.)
|
||||
randomdata = os.urandom(nbytes)
|
||||
return bytes2int(randomdata)
|
||||
|
||||
def ceil(x):
|
||||
"""ceil(x) -> int(math.ceil(x))"""
|
||||
|
||||
return int(math.ceil(x))
|
||||
|
||||
def randint(minvalue, maxvalue):
|
||||
"""Returns a random integer x with minvalue <= x <= maxvalue"""
|
||||
|
||||
# Safety - get a lot of random data even if the range is fairly
|
||||
# small
|
||||
min_nbits = 32
|
||||
|
||||
# The range of the random numbers we need to generate
|
||||
range = maxvalue - minvalue
|
||||
|
||||
# Which is this number of bytes
|
||||
rangebytes = ceil(math.log(range, 2) / 8.)
|
||||
|
||||
# Convert to bits, but make sure it's always at least min_nbits*2
|
||||
rangebits = max(rangebytes * 8, min_nbits * 2)
|
||||
|
||||
# Take a random number of bits between min_nbits and rangebits
|
||||
nbits = random.randint(min_nbits, rangebits)
|
||||
|
||||
return (read_random_int(nbits) % range) + minvalue
|
||||
|
||||
def fermat_little_theorem(p):
|
||||
"""Returns 1 if p may be prime, and something else if p definitely
|
||||
is not prime"""
|
||||
|
||||
a = randint(1, p-1)
|
||||
return fast_exponentiation(a, p-1, p)
|
||||
|
||||
def jacobi(a, b):
|
||||
"""Calculates the value of the Jacobi symbol (a/b)
|
||||
"""
|
||||
|
||||
if a % b == 0:
|
||||
return 0
|
||||
result = 1
|
||||
while a > 1:
|
||||
if a & 1:
|
||||
if ((a-1)*(b-1) >> 2) & 1:
|
||||
result = -result
|
||||
b, a = a, b % a
|
||||
else:
|
||||
if ((b ** 2 - 1) >> 3) & 1:
|
||||
result = -result
|
||||
a = a >> 1
|
||||
return result
|
||||
|
||||
def jacobi_witness(x, n):
|
||||
"""Returns False if n is an Euler pseudo-prime with base x, and
|
||||
True otherwise.
|
||||
"""
|
||||
|
||||
j = jacobi(x, n) % n
|
||||
f = fast_exponentiation(x, (n-1)/2, n)
|
||||
|
||||
if j == f: return False
|
||||
return True
|
||||
|
||||
def randomized_primality_testing(n, k):
|
||||
"""Calculates whether n is composite (which is always correct) or
|
||||
prime (which is incorrect with error probability 2**-k)
|
||||
|
||||
Returns False if the number if composite, and True if it's
|
||||
probably prime.
|
||||
"""
|
||||
|
||||
q = 0.5 # Property of the jacobi_witness function
|
||||
|
||||
# t = int(math.ceil(k / math.log(1/q, 2)))
|
||||
t = ceil(k / math.log(1/q, 2))
|
||||
for i in range(t+1):
|
||||
x = randint(1, n-1)
|
||||
if jacobi_witness(x, n): return False
|
||||
|
||||
return True
|
||||
|
||||
def is_prime(number):
|
||||
"""Returns True if the number is prime, and False otherwise.
|
||||
|
||||
>>> is_prime(42)
|
||||
0
|
||||
>>> is_prime(41)
|
||||
1
|
||||
"""
|
||||
|
||||
"""
|
||||
if not fermat_little_theorem(number) == 1:
|
||||
# Not prime, according to Fermat's little theorem
|
||||
return False
|
||||
"""
|
||||
|
||||
if randomized_primality_testing(number, 5):
|
||||
# Prime, according to Jacobi
|
||||
return True
|
||||
|
||||
# Not prime
|
||||
return False
|
||||
|
||||
|
||||
def getprime(nbits):
|
||||
"""Returns a prime number of max. 'math.ceil(nbits/8)*8' bits. In
|
||||
other words: nbits is rounded up to whole bytes.
|
||||
|
||||
>>> p = getprime(8)
|
||||
>>> is_prime(p-1)
|
||||
0
|
||||
>>> is_prime(p)
|
||||
1
|
||||
>>> is_prime(p+1)
|
||||
0
|
||||
"""
|
||||
|
||||
nbytes = int(math.ceil(nbits/8.))
|
||||
|
||||
while True:
|
||||
integer = read_random_int(nbits)
|
||||
|
||||
# Make sure it's odd
|
||||
integer |= 1
|
||||
|
||||
# Test for primeness
|
||||
if is_prime(integer): break
|
||||
|
||||
# Retry if not prime
|
||||
|
||||
return integer
|
||||
|
||||
def are_relatively_prime(a, b):
|
||||
"""Returns True if a and b are relatively prime, and False if they
|
||||
are not.
|
||||
|
||||
>>> are_relatively_prime(2, 3)
|
||||
1
|
||||
>>> are_relatively_prime(2, 4)
|
||||
0
|
||||
"""
|
||||
|
||||
d = gcd(a, b)
|
||||
return (d == 1)
|
||||
|
||||
def find_p_q(nbits):
|
||||
"""Returns a tuple of two different primes of nbits bits"""
|
||||
|
||||
p = getprime(nbits)
|
||||
while True:
|
||||
q = getprime(nbits)
|
||||
if not q == p: break
|
||||
|
||||
return (p, q)
|
||||
|
||||
def extended_euclid_gcd(a, b):
|
||||
"""Returns a tuple (d, i, j) such that d = gcd(a, b) = ia + jb
|
||||
"""
|
||||
|
||||
if b == 0:
|
||||
return (a, 1, 0)
|
||||
|
||||
q = abs(a % b)
|
||||
r = long(a / b)
|
||||
(d, k, l) = extended_euclid_gcd(b, q)
|
||||
|
||||
return (d, l, k - l*r)
|
||||
|
||||
# Main function: calculate encryption and decryption keys
|
||||
def calculate_keys(p, q, nbits):
|
||||
"""Calculates an encryption and a decryption key for p and q, and
|
||||
returns them as a tuple (e, d)"""
|
||||
|
||||
n = p * q
|
||||
phi_n = (p-1) * (q-1)
|
||||
|
||||
while True:
|
||||
# Make sure e has enough bits so we ensure "wrapping" through
|
||||
# modulo n
|
||||
e = getprime(max(8, nbits/2))
|
||||
if are_relatively_prime(e, n) and are_relatively_prime(e, phi_n): break
|
||||
|
||||
(d, i, j) = extended_euclid_gcd(e, phi_n)
|
||||
|
||||
if not d == 1:
|
||||
raise Exception("e (%d) and phi_n (%d) are not relatively prime" % (e, phi_n))
|
||||
|
||||
if not (e * i) % phi_n == 1:
|
||||
raise Exception("e (%d) and i (%d) are not mult. inv. modulo phi_n (%d)" % (e, i, phi_n))
|
||||
|
||||
return (e, i)
|
||||
|
||||
|
||||
def gen_keys(nbits):
|
||||
"""Generate RSA keys of nbits bits. Returns (p, q, e, d).
|
||||
|
||||
Note: this can take a long time, depending on the key size.
|
||||
"""
|
||||
|
||||
while True:
|
||||
(p, q) = find_p_q(nbits)
|
||||
(e, d) = calculate_keys(p, q, nbits)
|
||||
|
||||
# For some reason, d is sometimes negative. We don't know how
|
||||
# to fix it (yet), so we keep trying until everything is shiny
|
||||
if d > 0: break
|
||||
|
||||
return (p, q, e, d)
|
||||
|
||||
def gen_pubpriv_keys(nbits):
|
||||
"""Generates public and private keys, and returns them as (pub,
|
||||
priv).
|
||||
|
||||
The public key consists of a dict {e: ..., , n: ....). The private
|
||||
key consists of a dict {d: ...., p: ...., q: ....).
|
||||
"""
|
||||
|
||||
(p, q, e, d) = gen_keys(nbits)
|
||||
|
||||
return ( {'e': e, 'n': p*q}, {'d': d, 'p': p, 'q': q} )
|
||||
|
||||
def encrypt_int(message, ekey, n):
|
||||
"""Encrypts a message using encryption key 'ekey', working modulo
|
||||
n"""
|
||||
|
||||
if type(message) is types.IntType:
|
||||
return encrypt_int(long(message), ekey, n)
|
||||
|
||||
if not type(message) is types.LongType:
|
||||
raise TypeError("You must pass a long or an int")
|
||||
|
||||
if message > 0 and \
|
||||
math.floor(math.log(message, 2)) > math.floor(math.log(n, 2)):
|
||||
raise OverflowError("The message is too long")
|
||||
|
||||
return fast_exponentiation(message, ekey, n)
|
||||
|
||||
def decrypt_int(cyphertext, dkey, n):
|
||||
"""Decrypts a cypher text using the decryption key 'dkey', working
|
||||
modulo n"""
|
||||
|
||||
return encrypt_int(cyphertext, dkey, n)
|
||||
|
||||
def sign_int(message, dkey, n):
|
||||
"""Signs 'message' using key 'dkey', working modulo n"""
|
||||
|
||||
return decrypt_int(message, dkey, n)
|
||||
|
||||
def verify_int(signed, ekey, n):
|
||||
"""verifies 'signed' using key 'ekey', working modulo n"""
|
||||
|
||||
return encrypt_int(signed, ekey, n)
|
||||
|
||||
def picklechops(chops):
|
||||
"""Pickles and base64encodes it's argument chops"""
|
||||
|
||||
value = zlib.compress(dumps(chops))
|
||||
encoded = base64.encodestring(value)
|
||||
return encoded.strip()
|
||||
|
||||
def unpicklechops(string):
|
||||
"""base64decodes and unpickes it's argument string into chops"""
|
||||
|
||||
return loads(zlib.decompress(base64.decodestring(string)))
|
||||
|
||||
def chopstring(message, key, n, funcref):
|
||||
"""Splits 'message' into chops that are at most as long as n,
|
||||
converts these into integers, and calls funcref(integer, key, n)
|
||||
for each chop.
|
||||
|
||||
Used by 'encrypt' and 'sign'.
|
||||
"""
|
||||
|
||||
msglen = len(message)
|
||||
mbits = msglen * 8
|
||||
nbits = int(math.floor(math.log(n, 2)))
|
||||
nbytes = nbits / 8
|
||||
blocks = msglen / nbytes
|
||||
|
||||
if msglen % nbytes > 0:
|
||||
blocks += 1
|
||||
|
||||
cypher = []
|
||||
|
||||
for bindex in range(blocks):
|
||||
offset = bindex * nbytes
|
||||
block = message[offset:offset+nbytes]
|
||||
value = bytes2int(block)
|
||||
cypher.append(funcref(value, key, n))
|
||||
|
||||
return picklechops(cypher)
|
||||
|
||||
def gluechops(chops, key, n, funcref):
|
||||
"""Glues chops back together into a string. calls
|
||||
funcref(integer, key, n) for each chop.
|
||||
|
||||
Used by 'decrypt' and 'verify'.
|
||||
"""
|
||||
message = ""
|
||||
|
||||
chops = unpicklechops(chops)
|
||||
|
||||
for cpart in chops:
|
||||
mpart = funcref(cpart, key, n)
|
||||
message += int2bytes(mpart)
|
||||
|
||||
return message
|
||||
|
||||
def encrypt(message, key):
|
||||
"""Encrypts a string 'message' with the public key 'key'"""
|
||||
|
||||
return chopstring(message, key['e'], key['n'], encrypt_int)
|
||||
|
||||
def sign(message, key):
|
||||
"""Signs a string 'message' with the private key 'key'"""
|
||||
|
||||
return chopstring(message, key['d'], key['p']*key['q'], decrypt_int)
|
||||
|
||||
def decrypt(cypher, key):
|
||||
"""Decrypts a cypher with the private key 'key'"""
|
||||
|
||||
return gluechops(cypher, key['d'], key['p']*key['q'], decrypt_int)
|
||||
|
||||
def verify(cypher, key):
|
||||
"""Verifies a cypher with the public key 'key'"""
|
||||
|
||||
return gluechops(cypher, key['e'], key['n'], encrypt_int)
|
||||
|
||||
# Do doctest if we're not imported
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
__all__ = ["gen_pubpriv_keys", "encrypt", "decrypt", "sign", "verify"]
|
||||
|
|
@ -1,529 +0,0 @@
|
|||
"""RSA module
|
||||
|
||||
Module for calculating large primes, and RSA encryption, decryption,
|
||||
signing and verification. Includes generating public and private keys.
|
||||
|
||||
WARNING: this implementation does not use random padding, compression of the
|
||||
cleartext input to prevent repetitions, or other common security improvements.
|
||||
Use with care.
|
||||
|
||||
"""
|
||||
|
||||
__author__ = "Sybren Stuvel, Marloes de Boer, Ivo Tamboer, and Barry Mead"
|
||||
__date__ = "2010-02-08"
|
||||
__version__ = '2.0'
|
||||
|
||||
import math
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
import types
|
||||
from rsa._compat import byte
|
||||
|
||||
# Display a warning that this insecure version is imported.
|
||||
import warnings
|
||||
warnings.warn('Insecure version of the RSA module is imported as %s' % __name__)
|
||||
|
||||
|
||||
def bit_size(number):
|
||||
"""Returns the number of bits required to hold a specific long number"""
|
||||
|
||||
return int(math.ceil(math.log(number,2)))
|
||||
|
||||
def gcd(p, q):
|
||||
"""Returns the greatest common divisor of p and q
|
||||
>>> gcd(48, 180)
|
||||
12
|
||||
"""
|
||||
# Iterateive Version is faster and uses much less stack space
|
||||
while q != 0:
|
||||
if p < q: (p,q) = (q,p)
|
||||
(p,q) = (q, p % q)
|
||||
return p
|
||||
|
||||
|
||||
def bytes2int(bytes):
|
||||
"""Converts a list of bytes or a string to an integer
|
||||
|
||||
>>> (((128 * 256) + 64) * 256) + 15
|
||||
8405007
|
||||
>>> l = [128, 64, 15]
|
||||
>>> bytes2int(l) #same as bytes2int('\x80@\x0f')
|
||||
8405007
|
||||
"""
|
||||
|
||||
if not (type(bytes) is types.ListType or type(bytes) is types.StringType):
|
||||
raise TypeError("You must pass a string or a list")
|
||||
|
||||
# Convert byte stream to integer
|
||||
integer = 0
|
||||
for byte in bytes:
|
||||
integer *= 256
|
||||
if type(byte) is types.StringType: byte = ord(byte)
|
||||
integer += byte
|
||||
|
||||
return integer
|
||||
|
||||
def int2bytes(number):
|
||||
"""
|
||||
Converts a number to a string of bytes
|
||||
"""
|
||||
|
||||
if not (type(number) is types.LongType or type(number) is types.IntType):
|
||||
raise TypeError("You must pass a long or an int")
|
||||
|
||||
string = ""
|
||||
|
||||
while number > 0:
|
||||
string = "%s%s" % (byte(number & 0xFF), string)
|
||||
number /= 256
|
||||
|
||||
return string
|
||||
|
||||
def to64(number):
|
||||
"""Converts a number in the range of 0 to 63 into base 64 digit
|
||||
character in the range of '0'-'9', 'A'-'Z', 'a'-'z','-','_'.
|
||||
|
||||
>>> to64(10)
|
||||
'A'
|
||||
"""
|
||||
|
||||
if not (type(number) is types.LongType or type(number) is types.IntType):
|
||||
raise TypeError("You must pass a long or an int")
|
||||
|
||||
if 0 <= number <= 9: #00-09 translates to '0' - '9'
|
||||
return byte(number + 48)
|
||||
|
||||
if 10 <= number <= 35:
|
||||
return byte(number + 55) #10-35 translates to 'A' - 'Z'
|
||||
|
||||
if 36 <= number <= 61:
|
||||
return byte(number + 61) #36-61 translates to 'a' - 'z'
|
||||
|
||||
if number == 62: # 62 translates to '-' (minus)
|
||||
return byte(45)
|
||||
|
||||
if number == 63: # 63 translates to '_' (underscore)
|
||||
return byte(95)
|
||||
|
||||
raise ValueError('Invalid Base64 value: %i' % number)
|
||||
|
||||
|
||||
def from64(number):
|
||||
"""Converts an ordinal character value in the range of
|
||||
0-9,A-Z,a-z,-,_ to a number in the range of 0-63.
|
||||
|
||||
>>> from64(49)
|
||||
1
|
||||
"""
|
||||
|
||||
if not (type(number) is types.LongType or type(number) is types.IntType):
|
||||
raise TypeError("You must pass a long or an int")
|
||||
|
||||
if 48 <= number <= 57: #ord('0') - ord('9') translates to 0-9
|
||||
return(number - 48)
|
||||
|
||||
if 65 <= number <= 90: #ord('A') - ord('Z') translates to 10-35
|
||||
return(number - 55)
|
||||
|
||||
if 97 <= number <= 122: #ord('a') - ord('z') translates to 36-61
|
||||
return(number - 61)
|
||||
|
||||
if number == 45: #ord('-') translates to 62
|
||||
return(62)
|
||||
|
||||
if number == 95: #ord('_') translates to 63
|
||||
return(63)
|
||||
|
||||
raise ValueError('Invalid Base64 value: %i' % number)
|
||||
|
||||
|
||||
def int2str64(number):
|
||||
"""Converts a number to a string of base64 encoded characters in
|
||||
the range of '0'-'9','A'-'Z,'a'-'z','-','_'.
|
||||
|
||||
>>> int2str64(123456789)
|
||||
'7MyqL'
|
||||
"""
|
||||
|
||||
if not (type(number) is types.LongType or type(number) is types.IntType):
|
||||
raise TypeError("You must pass a long or an int")
|
||||
|
||||
string = ""
|
||||
|
||||
while number > 0:
|
||||
string = "%s%s" % (to64(number & 0x3F), string)
|
||||
number /= 64
|
||||
|
||||
return string
|
||||
|
||||
|
||||
def str642int(string):
|
||||
"""Converts a base64 encoded string into an integer.
|
||||
The chars of this string in in the range '0'-'9','A'-'Z','a'-'z','-','_'
|
||||
|
||||
>>> str642int('7MyqL')
|
||||
123456789
|
||||
"""
|
||||
|
||||
if not (type(string) is types.ListType or type(string) is types.StringType):
|
||||
raise TypeError("You must pass a string or a list")
|
||||
|
||||
integer = 0
|
||||
for byte in string:
|
||||
integer *= 64
|
||||
if type(byte) is types.StringType: byte = ord(byte)
|
||||
integer += from64(byte)
|
||||
|
||||
return integer
|
||||
|
||||
def read_random_int(nbits):
|
||||
"""Reads a random integer of approximately nbits bits rounded up
|
||||
to whole bytes"""
|
||||
|
||||
nbytes = int(math.ceil(nbits/8.))
|
||||
randomdata = os.urandom(nbytes)
|
||||
return bytes2int(randomdata)
|
||||
|
||||
def randint(minvalue, maxvalue):
|
||||
"""Returns a random integer x with minvalue <= x <= maxvalue"""
|
||||
|
||||
# Safety - get a lot of random data even if the range is fairly
|
||||
# small
|
||||
min_nbits = 32
|
||||
|
||||
# The range of the random numbers we need to generate
|
||||
range = (maxvalue - minvalue) + 1
|
||||
|
||||
# Which is this number of bytes
|
||||
rangebytes = ((bit_size(range) + 7) / 8)
|
||||
|
||||
# Convert to bits, but make sure it's always at least min_nbits*2
|
||||
rangebits = max(rangebytes * 8, min_nbits * 2)
|
||||
|
||||
# Take a random number of bits between min_nbits and rangebits
|
||||
nbits = random.randint(min_nbits, rangebits)
|
||||
|
||||
return (read_random_int(nbits) % range) + minvalue
|
||||
|
||||
def jacobi(a, b):
|
||||
"""Calculates the value of the Jacobi symbol (a/b)
|
||||
where both a and b are positive integers, and b is odd
|
||||
"""
|
||||
|
||||
if a == 0: return 0
|
||||
result = 1
|
||||
while a > 1:
|
||||
if a & 1:
|
||||
if ((a-1)*(b-1) >> 2) & 1:
|
||||
result = -result
|
||||
a, b = b % a, a
|
||||
else:
|
||||
if (((b * b) - 1) >> 3) & 1:
|
||||
result = -result
|
||||
a >>= 1
|
||||
if a == 0: return 0
|
||||
return result
|
||||
|
||||
def jacobi_witness(x, n):
|
||||
"""Returns False if n is an Euler pseudo-prime with base x, and
|
||||
True otherwise.
|
||||
"""
|
||||
|
||||
j = jacobi(x, n) % n
|
||||
f = pow(x, (n-1)/2, n)
|
||||
|
||||
if j == f: return False
|
||||
return True
|
||||
|
||||
def randomized_primality_testing(n, k):
|
||||
"""Calculates whether n is composite (which is always correct) or
|
||||
prime (which is incorrect with error probability 2**-k)
|
||||
|
||||
Returns False if the number is composite, and True if it's
|
||||
probably prime.
|
||||
"""
|
||||
|
||||
# 50% of Jacobi-witnesses can report compositness of non-prime numbers
|
||||
|
||||
for i in range(k):
|
||||
x = randint(1, n-1)
|
||||
if jacobi_witness(x, n): return False
|
||||
|
||||
return True
|
||||
|
||||
def is_prime(number):
|
||||
"""Returns True if the number is prime, and False otherwise.
|
||||
|
||||
>>> is_prime(42)
|
||||
0
|
||||
>>> is_prime(41)
|
||||
1
|
||||
"""
|
||||
|
||||
if randomized_primality_testing(number, 6):
|
||||
# Prime, according to Jacobi
|
||||
return True
|
||||
|
||||
# Not prime
|
||||
return False
|
||||
|
||||
|
||||
def getprime(nbits):
|
||||
"""Returns a prime number of max. 'math.ceil(nbits/8)*8' bits. In
|
||||
other words: nbits is rounded up to whole bytes.
|
||||
|
||||
>>> p = getprime(8)
|
||||
>>> is_prime(p-1)
|
||||
0
|
||||
>>> is_prime(p)
|
||||
1
|
||||
>>> is_prime(p+1)
|
||||
0
|
||||
"""
|
||||
|
||||
while True:
|
||||
integer = read_random_int(nbits)
|
||||
|
||||
# Make sure it's odd
|
||||
integer |= 1
|
||||
|
||||
# Test for primeness
|
||||
if is_prime(integer): break
|
||||
|
||||
# Retry if not prime
|
||||
|
||||
return integer
|
||||
|
||||
def are_relatively_prime(a, b):
|
||||
"""Returns True if a and b are relatively prime, and False if they
|
||||
are not.
|
||||
|
||||
>>> are_relatively_prime(2, 3)
|
||||
1
|
||||
>>> are_relatively_prime(2, 4)
|
||||
0
|
||||
"""
|
||||
|
||||
d = gcd(a, b)
|
||||
return (d == 1)
|
||||
|
||||
def find_p_q(nbits):
|
||||
"""Returns a tuple of two different primes of nbits bits"""
|
||||
pbits = nbits + (nbits/16) #Make sure that p and q aren't too close
|
||||
qbits = nbits - (nbits/16) #or the factoring programs can factor n
|
||||
p = getprime(pbits)
|
||||
while True:
|
||||
q = getprime(qbits)
|
||||
#Make sure p and q are different.
|
||||
if not q == p: break
|
||||
return (p, q)
|
||||
|
||||
def extended_gcd(a, b):
|
||||
"""Returns a tuple (r, i, j) such that r = gcd(a, b) = ia + jb
|
||||
"""
|
||||
# r = gcd(a,b) i = multiplicitive inverse of a mod b
|
||||
# or j = multiplicitive inverse of b mod a
|
||||
# Neg return values for i or j are made positive mod b or a respectively
|
||||
# Iterateive Version is faster and uses much less stack space
|
||||
x = 0
|
||||
y = 1
|
||||
lx = 1
|
||||
ly = 0
|
||||
oa = a #Remember original a/b to remove
|
||||
ob = b #negative values from return results
|
||||
while b != 0:
|
||||
q = long(a/b)
|
||||
(a, b) = (b, a % b)
|
||||
(x, lx) = ((lx - (q * x)),x)
|
||||
(y, ly) = ((ly - (q * y)),y)
|
||||
if (lx < 0): lx += ob #If neg wrap modulo orignal b
|
||||
if (ly < 0): ly += oa #If neg wrap modulo orignal a
|
||||
return (a, lx, ly) #Return only positive values
|
||||
|
||||
# Main function: calculate encryption and decryption keys
|
||||
def calculate_keys(p, q, nbits):
|
||||
"""Calculates an encryption and a decryption key for p and q, and
|
||||
returns them as a tuple (e, d)"""
|
||||
|
||||
n = p * q
|
||||
phi_n = (p-1) * (q-1)
|
||||
|
||||
while True:
|
||||
# Make sure e has enough bits so we ensure "wrapping" through
|
||||
# modulo n
|
||||
e = max(65537,getprime(nbits/4))
|
||||
if are_relatively_prime(e, n) and are_relatively_prime(e, phi_n): break
|
||||
|
||||
(d, i, j) = extended_gcd(e, phi_n)
|
||||
|
||||
if not d == 1:
|
||||
raise Exception("e (%d) and phi_n (%d) are not relatively prime" % (e, phi_n))
|
||||
if (i < 0):
|
||||
raise Exception("New extended_gcd shouldn't return negative values")
|
||||
if not (e * i) % phi_n == 1:
|
||||
raise Exception("e (%d) and i (%d) are not mult. inv. modulo phi_n (%d)" % (e, i, phi_n))
|
||||
|
||||
return (e, i)
|
||||
|
||||
|
||||
def gen_keys(nbits):
|
||||
"""Generate RSA keys of nbits bits. Returns (p, q, e, d).
|
||||
|
||||
Note: this can take a long time, depending on the key size.
|
||||
"""
|
||||
|
||||
(p, q) = find_p_q(nbits)
|
||||
(e, d) = calculate_keys(p, q, nbits)
|
||||
|
||||
return (p, q, e, d)
|
||||
|
||||
def newkeys(nbits):
|
||||
"""Generates public and private keys, and returns them as (pub,
|
||||
priv).
|
||||
|
||||
The public key consists of a dict {e: ..., , n: ....). The private
|
||||
key consists of a dict {d: ...., p: ...., q: ....).
|
||||
"""
|
||||
nbits = max(9,nbits) # Don't let nbits go below 9 bits
|
||||
(p, q, e, d) = gen_keys(nbits)
|
||||
|
||||
return ( {'e': e, 'n': p*q}, {'d': d, 'p': p, 'q': q} )
|
||||
|
||||
def encrypt_int(message, ekey, n):
|
||||
"""Encrypts a message using encryption key 'ekey', working modulo n"""
|
||||
|
||||
if type(message) is types.IntType:
|
||||
message = long(message)
|
||||
|
||||
if not type(message) is types.LongType:
|
||||
raise TypeError("You must pass a long or int")
|
||||
|
||||
if message < 0 or message > n:
|
||||
raise OverflowError("The message is too long")
|
||||
|
||||
#Note: Bit exponents start at zero (bit counts start at 1) this is correct
|
||||
safebit = bit_size(n) - 2 #compute safe bit (MSB - 1)
|
||||
message += (1 << safebit) #add safebit to ensure folding
|
||||
|
||||
return pow(message, ekey, n)
|
||||
|
||||
def decrypt_int(cyphertext, dkey, n):
|
||||
"""Decrypts a cypher text using the decryption key 'dkey', working
|
||||
modulo n"""
|
||||
|
||||
message = pow(cyphertext, dkey, n)
|
||||
|
||||
safebit = bit_size(n) - 2 #compute safe bit (MSB - 1)
|
||||
message -= (1 << safebit) #remove safebit before decode
|
||||
|
||||
return message
|
||||
|
||||
def encode64chops(chops):
|
||||
"""base64encodes chops and combines them into a ',' delimited string"""
|
||||
|
||||
chips = [] #chips are character chops
|
||||
|
||||
for value in chops:
|
||||
chips.append(int2str64(value))
|
||||
|
||||
#delimit chops with comma
|
||||
encoded = ','.join(chips)
|
||||
|
||||
return encoded
|
||||
|
||||
def decode64chops(string):
|
||||
"""base64decodes and makes a ',' delimited string into chops"""
|
||||
|
||||
chips = string.split(',') #split chops at commas
|
||||
|
||||
chops = []
|
||||
|
||||
for string in chips: #make char chops (chips) into chops
|
||||
chops.append(str642int(string))
|
||||
|
||||
return chops
|
||||
|
||||
def chopstring(message, key, n, funcref):
|
||||
"""Chops the 'message' into integers that fit into n,
|
||||
leaving room for a safebit to be added to ensure that all
|
||||
messages fold during exponentiation. The MSB of the number n
|
||||
is not independant modulo n (setting it could cause overflow), so
|
||||
use the next lower bit for the safebit. Therefore reserve 2-bits
|
||||
in the number n for non-data bits. Calls specified encryption
|
||||
function for each chop.
|
||||
|
||||
Used by 'encrypt' and 'sign'.
|
||||
"""
|
||||
|
||||
msglen = len(message)
|
||||
mbits = msglen * 8
|
||||
#Set aside 2-bits so setting of safebit won't overflow modulo n.
|
||||
nbits = bit_size(n) - 2 # leave room for safebit
|
||||
nbytes = nbits / 8
|
||||
blocks = msglen / nbytes
|
||||
|
||||
if msglen % nbytes > 0:
|
||||
blocks += 1
|
||||
|
||||
cypher = []
|
||||
|
||||
for bindex in range(blocks):
|
||||
offset = bindex * nbytes
|
||||
block = message[offset:offset+nbytes]
|
||||
value = bytes2int(block)
|
||||
cypher.append(funcref(value, key, n))
|
||||
|
||||
return encode64chops(cypher) #Encode encrypted ints to base64 strings
|
||||
|
||||
def gluechops(string, key, n, funcref):
|
||||
"""Glues chops back together into a string. calls
|
||||
funcref(integer, key, n) for each chop.
|
||||
|
||||
Used by 'decrypt' and 'verify'.
|
||||
"""
|
||||
message = ""
|
||||
|
||||
chops = decode64chops(string) #Decode base64 strings into integer chops
|
||||
|
||||
for cpart in chops:
|
||||
mpart = funcref(cpart, key, n) #Decrypt each chop
|
||||
message += int2bytes(mpart) #Combine decrypted strings into a msg
|
||||
|
||||
return message
|
||||
|
||||
def encrypt(message, key):
|
||||
"""Encrypts a string 'message' with the public key 'key'"""
|
||||
if 'n' not in key:
|
||||
raise Exception("You must use the public key with encrypt")
|
||||
|
||||
return chopstring(message, key['e'], key['n'], encrypt_int)
|
||||
|
||||
def sign(message, key):
|
||||
"""Signs a string 'message' with the private key 'key'"""
|
||||
if 'p' not in key:
|
||||
raise Exception("You must use the private key with sign")
|
||||
|
||||
return chopstring(message, key['d'], key['p']*key['q'], encrypt_int)
|
||||
|
||||
def decrypt(cypher, key):
|
||||
"""Decrypts a string 'cypher' with the private key 'key'"""
|
||||
if 'p' not in key:
|
||||
raise Exception("You must use the private key with decrypt")
|
||||
|
||||
return gluechops(cypher, key['d'], key['p']*key['q'], decrypt_int)
|
||||
|
||||
def verify(cypher, key):
|
||||
"""Verifies a string 'cypher' with the public key 'key'"""
|
||||
if 'n' not in key:
|
||||
raise Exception("You must use the public key with verify")
|
||||
|
||||
return gluechops(cypher, key['e'], key['n'], decrypt_int)
|
||||
|
||||
# Do doctest if we're not imported
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
__all__ = ["newkeys", "encrypt", "decrypt", "sign", "verify"]
|
||||
|
|
@ -1,35 +1,52 @@
|
|||
'''ASN.1 definitions.
|
||||
|
||||
Not all ASN.1-handling code use these definitions, but when it does, they should be here.
|
||||
'''
|
||||
|
||||
from pyasn1.type import univ, namedtype, tag
|
||||
|
||||
class PubKeyHeader(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('oid', univ.ObjectIdentifier()),
|
||||
namedtype.NamedType('parameters', univ.Null()),
|
||||
)
|
||||
|
||||
class OpenSSLPubKey(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('header', PubKeyHeader()),
|
||||
|
||||
# This little hack (the implicit tag) allows us to get a Bit String as Octet String
|
||||
namedtype.NamedType('key', univ.OctetString().subtype(
|
||||
implicitTag=tag.Tag(tagClass=0, tagFormat=0, tagId=3))),
|
||||
)
|
||||
|
||||
|
||||
class AsnPubKey(univ.Sequence):
|
||||
'''ASN.1 contents of DER encoded public key:
|
||||
|
||||
RSAPublicKey ::= SEQUENCE {
|
||||
modulus INTEGER, -- n
|
||||
publicExponent INTEGER, -- e
|
||||
'''
|
||||
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType('modulus', univ.Integer()),
|
||||
namedtype.NamedType('publicExponent', univ.Integer()),
|
||||
)
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""ASN.1 definitions.
|
||||
|
||||
Not all ASN.1-handling code use these definitions, but when it does, they should be here.
|
||||
"""
|
||||
|
||||
from pyasn1.type import univ, namedtype, tag
|
||||
|
||||
|
||||
class PubKeyHeader(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType("oid", univ.ObjectIdentifier()),
|
||||
namedtype.NamedType("parameters", univ.Null()),
|
||||
)
|
||||
|
||||
|
||||
class OpenSSLPubKey(univ.Sequence):
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType("header", PubKeyHeader()),
|
||||
# This little hack (the implicit tag) allows us to get a Bit String as Octet String
|
||||
namedtype.NamedType(
|
||||
"key",
|
||||
univ.OctetString().subtype(implicitTag=tag.Tag(tagClass=0, tagFormat=0, tagId=3)),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class AsnPubKey(univ.Sequence):
|
||||
"""ASN.1 contents of DER encoded public key:
|
||||
|
||||
RSAPublicKey ::= SEQUENCE {
|
||||
modulus INTEGER, -- n
|
||||
publicExponent INTEGER, -- e
|
||||
"""
|
||||
|
||||
componentType = namedtype.NamedTypes(
|
||||
namedtype.NamedType("modulus", univ.Integer()),
|
||||
namedtype.NamedType("publicExponent", univ.Integer()),
|
||||
)
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''Large file support
|
||||
|
||||
- break a file into smaller blocks, and encrypt them, and store the
|
||||
encrypted blocks in another file.
|
||||
|
||||
- take such an encrypted files, decrypt its blocks, and reconstruct the
|
||||
original file.
|
||||
|
||||
The encrypted file format is as follows, where || denotes byte concatenation:
|
||||
|
||||
FILE := VERSION || BLOCK || BLOCK ...
|
||||
|
||||
BLOCK := LENGTH || DATA
|
||||
|
||||
LENGTH := varint-encoded length of the subsequent data. Varint comes from
|
||||
Google Protobuf, and encodes an integer into a variable number of bytes.
|
||||
Each byte uses the 7 lowest bits to encode the value. The highest bit set
|
||||
to 1 indicates the next byte is also part of the varint. The last byte will
|
||||
have this bit set to 0.
|
||||
|
||||
This file format is called the VARBLOCK format, in line with the varint format
|
||||
used to denote the block sizes.
|
||||
|
||||
'''
|
||||
|
||||
from rsa import key, common, pkcs1, varblock
|
||||
from rsa._compat import byte
|
||||
|
||||
def encrypt_bigfile(infile, outfile, pub_key):
|
||||
'''Encrypts a file, writing it to 'outfile' in VARBLOCK format.
|
||||
|
||||
:param infile: file-like object to read the cleartext from
|
||||
:param outfile: file-like object to write the crypto in VARBLOCK format to
|
||||
:param pub_key: :py:class:`rsa.PublicKey` to encrypt with
|
||||
|
||||
'''
|
||||
|
||||
if not isinstance(pub_key, key.PublicKey):
|
||||
raise TypeError('Public key required, but got %r' % pub_key)
|
||||
|
||||
key_bytes = common.bit_size(pub_key.n) // 8
|
||||
blocksize = key_bytes - 11 # keep space for PKCS#1 padding
|
||||
|
||||
# Write the version number to the VARBLOCK file
|
||||
outfile.write(byte(varblock.VARBLOCK_VERSION))
|
||||
|
||||
# Encrypt and write each block
|
||||
for block in varblock.yield_fixedblocks(infile, blocksize):
|
||||
crypto = pkcs1.encrypt(block, pub_key)
|
||||
|
||||
varblock.write_varint(outfile, len(crypto))
|
||||
outfile.write(crypto)
|
||||
|
||||
def decrypt_bigfile(infile, outfile, priv_key):
|
||||
'''Decrypts an encrypted VARBLOCK file, writing it to 'outfile'
|
||||
|
||||
:param infile: file-like object to read the crypto in VARBLOCK format from
|
||||
:param outfile: file-like object to write the cleartext to
|
||||
:param priv_key: :py:class:`rsa.PrivateKey` to decrypt with
|
||||
|
||||
'''
|
||||
|
||||
if not isinstance(priv_key, key.PrivateKey):
|
||||
raise TypeError('Private key required, but got %r' % priv_key)
|
||||
|
||||
for block in varblock.yield_varblocks(infile):
|
||||
cleartext = pkcs1.decrypt(block, priv_key)
|
||||
outfile.write(cleartext)
|
||||
|
||||
__all__ = ['encrypt_bigfile', 'decrypt_bigfile']
|
||||
|
|
@ -1,379 +1,321 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''Commandline scripts.
|
||||
|
||||
These scripts are called by the executables defined in setup.py.
|
||||
'''
|
||||
|
||||
from __future__ import with_statement, print_function
|
||||
|
||||
import abc
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
|
||||
import rsa
|
||||
import rsa.bigfile
|
||||
import rsa.pkcs1
|
||||
|
||||
HASH_METHODS = sorted(rsa.pkcs1.HASH_METHODS.keys())
|
||||
|
||||
def keygen():
|
||||
'''Key generator.'''
|
||||
|
||||
# Parse the CLI options
|
||||
parser = OptionParser(usage='usage: %prog [options] keysize',
|
||||
description='Generates a new RSA keypair of "keysize" bits.')
|
||||
|
||||
parser.add_option('--pubout', type='string',
|
||||
help='Output filename for the public key. The public key is '
|
||||
'not saved if this option is not present. You can use '
|
||||
'pyrsa-priv2pub to create the public key file later.')
|
||||
|
||||
parser.add_option('-o', '--out', type='string',
|
||||
help='Output filename for the private key. The key is '
|
||||
'written to stdout if this option is not present.')
|
||||
|
||||
parser.add_option('--form',
|
||||
help='key format of the private and public keys - default PEM',
|
||||
choices=('PEM', 'DER'), default='PEM')
|
||||
|
||||
(cli, cli_args) = parser.parse_args(sys.argv[1:])
|
||||
|
||||
if len(cli_args) != 1:
|
||||
parser.print_help()
|
||||
raise SystemExit(1)
|
||||
|
||||
try:
|
||||
keysize = int(cli_args[0])
|
||||
except ValueError:
|
||||
parser.print_help()
|
||||
print('Not a valid number: %s' % cli_args[0], file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
print('Generating %i-bit key' % keysize, file=sys.stderr)
|
||||
(pub_key, priv_key) = rsa.newkeys(keysize)
|
||||
|
||||
|
||||
# Save public key
|
||||
if cli.pubout:
|
||||
print('Writing public key to %s' % cli.pubout, file=sys.stderr)
|
||||
data = pub_key.save_pkcs1(format=cli.form)
|
||||
with open(cli.pubout, 'wb') as outfile:
|
||||
outfile.write(data)
|
||||
|
||||
# Save private key
|
||||
data = priv_key.save_pkcs1(format=cli.form)
|
||||
|
||||
if cli.out:
|
||||
print('Writing private key to %s' % cli.out, file=sys.stderr)
|
||||
with open(cli.out, 'wb') as outfile:
|
||||
outfile.write(data)
|
||||
else:
|
||||
print('Writing private key to stdout', file=sys.stderr)
|
||||
sys.stdout.write(data)
|
||||
|
||||
|
||||
class CryptoOperation(object):
|
||||
'''CLI callable that operates with input, output, and a key.'''
|
||||
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
keyname = 'public' # or 'private'
|
||||
usage = 'usage: %%prog [options] %(keyname)s_key'
|
||||
description = None
|
||||
operation = 'decrypt'
|
||||
operation_past = 'decrypted'
|
||||
operation_progressive = 'decrypting'
|
||||
input_help = 'Name of the file to %(operation)s. Reads from stdin if ' \
|
||||
'not specified.'
|
||||
output_help = 'Name of the file to write the %(operation_past)s file ' \
|
||||
'to. Written to stdout if this option is not present.'
|
||||
expected_cli_args = 1
|
||||
has_output = True
|
||||
|
||||
key_class = rsa.PublicKey
|
||||
|
||||
def __init__(self):
|
||||
self.usage = self.usage % self.__class__.__dict__
|
||||
self.input_help = self.input_help % self.__class__.__dict__
|
||||
self.output_help = self.output_help % self.__class__.__dict__
|
||||
|
||||
@abc.abstractmethod
|
||||
def perform_operation(self, indata, key, cli_args=None):
|
||||
'''Performs the program's operation.
|
||||
|
||||
Implement in a subclass.
|
||||
|
||||
:returns: the data to write to the output.
|
||||
'''
|
||||
|
||||
def __call__(self):
|
||||
'''Runs the program.'''
|
||||
|
||||
(cli, cli_args) = self.parse_cli()
|
||||
|
||||
key = self.read_key(cli_args[0], cli.keyform)
|
||||
|
||||
indata = self.read_infile(cli.input)
|
||||
|
||||
print(self.operation_progressive.title(), file=sys.stderr)
|
||||
outdata = self.perform_operation(indata, key, cli_args)
|
||||
|
||||
if self.has_output:
|
||||
self.write_outfile(outdata, cli.output)
|
||||
|
||||
def parse_cli(self):
|
||||
'''Parse the CLI options
|
||||
|
||||
:returns: (cli_opts, cli_args)
|
||||
'''
|
||||
|
||||
parser = OptionParser(usage=self.usage, description=self.description)
|
||||
|
||||
parser.add_option('-i', '--input', type='string', help=self.input_help)
|
||||
|
||||
if self.has_output:
|
||||
parser.add_option('-o', '--output', type='string', help=self.output_help)
|
||||
|
||||
parser.add_option('--keyform',
|
||||
help='Key format of the %s key - default PEM' % self.keyname,
|
||||
choices=('PEM', 'DER'), default='PEM')
|
||||
|
||||
(cli, cli_args) = parser.parse_args(sys.argv[1:])
|
||||
|
||||
if len(cli_args) != self.expected_cli_args:
|
||||
parser.print_help()
|
||||
raise SystemExit(1)
|
||||
|
||||
return (cli, cli_args)
|
||||
|
||||
def read_key(self, filename, keyform):
|
||||
'''Reads a public or private key.'''
|
||||
|
||||
print('Reading %s key from %s' % (self.keyname, filename), file=sys.stderr)
|
||||
with open(filename, 'rb') as keyfile:
|
||||
keydata = keyfile.read()
|
||||
|
||||
return self.key_class.load_pkcs1(keydata, keyform)
|
||||
|
||||
def read_infile(self, inname):
|
||||
'''Read the input file'''
|
||||
|
||||
if inname:
|
||||
print('Reading input from %s' % inname, file=sys.stderr)
|
||||
with open(inname, 'rb') as infile:
|
||||
return infile.read()
|
||||
|
||||
print('Reading input from stdin', file=sys.stderr)
|
||||
return sys.stdin.read()
|
||||
|
||||
def write_outfile(self, outdata, outname):
|
||||
'''Write the output file'''
|
||||
|
||||
if outname:
|
||||
print('Writing output to %s' % outname, file=sys.stderr)
|
||||
with open(outname, 'wb') as outfile:
|
||||
outfile.write(outdata)
|
||||
else:
|
||||
print('Writing output to stdout', file=sys.stderr)
|
||||
sys.stdout.write(outdata)
|
||||
|
||||
class EncryptOperation(CryptoOperation):
|
||||
'''Encrypts a file.'''
|
||||
|
||||
keyname = 'public'
|
||||
description = ('Encrypts a file. The file must be shorter than the key '
|
||||
'length in order to be encrypted. For larger files, use the '
|
||||
'pyrsa-encrypt-bigfile command.')
|
||||
operation = 'encrypt'
|
||||
operation_past = 'encrypted'
|
||||
operation_progressive = 'encrypting'
|
||||
|
||||
|
||||
def perform_operation(self, indata, pub_key, cli_args=None):
|
||||
'''Encrypts files.'''
|
||||
|
||||
return rsa.encrypt(indata, pub_key)
|
||||
|
||||
class DecryptOperation(CryptoOperation):
|
||||
'''Decrypts a file.'''
|
||||
|
||||
keyname = 'private'
|
||||
description = ('Decrypts a file. The original file must be shorter than '
|
||||
'the key length in order to have been encrypted. For larger '
|
||||
'files, use the pyrsa-decrypt-bigfile command.')
|
||||
operation = 'decrypt'
|
||||
operation_past = 'decrypted'
|
||||
operation_progressive = 'decrypting'
|
||||
key_class = rsa.PrivateKey
|
||||
|
||||
def perform_operation(self, indata, priv_key, cli_args=None):
|
||||
'''Decrypts files.'''
|
||||
|
||||
return rsa.decrypt(indata, priv_key)
|
||||
|
||||
class SignOperation(CryptoOperation):
|
||||
'''Signs a file.'''
|
||||
|
||||
keyname = 'private'
|
||||
usage = 'usage: %%prog [options] private_key hash_method'
|
||||
description = ('Signs a file, outputs the signature. Choose the hash '
|
||||
'method from %s' % ', '.join(HASH_METHODS))
|
||||
operation = 'sign'
|
||||
operation_past = 'signature'
|
||||
operation_progressive = 'Signing'
|
||||
key_class = rsa.PrivateKey
|
||||
expected_cli_args = 2
|
||||
|
||||
output_help = ('Name of the file to write the signature to. Written '
|
||||
'to stdout if this option is not present.')
|
||||
|
||||
def perform_operation(self, indata, priv_key, cli_args):
|
||||
'''Decrypts files.'''
|
||||
|
||||
hash_method = cli_args[1]
|
||||
if hash_method not in HASH_METHODS:
|
||||
raise SystemExit('Invalid hash method, choose one of %s' %
|
||||
', '.join(HASH_METHODS))
|
||||
|
||||
return rsa.sign(indata, priv_key, hash_method)
|
||||
|
||||
class VerifyOperation(CryptoOperation):
|
||||
'''Verify a signature.'''
|
||||
|
||||
keyname = 'public'
|
||||
usage = 'usage: %%prog [options] public_key signature_file'
|
||||
description = ('Verifies a signature, exits with status 0 upon success, '
|
||||
'prints an error message and exits with status 1 upon error.')
|
||||
operation = 'verify'
|
||||
operation_past = 'verified'
|
||||
operation_progressive = 'Verifying'
|
||||
key_class = rsa.PublicKey
|
||||
expected_cli_args = 2
|
||||
has_output = False
|
||||
|
||||
def perform_operation(self, indata, pub_key, cli_args):
|
||||
'''Decrypts files.'''
|
||||
|
||||
signature_file = cli_args[1]
|
||||
|
||||
with open(signature_file, 'rb') as sigfile:
|
||||
signature = sigfile.read()
|
||||
|
||||
try:
|
||||
rsa.verify(indata, signature, pub_key)
|
||||
except rsa.VerificationError:
|
||||
raise SystemExit('Verification failed.')
|
||||
|
||||
print('Verification OK', file=sys.stderr)
|
||||
|
||||
|
||||
class BigfileOperation(CryptoOperation):
|
||||
'''CryptoOperation that doesn't read the entire file into memory.'''
|
||||
|
||||
def __init__(self):
|
||||
CryptoOperation.__init__(self)
|
||||
|
||||
self.file_objects = []
|
||||
|
||||
def __del__(self):
|
||||
'''Closes any open file handles.'''
|
||||
|
||||
for fobj in self.file_objects:
|
||||
fobj.close()
|
||||
|
||||
def __call__(self):
|
||||
'''Runs the program.'''
|
||||
|
||||
(cli, cli_args) = self.parse_cli()
|
||||
|
||||
key = self.read_key(cli_args[0], cli.keyform)
|
||||
|
||||
# Get the file handles
|
||||
infile = self.get_infile(cli.input)
|
||||
outfile = self.get_outfile(cli.output)
|
||||
|
||||
# Call the operation
|
||||
print(self.operation_progressive.title(), file=sys.stderr)
|
||||
self.perform_operation(infile, outfile, key, cli_args)
|
||||
|
||||
def get_infile(self, inname):
|
||||
'''Returns the input file object'''
|
||||
|
||||
if inname:
|
||||
print('Reading input from %s' % inname, file=sys.stderr)
|
||||
fobj = open(inname, 'rb')
|
||||
self.file_objects.append(fobj)
|
||||
else:
|
||||
print('Reading input from stdin', file=sys.stderr)
|
||||
fobj = sys.stdin
|
||||
|
||||
return fobj
|
||||
|
||||
def get_outfile(self, outname):
|
||||
'''Returns the output file object'''
|
||||
|
||||
if outname:
|
||||
print('Will write output to %s' % outname, file=sys.stderr)
|
||||
fobj = open(outname, 'wb')
|
||||
self.file_objects.append(fobj)
|
||||
else:
|
||||
print('Will write output to stdout', file=sys.stderr)
|
||||
fobj = sys.stdout
|
||||
|
||||
return fobj
|
||||
|
||||
class EncryptBigfileOperation(BigfileOperation):
|
||||
'''Encrypts a file to VARBLOCK format.'''
|
||||
|
||||
keyname = 'public'
|
||||
description = ('Encrypts a file to an encrypted VARBLOCK file. The file '
|
||||
'can be larger than the key length, but the output file is only '
|
||||
'compatible with Python-RSA.')
|
||||
operation = 'encrypt'
|
||||
operation_past = 'encrypted'
|
||||
operation_progressive = 'encrypting'
|
||||
|
||||
def perform_operation(self, infile, outfile, pub_key, cli_args=None):
|
||||
'''Encrypts files to VARBLOCK.'''
|
||||
|
||||
return rsa.bigfile.encrypt_bigfile(infile, outfile, pub_key)
|
||||
|
||||
class DecryptBigfileOperation(BigfileOperation):
|
||||
'''Decrypts a file in VARBLOCK format.'''
|
||||
|
||||
keyname = 'private'
|
||||
description = ('Decrypts an encrypted VARBLOCK file that was encrypted '
|
||||
'with pyrsa-encrypt-bigfile')
|
||||
operation = 'decrypt'
|
||||
operation_past = 'decrypted'
|
||||
operation_progressive = 'decrypting'
|
||||
key_class = rsa.PrivateKey
|
||||
|
||||
def perform_operation(self, infile, outfile, priv_key, cli_args=None):
|
||||
'''Decrypts a VARBLOCK file.'''
|
||||
|
||||
return rsa.bigfile.decrypt_bigfile(infile, outfile, priv_key)
|
||||
|
||||
|
||||
encrypt = EncryptOperation()
|
||||
decrypt = DecryptOperation()
|
||||
sign = SignOperation()
|
||||
verify = VerifyOperation()
|
||||
encrypt_bigfile = EncryptBigfileOperation()
|
||||
decrypt_bigfile = DecryptBigfileOperation()
|
||||
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Commandline scripts.
|
||||
|
||||
These scripts are called by the executables defined in setup.py.
|
||||
"""
|
||||
|
||||
import abc
|
||||
import sys
|
||||
import typing
|
||||
import optparse
|
||||
|
||||
import rsa
|
||||
import rsa.key
|
||||
import rsa.pkcs1
|
||||
|
||||
HASH_METHODS = sorted(rsa.pkcs1.HASH_METHODS.keys())
|
||||
Indexable = typing.Union[typing.Tuple, typing.List[str]]
|
||||
|
||||
|
||||
def keygen() -> None:
|
||||
"""Key generator."""
|
||||
|
||||
# Parse the CLI options
|
||||
parser = optparse.OptionParser(
|
||||
usage="usage: %prog [options] keysize",
|
||||
description='Generates a new RSA key pair of "keysize" bits.',
|
||||
)
|
||||
|
||||
parser.add_option(
|
||||
"--pubout",
|
||||
type="string",
|
||||
help="Output filename for the public key. The public key is "
|
||||
"not saved if this option is not present. You can use "
|
||||
"pyrsa-priv2pub to create the public key file later.",
|
||||
)
|
||||
|
||||
parser.add_option(
|
||||
"-o",
|
||||
"--out",
|
||||
type="string",
|
||||
help="Output filename for the private key. The key is "
|
||||
"written to stdout if this option is not present.",
|
||||
)
|
||||
|
||||
parser.add_option(
|
||||
"--form",
|
||||
help="key format of the private and public keys - default PEM",
|
||||
choices=("PEM", "DER"),
|
||||
default="PEM",
|
||||
)
|
||||
|
||||
(cli, cli_args) = parser.parse_args(sys.argv[1:])
|
||||
|
||||
if len(cli_args) != 1:
|
||||
parser.print_help()
|
||||
raise SystemExit(1)
|
||||
|
||||
try:
|
||||
keysize = int(cli_args[0])
|
||||
except ValueError as ex:
|
||||
parser.print_help()
|
||||
print("Not a valid number: %s" % cli_args[0], file=sys.stderr)
|
||||
raise SystemExit(1) from ex
|
||||
|
||||
print("Generating %i-bit key" % keysize, file=sys.stderr)
|
||||
(pub_key, priv_key) = rsa.newkeys(keysize)
|
||||
|
||||
# Save public key
|
||||
if cli.pubout:
|
||||
print("Writing public key to %s" % cli.pubout, file=sys.stderr)
|
||||
data = pub_key.save_pkcs1(format=cli.form)
|
||||
with open(cli.pubout, "wb") as outfile:
|
||||
outfile.write(data)
|
||||
|
||||
# Save private key
|
||||
data = priv_key.save_pkcs1(format=cli.form)
|
||||
|
||||
if cli.out:
|
||||
print("Writing private key to %s" % cli.out, file=sys.stderr)
|
||||
with open(cli.out, "wb") as outfile:
|
||||
outfile.write(data)
|
||||
else:
|
||||
print("Writing private key to stdout", file=sys.stderr)
|
||||
sys.stdout.buffer.write(data)
|
||||
|
||||
|
||||
class CryptoOperation(metaclass=abc.ABCMeta):
|
||||
"""CLI callable that operates with input, output, and a key."""
|
||||
|
||||
keyname = "public" # or 'private'
|
||||
usage = "usage: %%prog [options] %(keyname)s_key"
|
||||
description = ""
|
||||
operation = "decrypt"
|
||||
operation_past = "decrypted"
|
||||
operation_progressive = "decrypting"
|
||||
input_help = "Name of the file to %(operation)s. Reads from stdin if " "not specified."
|
||||
output_help = (
|
||||
"Name of the file to write the %(operation_past)s file "
|
||||
"to. Written to stdout if this option is not present."
|
||||
)
|
||||
expected_cli_args = 1
|
||||
has_output = True
|
||||
|
||||
key_class = rsa.PublicKey # type: typing.Type[rsa.key.AbstractKey]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.usage = self.usage % self.__class__.__dict__
|
||||
self.input_help = self.input_help % self.__class__.__dict__
|
||||
self.output_help = self.output_help % self.__class__.__dict__
|
||||
|
||||
@abc.abstractmethod
|
||||
def perform_operation(
|
||||
self, indata: bytes, key: rsa.key.AbstractKey, cli_args: Indexable
|
||||
) -> typing.Any:
|
||||
"""Performs the program's operation.
|
||||
|
||||
Implement in a subclass.
|
||||
|
||||
:returns: the data to write to the output.
|
||||
"""
|
||||
|
||||
def __call__(self) -> None:
|
||||
"""Runs the program."""
|
||||
|
||||
(cli, cli_args) = self.parse_cli()
|
||||
|
||||
key = self.read_key(cli_args[0], cli.keyform)
|
||||
|
||||
indata = self.read_infile(cli.input)
|
||||
|
||||
print(self.operation_progressive.title(), file=sys.stderr)
|
||||
outdata = self.perform_operation(indata, key, cli_args)
|
||||
|
||||
if self.has_output:
|
||||
self.write_outfile(outdata, cli.output)
|
||||
|
||||
def parse_cli(self) -> typing.Tuple[optparse.Values, typing.List[str]]:
|
||||
"""Parse the CLI options
|
||||
|
||||
:returns: (cli_opts, cli_args)
|
||||
"""
|
||||
|
||||
parser = optparse.OptionParser(usage=self.usage, description=self.description)
|
||||
|
||||
parser.add_option("-i", "--input", type="string", help=self.input_help)
|
||||
|
||||
if self.has_output:
|
||||
parser.add_option("-o", "--output", type="string", help=self.output_help)
|
||||
|
||||
parser.add_option(
|
||||
"--keyform",
|
||||
help="Key format of the %s key - default PEM" % self.keyname,
|
||||
choices=("PEM", "DER"),
|
||||
default="PEM",
|
||||
)
|
||||
|
||||
(cli, cli_args) = parser.parse_args(sys.argv[1:])
|
||||
|
||||
if len(cli_args) != self.expected_cli_args:
|
||||
parser.print_help()
|
||||
raise SystemExit(1)
|
||||
|
||||
return cli, cli_args
|
||||
|
||||
def read_key(self, filename: str, keyform: str) -> rsa.key.AbstractKey:
|
||||
"""Reads a public or private key."""
|
||||
|
||||
print("Reading %s key from %s" % (self.keyname, filename), file=sys.stderr)
|
||||
with open(filename, "rb") as keyfile:
|
||||
keydata = keyfile.read()
|
||||
|
||||
return self.key_class.load_pkcs1(keydata, keyform)
|
||||
|
||||
def read_infile(self, inname: str) -> bytes:
|
||||
"""Read the input file"""
|
||||
|
||||
if inname:
|
||||
print("Reading input from %s" % inname, file=sys.stderr)
|
||||
with open(inname, "rb") as infile:
|
||||
return infile.read()
|
||||
|
||||
print("Reading input from stdin", file=sys.stderr)
|
||||
return sys.stdin.buffer.read()
|
||||
|
||||
def write_outfile(self, outdata: bytes, outname: str) -> None:
|
||||
"""Write the output file"""
|
||||
|
||||
if outname:
|
||||
print("Writing output to %s" % outname, file=sys.stderr)
|
||||
with open(outname, "wb") as outfile:
|
||||
outfile.write(outdata)
|
||||
else:
|
||||
print("Writing output to stdout", file=sys.stderr)
|
||||
sys.stdout.buffer.write(outdata)
|
||||
|
||||
|
||||
class EncryptOperation(CryptoOperation):
|
||||
"""Encrypts a file."""
|
||||
|
||||
keyname = "public"
|
||||
description = (
|
||||
"Encrypts a file. The file must be shorter than the key " "length in order to be encrypted."
|
||||
)
|
||||
operation = "encrypt"
|
||||
operation_past = "encrypted"
|
||||
operation_progressive = "encrypting"
|
||||
|
||||
def perform_operation(
|
||||
self, indata: bytes, pub_key: rsa.key.AbstractKey, cli_args: Indexable = ()
|
||||
) -> bytes:
|
||||
"""Encrypts files."""
|
||||
assert isinstance(pub_key, rsa.key.PublicKey)
|
||||
return rsa.encrypt(indata, pub_key)
|
||||
|
||||
|
||||
class DecryptOperation(CryptoOperation):
|
||||
"""Decrypts a file."""
|
||||
|
||||
keyname = "private"
|
||||
description = (
|
||||
"Decrypts a file. The original file must be shorter than "
|
||||
"the key length in order to have been encrypted."
|
||||
)
|
||||
operation = "decrypt"
|
||||
operation_past = "decrypted"
|
||||
operation_progressive = "decrypting"
|
||||
key_class = rsa.PrivateKey
|
||||
|
||||
def perform_operation(
|
||||
self, indata: bytes, priv_key: rsa.key.AbstractKey, cli_args: Indexable = ()
|
||||
) -> bytes:
|
||||
"""Decrypts files."""
|
||||
assert isinstance(priv_key, rsa.key.PrivateKey)
|
||||
return rsa.decrypt(indata, priv_key)
|
||||
|
||||
|
||||
class SignOperation(CryptoOperation):
|
||||
"""Signs a file."""
|
||||
|
||||
keyname = "private"
|
||||
usage = "usage: %%prog [options] private_key hash_method"
|
||||
description = (
|
||||
"Signs a file, outputs the signature. Choose the hash "
|
||||
"method from %s" % ", ".join(HASH_METHODS)
|
||||
)
|
||||
operation = "sign"
|
||||
operation_past = "signature"
|
||||
operation_progressive = "Signing"
|
||||
key_class = rsa.PrivateKey
|
||||
expected_cli_args = 2
|
||||
|
||||
output_help = (
|
||||
"Name of the file to write the signature to. Written "
|
||||
"to stdout if this option is not present."
|
||||
)
|
||||
|
||||
def perform_operation(
|
||||
self, indata: bytes, priv_key: rsa.key.AbstractKey, cli_args: Indexable
|
||||
) -> bytes:
|
||||
"""Signs files."""
|
||||
assert isinstance(priv_key, rsa.key.PrivateKey)
|
||||
|
||||
hash_method = cli_args[1]
|
||||
if hash_method not in HASH_METHODS:
|
||||
raise SystemExit("Invalid hash method, choose one of %s" % ", ".join(HASH_METHODS))
|
||||
|
||||
return rsa.sign(indata, priv_key, hash_method)
|
||||
|
||||
|
||||
class VerifyOperation(CryptoOperation):
|
||||
"""Verify a signature."""
|
||||
|
||||
keyname = "public"
|
||||
usage = "usage: %%prog [options] public_key signature_file"
|
||||
description = (
|
||||
"Verifies a signature, exits with status 0 upon success, "
|
||||
"prints an error message and exits with status 1 upon error."
|
||||
)
|
||||
operation = "verify"
|
||||
operation_past = "verified"
|
||||
operation_progressive = "Verifying"
|
||||
key_class = rsa.PublicKey
|
||||
expected_cli_args = 2
|
||||
has_output = False
|
||||
|
||||
def perform_operation(
|
||||
self, indata: bytes, pub_key: rsa.key.AbstractKey, cli_args: Indexable
|
||||
) -> None:
|
||||
"""Verifies files."""
|
||||
assert isinstance(pub_key, rsa.key.PublicKey)
|
||||
|
||||
signature_file = cli_args[1]
|
||||
|
||||
with open(signature_file, "rb") as sigfile:
|
||||
signature = sigfile.read()
|
||||
|
||||
try:
|
||||
rsa.verify(indata, signature, pub_key)
|
||||
except rsa.VerificationError as ex:
|
||||
raise SystemExit("Verification failed.") from ex
|
||||
|
||||
print("Verification OK", file=sys.stderr)
|
||||
|
||||
|
||||
encrypt = EncryptOperation()
|
||||
decrypt = DecryptOperation()
|
||||
sign = SignOperation()
|
||||
verify = VerifyOperation()
|
||||
|
|
|
@ -1,185 +1,184 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''Common functionality shared by several modules.'''
|
||||
|
||||
|
||||
def bit_size(num):
|
||||
'''
|
||||
Number of bits needed to represent a integer excluding any prefix
|
||||
0 bits.
|
||||
|
||||
As per definition from http://wiki.python.org/moin/BitManipulation and
|
||||
to match the behavior of the Python 3 API.
|
||||
|
||||
Usage::
|
||||
|
||||
>>> bit_size(1023)
|
||||
10
|
||||
>>> bit_size(1024)
|
||||
11
|
||||
>>> bit_size(1025)
|
||||
11
|
||||
|
||||
:param num:
|
||||
Integer value. If num is 0, returns 0. Only the absolute value of the
|
||||
number is considered. Therefore, signed integers will be abs(num)
|
||||
before the number's bit length is determined.
|
||||
:returns:
|
||||
Returns the number of bits in the integer.
|
||||
'''
|
||||
if num == 0:
|
||||
return 0
|
||||
if num < 0:
|
||||
num = -num
|
||||
|
||||
# Make sure this is an int and not a float.
|
||||
num & 1
|
||||
|
||||
hex_num = "%x" % num
|
||||
return ((len(hex_num) - 1) * 4) + {
|
||||
'0':0, '1':1, '2':2, '3':2,
|
||||
'4':3, '5':3, '6':3, '7':3,
|
||||
'8':4, '9':4, 'a':4, 'b':4,
|
||||
'c':4, 'd':4, 'e':4, 'f':4,
|
||||
}[hex_num[0]]
|
||||
|
||||
|
||||
def _bit_size(number):
|
||||
'''
|
||||
Returns the number of bits required to hold a specific long number.
|
||||
'''
|
||||
if number < 0:
|
||||
raise ValueError('Only nonnegative numbers possible: %s' % number)
|
||||
|
||||
if number == 0:
|
||||
return 0
|
||||
|
||||
# This works, even with very large numbers. When using math.log(number, 2),
|
||||
# you'll get rounding errors and it'll fail.
|
||||
bits = 0
|
||||
while number:
|
||||
bits += 1
|
||||
number >>= 1
|
||||
|
||||
return bits
|
||||
|
||||
|
||||
def byte_size(number):
|
||||
'''
|
||||
Returns the number of bytes required to hold a specific long number.
|
||||
|
||||
The number of bytes is rounded up.
|
||||
|
||||
Usage::
|
||||
|
||||
>>> byte_size(1 << 1023)
|
||||
128
|
||||
>>> byte_size((1 << 1024) - 1)
|
||||
128
|
||||
>>> byte_size(1 << 1024)
|
||||
129
|
||||
|
||||
:param number:
|
||||
An unsigned integer
|
||||
:returns:
|
||||
The number of bytes required to hold a specific long number.
|
||||
'''
|
||||
quanta, mod = divmod(bit_size(number), 8)
|
||||
if mod or number == 0:
|
||||
quanta += 1
|
||||
return quanta
|
||||
#return int(math.ceil(bit_size(number) / 8.0))
|
||||
|
||||
|
||||
def extended_gcd(a, b):
|
||||
'''Returns a tuple (r, i, j) such that r = gcd(a, b) = ia + jb
|
||||
'''
|
||||
# r = gcd(a,b) i = multiplicitive inverse of a mod b
|
||||
# or j = multiplicitive inverse of b mod a
|
||||
# Neg return values for i or j are made positive mod b or a respectively
|
||||
# Iterateive Version is faster and uses much less stack space
|
||||
x = 0
|
||||
y = 1
|
||||
lx = 1
|
||||
ly = 0
|
||||
oa = a #Remember original a/b to remove
|
||||
ob = b #negative values from return results
|
||||
while b != 0:
|
||||
q = a // b
|
||||
(a, b) = (b, a % b)
|
||||
(x, lx) = ((lx - (q * x)),x)
|
||||
(y, ly) = ((ly - (q * y)),y)
|
||||
if (lx < 0): lx += ob #If neg wrap modulo orignal b
|
||||
if (ly < 0): ly += oa #If neg wrap modulo orignal a
|
||||
return (a, lx, ly) #Return only positive values
|
||||
|
||||
|
||||
def inverse(x, n):
|
||||
'''Returns x^-1 (mod n)
|
||||
|
||||
>>> inverse(7, 4)
|
||||
3
|
||||
>>> (inverse(143, 4) * 143) % 4
|
||||
1
|
||||
'''
|
||||
|
||||
(divider, inv, _) = extended_gcd(x, n)
|
||||
|
||||
if divider != 1:
|
||||
raise ValueError("x (%d) and n (%d) are not relatively prime" % (x, n))
|
||||
|
||||
return inv
|
||||
|
||||
|
||||
def crt(a_values, modulo_values):
|
||||
'''Chinese Remainder Theorem.
|
||||
|
||||
Calculates x such that x = a[i] (mod m[i]) for each i.
|
||||
|
||||
:param a_values: the a-values of the above equation
|
||||
:param modulo_values: the m-values of the above equation
|
||||
:returns: x such that x = a[i] (mod m[i]) for each i
|
||||
|
||||
|
||||
>>> crt([2, 3], [3, 5])
|
||||
8
|
||||
|
||||
>>> crt([2, 3, 2], [3, 5, 7])
|
||||
23
|
||||
|
||||
>>> crt([2, 3, 0], [7, 11, 15])
|
||||
135
|
||||
'''
|
||||
|
||||
m = 1
|
||||
x = 0
|
||||
|
||||
for modulo in modulo_values:
|
||||
m *= modulo
|
||||
|
||||
for (m_i, a_i) in zip(modulo_values, a_values):
|
||||
M_i = m // m_i
|
||||
inv = inverse(M_i, m_i)
|
||||
|
||||
x = (x + a_i * M_i * inv) % m
|
||||
|
||||
return x
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Common functionality shared by several modules."""
|
||||
|
||||
import typing
|
||||
|
||||
|
||||
class NotRelativePrimeError(ValueError):
|
||||
def __init__(self, a: int, b: int, d: int, msg: str = "") -> None:
|
||||
super().__init__(msg or "%d and %d are not relatively prime, divider=%i" % (a, b, d))
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.d = d
|
||||
|
||||
|
||||
def bit_size(num: int) -> int:
|
||||
"""
|
||||
Number of bits needed to represent a integer excluding any prefix
|
||||
0 bits.
|
||||
|
||||
Usage::
|
||||
|
||||
>>> bit_size(1023)
|
||||
10
|
||||
>>> bit_size(1024)
|
||||
11
|
||||
>>> bit_size(1025)
|
||||
11
|
||||
|
||||
:param num:
|
||||
Integer value. If num is 0, returns 0. Only the absolute value of the
|
||||
number is considered. Therefore, signed integers will be abs(num)
|
||||
before the number's bit length is determined.
|
||||
:returns:
|
||||
Returns the number of bits in the integer.
|
||||
"""
|
||||
|
||||
try:
|
||||
return num.bit_length()
|
||||
except AttributeError as ex:
|
||||
raise TypeError("bit_size(num) only supports integers, not %r" % type(num)) from ex
|
||||
|
||||
|
||||
def byte_size(number: int) -> int:
|
||||
"""
|
||||
Returns the number of bytes required to hold a specific long number.
|
||||
|
||||
The number of bytes is rounded up.
|
||||
|
||||
Usage::
|
||||
|
||||
>>> byte_size(1 << 1023)
|
||||
128
|
||||
>>> byte_size((1 << 1024) - 1)
|
||||
128
|
||||
>>> byte_size(1 << 1024)
|
||||
129
|
||||
|
||||
:param number:
|
||||
An unsigned integer
|
||||
:returns:
|
||||
The number of bytes required to hold a specific long number.
|
||||
"""
|
||||
if number == 0:
|
||||
return 1
|
||||
return ceil_div(bit_size(number), 8)
|
||||
|
||||
|
||||
def ceil_div(num: int, div: int) -> int:
|
||||
"""
|
||||
Returns the ceiling function of a division between `num` and `div`.
|
||||
|
||||
Usage::
|
||||
|
||||
>>> ceil_div(100, 7)
|
||||
15
|
||||
>>> ceil_div(100, 10)
|
||||
10
|
||||
>>> ceil_div(1, 4)
|
||||
1
|
||||
|
||||
:param num: Division's numerator, a number
|
||||
:param div: Division's divisor, a number
|
||||
|
||||
:return: Rounded up result of the division between the parameters.
|
||||
"""
|
||||
quanta, mod = divmod(num, div)
|
||||
if mod:
|
||||
quanta += 1
|
||||
return quanta
|
||||
|
||||
|
||||
def extended_gcd(a: int, b: int) -> typing.Tuple[int, int, int]:
|
||||
"""Returns a tuple (r, i, j) such that r = gcd(a, b) = ia + jb"""
|
||||
# r = gcd(a,b) i = multiplicitive inverse of a mod b
|
||||
# or j = multiplicitive inverse of b mod a
|
||||
# Neg return values for i or j are made positive mod b or a respectively
|
||||
# Iterateive Version is faster and uses much less stack space
|
||||
x = 0
|
||||
y = 1
|
||||
lx = 1
|
||||
ly = 0
|
||||
oa = a # Remember original a/b to remove
|
||||
ob = b # negative values from return results
|
||||
while b != 0:
|
||||
q = a // b
|
||||
(a, b) = (b, a % b)
|
||||
(x, lx) = ((lx - (q * x)), x)
|
||||
(y, ly) = ((ly - (q * y)), y)
|
||||
if lx < 0:
|
||||
lx += ob # If neg wrap modulo original b
|
||||
if ly < 0:
|
||||
ly += oa # If neg wrap modulo original a
|
||||
return a, lx, ly # Return only positive values
|
||||
|
||||
|
||||
def inverse(x: int, n: int) -> int:
|
||||
"""Returns the inverse of x % n under multiplication, a.k.a x^-1 (mod n)
|
||||
|
||||
>>> inverse(7, 4)
|
||||
3
|
||||
>>> (inverse(143, 4) * 143) % 4
|
||||
1
|
||||
"""
|
||||
|
||||
(divider, inv, _) = extended_gcd(x, n)
|
||||
|
||||
if divider != 1:
|
||||
raise NotRelativePrimeError(x, n, divider)
|
||||
|
||||
return inv
|
||||
|
||||
|
||||
def crt(a_values: typing.Iterable[int], modulo_values: typing.Iterable[int]) -> int:
|
||||
"""Chinese Remainder Theorem.
|
||||
|
||||
Calculates x such that x = a[i] (mod m[i]) for each i.
|
||||
|
||||
:param a_values: the a-values of the above equation
|
||||
:param modulo_values: the m-values of the above equation
|
||||
:returns: x such that x = a[i] (mod m[i]) for each i
|
||||
|
||||
|
||||
>>> crt([2, 3], [3, 5])
|
||||
8
|
||||
|
||||
>>> crt([2, 3, 2], [3, 5, 7])
|
||||
23
|
||||
|
||||
>>> crt([2, 3, 0], [7, 11, 15])
|
||||
135
|
||||
"""
|
||||
|
||||
m = 1
|
||||
x = 0
|
||||
|
||||
for modulo in modulo_values:
|
||||
m *= modulo
|
||||
|
||||
for (m_i, a_i) in zip(modulo_values, a_values):
|
||||
M_i = m // m_i
|
||||
inv = inverse(M_i, m_i)
|
||||
|
||||
x = (x + a_i * M_i * inv) % m
|
||||
|
||||
return x
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
|
|
@ -1,58 +1,53 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''Core mathematical operations.
|
||||
|
||||
This is the actual core RSA implementation, which is only defined
|
||||
mathematically on integers.
|
||||
'''
|
||||
|
||||
|
||||
from rsa._compat import is_integer
|
||||
|
||||
def assert_int(var, name):
|
||||
|
||||
if is_integer(var):
|
||||
return
|
||||
|
||||
raise TypeError('%s should be an integer, not %s' % (name, var.__class__))
|
||||
|
||||
def encrypt_int(message, ekey, n):
|
||||
'''Encrypts a message using encryption key 'ekey', working modulo n'''
|
||||
|
||||
assert_int(message, 'message')
|
||||
assert_int(ekey, 'ekey')
|
||||
assert_int(n, 'n')
|
||||
|
||||
if message < 0:
|
||||
raise ValueError('Only non-negative numbers are supported')
|
||||
|
||||
if message > n:
|
||||
raise OverflowError("The message %i is too long for n=%i" % (message, n))
|
||||
|
||||
return pow(message, ekey, n)
|
||||
|
||||
def decrypt_int(cyphertext, dkey, n):
|
||||
'''Decrypts a cypher text using the decryption key 'dkey', working
|
||||
modulo n'''
|
||||
|
||||
assert_int(cyphertext, 'cyphertext')
|
||||
assert_int(dkey, 'dkey')
|
||||
assert_int(n, 'n')
|
||||
|
||||
message = pow(cyphertext, dkey, n)
|
||||
return message
|
||||
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Core mathematical operations.
|
||||
|
||||
This is the actual core RSA implementation, which is only defined
|
||||
mathematically on integers.
|
||||
"""
|
||||
|
||||
|
||||
def assert_int(var: int, name: str) -> None:
|
||||
if isinstance(var, int):
|
||||
return
|
||||
|
||||
raise TypeError("%s should be an integer, not %s" % (name, var.__class__))
|
||||
|
||||
|
||||
def encrypt_int(message: int, ekey: int, n: int) -> int:
|
||||
"""Encrypts a message using encryption key 'ekey', working modulo n"""
|
||||
|
||||
assert_int(message, "message")
|
||||
assert_int(ekey, "ekey")
|
||||
assert_int(n, "n")
|
||||
|
||||
if message < 0:
|
||||
raise ValueError("Only non-negative numbers are supported")
|
||||
|
||||
if message > n:
|
||||
raise OverflowError("The message %i is too long for n=%i" % (message, n))
|
||||
|
||||
return pow(message, ekey, n)
|
||||
|
||||
|
||||
def decrypt_int(cyphertext: int, dkey: int, n: int) -> int:
|
||||
"""Decrypts a cypher text using the decryption key 'dkey', working modulo n"""
|
||||
|
||||
assert_int(cyphertext, "cyphertext")
|
||||
assert_int(dkey, "dkey")
|
||||
assert_int(n, "n")
|
||||
|
||||
message = pow(cyphertext, dkey, n)
|
||||
return message
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,94 +1,96 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''Functions for parallel computation on multiple cores.
|
||||
|
||||
Introduced in Python-RSA 3.1.
|
||||
|
||||
.. note::
|
||||
|
||||
Requires Python 2.6 or newer.
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import multiprocessing as mp
|
||||
|
||||
import rsa.prime
|
||||
import rsa.randnum
|
||||
|
||||
def _find_prime(nbits, pipe):
|
||||
while True:
|
||||
integer = rsa.randnum.read_random_int(nbits)
|
||||
|
||||
# Make sure it's odd
|
||||
integer |= 1
|
||||
|
||||
# Test for primeness
|
||||
if rsa.prime.is_prime(integer):
|
||||
pipe.send(integer)
|
||||
return
|
||||
|
||||
def getprime(nbits, poolsize):
|
||||
'''Returns a prime number that can be stored in 'nbits' bits.
|
||||
|
||||
Works in multiple threads at the same time.
|
||||
|
||||
>>> p = getprime(128, 3)
|
||||
>>> rsa.prime.is_prime(p-1)
|
||||
False
|
||||
>>> rsa.prime.is_prime(p)
|
||||
True
|
||||
>>> rsa.prime.is_prime(p+1)
|
||||
False
|
||||
|
||||
>>> from rsa import common
|
||||
>>> common.bit_size(p) == 128
|
||||
True
|
||||
|
||||
'''
|
||||
|
||||
(pipe_recv, pipe_send) = mp.Pipe(duplex=False)
|
||||
|
||||
# Create processes
|
||||
procs = [mp.Process(target=_find_prime, args=(nbits, pipe_send))
|
||||
for _ in range(poolsize)]
|
||||
[p.start() for p in procs]
|
||||
|
||||
result = pipe_recv.recv()
|
||||
|
||||
[p.terminate() for p in procs]
|
||||
|
||||
return result
|
||||
|
||||
__all__ = ['getprime']
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('Running doctests 1000x or until failure')
|
||||
import doctest
|
||||
|
||||
for count in range(100):
|
||||
(failures, tests) = doctest.testmod()
|
||||
if failures:
|
||||
break
|
||||
|
||||
if count and count % 10 == 0:
|
||||
print('%i times' % count)
|
||||
|
||||
print('Doctests done')
|
||||
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Functions for parallel computation on multiple cores.
|
||||
|
||||
Introduced in Python-RSA 3.1.
|
||||
|
||||
.. note::
|
||||
|
||||
Requires Python 2.6 or newer.
|
||||
|
||||
"""
|
||||
|
||||
import multiprocessing as mp
|
||||
from multiprocessing.connection import Connection
|
||||
|
||||
import rsa.prime
|
||||
import rsa.randnum
|
||||
|
||||
|
||||
def _find_prime(nbits: int, pipe: Connection) -> None:
|
||||
while True:
|
||||
integer = rsa.randnum.read_random_odd_int(nbits)
|
||||
|
||||
# Test for primeness
|
||||
if rsa.prime.is_prime(integer):
|
||||
pipe.send(integer)
|
||||
return
|
||||
|
||||
|
||||
def getprime(nbits: int, poolsize: int) -> int:
|
||||
"""Returns a prime number that can be stored in 'nbits' bits.
|
||||
|
||||
Works in multiple threads at the same time.
|
||||
|
||||
>>> p = getprime(128, 3)
|
||||
>>> rsa.prime.is_prime(p-1)
|
||||
False
|
||||
>>> rsa.prime.is_prime(p)
|
||||
True
|
||||
>>> rsa.prime.is_prime(p+1)
|
||||
False
|
||||
|
||||
>>> from rsa import common
|
||||
>>> common.bit_size(p) == 128
|
||||
True
|
||||
|
||||
"""
|
||||
|
||||
(pipe_recv, pipe_send) = mp.Pipe(duplex=False)
|
||||
|
||||
# Create processes
|
||||
try:
|
||||
procs = [mp.Process(target=_find_prime, args=(nbits, pipe_send)) for _ in range(poolsize)]
|
||||
# Start processes
|
||||
for p in procs:
|
||||
p.start()
|
||||
|
||||
result = pipe_recv.recv()
|
||||
finally:
|
||||
pipe_recv.close()
|
||||
pipe_send.close()
|
||||
|
||||
# Terminate processes
|
||||
for p in procs:
|
||||
p.terminate()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
__all__ = ["getprime"]
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Running doctests 1000x or until failure")
|
||||
import doctest
|
||||
|
||||
for count in range(100):
|
||||
(failures, tests) = doctest.testmod()
|
||||
if failures:
|
||||
break
|
||||
|
||||
if count % 10 == 0 and count:
|
||||
print("%i times" % count)
|
||||
|
||||
print("Doctests done")
|
||||
|
|
|
@ -1,120 +1,134 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''Functions that load and write PEM-encoded files.'''
|
||||
|
||||
import base64
|
||||
from rsa._compat import b, is_bytes
|
||||
|
||||
def _markers(pem_marker):
|
||||
'''
|
||||
Returns the start and end PEM markers
|
||||
'''
|
||||
|
||||
if is_bytes(pem_marker):
|
||||
pem_marker = pem_marker.decode('utf-8')
|
||||
|
||||
return (b('-----BEGIN %s-----' % pem_marker),
|
||||
b('-----END %s-----' % pem_marker))
|
||||
|
||||
def load_pem(contents, pem_marker):
|
||||
'''Loads a PEM file.
|
||||
|
||||
@param contents: the contents of the file to interpret
|
||||
@param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY'
|
||||
when your file has '-----BEGIN RSA PRIVATE KEY-----' and
|
||||
'-----END RSA PRIVATE KEY-----' markers.
|
||||
|
||||
@return the base64-decoded content between the start and end markers.
|
||||
|
||||
@raise ValueError: when the content is invalid, for example when the start
|
||||
marker cannot be found.
|
||||
|
||||
'''
|
||||
|
||||
(pem_start, pem_end) = _markers(pem_marker)
|
||||
|
||||
pem_lines = []
|
||||
in_pem_part = False
|
||||
|
||||
for line in contents.splitlines():
|
||||
line = line.strip()
|
||||
|
||||
# Skip empty lines
|
||||
if not line:
|
||||
continue
|
||||
|
||||
# Handle start marker
|
||||
if line == pem_start:
|
||||
if in_pem_part:
|
||||
raise ValueError('Seen start marker "%s" twice' % pem_start)
|
||||
|
||||
in_pem_part = True
|
||||
continue
|
||||
|
||||
# Skip stuff before first marker
|
||||
if not in_pem_part:
|
||||
continue
|
||||
|
||||
# Handle end marker
|
||||
if in_pem_part and line == pem_end:
|
||||
in_pem_part = False
|
||||
break
|
||||
|
||||
# Load fields
|
||||
if b(':') in line:
|
||||
continue
|
||||
|
||||
pem_lines.append(line)
|
||||
|
||||
# Do some sanity checks
|
||||
if not pem_lines:
|
||||
raise ValueError('No PEM start marker "%s" found' % pem_start)
|
||||
|
||||
if in_pem_part:
|
||||
raise ValueError('No PEM end marker "%s" found' % pem_end)
|
||||
|
||||
# Base64-decode the contents
|
||||
pem = b('').join(pem_lines)
|
||||
return base64.decodestring(pem)
|
||||
|
||||
|
||||
def save_pem(contents, pem_marker):
|
||||
'''Saves a PEM file.
|
||||
|
||||
@param contents: the contents to encode in PEM format
|
||||
@param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY'
|
||||
when your file has '-----BEGIN RSA PRIVATE KEY-----' and
|
||||
'-----END RSA PRIVATE KEY-----' markers.
|
||||
|
||||
@return the base64-encoded content between the start and end markers.
|
||||
|
||||
'''
|
||||
|
||||
(pem_start, pem_end) = _markers(pem_marker)
|
||||
|
||||
b64 = base64.encodestring(contents).replace(b('\n'), b(''))
|
||||
pem_lines = [pem_start]
|
||||
|
||||
for block_start in range(0, len(b64), 64):
|
||||
block = b64[block_start:block_start + 64]
|
||||
pem_lines.append(block)
|
||||
|
||||
pem_lines.append(pem_end)
|
||||
pem_lines.append(b(''))
|
||||
|
||||
return b('\n').join(pem_lines)
|
||||
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Functions that load and write PEM-encoded files."""
|
||||
|
||||
import base64
|
||||
import typing
|
||||
|
||||
# Should either be ASCII strings or bytes.
|
||||
FlexiText = typing.Union[str, bytes]
|
||||
|
||||
|
||||
def _markers(pem_marker: FlexiText) -> typing.Tuple[bytes, bytes]:
|
||||
"""
|
||||
Returns the start and end PEM markers, as bytes.
|
||||
"""
|
||||
|
||||
if not isinstance(pem_marker, bytes):
|
||||
pem_marker = pem_marker.encode("ascii")
|
||||
|
||||
return (
|
||||
b"-----BEGIN " + pem_marker + b"-----",
|
||||
b"-----END " + pem_marker + b"-----",
|
||||
)
|
||||
|
||||
|
||||
def _pem_lines(contents: bytes, pem_start: bytes, pem_end: bytes) -> typing.Iterator[bytes]:
|
||||
"""Generator over PEM lines between pem_start and pem_end."""
|
||||
|
||||
in_pem_part = False
|
||||
seen_pem_start = False
|
||||
|
||||
for line in contents.splitlines():
|
||||
line = line.strip()
|
||||
|
||||
# Skip empty lines
|
||||
if not line:
|
||||
continue
|
||||
|
||||
# Handle start marker
|
||||
if line == pem_start:
|
||||
if in_pem_part:
|
||||
raise ValueError('Seen start marker "%r" twice' % pem_start)
|
||||
|
||||
in_pem_part = True
|
||||
seen_pem_start = True
|
||||
continue
|
||||
|
||||
# Skip stuff before first marker
|
||||
if not in_pem_part:
|
||||
continue
|
||||
|
||||
# Handle end marker
|
||||
if in_pem_part and line == pem_end:
|
||||
in_pem_part = False
|
||||
break
|
||||
|
||||
# Load fields
|
||||
if b":" in line:
|
||||
continue
|
||||
|
||||
yield line
|
||||
|
||||
# Do some sanity checks
|
||||
if not seen_pem_start:
|
||||
raise ValueError('No PEM start marker "%r" found' % pem_start)
|
||||
|
||||
if in_pem_part:
|
||||
raise ValueError('No PEM end marker "%r" found' % pem_end)
|
||||
|
||||
|
||||
def load_pem(contents: FlexiText, pem_marker: FlexiText) -> bytes:
|
||||
"""Loads a PEM file.
|
||||
|
||||
:param contents: the contents of the file to interpret
|
||||
:param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY'
|
||||
when your file has '-----BEGIN RSA PRIVATE KEY-----' and
|
||||
'-----END RSA PRIVATE KEY-----' markers.
|
||||
|
||||
:return: the base64-decoded content between the start and end markers.
|
||||
|
||||
@raise ValueError: when the content is invalid, for example when the start
|
||||
marker cannot be found.
|
||||
|
||||
"""
|
||||
|
||||
# We want bytes, not text. If it's text, it can be converted to ASCII bytes.
|
||||
if not isinstance(contents, bytes):
|
||||
contents = contents.encode("ascii")
|
||||
|
||||
(pem_start, pem_end) = _markers(pem_marker)
|
||||
pem_lines = [line for line in _pem_lines(contents, pem_start, pem_end)]
|
||||
|
||||
# Base64-decode the contents
|
||||
pem = b"".join(pem_lines)
|
||||
return base64.standard_b64decode(pem)
|
||||
|
||||
|
||||
def save_pem(contents: bytes, pem_marker: FlexiText) -> bytes:
|
||||
"""Saves a PEM file.
|
||||
|
||||
:param contents: the contents to encode in PEM format
|
||||
:param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY'
|
||||
when your file has '-----BEGIN RSA PRIVATE KEY-----' and
|
||||
'-----END RSA PRIVATE KEY-----' markers.
|
||||
|
||||
:return: the base64-encoded content between the start and end markers, as bytes.
|
||||
|
||||
"""
|
||||
|
||||
(pem_start, pem_end) = _markers(pem_marker)
|
||||
|
||||
b64 = base64.standard_b64encode(contents).replace(b"\n", b"")
|
||||
pem_lines = [pem_start]
|
||||
|
||||
for block_start in range(0, len(b64), 64):
|
||||
block = b64[block_start : block_start + 64]
|
||||
pem_lines.append(block)
|
||||
|
||||
pem_lines.append(pem_end)
|
||||
pem_lines.append(b"")
|
||||
|
||||
return b"\n".join(pem_lines)
|
||||
|
|
|
@ -1,391 +1,485 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''Functions for PKCS#1 version 1.5 encryption and signing
|
||||
|
||||
This module implements certain functionality from PKCS#1 version 1.5. For a
|
||||
very clear example, read http://www.di-mgt.com.au/rsa_alg.html#pkcs1schemes
|
||||
|
||||
At least 8 bytes of random padding is used when encrypting a message. This makes
|
||||
these methods much more secure than the ones in the ``rsa`` module.
|
||||
|
||||
WARNING: this module leaks information when decryption or verification fails.
|
||||
The exceptions that are raised contain the Python traceback information, which
|
||||
can be used to deduce where in the process the failure occurred. DO NOT PASS
|
||||
SUCH INFORMATION to your users.
|
||||
'''
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
from rsa._compat import b
|
||||
from rsa import common, transform, core, varblock
|
||||
|
||||
# ASN.1 codes that describe the hash algorithm used.
|
||||
HASH_ASN1 = {
|
||||
'MD5': b('\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10'),
|
||||
'SHA-1': b('\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'),
|
||||
'SHA-256': b('\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20'),
|
||||
'SHA-384': b('\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30'),
|
||||
'SHA-512': b('\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40'),
|
||||
}
|
||||
|
||||
HASH_METHODS = {
|
||||
'MD5': hashlib.md5,
|
||||
'SHA-1': hashlib.sha1,
|
||||
'SHA-256': hashlib.sha256,
|
||||
'SHA-384': hashlib.sha384,
|
||||
'SHA-512': hashlib.sha512,
|
||||
}
|
||||
|
||||
class CryptoError(Exception):
|
||||
'''Base class for all exceptions in this module.'''
|
||||
|
||||
class DecryptionError(CryptoError):
|
||||
'''Raised when decryption fails.'''
|
||||
|
||||
class VerificationError(CryptoError):
|
||||
'''Raised when verification fails.'''
|
||||
|
||||
def _pad_for_encryption(message, target_length):
|
||||
r'''Pads the message for encryption, returning the padded message.
|
||||
|
||||
:return: 00 02 RANDOM_DATA 00 MESSAGE
|
||||
|
||||
>>> block = _pad_for_encryption('hello', 16)
|
||||
>>> len(block)
|
||||
16
|
||||
>>> block[0:2]
|
||||
'\x00\x02'
|
||||
>>> block[-6:]
|
||||
'\x00hello'
|
||||
|
||||
'''
|
||||
|
||||
max_msglength = target_length - 11
|
||||
msglength = len(message)
|
||||
|
||||
if msglength > max_msglength:
|
||||
raise OverflowError('%i bytes needed for message, but there is only'
|
||||
' space for %i' % (msglength, max_msglength))
|
||||
|
||||
# Get random padding
|
||||
padding = b('')
|
||||
padding_length = target_length - msglength - 3
|
||||
|
||||
# We remove 0-bytes, so we'll end up with less padding than we've asked for,
|
||||
# so keep adding data until we're at the correct length.
|
||||
while len(padding) < padding_length:
|
||||
needed_bytes = padding_length - len(padding)
|
||||
|
||||
# Always read at least 8 bytes more than we need, and trim off the rest
|
||||
# after removing the 0-bytes. This increases the chance of getting
|
||||
# enough bytes, especially when needed_bytes is small
|
||||
new_padding = os.urandom(needed_bytes + 5)
|
||||
new_padding = new_padding.replace(b('\x00'), b(''))
|
||||
padding = padding + new_padding[:needed_bytes]
|
||||
|
||||
assert len(padding) == padding_length
|
||||
|
||||
return b('').join([b('\x00\x02'),
|
||||
padding,
|
||||
b('\x00'),
|
||||
message])
|
||||
|
||||
|
||||
def _pad_for_signing(message, target_length):
|
||||
r'''Pads the message for signing, returning the padded message.
|
||||
|
||||
The padding is always a repetition of FF bytes.
|
||||
|
||||
:return: 00 01 PADDING 00 MESSAGE
|
||||
|
||||
>>> block = _pad_for_signing('hello', 16)
|
||||
>>> len(block)
|
||||
16
|
||||
>>> block[0:2]
|
||||
'\x00\x01'
|
||||
>>> block[-6:]
|
||||
'\x00hello'
|
||||
>>> block[2:-6]
|
||||
'\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
|
||||
'''
|
||||
|
||||
max_msglength = target_length - 11
|
||||
msglength = len(message)
|
||||
|
||||
if msglength > max_msglength:
|
||||
raise OverflowError('%i bytes needed for message, but there is only'
|
||||
' space for %i' % (msglength, max_msglength))
|
||||
|
||||
padding_length = target_length - msglength - 3
|
||||
|
||||
return b('').join([b('\x00\x01'),
|
||||
padding_length * b('\xff'),
|
||||
b('\x00'),
|
||||
message])
|
||||
|
||||
|
||||
def encrypt(message, pub_key):
|
||||
'''Encrypts the given message using PKCS#1 v1.5
|
||||
|
||||
:param message: the message to encrypt. Must be a byte string no longer than
|
||||
``k-11`` bytes, where ``k`` is the number of bytes needed to encode
|
||||
the ``n`` component of the public key.
|
||||
:param pub_key: the :py:class:`rsa.PublicKey` to encrypt with.
|
||||
:raise OverflowError: when the message is too large to fit in the padded
|
||||
block.
|
||||
|
||||
>>> from rsa import key, common
|
||||
>>> (pub_key, priv_key) = key.newkeys(256)
|
||||
>>> message = 'hello'
|
||||
>>> crypto = encrypt(message, pub_key)
|
||||
|
||||
The crypto text should be just as long as the public key 'n' component:
|
||||
|
||||
>>> len(crypto) == common.byte_size(pub_key.n)
|
||||
True
|
||||
|
||||
'''
|
||||
|
||||
keylength = common.byte_size(pub_key.n)
|
||||
padded = _pad_for_encryption(message, keylength)
|
||||
|
||||
payload = transform.bytes2int(padded)
|
||||
encrypted = core.encrypt_int(payload, pub_key.e, pub_key.n)
|
||||
block = transform.int2bytes(encrypted, keylength)
|
||||
|
||||
return block
|
||||
|
||||
def decrypt(crypto, priv_key):
|
||||
r'''Decrypts the given message using PKCS#1 v1.5
|
||||
|
||||
The decryption is considered 'failed' when the resulting cleartext doesn't
|
||||
start with the bytes 00 02, or when the 00 byte between the padding and
|
||||
the message cannot be found.
|
||||
|
||||
:param crypto: the crypto text as returned by :py:func:`rsa.encrypt`
|
||||
:param priv_key: the :py:class:`rsa.PrivateKey` to decrypt with.
|
||||
:raise DecryptionError: when the decryption fails. No details are given as
|
||||
to why the code thinks the decryption fails, as this would leak
|
||||
information about the private key.
|
||||
|
||||
|
||||
>>> import rsa
|
||||
>>> (pub_key, priv_key) = rsa.newkeys(256)
|
||||
|
||||
It works with strings:
|
||||
|
||||
>>> crypto = encrypt('hello', pub_key)
|
||||
>>> decrypt(crypto, priv_key)
|
||||
'hello'
|
||||
|
||||
And with binary data:
|
||||
|
||||
>>> crypto = encrypt('\x00\x00\x00\x00\x01', pub_key)
|
||||
>>> decrypt(crypto, priv_key)
|
||||
'\x00\x00\x00\x00\x01'
|
||||
|
||||
Altering the encrypted information will *likely* cause a
|
||||
:py:class:`rsa.pkcs1.DecryptionError`. If you want to be *sure*, use
|
||||
:py:func:`rsa.sign`.
|
||||
|
||||
|
||||
.. warning::
|
||||
|
||||
Never display the stack trace of a
|
||||
:py:class:`rsa.pkcs1.DecryptionError` exception. It shows where in the
|
||||
code the exception occurred, and thus leaks information about the key.
|
||||
It's only a tiny bit of information, but every bit makes cracking the
|
||||
keys easier.
|
||||
|
||||
>>> crypto = encrypt('hello', pub_key)
|
||||
>>> crypto = crypto[0:5] + 'X' + crypto[6:] # change a byte
|
||||
>>> decrypt(crypto, priv_key)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
DecryptionError: Decryption failed
|
||||
|
||||
'''
|
||||
|
||||
blocksize = common.byte_size(priv_key.n)
|
||||
encrypted = transform.bytes2int(crypto)
|
||||
decrypted = core.decrypt_int(encrypted, priv_key.d, priv_key.n)
|
||||
cleartext = transform.int2bytes(decrypted, blocksize)
|
||||
|
||||
# If we can't find the cleartext marker, decryption failed.
|
||||
if cleartext[0:2] != b('\x00\x02'):
|
||||
raise DecryptionError('Decryption failed')
|
||||
|
||||
# Find the 00 separator between the padding and the message
|
||||
try:
|
||||
sep_idx = cleartext.index(b('\x00'), 2)
|
||||
except ValueError:
|
||||
raise DecryptionError('Decryption failed')
|
||||
|
||||
return cleartext[sep_idx+1:]
|
||||
|
||||
def sign(message, priv_key, hash):
|
||||
'''Signs the message with the private key.
|
||||
|
||||
Hashes the message, then signs the hash with the given key. This is known
|
||||
as a "detached signature", because the message itself isn't altered.
|
||||
|
||||
:param message: the message to sign. Can be an 8-bit string or a file-like
|
||||
object. If ``message`` has a ``read()`` method, it is assumed to be a
|
||||
file-like object.
|
||||
:param priv_key: the :py:class:`rsa.PrivateKey` to sign with
|
||||
:param hash: the hash method used on the message. Use 'MD5', 'SHA-1',
|
||||
'SHA-256', 'SHA-384' or 'SHA-512'.
|
||||
:return: a message signature block.
|
||||
:raise OverflowError: if the private key is too small to contain the
|
||||
requested hash.
|
||||
|
||||
'''
|
||||
|
||||
# Get the ASN1 code for this hash method
|
||||
if hash not in HASH_ASN1:
|
||||
raise ValueError('Invalid hash method: %s' % hash)
|
||||
asn1code = HASH_ASN1[hash]
|
||||
|
||||
# Calculate the hash
|
||||
hash = _hash(message, hash)
|
||||
|
||||
# Encrypt the hash with the private key
|
||||
cleartext = asn1code + hash
|
||||
keylength = common.byte_size(priv_key.n)
|
||||
padded = _pad_for_signing(cleartext, keylength)
|
||||
|
||||
payload = transform.bytes2int(padded)
|
||||
encrypted = core.encrypt_int(payload, priv_key.d, priv_key.n)
|
||||
block = transform.int2bytes(encrypted, keylength)
|
||||
|
||||
return block
|
||||
|
||||
def verify(message, signature, pub_key):
|
||||
'''Verifies that the signature matches the message.
|
||||
|
||||
The hash method is detected automatically from the signature.
|
||||
|
||||
:param message: the signed message. Can be an 8-bit string or a file-like
|
||||
object. If ``message`` has a ``read()`` method, it is assumed to be a
|
||||
file-like object.
|
||||
:param signature: the signature block, as created with :py:func:`rsa.sign`.
|
||||
:param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
|
||||
:raise VerificationError: when the signature doesn't match the message.
|
||||
|
||||
.. warning::
|
||||
|
||||
Never display the stack trace of a
|
||||
:py:class:`rsa.pkcs1.VerificationError` exception. It shows where in
|
||||
the code the exception occurred, and thus leaks information about the
|
||||
key. It's only a tiny bit of information, but every bit makes cracking
|
||||
the keys easier.
|
||||
|
||||
'''
|
||||
|
||||
blocksize = common.byte_size(pub_key.n)
|
||||
encrypted = transform.bytes2int(signature)
|
||||
decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
|
||||
clearsig = transform.int2bytes(decrypted, blocksize)
|
||||
|
||||
# If we can't find the signature marker, verification failed.
|
||||
if clearsig[0:2] != b('\x00\x01'):
|
||||
raise VerificationError('Verification failed')
|
||||
|
||||
# Find the 00 separator between the padding and the payload
|
||||
try:
|
||||
sep_idx = clearsig.index(b('\x00'), 2)
|
||||
except ValueError:
|
||||
raise VerificationError('Verification failed')
|
||||
|
||||
# Get the hash and the hash method
|
||||
(method_name, signature_hash) = _find_method_hash(clearsig[sep_idx+1:])
|
||||
message_hash = _hash(message, method_name)
|
||||
|
||||
# Compare the real hash to the hash in the signature
|
||||
if message_hash != signature_hash:
|
||||
raise VerificationError('Verification failed')
|
||||
|
||||
return True
|
||||
|
||||
def _hash(message, method_name):
|
||||
'''Returns the message digest.
|
||||
|
||||
:param message: the signed message. Can be an 8-bit string or a file-like
|
||||
object. If ``message`` has a ``read()`` method, it is assumed to be a
|
||||
file-like object.
|
||||
:param method_name: the hash method, must be a key of
|
||||
:py:const:`HASH_METHODS`.
|
||||
|
||||
'''
|
||||
|
||||
if method_name not in HASH_METHODS:
|
||||
raise ValueError('Invalid hash method: %s' % method_name)
|
||||
|
||||
method = HASH_METHODS[method_name]
|
||||
hasher = method()
|
||||
|
||||
if hasattr(message, 'read') and hasattr(message.read, '__call__'):
|
||||
# read as 1K blocks
|
||||
for block in varblock.yield_fixedblocks(message, 1024):
|
||||
hasher.update(block)
|
||||
else:
|
||||
# hash the message object itself.
|
||||
hasher.update(message)
|
||||
|
||||
return hasher.digest()
|
||||
|
||||
|
||||
def _find_method_hash(method_hash):
|
||||
'''Finds the hash method and the hash itself.
|
||||
|
||||
:param method_hash: ASN1 code for the hash method concatenated with the
|
||||
hash itself.
|
||||
|
||||
:return: tuple (method, hash) where ``method`` is the used hash method, and
|
||||
``hash`` is the hash itself.
|
||||
|
||||
:raise VerificationFailed: when the hash method cannot be found
|
||||
|
||||
'''
|
||||
|
||||
for (hashname, asn1code) in HASH_ASN1.items():
|
||||
if not method_hash.startswith(asn1code):
|
||||
continue
|
||||
|
||||
return (hashname, method_hash[len(asn1code):])
|
||||
|
||||
raise VerificationError('Verification failed')
|
||||
|
||||
|
||||
__all__ = ['encrypt', 'decrypt', 'sign', 'verify',
|
||||
'DecryptionError', 'VerificationError', 'CryptoError']
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('Running doctests 1000x or until failure')
|
||||
import doctest
|
||||
|
||||
for count in range(1000):
|
||||
(failures, tests) = doctest.testmod()
|
||||
if failures:
|
||||
break
|
||||
|
||||
if count and count % 100 == 0:
|
||||
print('%i times' % count)
|
||||
|
||||
print('Doctests done')
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Functions for PKCS#1 version 1.5 encryption and signing
|
||||
|
||||
This module implements certain functionality from PKCS#1 version 1.5. For a
|
||||
very clear example, read http://www.di-mgt.com.au/rsa_alg.html#pkcs1schemes
|
||||
|
||||
At least 8 bytes of random padding is used when encrypting a message. This makes
|
||||
these methods much more secure than the ones in the ``rsa`` module.
|
||||
|
||||
WARNING: this module leaks information when decryption fails. The exceptions
|
||||
that are raised contain the Python traceback information, which can be used to
|
||||
deduce where in the process the failure occurred. DO NOT PASS SUCH INFORMATION
|
||||
to your users.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
import typing
|
||||
from hmac import compare_digest
|
||||
|
||||
from . import common, transform, core, key
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
HashType = hashlib._Hash
|
||||
else:
|
||||
HashType = typing.Any
|
||||
|
||||
# ASN.1 codes that describe the hash algorithm used.
|
||||
HASH_ASN1 = {
|
||||
"MD5": b"\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10",
|
||||
"SHA-1": b"\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14",
|
||||
"SHA-224": b"\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x05\x00\x04\x1c",
|
||||
"SHA-256": b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20",
|
||||
"SHA-384": b"\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30",
|
||||
"SHA-512": b"\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40",
|
||||
}
|
||||
|
||||
HASH_METHODS: typing.Dict[str, typing.Callable[[], HashType]] = {
|
||||
"MD5": hashlib.md5,
|
||||
"SHA-1": hashlib.sha1,
|
||||
"SHA-224": hashlib.sha224,
|
||||
"SHA-256": hashlib.sha256,
|
||||
"SHA-384": hashlib.sha384,
|
||||
"SHA-512": hashlib.sha512,
|
||||
}
|
||||
"""Hash methods supported by this library."""
|
||||
|
||||
|
||||
if sys.version_info >= (3, 6):
|
||||
# Python 3.6 introduced SHA3 support.
|
||||
HASH_ASN1.update(
|
||||
{
|
||||
"SHA3-256": b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x08\x05\x00\x04\x20",
|
||||
"SHA3-384": b"\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x09\x05\x00\x04\x30",
|
||||
"SHA3-512": b"\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x0a\x05\x00\x04\x40",
|
||||
}
|
||||
)
|
||||
|
||||
HASH_METHODS.update(
|
||||
{
|
||||
"SHA3-256": hashlib.sha3_256,
|
||||
"SHA3-384": hashlib.sha3_384,
|
||||
"SHA3-512": hashlib.sha3_512,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class CryptoError(Exception):
|
||||
"""Base class for all exceptions in this module."""
|
||||
|
||||
|
||||
class DecryptionError(CryptoError):
|
||||
"""Raised when decryption fails."""
|
||||
|
||||
|
||||
class VerificationError(CryptoError):
|
||||
"""Raised when verification fails."""
|
||||
|
||||
|
||||
def _pad_for_encryption(message: bytes, target_length: int) -> bytes:
|
||||
r"""Pads the message for encryption, returning the padded message.
|
||||
|
||||
:return: 00 02 RANDOM_DATA 00 MESSAGE
|
||||
|
||||
>>> block = _pad_for_encryption(b'hello', 16)
|
||||
>>> len(block)
|
||||
16
|
||||
>>> block[0:2]
|
||||
b'\x00\x02'
|
||||
>>> block[-6:]
|
||||
b'\x00hello'
|
||||
|
||||
"""
|
||||
|
||||
max_msglength = target_length - 11
|
||||
msglength = len(message)
|
||||
|
||||
if msglength > max_msglength:
|
||||
raise OverflowError(
|
||||
"%i bytes needed for message, but there is only"
|
||||
" space for %i" % (msglength, max_msglength)
|
||||
)
|
||||
|
||||
# Get random padding
|
||||
padding = b""
|
||||
padding_length = target_length - msglength - 3
|
||||
|
||||
# We remove 0-bytes, so we'll end up with less padding than we've asked for,
|
||||
# so keep adding data until we're at the correct length.
|
||||
while len(padding) < padding_length:
|
||||
needed_bytes = padding_length - len(padding)
|
||||
|
||||
# Always read at least 8 bytes more than we need, and trim off the rest
|
||||
# after removing the 0-bytes. This increases the chance of getting
|
||||
# enough bytes, especially when needed_bytes is small
|
||||
new_padding = os.urandom(needed_bytes + 5)
|
||||
new_padding = new_padding.replace(b"\x00", b"")
|
||||
padding = padding + new_padding[:needed_bytes]
|
||||
|
||||
assert len(padding) == padding_length
|
||||
|
||||
return b"".join([b"\x00\x02", padding, b"\x00", message])
|
||||
|
||||
|
||||
def _pad_for_signing(message: bytes, target_length: int) -> bytes:
|
||||
r"""Pads the message for signing, returning the padded message.
|
||||
|
||||
The padding is always a repetition of FF bytes.
|
||||
|
||||
:return: 00 01 PADDING 00 MESSAGE
|
||||
|
||||
>>> block = _pad_for_signing(b'hello', 16)
|
||||
>>> len(block)
|
||||
16
|
||||
>>> block[0:2]
|
||||
b'\x00\x01'
|
||||
>>> block[-6:]
|
||||
b'\x00hello'
|
||||
>>> block[2:-6]
|
||||
b'\xff\xff\xff\xff\xff\xff\xff\xff'
|
||||
|
||||
"""
|
||||
|
||||
max_msglength = target_length - 11
|
||||
msglength = len(message)
|
||||
|
||||
if msglength > max_msglength:
|
||||
raise OverflowError(
|
||||
"%i bytes needed for message, but there is only"
|
||||
" space for %i" % (msglength, max_msglength)
|
||||
)
|
||||
|
||||
padding_length = target_length - msglength - 3
|
||||
|
||||
return b"".join([b"\x00\x01", padding_length * b"\xff", b"\x00", message])
|
||||
|
||||
|
||||
def encrypt(message: bytes, pub_key: key.PublicKey) -> bytes:
|
||||
"""Encrypts the given message using PKCS#1 v1.5
|
||||
|
||||
:param message: the message to encrypt. Must be a byte string no longer than
|
||||
``k-11`` bytes, where ``k`` is the number of bytes needed to encode
|
||||
the ``n`` component of the public key.
|
||||
:param pub_key: the :py:class:`rsa.PublicKey` to encrypt with.
|
||||
:raise OverflowError: when the message is too large to fit in the padded
|
||||
block.
|
||||
|
||||
>>> from rsa import key, common
|
||||
>>> (pub_key, priv_key) = key.newkeys(256)
|
||||
>>> message = b'hello'
|
||||
>>> crypto = encrypt(message, pub_key)
|
||||
|
||||
The crypto text should be just as long as the public key 'n' component:
|
||||
|
||||
>>> len(crypto) == common.byte_size(pub_key.n)
|
||||
True
|
||||
|
||||
"""
|
||||
|
||||
keylength = common.byte_size(pub_key.n)
|
||||
padded = _pad_for_encryption(message, keylength)
|
||||
|
||||
payload = transform.bytes2int(padded)
|
||||
encrypted = core.encrypt_int(payload, pub_key.e, pub_key.n)
|
||||
block = transform.int2bytes(encrypted, keylength)
|
||||
|
||||
return block
|
||||
|
||||
|
||||
def decrypt(crypto: bytes, priv_key: key.PrivateKey) -> bytes:
|
||||
r"""Decrypts the given message using PKCS#1 v1.5
|
||||
|
||||
The decryption is considered 'failed' when the resulting cleartext doesn't
|
||||
start with the bytes 00 02, or when the 00 byte between the padding and
|
||||
the message cannot be found.
|
||||
|
||||
:param crypto: the crypto text as returned by :py:func:`rsa.encrypt`
|
||||
:param priv_key: the :py:class:`rsa.PrivateKey` to decrypt with.
|
||||
:raise DecryptionError: when the decryption fails. No details are given as
|
||||
to why the code thinks the decryption fails, as this would leak
|
||||
information about the private key.
|
||||
|
||||
|
||||
>>> import rsa
|
||||
>>> (pub_key, priv_key) = rsa.newkeys(256)
|
||||
|
||||
It works with strings:
|
||||
|
||||
>>> crypto = encrypt(b'hello', pub_key)
|
||||
>>> decrypt(crypto, priv_key)
|
||||
b'hello'
|
||||
|
||||
And with binary data:
|
||||
|
||||
>>> crypto = encrypt(b'\x00\x00\x00\x00\x01', pub_key)
|
||||
>>> decrypt(crypto, priv_key)
|
||||
b'\x00\x00\x00\x00\x01'
|
||||
|
||||
Altering the encrypted information will *likely* cause a
|
||||
:py:class:`rsa.pkcs1.DecryptionError`. If you want to be *sure*, use
|
||||
:py:func:`rsa.sign`.
|
||||
|
||||
|
||||
.. warning::
|
||||
|
||||
Never display the stack trace of a
|
||||
:py:class:`rsa.pkcs1.DecryptionError` exception. It shows where in the
|
||||
code the exception occurred, and thus leaks information about the key.
|
||||
It's only a tiny bit of information, but every bit makes cracking the
|
||||
keys easier.
|
||||
|
||||
>>> crypto = encrypt(b'hello', pub_key)
|
||||
>>> crypto = crypto[0:5] + b'X' + crypto[6:] # change a byte
|
||||
>>> decrypt(crypto, priv_key)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
rsa.pkcs1.DecryptionError: Decryption failed
|
||||
|
||||
"""
|
||||
|
||||
blocksize = common.byte_size(priv_key.n)
|
||||
encrypted = transform.bytes2int(crypto)
|
||||
decrypted = priv_key.blinded_decrypt(encrypted)
|
||||
cleartext = transform.int2bytes(decrypted, blocksize)
|
||||
|
||||
# Detect leading zeroes in the crypto. These are not reflected in the
|
||||
# encrypted value (as leading zeroes do not influence the value of an
|
||||
# integer). This fixes CVE-2020-13757.
|
||||
if len(crypto) > blocksize:
|
||||
# This is operating on public information, so doesn't need to be constant-time.
|
||||
raise DecryptionError("Decryption failed")
|
||||
|
||||
# If we can't find the cleartext marker, decryption failed.
|
||||
cleartext_marker_bad = not compare_digest(cleartext[:2], b"\x00\x02")
|
||||
|
||||
# Find the 00 separator between the padding and the message
|
||||
sep_idx = cleartext.find(b"\x00", 2)
|
||||
|
||||
# sep_idx indicates the position of the `\x00` separator that separates the
|
||||
# padding from the actual message. The padding should be at least 8 bytes
|
||||
# long (see https://tools.ietf.org/html/rfc8017#section-7.2.2 step 3), which
|
||||
# means the separator should be at least at index 10 (because of the
|
||||
# `\x00\x02` marker that precedes it).
|
||||
sep_idx_bad = sep_idx < 10
|
||||
|
||||
anything_bad = cleartext_marker_bad | sep_idx_bad
|
||||
if anything_bad:
|
||||
raise DecryptionError("Decryption failed")
|
||||
|
||||
return cleartext[sep_idx + 1 :]
|
||||
|
||||
|
||||
def sign_hash(hash_value: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes:
|
||||
"""Signs a precomputed hash with the private key.
|
||||
|
||||
Hashes the message, then signs the hash with the given key. This is known
|
||||
as a "detached signature", because the message itself isn't altered.
|
||||
|
||||
:param hash_value: A precomputed hash to sign (ignores message).
|
||||
:param priv_key: the :py:class:`rsa.PrivateKey` to sign with
|
||||
:param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
|
||||
'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
|
||||
:return: a message signature block.
|
||||
:raise OverflowError: if the private key is too small to contain the
|
||||
requested hash.
|
||||
|
||||
"""
|
||||
|
||||
# Get the ASN1 code for this hash method
|
||||
if hash_method not in HASH_ASN1:
|
||||
raise ValueError("Invalid hash method: %s" % hash_method)
|
||||
asn1code = HASH_ASN1[hash_method]
|
||||
|
||||
# Encrypt the hash with the private key
|
||||
cleartext = asn1code + hash_value
|
||||
keylength = common.byte_size(priv_key.n)
|
||||
padded = _pad_for_signing(cleartext, keylength)
|
||||
|
||||
payload = transform.bytes2int(padded)
|
||||
encrypted = priv_key.blinded_encrypt(payload)
|
||||
block = transform.int2bytes(encrypted, keylength)
|
||||
|
||||
return block
|
||||
|
||||
|
||||
def sign(message: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes:
|
||||
"""Signs the message with the private key.
|
||||
|
||||
Hashes the message, then signs the hash with the given key. This is known
|
||||
as a "detached signature", because the message itself isn't altered.
|
||||
|
||||
:param message: the message to sign. Can be an 8-bit string or a file-like
|
||||
object. If ``message`` has a ``read()`` method, it is assumed to be a
|
||||
file-like object.
|
||||
:param priv_key: the :py:class:`rsa.PrivateKey` to sign with
|
||||
:param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
|
||||
'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
|
||||
:return: a message signature block.
|
||||
:raise OverflowError: if the private key is too small to contain the
|
||||
requested hash.
|
||||
|
||||
"""
|
||||
|
||||
msg_hash = compute_hash(message, hash_method)
|
||||
return sign_hash(msg_hash, priv_key, hash_method)
|
||||
|
||||
|
||||
def verify(message: bytes, signature: bytes, pub_key: key.PublicKey) -> str:
|
||||
"""Verifies that the signature matches the message.
|
||||
|
||||
The hash method is detected automatically from the signature.
|
||||
|
||||
:param message: the signed message. Can be an 8-bit string or a file-like
|
||||
object. If ``message`` has a ``read()`` method, it is assumed to be a
|
||||
file-like object.
|
||||
:param signature: the signature block, as created with :py:func:`rsa.sign`.
|
||||
:param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
|
||||
:raise VerificationError: when the signature doesn't match the message.
|
||||
:returns: the name of the used hash.
|
||||
|
||||
"""
|
||||
|
||||
keylength = common.byte_size(pub_key.n)
|
||||
encrypted = transform.bytes2int(signature)
|
||||
decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
|
||||
clearsig = transform.int2bytes(decrypted, keylength)
|
||||
|
||||
# Get the hash method
|
||||
method_name = _find_method_hash(clearsig)
|
||||
message_hash = compute_hash(message, method_name)
|
||||
|
||||
# Reconstruct the expected padded hash
|
||||
cleartext = HASH_ASN1[method_name] + message_hash
|
||||
expected = _pad_for_signing(cleartext, keylength)
|
||||
|
||||
if len(signature) != keylength:
|
||||
raise VerificationError("Verification failed")
|
||||
|
||||
# Compare with the signed one
|
||||
if expected != clearsig:
|
||||
raise VerificationError("Verification failed")
|
||||
|
||||
return method_name
|
||||
|
||||
|
||||
def find_signature_hash(signature: bytes, pub_key: key.PublicKey) -> str:
|
||||
"""Returns the hash name detected from the signature.
|
||||
|
||||
If you also want to verify the message, use :py:func:`rsa.verify()` instead.
|
||||
It also returns the name of the used hash.
|
||||
|
||||
:param signature: the signature block, as created with :py:func:`rsa.sign`.
|
||||
:param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
|
||||
:returns: the name of the used hash.
|
||||
"""
|
||||
|
||||
keylength = common.byte_size(pub_key.n)
|
||||
encrypted = transform.bytes2int(signature)
|
||||
decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
|
||||
clearsig = transform.int2bytes(decrypted, keylength)
|
||||
|
||||
return _find_method_hash(clearsig)
|
||||
|
||||
|
||||
def yield_fixedblocks(infile: typing.BinaryIO, blocksize: int) -> typing.Iterator[bytes]:
|
||||
"""Generator, yields each block of ``blocksize`` bytes in the input file.
|
||||
|
||||
:param infile: file to read and separate in blocks.
|
||||
:param blocksize: block size in bytes.
|
||||
:returns: a generator that yields the contents of each block
|
||||
"""
|
||||
|
||||
while True:
|
||||
block = infile.read(blocksize)
|
||||
|
||||
read_bytes = len(block)
|
||||
if read_bytes == 0:
|
||||
break
|
||||
|
||||
yield block
|
||||
|
||||
if read_bytes < blocksize:
|
||||
break
|
||||
|
||||
|
||||
def compute_hash(message: typing.Union[bytes, typing.BinaryIO], method_name: str) -> bytes:
|
||||
"""Returns the message digest.
|
||||
|
||||
:param message: the signed message. Can be an 8-bit string or a file-like
|
||||
object. If ``message`` has a ``read()`` method, it is assumed to be a
|
||||
file-like object.
|
||||
:param method_name: the hash method, must be a key of
|
||||
:py:const:`rsa.pkcs1.HASH_METHODS`.
|
||||
|
||||
"""
|
||||
|
||||
if method_name not in HASH_METHODS:
|
||||
raise ValueError("Invalid hash method: %s" % method_name)
|
||||
|
||||
method = HASH_METHODS[method_name]
|
||||
hasher = method()
|
||||
|
||||
if isinstance(message, bytes):
|
||||
hasher.update(message)
|
||||
else:
|
||||
assert hasattr(message, "read") and hasattr(message.read, "__call__")
|
||||
# read as 1K blocks
|
||||
for block in yield_fixedblocks(message, 1024):
|
||||
hasher.update(block)
|
||||
|
||||
return hasher.digest()
|
||||
|
||||
|
||||
def _find_method_hash(clearsig: bytes) -> str:
|
||||
"""Finds the hash method.
|
||||
|
||||
:param clearsig: full padded ASN1 and hash.
|
||||
:return: the used hash method.
|
||||
:raise VerificationFailed: when the hash method cannot be found
|
||||
"""
|
||||
|
||||
for (hashname, asn1code) in HASH_ASN1.items():
|
||||
if asn1code in clearsig:
|
||||
return hashname
|
||||
|
||||
raise VerificationError("Verification failed")
|
||||
|
||||
|
||||
__all__ = [
|
||||
"encrypt",
|
||||
"decrypt",
|
||||
"sign",
|
||||
"verify",
|
||||
"DecryptionError",
|
||||
"VerificationError",
|
||||
"CryptoError",
|
||||
]
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Running doctests 1000x or until failure")
|
||||
import doctest
|
||||
|
||||
for count in range(1000):
|
||||
(failures, tests) = doctest.testmod()
|
||||
if failures:
|
||||
break
|
||||
|
||||
if count % 100 == 0 and count:
|
||||
print("%i times" % count)
|
||||
|
||||
print("Doctests done")
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Functions for PKCS#1 version 2 encryption and signing
|
||||
|
||||
This module implements certain functionality from PKCS#1 version 2. Main
|
||||
documentation is RFC 2437: https://tools.ietf.org/html/rfc2437
|
||||
"""
|
||||
|
||||
from rsa import (
|
||||
common,
|
||||
pkcs1,
|
||||
transform,
|
||||
)
|
||||
|
||||
|
||||
def mgf1(seed: bytes, length: int, hasher: str = "SHA-1") -> bytes:
|
||||
"""
|
||||
MGF1 is a Mask Generation Function based on a hash function.
|
||||
|
||||
A mask generation function takes an octet string of variable length and a
|
||||
desired output length as input, and outputs an octet string of the desired
|
||||
length. The plaintext-awareness of RSAES-OAEP relies on the random nature of
|
||||
the output of the mask generation function, which in turn relies on the
|
||||
random nature of the underlying hash.
|
||||
|
||||
:param bytes seed: seed from which mask is generated, an octet string
|
||||
:param int length: intended length in octets of the mask, at most 2^32(hLen)
|
||||
:param str hasher: hash function (hLen denotes the length in octets of the hash
|
||||
function output)
|
||||
|
||||
:return: mask, an octet string of length `length`
|
||||
:rtype: bytes
|
||||
|
||||
:raise OverflowError: when `length` is too large for the specified `hasher`
|
||||
:raise ValueError: when specified `hasher` is invalid
|
||||
"""
|
||||
|
||||
try:
|
||||
hash_length = pkcs1.HASH_METHODS[hasher]().digest_size
|
||||
except KeyError as ex:
|
||||
raise ValueError(
|
||||
"Invalid `hasher` specified. Please select one of: {hash_list}".format(
|
||||
hash_list=", ".join(sorted(pkcs1.HASH_METHODS.keys()))
|
||||
)
|
||||
) from ex
|
||||
|
||||
# If l > 2^32(hLen), output "mask too long" and stop.
|
||||
if length > (2 ** 32 * hash_length):
|
||||
raise OverflowError(
|
||||
"Desired length should be at most 2**32 times the hasher's output "
|
||||
"length ({hash_length} for {hasher} function)".format(
|
||||
hash_length=hash_length,
|
||||
hasher=hasher,
|
||||
)
|
||||
)
|
||||
|
||||
# Looping `counter` from 0 to ceil(l / hLen)-1, build `output` based on the
|
||||
# hashes formed by (`seed` + C), being `C` an octet string of length 4
|
||||
# generated by converting `counter` with the primitive I2OSP
|
||||
output = b"".join(
|
||||
pkcs1.compute_hash(
|
||||
seed + transform.int2bytes(counter, fill_size=4),
|
||||
method_name=hasher,
|
||||
)
|
||||
for counter in range(common.ceil_div(length, hash_length) + 1)
|
||||
)
|
||||
|
||||
# Output the leading `length` octets of `output` as the octet string mask.
|
||||
return output[:length]
|
||||
|
||||
|
||||
__all__ = [
|
||||
"mgf1",
|
||||
]
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Running doctests 1000x or until failure")
|
||||
import doctest
|
||||
|
||||
for count in range(1000):
|
||||
(failures, tests) = doctest.testmod()
|
||||
if failures:
|
||||
break
|
||||
|
||||
if count % 100 == 0 and count:
|
||||
print("%i times" % count)
|
||||
|
||||
print("Doctests done")
|
|
@ -1,166 +1,198 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''Numerical functions related to primes.
|
||||
|
||||
Implementation based on the book Algorithm Design by Michael T. Goodrich and
|
||||
Roberto Tamassia, 2002.
|
||||
'''
|
||||
|
||||
__all__ = [ 'getprime', 'are_relatively_prime']
|
||||
|
||||
import rsa.randnum
|
||||
|
||||
def gcd(p, q):
|
||||
'''Returns the greatest common divisor of p and q
|
||||
|
||||
>>> gcd(48, 180)
|
||||
12
|
||||
'''
|
||||
|
||||
while q != 0:
|
||||
if p < q: (p,q) = (q,p)
|
||||
(p,q) = (q, p % q)
|
||||
return p
|
||||
|
||||
|
||||
def jacobi(a, b):
|
||||
'''Calculates the value of the Jacobi symbol (a/b) where both a and b are
|
||||
positive integers, and b is odd
|
||||
|
||||
:returns: -1, 0 or 1
|
||||
'''
|
||||
|
||||
assert a > 0
|
||||
assert b > 0
|
||||
|
||||
if a == 0: return 0
|
||||
result = 1
|
||||
while a > 1:
|
||||
if a & 1:
|
||||
if ((a-1)*(b-1) >> 2) & 1:
|
||||
result = -result
|
||||
a, b = b % a, a
|
||||
else:
|
||||
if (((b * b) - 1) >> 3) & 1:
|
||||
result = -result
|
||||
a >>= 1
|
||||
if a == 0: return 0
|
||||
return result
|
||||
|
||||
def jacobi_witness(x, n):
|
||||
'''Returns False if n is an Euler pseudo-prime with base x, and
|
||||
True otherwise.
|
||||
'''
|
||||
|
||||
j = jacobi(x, n) % n
|
||||
|
||||
f = pow(x, n >> 1, n)
|
||||
|
||||
if j == f: return False
|
||||
return True
|
||||
|
||||
def randomized_primality_testing(n, k):
|
||||
'''Calculates whether n is composite (which is always correct) or
|
||||
prime (which is incorrect with error probability 2**-k)
|
||||
|
||||
Returns False if the number is composite, and True if it's
|
||||
probably prime.
|
||||
'''
|
||||
|
||||
# 50% of Jacobi-witnesses can report compositness of non-prime numbers
|
||||
|
||||
# The implemented algorithm using the Jacobi witness function has error
|
||||
# probability q <= 0.5, according to Goodrich et. al
|
||||
#
|
||||
# q = 0.5
|
||||
# t = int(math.ceil(k / log(1 / q, 2)))
|
||||
# So t = k / log(2, 2) = k / 1 = k
|
||||
# this means we can use range(k) rather than range(t)
|
||||
|
||||
for _ in range(k):
|
||||
x = rsa.randnum.randint(n-1)
|
||||
if jacobi_witness(x, n): return False
|
||||
|
||||
return True
|
||||
|
||||
def is_prime(number):
|
||||
'''Returns True if the number is prime, and False otherwise.
|
||||
|
||||
>>> is_prime(42)
|
||||
False
|
||||
>>> is_prime(41)
|
||||
True
|
||||
'''
|
||||
|
||||
return randomized_primality_testing(number, 6)
|
||||
|
||||
def getprime(nbits):
|
||||
'''Returns a prime number that can be stored in 'nbits' bits.
|
||||
|
||||
>>> p = getprime(128)
|
||||
>>> is_prime(p-1)
|
||||
False
|
||||
>>> is_prime(p)
|
||||
True
|
||||
>>> is_prime(p+1)
|
||||
False
|
||||
|
||||
>>> from rsa import common
|
||||
>>> common.bit_size(p) == 128
|
||||
True
|
||||
|
||||
'''
|
||||
|
||||
while True:
|
||||
integer = rsa.randnum.read_random_int(nbits)
|
||||
|
||||
# Make sure it's odd
|
||||
integer |= 1
|
||||
|
||||
# Test for primeness
|
||||
if is_prime(integer):
|
||||
return integer
|
||||
|
||||
# Retry if not prime
|
||||
|
||||
|
||||
def are_relatively_prime(a, b):
|
||||
'''Returns True if a and b are relatively prime, and False if they
|
||||
are not.
|
||||
|
||||
>>> are_relatively_prime(2, 3)
|
||||
1
|
||||
>>> are_relatively_prime(2, 4)
|
||||
0
|
||||
'''
|
||||
|
||||
d = gcd(a, b)
|
||||
return (d == 1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('Running doctests 1000x or until failure')
|
||||
import doctest
|
||||
|
||||
for count in range(1000):
|
||||
(failures, tests) = doctest.testmod()
|
||||
if failures:
|
||||
break
|
||||
|
||||
if count and count % 100 == 0:
|
||||
print('%i times' % count)
|
||||
|
||||
print('Doctests done')
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Numerical functions related to primes.
|
||||
|
||||
Implementation based on the book Algorithm Design by Michael T. Goodrich and
|
||||
Roberto Tamassia, 2002.
|
||||
"""
|
||||
|
||||
import rsa.common
|
||||
import rsa.randnum
|
||||
|
||||
__all__ = ["getprime", "are_relatively_prime"]
|
||||
|
||||
|
||||
def gcd(p: int, q: int) -> int:
|
||||
"""Returns the greatest common divisor of p and q
|
||||
|
||||
>>> gcd(48, 180)
|
||||
12
|
||||
"""
|
||||
|
||||
while q != 0:
|
||||
(p, q) = (q, p % q)
|
||||
return p
|
||||
|
||||
|
||||
def get_primality_testing_rounds(number: int) -> int:
|
||||
"""Returns minimum number of rounds for Miller-Rabing primality testing,
|
||||
based on number bitsize.
|
||||
|
||||
According to NIST FIPS 186-4, Appendix C, Table C.3, minimum number of
|
||||
rounds of M-R testing, using an error probability of 2 ** (-100), for
|
||||
different p, q bitsizes are:
|
||||
* p, q bitsize: 512; rounds: 7
|
||||
* p, q bitsize: 1024; rounds: 4
|
||||
* p, q bitsize: 1536; rounds: 3
|
||||
See: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
|
||||
"""
|
||||
|
||||
# Calculate number bitsize.
|
||||
bitsize = rsa.common.bit_size(number)
|
||||
# Set number of rounds.
|
||||
if bitsize >= 1536:
|
||||
return 3
|
||||
if bitsize >= 1024:
|
||||
return 4
|
||||
if bitsize >= 512:
|
||||
return 7
|
||||
# For smaller bitsizes, set arbitrary number of rounds.
|
||||
return 10
|
||||
|
||||
|
||||
def miller_rabin_primality_testing(n: int, k: int) -> bool:
|
||||
"""Calculates whether n is composite (which is always correct) or prime
|
||||
(which theoretically is incorrect with error probability 4**-k), by
|
||||
applying Miller-Rabin primality testing.
|
||||
|
||||
For reference and implementation example, see:
|
||||
https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test
|
||||
|
||||
:param n: Integer to be tested for primality.
|
||||
:type n: int
|
||||
:param k: Number of rounds (witnesses) of Miller-Rabin testing.
|
||||
:type k: int
|
||||
:return: False if the number is composite, True if it's probably prime.
|
||||
:rtype: bool
|
||||
"""
|
||||
|
||||
# prevent potential infinite loop when d = 0
|
||||
if n < 2:
|
||||
return False
|
||||
|
||||
# Decompose (n - 1) to write it as (2 ** r) * d
|
||||
# While d is even, divide it by 2 and increase the exponent.
|
||||
d = n - 1
|
||||
r = 0
|
||||
|
||||
while not (d & 1):
|
||||
r += 1
|
||||
d >>= 1
|
||||
|
||||
# Test k witnesses.
|
||||
for _ in range(k):
|
||||
# Generate random integer a, where 2 <= a <= (n - 2)
|
||||
a = rsa.randnum.randint(n - 3) + 1
|
||||
|
||||
x = pow(a, d, n)
|
||||
if x == 1 or x == n - 1:
|
||||
continue
|
||||
|
||||
for _ in range(r - 1):
|
||||
x = pow(x, 2, n)
|
||||
if x == 1:
|
||||
# n is composite.
|
||||
return False
|
||||
if x == n - 1:
|
||||
# Exit inner loop and continue with next witness.
|
||||
break
|
||||
else:
|
||||
# If loop doesn't break, n is composite.
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def is_prime(number: int) -> bool:
|
||||
"""Returns True if the number is prime, and False otherwise.
|
||||
|
||||
>>> is_prime(2)
|
||||
True
|
||||
>>> is_prime(42)
|
||||
False
|
||||
>>> is_prime(41)
|
||||
True
|
||||
"""
|
||||
|
||||
# Check for small numbers.
|
||||
if number < 10:
|
||||
return number in {2, 3, 5, 7}
|
||||
|
||||
# Check for even numbers.
|
||||
if not (number & 1):
|
||||
return False
|
||||
|
||||
# Calculate minimum number of rounds.
|
||||
k = get_primality_testing_rounds(number)
|
||||
|
||||
# Run primality testing with (minimum + 1) rounds.
|
||||
return miller_rabin_primality_testing(number, k + 1)
|
||||
|
||||
|
||||
def getprime(nbits: int) -> int:
|
||||
"""Returns a prime number that can be stored in 'nbits' bits.
|
||||
|
||||
>>> p = getprime(128)
|
||||
>>> is_prime(p-1)
|
||||
False
|
||||
>>> is_prime(p)
|
||||
True
|
||||
>>> is_prime(p+1)
|
||||
False
|
||||
|
||||
>>> from rsa import common
|
||||
>>> common.bit_size(p) == 128
|
||||
True
|
||||
"""
|
||||
|
||||
assert nbits > 3 # the loop will hang on too small numbers
|
||||
|
||||
while True:
|
||||
integer = rsa.randnum.read_random_odd_int(nbits)
|
||||
|
||||
# Test for primeness
|
||||
if is_prime(integer):
|
||||
return integer
|
||||
|
||||
# Retry if not prime
|
||||
|
||||
|
||||
def are_relatively_prime(a: int, b: int) -> bool:
|
||||
"""Returns True if a and b are relatively prime, and False if they
|
||||
are not.
|
||||
|
||||
>>> are_relatively_prime(2, 3)
|
||||
True
|
||||
>>> are_relatively_prime(2, 4)
|
||||
False
|
||||
"""
|
||||
|
||||
d = gcd(a, b)
|
||||
return d == 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Running doctests 1000x or until failure")
|
||||
import doctest
|
||||
|
||||
for count in range(1000):
|
||||
(failures, tests) = doctest.testmod()
|
||||
if failures:
|
||||
break
|
||||
|
||||
if count % 100 == 0 and count:
|
||||
print("%i times" % count)
|
||||
|
||||
print("Doctests done")
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
# Marker file for PEP 561. The rsa package uses inline types.
|
|
@ -1,85 +1,95 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''Functions for generating random numbers.'''
|
||||
|
||||
# Source inspired by code by Yesudeep Mangalapilly <yesudeep@gmail.com>
|
||||
|
||||
import os
|
||||
|
||||
from rsa import common, transform
|
||||
from rsa._compat import byte
|
||||
|
||||
def read_random_bits(nbits):
|
||||
'''Reads 'nbits' random bits.
|
||||
|
||||
If nbits isn't a whole number of bytes, an extra byte will be appended with
|
||||
only the lower bits set.
|
||||
'''
|
||||
|
||||
nbytes, rbits = divmod(nbits, 8)
|
||||
|
||||
# Get the random bytes
|
||||
randomdata = os.urandom(nbytes)
|
||||
|
||||
# Add the remaining random bits
|
||||
if rbits > 0:
|
||||
randomvalue = ord(os.urandom(1))
|
||||
randomvalue >>= (8 - rbits)
|
||||
randomdata = byte(randomvalue) + randomdata
|
||||
|
||||
return randomdata
|
||||
|
||||
|
||||
def read_random_int(nbits):
|
||||
'''Reads a random integer of approximately nbits bits.
|
||||
'''
|
||||
|
||||
randomdata = read_random_bits(nbits)
|
||||
value = transform.bytes2int(randomdata)
|
||||
|
||||
# Ensure that the number is large enough to just fill out the required
|
||||
# number of bits.
|
||||
value |= 1 << (nbits - 1)
|
||||
|
||||
return value
|
||||
|
||||
def randint(maxvalue):
|
||||
'''Returns a random integer x with 1 <= x <= maxvalue
|
||||
|
||||
May take a very long time in specific situations. If maxvalue needs N bits
|
||||
to store, the closer maxvalue is to (2 ** N) - 1, the faster this function
|
||||
is.
|
||||
'''
|
||||
|
||||
bit_size = common.bit_size(maxvalue)
|
||||
|
||||
tries = 0
|
||||
while True:
|
||||
value = read_random_int(bit_size)
|
||||
if value <= maxvalue:
|
||||
break
|
||||
|
||||
if tries and tries % 10 == 0:
|
||||
# After a lot of tries to get the right number of bits but still
|
||||
# smaller than maxvalue, decrease the number of bits by 1. That'll
|
||||
# dramatically increase the chances to get a large enough number.
|
||||
bit_size -= 1
|
||||
tries += 1
|
||||
|
||||
return value
|
||||
|
||||
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Functions for generating random numbers."""
|
||||
|
||||
# Source inspired by code by Yesudeep Mangalapilly <yesudeep@gmail.com>
|
||||
|
||||
import os
|
||||
import struct
|
||||
|
||||
from rsa import common, transform
|
||||
|
||||
|
||||
def read_random_bits(nbits: int) -> bytes:
|
||||
"""Reads 'nbits' random bits.
|
||||
|
||||
If nbits isn't a whole number of bytes, an extra byte will be appended with
|
||||
only the lower bits set.
|
||||
"""
|
||||
|
||||
nbytes, rbits = divmod(nbits, 8)
|
||||
|
||||
# Get the random bytes
|
||||
randomdata = os.urandom(nbytes)
|
||||
|
||||
# Add the remaining random bits
|
||||
if rbits > 0:
|
||||
randomvalue = ord(os.urandom(1))
|
||||
randomvalue >>= 8 - rbits
|
||||
randomdata = struct.pack("B", randomvalue) + randomdata
|
||||
|
||||
return randomdata
|
||||
|
||||
|
||||
def read_random_int(nbits: int) -> int:
|
||||
"""Reads a random integer of approximately nbits bits."""
|
||||
|
||||
randomdata = read_random_bits(nbits)
|
||||
value = transform.bytes2int(randomdata)
|
||||
|
||||
# Ensure that the number is large enough to just fill out the required
|
||||
# number of bits.
|
||||
value |= 1 << (nbits - 1)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def read_random_odd_int(nbits: int) -> int:
|
||||
"""Reads a random odd integer of approximately nbits bits.
|
||||
|
||||
>>> read_random_odd_int(512) & 1
|
||||
1
|
||||
"""
|
||||
|
||||
value = read_random_int(nbits)
|
||||
|
||||
# Make sure it's odd
|
||||
return value | 1
|
||||
|
||||
|
||||
def randint(maxvalue: int) -> int:
|
||||
"""Returns a random integer x with 1 <= x <= maxvalue
|
||||
|
||||
May take a very long time in specific situations. If maxvalue needs N bits
|
||||
to store, the closer maxvalue is to (2 ** N) - 1, the faster this function
|
||||
is.
|
||||
"""
|
||||
|
||||
bit_size = common.bit_size(maxvalue)
|
||||
|
||||
tries = 0
|
||||
while True:
|
||||
value = read_random_int(bit_size)
|
||||
if value <= maxvalue:
|
||||
break
|
||||
|
||||
if tries % 10 == 0 and tries:
|
||||
# After a lot of tries to get the right number of bits but still
|
||||
# smaller than maxvalue, decrease the number of bits by 1. That'll
|
||||
# dramatically increase the chances to get a large enough number.
|
||||
bit_size -= 1
|
||||
tries += 1
|
||||
|
||||
return value
|
||||
|
|
|
@ -1,220 +1,72 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''Data transformation functions.
|
||||
|
||||
From bytes to a number, number to bytes, etc.
|
||||
'''
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
try:
|
||||
# We'll use psyco if available on 32-bit architectures to speed up code.
|
||||
# Using psyco (if available) cuts down the execution time on Python 2.5
|
||||
# at least by half.
|
||||
import psyco
|
||||
psyco.full()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import binascii
|
||||
from struct import pack
|
||||
from rsa import common
|
||||
from rsa._compat import is_integer, b, byte, get_word_alignment, ZERO_BYTE, EMPTY_BYTE
|
||||
|
||||
|
||||
def bytes2int(raw_bytes):
|
||||
r'''Converts a list of bytes or an 8-bit string to an integer.
|
||||
|
||||
When using unicode strings, encode it to some encoding like UTF8 first.
|
||||
|
||||
>>> (((128 * 256) + 64) * 256) + 15
|
||||
8405007
|
||||
>>> bytes2int('\x80@\x0f')
|
||||
8405007
|
||||
|
||||
'''
|
||||
|
||||
return int(binascii.hexlify(raw_bytes), 16)
|
||||
|
||||
|
||||
def _int2bytes(number, block_size=None):
|
||||
r'''Converts a number to a string of bytes.
|
||||
|
||||
Usage::
|
||||
|
||||
>>> _int2bytes(123456789)
|
||||
'\x07[\xcd\x15'
|
||||
>>> bytes2int(_int2bytes(123456789))
|
||||
123456789
|
||||
|
||||
>>> _int2bytes(123456789, 6)
|
||||
'\x00\x00\x07[\xcd\x15'
|
||||
>>> bytes2int(_int2bytes(123456789, 128))
|
||||
123456789
|
||||
|
||||
>>> _int2bytes(123456789, 3)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
OverflowError: Needed 4 bytes for number, but block size is 3
|
||||
|
||||
@param number: the number to convert
|
||||
@param block_size: the number of bytes to output. If the number encoded to
|
||||
bytes is less than this, the block will be zero-padded. When not given,
|
||||
the returned block is not padded.
|
||||
|
||||
@throws OverflowError when block_size is given and the number takes up more
|
||||
bytes than fit into the block.
|
||||
'''
|
||||
# Type checking
|
||||
if not is_integer(number):
|
||||
raise TypeError("You must pass an integer for 'number', not %s" %
|
||||
number.__class__)
|
||||
|
||||
if number < 0:
|
||||
raise ValueError('Negative numbers cannot be used: %i' % number)
|
||||
|
||||
# Do some bounds checking
|
||||
if number == 0:
|
||||
needed_bytes = 1
|
||||
raw_bytes = [ZERO_BYTE]
|
||||
else:
|
||||
needed_bytes = common.byte_size(number)
|
||||
raw_bytes = []
|
||||
|
||||
# You cannot compare None > 0 in Python 3x. It will fail with a TypeError.
|
||||
if block_size and block_size > 0:
|
||||
if needed_bytes > block_size:
|
||||
raise OverflowError('Needed %i bytes for number, but block size '
|
||||
'is %i' % (needed_bytes, block_size))
|
||||
|
||||
# Convert the number to bytes.
|
||||
while number > 0:
|
||||
raw_bytes.insert(0, byte(number & 0xFF))
|
||||
number >>= 8
|
||||
|
||||
# Pad with zeroes to fill the block
|
||||
if block_size and block_size > 0:
|
||||
padding = (block_size - needed_bytes) * ZERO_BYTE
|
||||
else:
|
||||
padding = EMPTY_BYTE
|
||||
|
||||
return padding + EMPTY_BYTE.join(raw_bytes)
|
||||
|
||||
|
||||
def bytes_leading(raw_bytes, needle=ZERO_BYTE):
|
||||
'''
|
||||
Finds the number of prefixed byte occurrences in the haystack.
|
||||
|
||||
Useful when you want to deal with padding.
|
||||
|
||||
:param raw_bytes:
|
||||
Raw bytes.
|
||||
:param needle:
|
||||
The byte to count. Default \000.
|
||||
:returns:
|
||||
The number of leading needle bytes.
|
||||
'''
|
||||
leading = 0
|
||||
# Indexing keeps compatibility between Python 2.x and Python 3.x
|
||||
_byte = needle[0]
|
||||
for x in raw_bytes:
|
||||
if x == _byte:
|
||||
leading += 1
|
||||
else:
|
||||
break
|
||||
return leading
|
||||
|
||||
|
||||
def int2bytes(number, fill_size=None, chunk_size=None, overflow=False):
|
||||
'''
|
||||
Convert an unsigned integer to bytes (base-256 representation)::
|
||||
|
||||
Does not preserve leading zeros if you don't specify a chunk size or
|
||||
fill size.
|
||||
|
||||
.. NOTE:
|
||||
You must not specify both fill_size and chunk_size. Only one
|
||||
of them is allowed.
|
||||
|
||||
:param number:
|
||||
Integer value
|
||||
:param fill_size:
|
||||
If the optional fill size is given the length of the resulting
|
||||
byte string is expected to be the fill size and will be padded
|
||||
with prefix zero bytes to satisfy that length.
|
||||
:param chunk_size:
|
||||
If optional chunk size is given and greater than zero, pad the front of
|
||||
the byte string with binary zeros so that the length is a multiple of
|
||||
``chunk_size``.
|
||||
:param overflow:
|
||||
``False`` (default). If this is ``True``, no ``OverflowError``
|
||||
will be raised when the fill_size is shorter than the length
|
||||
of the generated byte sequence. Instead the byte sequence will
|
||||
be returned as is.
|
||||
:returns:
|
||||
Raw bytes (base-256 representation).
|
||||
:raises:
|
||||
``OverflowError`` when fill_size is given and the number takes up more
|
||||
bytes than fit into the block. This requires the ``overflow``
|
||||
argument to this function to be set to ``False`` otherwise, no
|
||||
error will be raised.
|
||||
'''
|
||||
if number < 0:
|
||||
raise ValueError("Number must be an unsigned integer: %d" % number)
|
||||
|
||||
if fill_size and chunk_size:
|
||||
raise ValueError("You can either fill or pad chunks, but not both")
|
||||
|
||||
# Ensure these are integers.
|
||||
number & 1
|
||||
|
||||
raw_bytes = b('')
|
||||
|
||||
# Pack the integer one machine word at a time into bytes.
|
||||
num = number
|
||||
word_bits, _, max_uint, pack_type = get_word_alignment(num)
|
||||
pack_format = ">%s" % pack_type
|
||||
while num > 0:
|
||||
raw_bytes = pack(pack_format, num & max_uint) + raw_bytes
|
||||
num >>= word_bits
|
||||
# Obtain the index of the first non-zero byte.
|
||||
zero_leading = bytes_leading(raw_bytes)
|
||||
if number == 0:
|
||||
raw_bytes = ZERO_BYTE
|
||||
# De-padding.
|
||||
raw_bytes = raw_bytes[zero_leading:]
|
||||
|
||||
length = len(raw_bytes)
|
||||
if fill_size and fill_size > 0:
|
||||
if not overflow and length > fill_size:
|
||||
raise OverflowError(
|
||||
"Need %d bytes for number, but fill size is %d" %
|
||||
(length, fill_size)
|
||||
)
|
||||
raw_bytes = raw_bytes.rjust(fill_size, ZERO_BYTE)
|
||||
elif chunk_size and chunk_size > 0:
|
||||
remainder = length % chunk_size
|
||||
if remainder:
|
||||
padding_size = chunk_size - remainder
|
||||
raw_bytes = raw_bytes.rjust(length + padding_size, ZERO_BYTE)
|
||||
return raw_bytes
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Data transformation functions.
|
||||
|
||||
From bytes to a number, number to bytes, etc.
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
|
||||
def bytes2int(raw_bytes: bytes) -> int:
|
||||
r"""Converts a list of bytes or an 8-bit string to an integer.
|
||||
|
||||
When using unicode strings, encode it to some encoding like UTF8 first.
|
||||
|
||||
>>> (((128 * 256) + 64) * 256) + 15
|
||||
8405007
|
||||
>>> bytes2int(b'\x80@\x0f')
|
||||
8405007
|
||||
|
||||
"""
|
||||
return int.from_bytes(raw_bytes, "big", signed=False)
|
||||
|
||||
|
||||
def int2bytes(number: int, fill_size: int = 0) -> bytes:
|
||||
"""
|
||||
Convert an unsigned integer to bytes (big-endian)::
|
||||
|
||||
Does not preserve leading zeros if you don't specify a fill size.
|
||||
|
||||
:param number:
|
||||
Integer value
|
||||
:param fill_size:
|
||||
If the optional fill size is given the length of the resulting
|
||||
byte string is expected to be the fill size and will be padded
|
||||
with prefix zero bytes to satisfy that length.
|
||||
:returns:
|
||||
Raw bytes (base-256 representation).
|
||||
:raises:
|
||||
``OverflowError`` when fill_size is given and the number takes up more
|
||||
bytes than fit into the block. This requires the ``overflow``
|
||||
argument to this function to be set to ``False`` otherwise, no
|
||||
error will be raised.
|
||||
"""
|
||||
|
||||
if number < 0:
|
||||
raise ValueError("Number must be an unsigned integer: %d" % number)
|
||||
|
||||
bytes_required = max(1, math.ceil(number.bit_length() / 8))
|
||||
|
||||
if fill_size > 0:
|
||||
return number.to_bytes(fill_size, "big")
|
||||
|
||||
return number.to_bytes(bytes_required, "big")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
|
|
@ -1,81 +1,97 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''Utility functions.'''
|
||||
|
||||
from __future__ import with_statement, print_function
|
||||
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
|
||||
import rsa.key
|
||||
|
||||
def private_to_public():
|
||||
'''Reads a private key and outputs the corresponding public key.'''
|
||||
|
||||
# Parse the CLI options
|
||||
parser = OptionParser(usage='usage: %prog [options]',
|
||||
description='Reads a private key and outputs the '
|
||||
'corresponding public key. Both private and public keys use '
|
||||
'the format described in PKCS#1 v1.5')
|
||||
|
||||
parser.add_option('-i', '--input', dest='infilename', type='string',
|
||||
help='Input filename. Reads from stdin if not specified')
|
||||
parser.add_option('-o', '--output', dest='outfilename', type='string',
|
||||
help='Output filename. Writes to stdout of not specified')
|
||||
|
||||
parser.add_option('--inform', dest='inform',
|
||||
help='key format of input - default PEM',
|
||||
choices=('PEM', 'DER'), default='PEM')
|
||||
|
||||
parser.add_option('--outform', dest='outform',
|
||||
help='key format of output - default PEM',
|
||||
choices=('PEM', 'DER'), default='PEM')
|
||||
|
||||
(cli, cli_args) = parser.parse_args(sys.argv)
|
||||
|
||||
# Read the input data
|
||||
if cli.infilename:
|
||||
print('Reading private key from %s in %s format' % \
|
||||
(cli.infilename, cli.inform), file=sys.stderr)
|
||||
with open(cli.infilename, 'rb') as infile:
|
||||
in_data = infile.read()
|
||||
else:
|
||||
print('Reading private key from stdin in %s format' % cli.inform,
|
||||
file=sys.stderr)
|
||||
in_data = sys.stdin.read().encode('ascii')
|
||||
|
||||
assert type(in_data) == bytes, type(in_data)
|
||||
|
||||
|
||||
# Take the public fields and create a public key
|
||||
priv_key = rsa.key.PrivateKey.load_pkcs1(in_data, cli.inform)
|
||||
pub_key = rsa.key.PublicKey(priv_key.n, priv_key.e)
|
||||
|
||||
# Save to the output file
|
||||
out_data = pub_key.save_pkcs1(cli.outform)
|
||||
|
||||
if cli.outfilename:
|
||||
print('Writing public key to %s in %s format' % \
|
||||
(cli.outfilename, cli.outform), file=sys.stderr)
|
||||
with open(cli.outfilename, 'wb') as outfile:
|
||||
outfile.write(out_data)
|
||||
else:
|
||||
print('Writing public key to stdout in %s format' % cli.outform,
|
||||
file=sys.stderr)
|
||||
sys.stdout.write(out_data.decode('ascii'))
|
||||
|
||||
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Utility functions."""
|
||||
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
|
||||
import rsa.key
|
||||
|
||||
|
||||
def private_to_public() -> None:
|
||||
"""Reads a private key and outputs the corresponding public key."""
|
||||
|
||||
# Parse the CLI options
|
||||
parser = OptionParser(
|
||||
usage="usage: %prog [options]",
|
||||
description="Reads a private key and outputs the "
|
||||
"corresponding public key. Both private and public keys use "
|
||||
"the format described in PKCS#1 v1.5",
|
||||
)
|
||||
|
||||
parser.add_option(
|
||||
"-i",
|
||||
"--input",
|
||||
dest="infilename",
|
||||
type="string",
|
||||
help="Input filename. Reads from stdin if not specified",
|
||||
)
|
||||
parser.add_option(
|
||||
"-o",
|
||||
"--output",
|
||||
dest="outfilename",
|
||||
type="string",
|
||||
help="Output filename. Writes to stdout of not specified",
|
||||
)
|
||||
|
||||
parser.add_option(
|
||||
"--inform",
|
||||
dest="inform",
|
||||
help="key format of input - default PEM",
|
||||
choices=("PEM", "DER"),
|
||||
default="PEM",
|
||||
)
|
||||
|
||||
parser.add_option(
|
||||
"--outform",
|
||||
dest="outform",
|
||||
help="key format of output - default PEM",
|
||||
choices=("PEM", "DER"),
|
||||
default="PEM",
|
||||
)
|
||||
|
||||
(cli, cli_args) = parser.parse_args(sys.argv)
|
||||
|
||||
# Read the input data
|
||||
if cli.infilename:
|
||||
print(
|
||||
"Reading private key from %s in %s format" % (cli.infilename, cli.inform),
|
||||
file=sys.stderr,
|
||||
)
|
||||
with open(cli.infilename, "rb") as infile:
|
||||
in_data = infile.read()
|
||||
else:
|
||||
print("Reading private key from stdin in %s format" % cli.inform, file=sys.stderr)
|
||||
in_data = sys.stdin.read().encode("ascii")
|
||||
|
||||
assert type(in_data) == bytes, type(in_data)
|
||||
|
||||
# Take the public fields and create a public key
|
||||
priv_key = rsa.key.PrivateKey.load_pkcs1(in_data, cli.inform)
|
||||
pub_key = rsa.key.PublicKey(priv_key.n, priv_key.e)
|
||||
|
||||
# Save to the output file
|
||||
out_data = pub_key.save_pkcs1(cli.outform)
|
||||
|
||||
if cli.outfilename:
|
||||
print(
|
||||
"Writing public key to %s in %s format" % (cli.outfilename, cli.outform),
|
||||
file=sys.stderr,
|
||||
)
|
||||
with open(cli.outfilename, "wb") as outfile:
|
||||
outfile.write(out_data)
|
||||
else:
|
||||
print("Writing public key to stdout in %s format" % cli.outform, file=sys.stderr)
|
||||
sys.stdout.write(out_data.decode("ascii"))
|
||||
|
|
|
@ -1,155 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''VARBLOCK file support
|
||||
|
||||
The VARBLOCK file format is as follows, where || denotes byte concatenation:
|
||||
|
||||
FILE := VERSION || BLOCK || BLOCK ...
|
||||
|
||||
BLOCK := LENGTH || DATA
|
||||
|
||||
LENGTH := varint-encoded length of the subsequent data. Varint comes from
|
||||
Google Protobuf, and encodes an integer into a variable number of bytes.
|
||||
Each byte uses the 7 lowest bits to encode the value. The highest bit set
|
||||
to 1 indicates the next byte is also part of the varint. The last byte will
|
||||
have this bit set to 0.
|
||||
|
||||
This file format is called the VARBLOCK format, in line with the varint format
|
||||
used to denote the block sizes.
|
||||
|
||||
'''
|
||||
|
||||
from rsa._compat import byte, b
|
||||
|
||||
|
||||
ZERO_BYTE = b('\x00')
|
||||
VARBLOCK_VERSION = 1
|
||||
|
||||
def read_varint(infile):
|
||||
'''Reads a varint from the file.
|
||||
|
||||
When the first byte to be read indicates EOF, (0, 0) is returned. When an
|
||||
EOF occurs when at least one byte has been read, an EOFError exception is
|
||||
raised.
|
||||
|
||||
@param infile: the file-like object to read from. It should have a read()
|
||||
method.
|
||||
@returns (varint, length), the read varint and the number of read bytes.
|
||||
'''
|
||||
|
||||
varint = 0
|
||||
read_bytes = 0
|
||||
|
||||
while True:
|
||||
char = infile.read(1)
|
||||
if len(char) == 0:
|
||||
if read_bytes == 0:
|
||||
return (0, 0)
|
||||
raise EOFError('EOF while reading varint, value is %i so far' %
|
||||
varint)
|
||||
|
||||
byte = ord(char)
|
||||
varint += (byte & 0x7F) << (7 * read_bytes)
|
||||
|
||||
read_bytes += 1
|
||||
|
||||
if not byte & 0x80:
|
||||
return (varint, read_bytes)
|
||||
|
||||
|
||||
def write_varint(outfile, value):
|
||||
'''Writes a varint to a file.
|
||||
|
||||
@param outfile: the file-like object to write to. It should have a write()
|
||||
method.
|
||||
@returns the number of written bytes.
|
||||
'''
|
||||
|
||||
# there is a big difference between 'write the value 0' (this case) and
|
||||
# 'there is nothing left to write' (the false-case of the while loop)
|
||||
|
||||
if value == 0:
|
||||
outfile.write(ZERO_BYTE)
|
||||
return 1
|
||||
|
||||
written_bytes = 0
|
||||
while value > 0:
|
||||
to_write = value & 0x7f
|
||||
value = value >> 7
|
||||
|
||||
if value > 0:
|
||||
to_write |= 0x80
|
||||
|
||||
outfile.write(byte(to_write))
|
||||
written_bytes += 1
|
||||
|
||||
return written_bytes
|
||||
|
||||
|
||||
def yield_varblocks(infile):
|
||||
'''Generator, yields each block in the input file.
|
||||
|
||||
@param infile: file to read, is expected to have the VARBLOCK format as
|
||||
described in the module's docstring.
|
||||
@yields the contents of each block.
|
||||
'''
|
||||
|
||||
# Check the version number
|
||||
first_char = infile.read(1)
|
||||
if len(first_char) == 0:
|
||||
raise EOFError('Unable to read VARBLOCK version number')
|
||||
|
||||
version = ord(first_char)
|
||||
if version != VARBLOCK_VERSION:
|
||||
raise ValueError('VARBLOCK version %i not supported' % version)
|
||||
|
||||
while True:
|
||||
(block_size, read_bytes) = read_varint(infile)
|
||||
|
||||
# EOF at block boundary, that's fine.
|
||||
if read_bytes == 0 and block_size == 0:
|
||||
break
|
||||
|
||||
block = infile.read(block_size)
|
||||
|
||||
read_size = len(block)
|
||||
if read_size != block_size:
|
||||
raise EOFError('Block size is %i, but could read only %i bytes' %
|
||||
(block_size, read_size))
|
||||
|
||||
yield block
|
||||
|
||||
|
||||
def yield_fixedblocks(infile, blocksize):
|
||||
'''Generator, yields each block of ``blocksize`` bytes in the input file.
|
||||
|
||||
:param infile: file to read and separate in blocks.
|
||||
:returns: a generator that yields the contents of each block
|
||||
'''
|
||||
|
||||
while True:
|
||||
block = infile.read(blocksize)
|
||||
|
||||
read_bytes = len(block)
|
||||
if read_bytes == 0:
|
||||
break
|
||||
|
||||
yield block
|
||||
|
||||
if read_bytes < blocksize:
|
||||
break
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest2 as unittest
|
||||
|
||||
current_path = os.path.abspath(os.path.dirname(__file__))
|
||||
tests_path = os.path.join(current_path, 'tests')
|
||||
sys.path[0:0] = [
|
||||
current_path,
|
||||
tests_path,
|
||||
]
|
||||
|
||||
all_tests = [f[:-3] for f in os.listdir(tests_path)
|
||||
if f.startswith('test_') and f.endswith(".py")]
|
||||
|
||||
def get_suite(tests):
|
||||
tests = sorted(tests)
|
||||
suite = unittest.TestSuite()
|
||||
loader = unittest.TestLoader()
|
||||
for test in tests:
|
||||
suite.addTest(loader.loadTestsFromName(test))
|
||||
return suite
|
||||
|
||||
if __name__ == '__main__':
|
||||
"""
|
||||
To run all tests:
|
||||
$ python run_tests.py
|
||||
To run a single test:
|
||||
$ python run_tests.py app
|
||||
To run a couple of tests:
|
||||
$ python run_tests.py app config sessions
|
||||
To run code coverage:
|
||||
$ coverage run run_tests.py
|
||||
$ coverage report -m
|
||||
"""
|
||||
tests = sys.argv[1:]
|
||||
if not tests:
|
||||
tests = all_tests
|
||||
tests = ['%s' % t for t in tests]
|
||||
suite = get_suite(tests)
|
||||
unittest.TextTestRunner(verbosity=1).run(suite)
|
|
@ -1,8 +0,0 @@
|
|||
[nosetests]
|
||||
verbosity = 2
|
||||
|
||||
[egg_info]
|
||||
tag_date = 0
|
||||
tag_build =
|
||||
tag_svn_revision = 0
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
import rsa
|
||||
|
||||
setup(name='rsa',
|
||||
version=rsa.__version__,
|
||||
description='Pure-Python RSA implementation',
|
||||
author='Sybren A. Stuvel',
|
||||
author_email='sybren@stuvel.eu',
|
||||
maintainer='Sybren A. Stuvel',
|
||||
maintainer_email='sybren@stuvel.eu',
|
||||
url='http://stuvel.eu/rsa',
|
||||
packages=['rsa'],
|
||||
license='ASL 2',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: Education',
|
||||
'Intended Audience :: Information Technology',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Security :: Cryptography',
|
||||
],
|
||||
install_requires=[
|
||||
'pyasn1 >= 0.1.3',
|
||||
],
|
||||
entry_points={ 'console_scripts': [
|
||||
'pyrsa-priv2pub = rsa.util:private_to_public',
|
||||
'pyrsa-keygen = rsa.cli:keygen',
|
||||
'pyrsa-encrypt = rsa.cli:encrypt',
|
||||
'pyrsa-decrypt = rsa.cli:decrypt',
|
||||
'pyrsa-sign = rsa.cli:sign',
|
||||
'pyrsa-verify = rsa.cli:verify',
|
||||
'pyrsa-encrypt-bigfile = rsa.cli:encrypt_bigfile',
|
||||
'pyrsa-decrypt-bigfile = rsa.cli:decrypt_bigfile',
|
||||
]},
|
||||
|
||||
)
|
Загрузка…
Ссылка в новой задаче