aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHaibo Huang <hhb@google.com>2020-07-29 02:03:57 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-07-29 02:03:57 +0000
commit933b43f37aa0ab970da052e42eff858016c6a5c6 (patch)
tree51130a47b9f85ff001627c9fdabf69dfc7293a1b
parenteac7df373b626ac825bc883603dbc1b7835a045c (diff)
parentb8eae8a9eef1815cf17064e9a66a108bd21d5f8c (diff)
downloadrsa-933b43f37aa0ab970da052e42eff858016c6a5c6.tar.gz
Upgrade python/rsa to version-4.6 am: 248aba47a6 am: fba0d0316f am: 24f0088831 am: b8eae8a9ee
Original change: https://android-review.googlesource.com/c/platform/external/python/rsa/+/1361102 Change-Id: Id67d2c3d00bcdc9a9862f7e8a887a19ad62b18d1
-rw-r--r--.codeclimate.yml10
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml26
-rw-r--r--CHANGELOG.md (renamed from CHANGELOG.txt)117
-rw-r--r--METADATA8
-rw-r--r--Pipfile17
-rw-r--r--Pipfile.lock513
-rw-r--r--README.md9
-rwxr-xr-xcreate_timing_table.py2
-rw-r--r--doc/compatibility.rst2
-rw-r--r--doc/conf.py4
-rw-r--r--doc/installation.rst25
-rw-r--r--rsa/__init__.py8
-rw-r--r--rsa/_compat.py118
-rw-r--r--rsa/asn1.py2
-rw-r--r--rsa/cli.py64
-rw-r--r--rsa/common.py23
-rw-r--r--rsa/core.py12
-rw-r--r--rsa/key.py120
-rw-r--r--rsa/machine_size.py74
-rw-r--r--rsa/parallel.py10
-rw-r--r--rsa/pem.py70
-rw-r--r--rsa/pkcs1.py64
-rw-r--r--rsa/pkcs1_v2.py5
-rw-r--r--rsa/prime.py15
-rw-r--r--rsa/randnum.py14
-rw-r--r--rsa/transform.py167
-rw-r--r--rsa/util.py6
-rw-r--r--setup.cfg14
-rwxr-xr-xsetup.py13
-rw-r--r--tests/test_cli.py46
-rw-r--r--tests/test_common.py2
-rw-r--r--tests/test_compat.py6
-rw-r--r--tests/test_integers.py2
-rw-r--r--tests/test_load_save_keys.py5
-rw-r--r--tests/test_mypy.py27
-rw-r--r--tests/test_pem.py19
-rw-r--r--tests/test_pkcs1.py63
-rw-r--r--tests/test_pkcs1_v2.py2
-rw-r--r--tests/test_prime.py3
-rw-r--r--tests/test_strings.py8
-rw-r--r--tests/test_transform.py25
-rw-r--r--tox.ini18
-rwxr-xr-xupdate_version.sh18
44 files changed, 890 insertions, 887 deletions
diff --git a/.codeclimate.yml b/.codeclimate.yml
index 907c00b..077382c 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -15,3 +15,13 @@ ratings:
- "**.py"
exclude_paths:
- tests/**/*
+checks:
+ argument-count:
+ config:
+ threshold: 5
+ file-lines:
+ config:
+ threshold: 1000
+ method-complexity:
+ config:
+ threshold: 17
diff --git a/.gitignore b/.gitignore
index 1f5a640..e31443a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@
.coverage
.coverage.*
.cache/
+.mypy_cache/
.pytest_cache/
__pycache__/
diff --git a/.travis.yml b/.travis.yml
index 9b4da02..0fed68b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,30 +1,32 @@
language: python
+dist: bionic # required for Python >= 3.7
cache: pip
# Environment changes have to be manually synced with 'tox.ini'.
# See: https://github.com/travis-ci/travis-ci/issues/3024
-# Python 3.7 is not yet supported by Travis CI.
-# See: https://github.com/travis-ci/travis-ci/issues/9815
-
python:
- - "2.7"
- - "3.4"
- - "3.5"
- "3.6"
- - "3.7-dev"
+ - "3.7"
+ - "3.8"
+
+matrix:
+ include:
+ - python: 3.5
+ dist: xenial # Bionic has no Python 3.5
+ script: pip install zipp
-# This is blocked by https://github.com/pypa/pipenv/issues/2449,
-# also see https://github.com/pypa/pipenv/projects/7
-# - "pypy"
- - "pypy3.5"
+ # Disabled, see https://github.com/sybrenstuvel/python-rsa/issues/131
+ #- python: pypy3.5
+ # dist: xenial # Bionic has no Python 3.5
install:
+ - pip install -U pip setuptools # https://github.com/pypa/virtualenv/issues/1630
- pip install pipenv
- pipenv install --dev
script:
- - pipenv run py.test
+ - pipenv run py.test tests/
after_success:
- pipenv run coveralls
diff --git a/CHANGELOG.txt b/CHANGELOG.md
index f8ed650..0fa3054 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.md
@@ -1,8 +1,66 @@
-Python-RSA changelog
-========================================
+# Python-RSA changelog
-Version 4.0 - released 2018-09-16
-----------------------------------------
+
+## Version 4.4 & 4.6 - released 2020-06-12
+
+Version 4.4 and 4.6 are almost a re-tagged release of version 4.2. It requires
+Python 3.5+. To avoid older Python installations from trying to upgrade to RSA
+4.4, this is now made explicit in the `python_requires` argument in `setup.py`.
+There was a mistake releasing 4.4 as "3.5+ only", which made it necessary to
+retag 4.4 as 4.6 as well.
+
+No functional changes compared to version 4.2.
+
+
+## Version 4.3 - released 2020-06-12
+
+Version 4.3 and 4.5 are almost a re-tagged release of version 4.0. It is the
+last to support Python 2.7. This is now made explicit in the `python_requires`
+argument in `setup.py`. Python 3.4 is not supported by this release. There was a
+mistake releasing 4.4 as "3.5+ only", which made it necessary to retag 4.3 as
+4.5 as well.
+
+Two security fixes have also been backported, so 4.3 = 4.0 + these two fixes.
+
+- Choose blinding factor relatively prime to N. Thanks Christian Heimes for pointing this out.
+- Reject cyphertexts (when decrypting) and signatures (when verifying) that have
+ been modified by prepending zero bytes. This resolves CVE-2020-13757. Thanks
+ Carnil for pointing this out.
+
+
+## Version 4.2 - released 2020-06-10
+
+- Rolled back the switch to Poetry, and reverted back to using Pipenv + setup.py
+ for dependency management. There apparently is an issue no-binary installs of
+ packages build with Poetry. This fixes
+ [#148](https://github.com/sybrenstuvel/python-rsa/issues/148)
+- Limited SHA3 support to those Python versions (3.6+) that support it natively.
+ The third-party library that adds support for this to Python 3.5 is a binary
+ package, and thus breaks the pure-Python nature of Python-RSA.
+ This should fix [#147](https://github.com/sybrenstuvel/python-rsa/issues/147).
+
+
+## Version 4.1 - released 2020-06-10
+
+- Added support for Python 3.8.
+- Dropped support for Python 2 and 3.4.
+- Added type annotations to the source code. This will make Python-RSA easier to use in
+ your IDE, and allows better type checking.
+- Added static type checking via [MyPy](http://mypy-lang.org/).
+- Fix [#129](https://github.com/sybrenstuvel/python-rsa/issues/129) Installing from source
+ gives UnicodeDecodeError.
+- Switched to using [Poetry](https://poetry.eustace.io/) for package
+ management.
+- Added support for SHA3 hashing: SHA3-256, SHA3-384, SHA3-512. This
+ is natively supported by Python 3.6+ and supported via a third-party
+ library on Python 3.5.
+- Choose blinding factor relatively prime to N. Thanks Christian Heimes for pointing this out.
+- Reject cyphertexts (when decrypting) and signatures (when verifying) that have
+ been modified by prepending zero bytes. This resolves CVE-2020-13757. Thanks
+ Adelapie for pointing this out.
+
+
+## Version 4.0 - released 2018-09-16
- Removed deprecated modules:
- rsa.varblock
@@ -24,21 +82,18 @@ Version 4.0 - released 2018-09-16
- Transitioned from `requirements.txt` to Pipenv for package management.
-Version 3.4.2 - released 2016-03-29
-----------------------------------------
+## Version 3.4.2 - released 2016-03-29
- Fixed dates in CHANGELOG.txt
-Version 3.4.1 - released 2016-03-26
-----------------------------------------
+## Version 3.4.1 - released 2016-03-26
- Included tests/private.pem in MANIFEST.in
- Included README.md and CHANGELOG.txt in MANIFEST.in
-Version 3.4 - released 2016-03-17
-----------------------------------------
+## Version 3.4 - released 2016-03-17
- Moved development to GitHub: https://github.com/sybrenstuvel/python-rsa
- Solved side-channel vulnerability by implementing blinding, fixes #19
@@ -59,8 +114,7 @@ Version 3.4 - released 2016-03-17
[4] http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
-Version 3.3 - released 2016-01-13
-----------------------------------------
+## Version 3.3 - released 2016-01-13
- Thanks to Filippo Valsorda: Fix BB'06 attack in verify() by
switching from parsing to comparison. See [1] for more information.
@@ -71,54 +125,46 @@ Version 3.3 - released 2016-01-13
[1] https://blog.filippo.io/bleichenbacher-06-signature-forgery-in-python-rsa/
-Version 3.2.3 - released 2015-11-05
-----------------------------------------
+## Version 3.2.3 - released 2015-11-05
- Added character encoding markers for Python 2.x
-Version 3.2.1 - released 2015-11-05
-----------------------------------------
+## Version 3.2.1 - released 2015-11-05
- Added per-file licenses
- Added support for wheel packages
- Made example code more consistent and up to date with Python 3.4
-Version 3.2 - released 2015-07-29
-----------------------------------------
+## Version 3.2 - released 2015-07-29
- Mentioned support for Python 3 in setup.py
-Version 3.1.4 - released 2014-02-22
-----------------------------------------
+## Version 3.1.4 - released 2014-02-22
- Fixed some bugs
-Version 3.1.3 - released 2014-02-02
-----------------------------------------
+## Version 3.1.3 - released 2014-02-02
- Dropped support for Python 2.5
-Version 3.1.2 - released 2013-09-15
-----------------------------------------
+## Version 3.1.2 - released 2013-09-15
- Added Python 3.3 to the test environment.
- Removed dependency on Distribute
- Added support for loading public keys from OpenSSL
-Version 3.1.1 - released 2012-06-18
-----------------------------------------
+## Version 3.1.1 - released 2012-06-18
- Fixed doctests for Python 2.7
- Removed obsolete unittest so all tests run fine on Python 3.2
-Version 3.1 - released 2012-06-17
-----------------------------------------
+## Version 3.1 - released 2012-06-17
- Big, big credits to Yesudeep Mangalapilly for all the changes listed
below!
@@ -131,34 +177,25 @@ Version 3.1 - released 2012-06-17
-Version 3.0.1 - released 2011-08-07
-----------------------------------------
+## Version 3.0.1 - released 2011-08-07
- Removed unused import of abc module
-Version 3.0 - released 2011-08-05
-----------------------------------------
+## Version 3.0 - released 2011-08-05
- Changed the meaning of the keysize to mean the size of ``n`` rather than
the size of both ``p`` and ``q``. This is the common interpretation of
RSA keysize. To get the old behaviour, double the keysize when generating a
new key.
-
- Added a lot of doctests
-
- Added random-padded encryption and decryption using PKCS#1 version 1.5
-
- Added hash-based signatures and verification using PKCS#1v1.5
-
- Modeling private and public key as real objects rather than dicts.
-
- Support for saving and loading keys as PEM and DER files.
-
- Ability to extract a public key from a private key (PEM+DER)
-Version 2.0
-----------------------------------------
+## Version 2.0
- Security improvements by Barry Mead.
diff --git a/METADATA b/METADATA
index 98a8942..5c228ef 100644
--- a/METADATA
+++ b/METADATA
@@ -9,11 +9,11 @@ third_party {
type: GIT
value: "https://github.com/sybrenstuvel/python-rsa/"
}
- version: "version-4.0"
+ version: "version-4.6"
license_type: NOTICE
last_upgrade_date {
- year: 2019
- month: 2
- day: 1
+ year: 2020
+ month: 7
+ day: 10
}
}
diff --git a/Pipfile b/Pipfile
index 89ec5fd..a6f846c 100644
--- a/Pipfile
+++ b/Pipfile
@@ -7,13 +7,14 @@ name = "pypi"
"pyasn1" = ">=0.1.3"
[dev-packages]
-tox = "*"
-mock = ">=2.0.0"
-Sphinx = "*"
-coveralls = "*"
-pytest = "*"
-pytest-cov = "*"
-pathlib2 = {version = "*", markers="python_version < '3.6'"}
+coveralls = "~=1.8, >=1.8"
+"Sphinx" = "~=2.1, >=2.1"
+"pathlib2" = {version = "~=2.3, >=2.3.4",markers = "python_version < '3.6'"}
+"pytest" = "~=5.0, >=5.0"
+"pytest-cov" = "~=2.7, >=2.7"
+"tox" = "~=3.13, >=3.13"
+"mypy" = "~=0.720, >=0.720"
+"flake8" = "~=3.7, >=3.7"
[requires]
-python_version = "3.6"
+python_version = "3.7"
diff --git a/Pipfile.lock b/Pipfile.lock
index 03fc240..702edae 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,11 +1,11 @@
{
"_meta": {
"hash": {
- "sha256": "a86e76a85c3a86f6a44f1b5f48205749c451c830746cbc535c66e72d8f5313cb"
+ "sha256": "4df253faa2a1f6d6665fddc4c13f5e278a4127c27d7b76e59607a8154f96b1ab"
},
"pipfile-spec": 6,
"requires": {
- "python_version": "3.6"
+ "python_version": "3.7"
},
"sources": [
{
@@ -18,49 +18,61 @@
"default": {
"pyasn1": {
"hashes": [
- "sha256:b9d3abc5031e61927c82d4d96c1cec1e55676c1a991623cfed28faea73cdd7ca",
- "sha256:f58f2a3d12fd754aa123e9fa74fb7345333000a035f3921dbdaa08597aa53137"
+ "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
+ "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
+ "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
+ "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
+ "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
+ "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
+ "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
+ "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
+ "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
+ "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
+ "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
+ "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
+ "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
],
"index": "pypi",
- "version": "==0.4.4"
+ "version": "==0.4.8"
}
},
"develop": {
"alabaster": {
"hashes": [
- "sha256:674bb3bab080f598371f4443c5008cbfeb1a5e622dd312395d2d82af2c54c456",
- "sha256:b63b1f4dc77c074d386752ec4a8a7517600f6c0db8cd42980cae17ab7b3275d7"
+ "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
+ "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"
],
- "version": "==0.7.11"
+ "version": "==0.7.12"
},
- "atomicwrites": {
+ "appdirs": {
"hashes": [
- "sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
- "sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"
+ "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
+ "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
],
- "markers": "python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.2.*'",
- "version": "==1.2.1"
+ "version": "==1.4.4"
},
"attrs": {
"hashes": [
- "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
- "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
+ "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
+ "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
],
- "version": "==18.2.0"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==19.3.0"
},
"babel": {
"hashes": [
- "sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669",
- "sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23"
+ "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38",
+ "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"
],
- "version": "==2.6.0"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.8.0"
},
"certifi": {
"hashes": [
- "sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638",
- "sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"
+ "sha256:5ad7e9a056d25ffa5082862e36f119f7f7cec6457fa07ee2f8c339814b80c9b1",
+ "sha256:9cd41137dc19af6a5e03b630eefe7d1f458d964d406342dd3edf625839b944cc"
],
- "version": "==2018.8.24"
+ "version": "==2020.4.5.2"
},
"chardet": {
"hashes": [
@@ -69,58 +81,56 @@
],
"version": "==3.0.4"
},
- "colorama": {
- "hashes": [
- "sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda",
- "sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"
- ],
- "markers": "sys_platform == 'win32'",
- "version": "==0.3.9"
- },
"coverage": {
"hashes": [
- "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba",
- "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed",
- "sha256:10a46017fef60e16694a30627319f38a2b9b52e90182dddb6e37dcdab0f4bf95",
- "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640",
- "sha256:23d341cdd4a0371820eb2b0bd6b88f5003a7438bbedb33688cd33b8eae59affd",
- "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162",
- "sha256:2a5b73210bad5279ddb558d9a2bfedc7f4bf6ad7f3c988641d83c40293deaec1",
- "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508",
- "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249",
- "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694",
- "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a",
- "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287",
- "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1",
- "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000",
- "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1",
- "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e",
- "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5",
- "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062",
- "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba",
- "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc",
- "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc",
- "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99",
- "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653",
- "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c",
- "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558",
- "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f",
- "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9",
- "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd",
- "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d",
- "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6",
- "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80"
- ],
- "markers": "python_version != '3.1.*' and python_version >= '2.6' and python_version != '3.2.*' and python_version != '3.0.*' and python_version < '4'",
- "version": "==4.5.1"
+ "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a",
+ "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355",
+ "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65",
+ "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7",
+ "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9",
+ "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1",
+ "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0",
+ "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55",
+ "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c",
+ "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6",
+ "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef",
+ "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019",
+ "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e",
+ "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0",
+ "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf",
+ "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24",
+ "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2",
+ "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c",
+ "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4",
+ "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0",
+ "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd",
+ "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04",
+ "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e",
+ "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730",
+ "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2",
+ "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768",
+ "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796",
+ "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7",
+ "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a",
+ "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489",
+ "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
+ "version": "==5.1"
},
"coveralls": {
"hashes": [
- "sha256:9dee67e78ec17b36c52b778247762851c8e19a893c9a14e921a2fc37f05fac22",
- "sha256:aec5a1f5e34224b9089664a1b62217732381c7de361b6ed1b3c394d7187b352a"
+ "sha256:4b6bfc2a2a77b890f556bc631e35ba1ac21193c356393b66c84465c06218e135",
+ "sha256:67188c7ec630c5f708c31552f2bcdac4580e172219897c4136504f14b823132f"
],
"index": "pypi",
- "version": "==1.5.0"
+ "version": "==1.11.1"
+ },
+ "distlib": {
+ "hashes": [
+ "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"
+ ],
+ "version": "==0.3.0"
},
"docopt": {
"hashes": [
@@ -130,190 +140,385 @@
},
"docutils": {
"hashes": [
- "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
- "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274",
- "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"
+ "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
+ "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
],
- "version": "==0.14"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==0.16"
+ },
+ "filelock": {
+ "hashes": [
+ "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59",
+ "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"
+ ],
+ "version": "==3.0.12"
+ },
+ "flake8": {
+ "hashes": [
+ "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c",
+ "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"
+ ],
+ "index": "pypi",
+ "version": "==3.8.3"
},
"idna": {
"hashes": [
- "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
- "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
+ "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
+ "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
],
- "version": "==2.7"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.9"
},
"imagesize": {
"hashes": [
- "sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8",
- "sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"
+ "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1",
+ "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"
],
- "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*'",
- "version": "==1.1.0"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==1.2.0"
+ },
+ "importlib-metadata": {
+ "hashes": [
+ "sha256:0505dd08068cfec00f53a74a0ad927676d7757da81b7436a6eefe4c7cf75c545",
+ "sha256:15ec6c0fd909e893e3a08b3a7c76ecb149122fb14b7efe1199ddd4c7c57ea958"
+ ],
+ "markers": "python_version < '3.8'",
+ "version": "==1.6.1"
},
"jinja2": {
"hashes": [
- "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
- "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
+ "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
+ "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
],
- "version": "==2.10"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==2.11.2"
},
"markupsafe": {
"hashes": [
- "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
- ],
- "version": "==1.0"
+ "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
+ "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
+ "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
+ "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
+ "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
+ "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
+ "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
+ "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
+ "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
+ "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
+ "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
+ "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
+ "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
+ "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
+ "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
+ "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
+ "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
+ "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
+ "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
+ "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
+ "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
+ "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
+ "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
+ "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
+ "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
+ "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
+ "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
+ "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
+ "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
+ "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
+ "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
+ "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
+ "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==1.1.1"
+ },
+ "mccabe": {
+ "hashes": [
+ "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
+ "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
+ ],
+ "version": "==0.6.1"
},
- "mock": {
+ "more-itertools": {
"hashes": [
- "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1",
- "sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"
+ "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be",
+ "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==8.3.0"
+ },
+ "mypy": {
+ "hashes": [
+ "sha256:00cb1964a7476e871d6108341ac9c1a857d6bd20bf5877f4773ac5e9d92cd3cd",
+ "sha256:127de5a9b817a03a98c5ae8a0c46a20dc44442af6dcfa2ae7f96cb519b312efa",
+ "sha256:1f3976a945ad7f0a0727aafdc5651c2d3278e3c88dee94e2bf75cd3386b7b2f4",
+ "sha256:2f8c098f12b402c19b735aec724cc9105cc1a9eea405d08814eb4b14a6fb1a41",
+ "sha256:4ef13b619a289aa025f2273e05e755f8049bb4eaba6d703a425de37d495d178d",
+ "sha256:5d142f219bf8c7894dfa79ebfb7d352c4c63a325e75f10dfb4c3db9417dcd135",
+ "sha256:62eb5dd4ea86bda8ce386f26684f7f26e4bfe6283c9f2b6ca6d17faf704dcfad",
+ "sha256:64c36eb0936d0bfb7d8da49f92c18e312ad2e3ed46e5548ae4ca997b0d33bd59",
+ "sha256:75eed74d2faf2759f79c5f56f17388defd2fc994222312ec54ee921e37b31ad4",
+ "sha256:974bebe3699b9b46278a7f076635d219183da26e1a675c1f8243a69221758273",
+ "sha256:a5e5bb12b7982b179af513dddb06fca12285f0316d74f3964078acbfcf4c68f2",
+ "sha256:d31291df31bafb997952dc0a17ebb2737f802c754aed31dd155a8bfe75112c57",
+ "sha256:d3b4941de44341227ece1caaf5b08b23e42ad4eeb8b603219afb11e9d4cfb437",
+ "sha256:eadb865126da4e3c4c95bdb47fe1bb087a3e3ea14d39a3b13224b8a4d9f9a102"
],
"index": "pypi",
- "version": "==2.0.0"
+ "version": "==0.780"
},
- "more-itertools": {
+ "mypy-extensions": {
"hashes": [
- "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
- "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
- "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
+ "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
+ "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
],
- "version": "==4.3.0"
+ "version": "==0.4.3"
},
"packaging": {
"hashes": [
- "sha256:e9215d2d2535d3ae866c3d6efc77d5b24a0192cce0ff20e42896cc0664f889c0",
- "sha256:f019b770dd64e585a99714f1fd5e01c7a8f11b45635aa953fd41c689a657375b"
+ "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
+ "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"
],
- "version": "==17.1"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==20.4"
},
- "pbr": {
+ "pathlib2": {
"hashes": [
- "sha256:1b8be50d938c9bb75d0eaf7eda111eec1bf6dc88a62a6412e33bf077457e0f45",
- "sha256:b486975c0cafb6beeb50ca0e17ba047647f229087bd74e37f4a7e2cac17d2caa"
+ "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db",
+ "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"
],
- "version": "==4.2.0"
+ "markers": "python_version < '3.6'",
+ "version": "==2.3.5"
},
"pluggy": {
"hashes": [
- "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1",
- "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"
+ "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
+ "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
],
- "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*'",
- "version": "==0.7.1"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==0.13.1"
},
"py": {
"hashes": [
- "sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1",
- "sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6"
+ "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa",
+ "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"
],
- "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*'",
- "version": "==1.6.0"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==1.8.1"
},
- "pygments": {
+ "pycodestyle": {
+ "hashes": [
+ "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367",
+ "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.6.0"
+ },
+ "pyflakes": {
"hashes": [
- "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d",
- "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"
+ "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92",
+ "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"
],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.2.0"
},
+ "pygments": {
+ "hashes": [
+ "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44",
+ "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==2.6.1"
+ },
"pyparsing": {
"hashes": [
- "sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04",
- "sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010"
+ "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
+ "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
],
- "version": "==2.2.0"
+ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.4.7"
},
"pytest": {
"hashes": [
- "sha256:453cbbbe5ce6db38717d282b758b917de84802af4288910c12442984bde7b823",
- "sha256:a8a07f84e680482eb51e244370aaf2caa6301ef265f37c2bdefb3dd3b663f99d"
+ "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1",
+ "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"
],
"index": "pypi",
- "version": "==3.8.0"
+ "version": "==5.4.3"
},
"pytest-cov": {
"hashes": [
- "sha256:513c425e931a0344944f84ea47f3956be0e416d95acbd897a44970c8d926d5d7",
- "sha256:e360f048b7dae3f2f2a9a4d067b2dd6b6a015d384d1577c994a43f3f7cbad762"
+ "sha256:b6a814b8ed6247bd81ff47f038511b57fe1ce7f4cc25b9106f1a4b106f1d9322",
+ "sha256:c87dfd8465d865655a8213859f1b4749b43448b5fae465cb981e16d52a811424"
],
"index": "pypi",
- "version": "==2.6.0"
+ "version": "==2.9.0"
},
"pytz": {
"hashes": [
- "sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053",
- "sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277"
+ "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
+ "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
],
- "version": "==2018.5"
+ "version": "==2020.1"
},
"requests": {
"hashes": [
- "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
- "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
+ "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
+ "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
],
- "version": "==2.19.1"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==2.23.0"
},
"six": {
"hashes": [
- "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
- "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
+ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
+ "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
- "version": "==1.11.0"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==1.15.0"
},
"snowballstemmer": {
"hashes": [
- "sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128",
- "sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89"
+ "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0",
+ "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"
],
- "version": "==1.2.1"
+ "version": "==2.0.0"
},
"sphinx": {
"hashes": [
- "sha256:95acd6648902333647a0e0564abdb28a74b0a76d2333148aa35e5ed1f56d3c4b",
- "sha256:c091dbdd5cc5aac6eb95d591a819fd18bccec90ffb048ec465b165a48b839b45"
+ "sha256:b4c750d546ab6d7e05bdff6ac24db8ae3e8b8253a3569b754e445110a0a12b66",
+ "sha256:fc312670b56cb54920d6cc2ced455a22a547910de10b3142276495ced49231cb"
],
"index": "pypi",
- "version": "==1.8.0"
+ "version": "==2.4.4"
+ },
+ "sphinxcontrib-applehelp": {
+ "hashes": [
+ "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a",
+ "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==1.0.2"
},
- "sphinxcontrib-websupport": {
+ "sphinxcontrib-devhelp": {
"hashes": [
- "sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd",
- "sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9"
+ "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e",
+ "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"
],
- "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*'",
- "version": "==1.1.0"
+ "markers": "python_version >= '3.5'",
+ "version": "==1.0.2"
+ },
+ "sphinxcontrib-htmlhelp": {
+ "hashes": [
+ "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f",
+ "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==1.0.3"
+ },
+ "sphinxcontrib-jsmath": {
+ "hashes": [
+ "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178",
+ "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==1.0.1"
+ },
+ "sphinxcontrib-qthelp": {
+ "hashes": [
+ "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72",
+ "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==1.0.3"
+ },
+ "sphinxcontrib-serializinghtml": {
+ "hashes": [
+ "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc",
+ "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==1.1.4"
},
"toml": {
"hashes": [
- "sha256:380178cde50a6a79f9d2cf6f42a62a5174febe5eea4126fe4038785f1d888d42",
- "sha256:a7901919d3e4f92ffba7ff40a9d697e35bbbc8a8049fe8da742f34c83606d957"
+ "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f",
+ "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"
],
- "version": "==0.9.6"
+ "version": "==0.10.1"
},
"tox": {
"hashes": [
- "sha256:433bb93c57edae263150767e672a0d468ab4fefcc1958eb4013e56a670bb851e",
- "sha256:bfb4e4efb7c61a54bc010a5c00fdbe0973bc4bdf04090bfcd3c93c901006177c"
+ "sha256:50a188b8e17580c1fb931f494a754e6507d4185f54fb18aca5ba3e12d2ffd55e",
+ "sha256:c696d36cd7c6a28ada2da780400e44851b20ee19ef08cfe73344a1dcebbbe9f3"
],
"index": "pypi",
- "version": "==3.3.0"
+ "version": "==3.15.2"
+ },
+ "typed-ast": {
+ "hashes": [
+ "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355",
+ "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919",
+ "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa",
+ "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652",
+ "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75",
+ "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01",
+ "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d",
+ "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1",
+ "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907",
+ "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c",
+ "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3",
+ "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b",
+ "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614",
+ "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb",
+ "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b",
+ "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41",
+ "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6",
+ "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34",
+ "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe",
+ "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
+ "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
+ ],
+ "version": "==1.4.1"
+ },
+ "typing-extensions": {
+ "hashes": [
+ "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5",
+ "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae",
+ "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392"
+ ],
+ "version": "==3.7.4.2"
},
"urllib3": {
"hashes": [
- "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
- "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
+ "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527",
+ "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"
],
- "markers": "python_version != '3.1.*' and python_version >= '2.6' and python_version != '3.3.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version < '4'",
- "version": "==1.23"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
+ "version": "==1.25.9"
},
"virtualenv": {
"hashes": [
- "sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669",
- "sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752"
+ "sha256:a116629d4e7f4d03433b8afa27f43deba09d48bc48f5ecefa4f015a178efb6cf",
+ "sha256:a730548b27366c5e6cbdf6f97406d861cccece2e22275e8e1a757aeff5e00c70"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==20.0.21"
+ },
+ "wcwidth": {
+ "hashes": [
+ "sha256:79375666b9954d4a1a10739315816324c3e73110af9d0e102d906fdb0aec009f",
+ "sha256:8c6b5b6ee1360b842645f336d9e5d68c55817c26d3050f46b235ef2bc650e48f"
+ ],
+ "version": "==0.2.4"
+ },
+ "zipp": {
+ "hashes": [
+ "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b",
+ "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"
],
- "markers": "python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.7'",
- "version": "==16.0.0"
+ "markers": "python_version >= '3.6'",
+ "version": "==3.1.0"
}
}
}
diff --git a/README.md b/README.md
index c5e0f2d..ea24210 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ 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://img.shields.io/codeclimate/github/sybrenstuvel/python-rsa.svg)](https://codeclimate.com/github/sybrenstuvel/python-rsa)
+[![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
@@ -12,7 +12,7 @@ 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).
+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:
@@ -23,6 +23,11 @@ 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)
+Major changes in 4.1
+--------------------
+
+Version 4.0 was the last version to support Python 2 and 3.4. Version 4.1 is compatible with Python 3.5+ only.
+
Major changes in 4.0
--------------------
diff --git a/create_timing_table.py b/create_timing_table.py
index 6163916..498e6a0 100755
--- a/create_timing_table.py
+++ b/create_timing_table.py
@@ -1,6 +1,4 @@
#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/doc/compatibility.rst b/doc/compatibility.rst
index be4d295..1429553 100644
--- a/doc/compatibility.rst
+++ b/doc/compatibility.rst
@@ -16,7 +16,7 @@ Encryption:
Signatures:
PKCS#1 v1.5 using the following hash methods:
- MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512
+ MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, SHA3-256, SHA3-384, SHA3-512
Private keys:
PKCS#1 v1.5 in PEM and DER format, ASN.1 type RSAPrivateKey
diff --git a/doc/conf.py b/doc/conf.py
index 3331a86..60201b1 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Python-RSA documentation build configuration file, created by
# sphinx-quickstart on Sat Jul 30 23:11:07 2011.
#
@@ -46,7 +44,7 @@ master_doc = 'index'
# General information about the project.
project = u'Python-RSA'
-copyright = u'2011-2018, Sybren A. Stüvel'
+copyright = u'2011-2019, Sybren A. Stüvel'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
diff --git a/doc/installation.rst b/doc/installation.rst
index 32dc257..3ab3ab1 100644
--- a/doc/installation.rst
+++ b/doc/installation.rst
@@ -1,13 +1,13 @@
Installation
============
-Installation can be done in various ways. The simplest form uses pip
-or easy_install. Either one will work::
+Installation can be done in various ways. The simplest form uses pip::
pip install rsa
Depending on your system you may need to use ``sudo pip`` if you want to install
-the library system-wide.
+the library system-wide, or use ``pip install --user rsa`` to install the
+library in your home directory.
Installation from source is also quite easy. Download the source and
then type::
@@ -25,6 +25,9 @@ GitHub. It also hosts the `issue tracker`_.
Dependencies
------------
+Python-RSA is compatible with Python versions 3.5 and newer. The last
+version with Python 2.7 support was Python-RSA 4.0.
+
Python-RSA has very few dependencies. As a matter of fact, to use it
you only need Python itself. Loading and saving keys does require an
extra module, though: pyasn1. If you used pip or easy_install like
@@ -34,17 +37,15 @@ described above, you should be ready to go.
Development dependencies
------------------------
-In order to start developing on Python-RSA you need a bit more. Use
-pip to install the development requirements in a virtual environment::
-
- virtualenv -p /path/to/your-python-version python-rsa-venv
- . python-rsa-venv/bin/activate
- pip install -r python-rsa/requirements.txt
+In order to start developing on Python-RSA, use Git_ to get a copy of
+the source::
+ git clone https://github.com/sybrenstuvel/python-rsa.git
-Once these are installed, use Git_ to get a copy of the source::
+Use Poetry_ to install the development requirements in a virtual environment::
- git clone https://github.com/sybrenstuvel/python-rsa.git
- python setup.py develop
+ cd python-rsa
+ poetry install
.. _Git: https://git-scm.com/
+.. _Poetry: https://poetry.eustace.io/
diff --git a/rsa/__init__.py b/rsa/__init__.py
index 9b05c6c..1567dc1 100644
--- a/rsa/__init__.py
+++ b/rsa/__init__.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,8 +26,8 @@ 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__ = "2018-09-16"
-__version__ = '4.0'
+__date__ = '2020-06-12'
+__version__ = '4.6'
# Do doctest if we're run directly
if __name__ == "__main__":
@@ -39,4 +37,4 @@ if __name__ == "__main__":
__all__ = ["newkeys", "encrypt", "decrypt", "sign", "verify", 'PublicKey',
'PrivateKey', 'DecryptionError', 'VerificationError',
- 'compute_hash', 'sign_hash']
+ 'find_signature_hash', 'compute_hash', 'sign_hash']
diff --git a/rsa/_compat.py b/rsa/_compat.py
index 71197a5..050e81b 100644
--- a/rsa/_compat.py
+++ b/rsa/_compat.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,84 +14,14 @@
"""Python compatibility wrappers."""
-from __future__ import absolute_import
-
-import itertools
-import sys
from struct import pack
-MAX_INT = sys.maxsize
-MAX_INT64 = (1 << 63) - 1
-MAX_INT32 = (1 << 31) - 1
-MAX_INT16 = (1 << 15) - 1
-
-PY2 = sys.version_info[0] == 2
-
-# 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
-
-if PY2:
- integer_types = (int, long)
- range = xrange
- zip = itertools.izip
-else:
- integer_types = (int, )
- range = range
- zip = zip
-
-def write_to_stdout(data):
- """Writes bytes to stdout
-
- :type data: bytes
- """
- if PY2:
- sys.stdout.write(data)
- else:
- # On Py3 we must use the buffer interface to write bytes.
- sys.stdout.buffer.write(data)
-
-
-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)
-
-
-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):
+def byte(num: int) -> bytes:
"""
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:
@@ -102,7 +30,7 @@ def byte(num):
return pack("B", num)
-def xor_bytes(b1, b2):
+def xor_bytes(b1: bytes, b2: bytes) -> bytes:
"""
Returns the bitwise XOR result between two bytes objects, b1 ^ b2.
@@ -117,46 +45,4 @@ def xor_bytes(b1, b2):
:returns:
Bytes object, result of XOR operation.
"""
- if PY2:
- return ''.join(byte(ord(x) ^ ord(y)) for x, y in zip(b1, b2))
-
return bytes(x ^ y for x, y in zip(b1, b2))
-
-
-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"
diff --git a/rsa/asn1.py b/rsa/asn1.py
index b724b8f..32b1eb4 100644
--- a/rsa/asn1.py
+++ b/rsa/asn1.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/rsa/cli.py b/rsa/cli.py
index 6450af4..3166150 100644
--- a/rsa/cli.py
+++ b/rsa/cli.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,24 +17,25 @@
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 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():
+def keygen() -> None:
"""Key generator."""
# Parse the CLI options
- parser = OptionParser(usage='usage: %prog [options] keysize',
- description='Generates a new RSA keypair of "keysize" bits.')
+ parser = optparse.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 '
@@ -83,17 +82,15 @@ def keygen():
outfile.write(data)
else:
print('Writing private key to stdout', file=sys.stderr)
- rsa._compat.write_to_stdout(data)
+ sys.stdout.buffer.write(data)
-class CryptoOperation(object):
+class CryptoOperation(metaclass=abc.ABCMeta):
"""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
+ description = ''
operation = 'decrypt'
operation_past = 'decrypted'
operation_progressive = 'decrypting'
@@ -104,15 +101,16 @@ class CryptoOperation(object):
expected_cli_args = 1
has_output = True
- key_class = rsa.PublicKey
+ key_class = rsa.PublicKey # type: typing.Type[rsa.key.AbstractKey]
- def __init__(self):
+ 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, key, cli_args):
+ def perform_operation(self, indata: bytes, key: rsa.key.AbstractKey,
+ cli_args: Indexable) -> typing.Any:
"""Performs the program's operation.
Implement in a subclass.
@@ -120,7 +118,7 @@ class CryptoOperation(object):
:returns: the data to write to the output.
"""
- def __call__(self):
+ def __call__(self) -> None:
"""Runs the program."""
(cli, cli_args) = self.parse_cli()
@@ -135,13 +133,13 @@ class CryptoOperation(object):
if self.has_output:
self.write_outfile(outdata, cli.output)
- def parse_cli(self):
+ def parse_cli(self) -> typing.Tuple[optparse.Values, typing.List[str]]:
"""Parse the CLI options
:returns: (cli_opts, cli_args)
"""
- parser = OptionParser(usage=self.usage, description=self.description)
+ parser = optparse.OptionParser(usage=self.usage, description=self.description)
parser.add_option('-i', '--input', type='string', help=self.input_help)
@@ -160,7 +158,7 @@ class CryptoOperation(object):
return cli, cli_args
- def read_key(self, filename, keyform):
+ 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)
@@ -169,7 +167,7 @@ class CryptoOperation(object):
return self.key_class.load_pkcs1(keydata, keyform)
- def read_infile(self, inname):
+ def read_infile(self, inname: str) -> bytes:
"""Read the input file"""
if inname:
@@ -178,9 +176,9 @@ class CryptoOperation(object):
return infile.read()
print('Reading input from stdin', file=sys.stderr)
- return sys.stdin.read()
+ return sys.stdin.buffer.read()
- def write_outfile(self, outdata, outname):
+ def write_outfile(self, outdata: bytes, outname: str) -> None:
"""Write the output file"""
if outname:
@@ -189,7 +187,7 @@ class CryptoOperation(object):
outfile.write(outdata)
else:
print('Writing output to stdout', file=sys.stderr)
- rsa._compat.write_to_stdout(outdata)
+ sys.stdout.buffer.write(outdata)
class EncryptOperation(CryptoOperation):
@@ -202,9 +200,10 @@ class EncryptOperation(CryptoOperation):
operation_past = 'encrypted'
operation_progressive = 'encrypting'
- def perform_operation(self, indata, pub_key, cli_args=None):
+ 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)
@@ -219,9 +218,10 @@ class DecryptOperation(CryptoOperation):
operation_progressive = 'decrypting'
key_class = rsa.PrivateKey
- def perform_operation(self, indata, priv_key, cli_args=None):
+ 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)
@@ -241,8 +241,10 @@ class SignOperation(CryptoOperation):
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):
+ 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:
@@ -266,8 +268,10 @@ class VerifyOperation(CryptoOperation):
expected_cli_args = 2
has_output = False
- def perform_operation(self, indata, pub_key, cli_args):
+ 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]
diff --git a/rsa/common.py b/rsa/common.py
index f7aa2d1..e7df21d 100644
--- a/rsa/common.py
+++ b/rsa/common.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,21 +12,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from rsa._compat import zip
-
"""Common functionality shared by several modules."""
+import typing
+
class NotRelativePrimeError(ValueError):
- def __init__(self, a, b, d, msg=None):
- super(NotRelativePrimeError, self).__init__(
- msg or "%d and %d are not relatively prime, divider=%i" % (a, b, d))
+ 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):
+def bit_size(num: int) -> int:
"""
Number of bits needed to represent a integer excluding any prefix
0 bits.
@@ -56,7 +53,7 @@ def bit_size(num):
raise TypeError('bit_size(num) only supports integers, not %r' % type(num))
-def byte_size(number):
+def byte_size(number: int) -> int:
"""
Returns the number of bytes required to hold a specific long number.
@@ -81,7 +78,7 @@ def byte_size(number):
return ceil_div(bit_size(number), 8)
-def ceil_div(num, div):
+def ceil_div(num: int, div: int) -> int:
"""
Returns the ceiling function of a division between `num` and `div`.
@@ -105,7 +102,7 @@ def ceil_div(num, div):
return quanta
-def extended_gcd(a, b):
+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
@@ -130,7 +127,7 @@ def extended_gcd(a, b):
return a, lx, ly # Return only positive values
-def inverse(x, n):
+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)
@@ -147,7 +144,7 @@ def inverse(x, n):
return inv
-def crt(a_values, modulo_values):
+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.
diff --git a/rsa/core.py b/rsa/core.py
index b3114d9..23032e3 100644
--- a/rsa/core.py
+++ b/rsa/core.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,17 +18,15 @@ 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):
+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, ekey, n):
+def encrypt_int(message: int, ekey: int, n: int) -> int:
"""Encrypts a message using encryption key 'ekey', working modulo n"""
assert_int(message, 'message')
@@ -46,7 +42,7 @@ def encrypt_int(message, ekey, n):
return pow(message, ekey, n)
-def decrypt_int(cyphertext, dkey, 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')
diff --git a/rsa/key.py b/rsa/key.py
index 1004412..b1e2030 100644
--- a/rsa/key.py
+++ b/rsa/key.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -34,9 +32,9 @@ of pyasn1.
"""
import logging
+import typing
import warnings
-from rsa._compat import range
import rsa.prime
import rsa.pem
import rsa.common
@@ -48,17 +46,17 @@ log = logging.getLogger(__name__)
DEFAULT_EXPONENT = 65537
-class AbstractKey(object):
+class AbstractKey:
"""Abstract superclass for private and public keys."""
__slots__ = ('n', 'e')
- def __init__(self, n, e):
+ def __init__(self, n: int, e: int) -> None:
self.n = n
self.e = e
@classmethod
- def _load_pkcs1_pem(cls, keyfile):
+ def _load_pkcs1_pem(cls, keyfile: bytes) -> 'AbstractKey':
"""Loads a key in PKCS#1 PEM format, implement in a subclass.
:param keyfile: contents of a PEM-encoded file that contains
@@ -70,7 +68,7 @@ class AbstractKey(object):
"""
@classmethod
- def _load_pkcs1_der(cls, keyfile):
+ def _load_pkcs1_der(cls, keyfile: bytes) -> 'AbstractKey':
"""Loads a key in PKCS#1 PEM format, implement in a subclass.
:param keyfile: contents of a DER-encoded file that contains
@@ -81,14 +79,14 @@ class AbstractKey(object):
:rtype: AbstractKey
"""
- def _save_pkcs1_pem(self):
+ def _save_pkcs1_pem(self) -> bytes:
"""Saves the key in PKCS#1 PEM format, implement in a subclass.
:returns: the PEM-encoded key.
:rtype: bytes
"""
- def _save_pkcs1_der(self):
+ def _save_pkcs1_der(self) -> bytes:
"""Saves the key in PKCS#1 DER format, implement in a subclass.
:returns: the DER-encoded key.
@@ -96,7 +94,7 @@ class AbstractKey(object):
"""
@classmethod
- def load_pkcs1(cls, keyfile, format='PEM'):
+ def load_pkcs1(cls, keyfile: bytes, format: str = 'PEM') -> 'AbstractKey':
"""Loads a key in PKCS#1 DER or PEM format.
:param keyfile: contents of a DER- or PEM-encoded file that contains
@@ -118,7 +116,8 @@ class AbstractKey(object):
return method(keyfile)
@staticmethod
- def _assert_format_exists(file_format, methods):
+ def _assert_format_exists(file_format: str, methods: typing.Mapping[str, typing.Callable]) \
+ -> typing.Callable:
"""Checks whether the given file format exists in 'methods'.
"""
@@ -129,7 +128,7 @@ class AbstractKey(object):
raise ValueError('Unsupported format: %r, try one of %s' % (file_format,
formats))
- def save_pkcs1(self, format='PEM'):
+ def save_pkcs1(self, format: str = 'PEM') -> bytes:
"""Saves the key in PKCS#1 DER or PEM format.
:param format: the format to save; 'PEM' or 'DER'
@@ -146,7 +145,7 @@ class AbstractKey(object):
method = self._assert_format_exists(format, methods)
return method()
- def blind(self, message, r):
+ def blind(self, message: int, r: int) -> int:
"""Performs blinding on the message using random number 'r'.
:param message: the message, as integer, to blind.
@@ -163,7 +162,7 @@ class AbstractKey(object):
return (message * pow(r, self.e, self.n)) % self.n
- def unblind(self, blinded, r):
+ def unblind(self, blinded: int, r: int) -> int:
"""Performs blinding on the message using random number 'r'.
:param blinded: the blinded message, as integer, to unblind.
@@ -204,21 +203,21 @@ class PublicKey(AbstractKey):
__slots__ = ('n', 'e')
- def __getitem__(self, key):
+ def __getitem__(self, key: str) -> int:
return getattr(self, key)
- def __repr__(self):
+ def __repr__(self) -> str:
return 'PublicKey(%i, %i)' % (self.n, self.e)
- def __getstate__(self):
+ def __getstate__(self) -> typing.Tuple[int, int]:
"""Returns the key as tuple for pickling."""
return self.n, self.e
- def __setstate__(self, state):
+ def __setstate__(self, state: typing.Tuple[int, int]) -> None:
"""Sets the key from tuple."""
self.n, self.e = state
- def __eq__(self, other):
+ def __eq__(self, other: typing.Any) -> bool:
if other is None:
return False
@@ -227,14 +226,14 @@ class PublicKey(AbstractKey):
return self.n == other.n and self.e == other.e
- def __ne__(self, other):
+ def __ne__(self, other: typing.Any) -> bool:
return not (self == other)
- def __hash__(self):
+ def __hash__(self) -> int:
return hash((self.n, self.e))
@classmethod
- def _load_pkcs1_der(cls, keyfile):
+ def _load_pkcs1_der(cls, keyfile: bytes) -> 'PublicKey':
"""Loads a key in PKCS#1 DER format.
:param keyfile: contents of a DER-encoded file that contains the public
@@ -260,7 +259,7 @@ class PublicKey(AbstractKey):
(priv, _) = decoder.decode(keyfile, asn1Spec=AsnPubKey())
return cls(n=int(priv['modulus']), e=int(priv['publicExponent']))
- def _save_pkcs1_der(self):
+ def _save_pkcs1_der(self) -> bytes:
"""Saves the public key in PKCS#1 DER format.
:returns: the DER-encoded public key.
@@ -278,7 +277,7 @@ class PublicKey(AbstractKey):
return encoder.encode(asn_key)
@classmethod
- def _load_pkcs1_pem(cls, keyfile):
+ def _load_pkcs1_pem(cls, keyfile: bytes) -> 'PublicKey':
"""Loads a PKCS#1 PEM-encoded public key file.
The contents of the file before the "-----BEGIN RSA PUBLIC KEY-----" and
@@ -292,7 +291,7 @@ class PublicKey(AbstractKey):
der = rsa.pem.load_pem(keyfile, 'RSA PUBLIC KEY')
return cls._load_pkcs1_der(der)
- def _save_pkcs1_pem(self):
+ def _save_pkcs1_pem(self) -> bytes:
"""Saves a PKCS#1 PEM-encoded public key file.
:return: contents of a PEM-encoded file that contains the public key.
@@ -303,7 +302,7 @@ class PublicKey(AbstractKey):
return rsa.pem.save_pem(der, 'RSA PUBLIC KEY')
@classmethod
- def load_pkcs1_openssl_pem(cls, keyfile):
+ def load_pkcs1_openssl_pem(cls, keyfile: bytes) -> 'PublicKey':
"""Loads a PKCS#1.5 PEM-encoded public key file from OpenSSL.
These files can be recognised in that they start with BEGIN PUBLIC KEY
@@ -322,14 +321,12 @@ class PublicKey(AbstractKey):
return cls.load_pkcs1_openssl_der(der)
@classmethod
- def load_pkcs1_openssl_der(cls, keyfile):
+ def load_pkcs1_openssl_der(cls, keyfile: bytes) -> 'PublicKey':
"""Loads a PKCS#1 DER-encoded public key file from OpenSSL.
:param keyfile: contents of a DER-encoded file that contains the public
key, from OpenSSL.
:return: a PublicKey object
- :rtype: bytes
-
"""
from rsa.asn1 import OpenSSLPubKey
@@ -370,7 +367,7 @@ class PrivateKey(AbstractKey):
__slots__ = ('n', 'e', 'd', 'p', 'q', 'exp1', 'exp2', 'coef')
- def __init__(self, n, e, d, p, q):
+ def __init__(self, n: int, e: int, d: int, p: int, q: int) -> None:
AbstractKey.__init__(self, n, e)
self.d = d
self.p = p
@@ -381,21 +378,21 @@ class PrivateKey(AbstractKey):
self.exp2 = int(d % (q - 1))
self.coef = rsa.common.inverse(q, p)
- def __getitem__(self, key):
+ def __getitem__(self, key: str) -> int:
return getattr(self, key)
- def __repr__(self):
- return 'PrivateKey(%(n)i, %(e)i, %(d)i, %(p)i, %(q)i)' % self
+ def __repr__(self) -> str:
+ return 'PrivateKey(%i, %i, %i, %i, %i)' % (self.n, self.e, self.d, self.p, self.q)
- def __getstate__(self):
+ def __getstate__(self) -> typing.Tuple[int, int, int, int, int, int, int, int]:
"""Returns the key as tuple for pickling."""
return self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef
- def __setstate__(self, state):
+ def __setstate__(self, state: typing.Tuple[int, int, int, int, int, int, int, int]) -> None:
"""Sets the key from tuple."""
self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef = state
- def __eq__(self, other):
+ def __eq__(self, other: typing.Any) -> bool:
if other is None:
return False
@@ -411,13 +408,20 @@ class PrivateKey(AbstractKey):
self.exp2 == other.exp2 and
self.coef == other.coef)
- def __ne__(self, other):
+ def __ne__(self, other: typing.Any) -> bool:
return not (self == other)
- def __hash__(self):
+ def __hash__(self) -> int:
return hash((self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef))
- def blinded_decrypt(self, encrypted):
+ def _get_blinding_factor(self) -> int:
+ for _ in range(1000):
+ blind_r = rsa.randnum.randint(self.n - 1)
+ if rsa.prime.are_relatively_prime(self.n, blind_r):
+ return blind_r
+ raise RuntimeError('unable to find blinding factor')
+
+ def blinded_decrypt(self, encrypted: int) -> int:
"""Decrypts the message using blinding to prevent side-channel attacks.
:param encrypted: the encrypted message
@@ -427,13 +431,13 @@ class PrivateKey(AbstractKey):
:rtype: int
"""
- blind_r = rsa.randnum.randint(self.n - 1)
+ blind_r = self._get_blinding_factor()
blinded = self.blind(encrypted, blind_r) # blind before decrypting
decrypted = rsa.core.decrypt_int(blinded, self.d, self.n)
return self.unblind(decrypted, blind_r)
- def blinded_encrypt(self, message):
+ def blinded_encrypt(self, message: int) -> int:
"""Encrypts the message using blinding to prevent side-channel attacks.
:param message: the message to encrypt
@@ -443,13 +447,13 @@ class PrivateKey(AbstractKey):
:rtype: int
"""
- blind_r = rsa.randnum.randint(self.n - 1)
+ blind_r = self._get_blinding_factor()
blinded = self.blind(message, blind_r) # blind before encrypting
encrypted = rsa.core.encrypt_int(blinded, self.d, self.n)
return self.unblind(encrypted, blind_r)
@classmethod
- def _load_pkcs1_der(cls, keyfile):
+ def _load_pkcs1_der(cls, keyfile: bytes) -> 'PrivateKey':
"""Loads a key in PKCS#1 DER format.
:param keyfile: contents of a DER-encoded file that contains the private
@@ -506,7 +510,7 @@ class PrivateKey(AbstractKey):
return key
- def _save_pkcs1_der(self):
+ def _save_pkcs1_der(self) -> bytes:
"""Saves the private key in PKCS#1 DER format.
:returns: the DER-encoded private key.
@@ -544,7 +548,7 @@ class PrivateKey(AbstractKey):
return encoder.encode(asn_key)
@classmethod
- def _load_pkcs1_pem(cls, keyfile):
+ def _load_pkcs1_pem(cls, keyfile: bytes) -> 'PrivateKey':
"""Loads a PKCS#1 PEM-encoded private key file.
The contents of the file before the "-----BEGIN RSA PRIVATE KEY-----" and
@@ -559,7 +563,7 @@ class PrivateKey(AbstractKey):
der = rsa.pem.load_pem(keyfile, b'RSA PRIVATE KEY')
return cls._load_pkcs1_der(der)
- def _save_pkcs1_pem(self):
+ def _save_pkcs1_pem(self) -> bytes:
"""Saves a PKCS#1 PEM-encoded private key file.
:return: contents of a PEM-encoded file that contains the private key.
@@ -570,7 +574,9 @@ class PrivateKey(AbstractKey):
return rsa.pem.save_pem(der, b'RSA PRIVATE KEY')
-def find_p_q(nbits, getprime_func=rsa.prime.getprime, accurate=True):
+def find_p_q(nbits: int,
+ getprime_func: typing.Callable[[int], int] = rsa.prime.getprime,
+ accurate: bool = True) -> typing.Tuple[int, int]:
"""Returns a tuple of two different primes of nbits bits each.
The resulting p * q has exacty 2 * nbits bits, and the returned p and q
@@ -615,7 +621,7 @@ def find_p_q(nbits, getprime_func=rsa.prime.getprime, accurate=True):
log.debug('find_p_q(%i): Finding q', nbits)
q = getprime_func(qbits)
- def is_acceptable(p, q):
+ def is_acceptable(p: int, q: int) -> bool:
"""Returns True iff p and q are acceptable:
- p and q differ
@@ -648,7 +654,7 @@ def find_p_q(nbits, getprime_func=rsa.prime.getprime, accurate=True):
return max(p, q), min(p, q)
-def calculate_keys_custom_exponent(p, q, exponent):
+def calculate_keys_custom_exponent(p: int, q: int, exponent: int) -> typing.Tuple[int, int]:
"""Calculates an encryption and a decryption key given p, q and an exponent,
and returns them as a tuple (e, d)
@@ -678,7 +684,7 @@ def calculate_keys_custom_exponent(p, q, exponent):
return exponent, d
-def calculate_keys(p, q):
+def calculate_keys(p: int, q: int) -> typing.Tuple[int, int]:
"""Calculates an encryption and a decryption key given p and q, and
returns them as a tuple (e, d)
@@ -691,7 +697,10 @@ def calculate_keys(p, q):
return calculate_keys_custom_exponent(p, q, DEFAULT_EXPONENT)
-def gen_keys(nbits, getprime_func, accurate=True, exponent=DEFAULT_EXPONENT):
+def gen_keys(nbits: int,
+ getprime_func: typing.Callable[[int], int],
+ accurate: bool = True,
+ exponent: int = DEFAULT_EXPONENT) -> typing.Tuple[int, int, int, int]:
"""Generate RSA keys of nbits bits. Returns (p, q, e, d).
Note: this can take a long time, depending on the key size.
@@ -719,7 +728,10 @@ def gen_keys(nbits, getprime_func, accurate=True, exponent=DEFAULT_EXPONENT):
return p, q, e, d
-def newkeys(nbits, accurate=True, poolsize=1, exponent=DEFAULT_EXPONENT):
+def newkeys(nbits: int,
+ accurate: bool = True,
+ poolsize: int = 1,
+ exponent: int = DEFAULT_EXPONENT) -> typing.Tuple[PublicKey, PrivateKey]:
"""Generates public and private keys, and returns them as (pub, priv).
The public key is also known as the 'encryption key', and is a
@@ -754,9 +766,9 @@ def newkeys(nbits, accurate=True, poolsize=1, exponent=DEFAULT_EXPONENT):
# Determine which getprime function to use
if poolsize > 1:
from rsa import parallel
- import functools
- getprime_func = functools.partial(parallel.getprime, poolsize=poolsize)
+ def getprime_func(nbits: int) -> int:
+ return parallel.getprime(nbits, poolsize=poolsize)
else:
getprime_func = rsa.prime.getprime
diff --git a/rsa/machine_size.py b/rsa/machine_size.py
deleted file mode 100644
index 2a871b8..0000000
--- a/rsa/machine_size.py
+++ /dev/null
@@ -1,74 +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
-#
-# 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.
-
-"""Detection of 32-bit and 64-bit machines and byte alignment."""
-
-import sys
-
-MAX_INT = sys.maxsize
-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
-
-
-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"
diff --git a/rsa/parallel.py b/rsa/parallel.py
index a3fe312..f9afedb 100644
--- a/rsa/parallel.py
+++ b/rsa/parallel.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,16 +22,14 @@ Introduced in Python-RSA 3.1.
"""
-from __future__ import print_function
-
import multiprocessing as mp
+from multiprocessing.connection import Connection
-from rsa._compat import range
import rsa.prime
import rsa.randnum
-def _find_prime(nbits, pipe):
+def _find_prime(nbits: int, pipe: Connection) -> None:
while True:
integer = rsa.randnum.read_random_odd_int(nbits)
@@ -43,7 +39,7 @@ def _find_prime(nbits, pipe):
return
-def getprime(nbits, poolsize):
+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.
diff --git a/rsa/pem.py b/rsa/pem.py
index 2ddfae8..1ffb446 100644
--- a/rsa/pem.py
+++ b/rsa/pem.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,45 +15,29 @@
"""Functions that load and write PEM-encoded files."""
import base64
+import typing
-from rsa._compat import is_bytes, range
+# Should either be ASCII strings or bytes.
+FlexiText = typing.Union[str, bytes]
-def _markers(pem_marker):
+def _markers(pem_marker: FlexiText) -> typing.Tuple[bytes, bytes]:
"""
Returns the start and end PEM markers, as bytes.
"""
- if not is_bytes(pem_marker):
+ if not isinstance(pem_marker, bytes):
pem_marker = pem_marker.encode('ascii')
return (b'-----BEGIN ' + pem_marker + b'-----',
b'-----END ' + pem_marker + b'-----')
-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.
-
- """
-
- # We want bytes, not text. If it's text, it can be converted to ASCII bytes.
- if not is_bytes(contents):
- contents = contents.encode('ascii')
-
- (pem_start, pem_end) = _markers(pem_marker)
+def _pem_lines(contents: bytes, pem_start: bytes, pem_end: bytes) -> typing.Iterator[bytes]:
+ """Generator over PEM lines between pem_start and pem_end."""
- pem_lines = []
in_pem_part = False
+ seen_pem_start = False
for line in contents.splitlines():
line = line.strip()
@@ -67,9 +49,10 @@ def load_pem(contents, pem_marker):
# Handle start marker
if line == pem_start:
if in_pem_part:
- raise ValueError('Seen start marker "%s" twice' % pem_start)
+ raise ValueError('Seen start marker "%r" twice' % pem_start)
in_pem_part = True
+ seen_pem_start = True
continue
# Skip stuff before first marker
@@ -85,21 +68,44 @@ def load_pem(contents, pem_marker):
if b':' in line:
continue
- pem_lines.append(line)
+ yield line
# Do some sanity checks
- if not pem_lines:
- raise ValueError('No PEM start marker "%s" found' % pem_start)
+ 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 "%s" found' % pem_end)
+ 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, pem_marker):
+def save_pem(contents: bytes, pem_marker: FlexiText) -> bytes:
"""Saves a PEM file.
:param contents: the contents to encode in PEM format
diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py
index 84f0e3b..57b0276 100644
--- a/rsa/pkcs1.py
+++ b/rsa/pkcs1.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,9 +28,10 @@ to your users.
import hashlib
import os
+import sys
+import typing
-from rsa._compat import range
-from rsa import common, transform, core
+from . import common, transform, core, key
# ASN.1 codes that describe the hash algorithm used.
HASH_ASN1 = {
@@ -54,6 +53,21 @@ HASH_METHODS = {
}
+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."""
@@ -66,7 +80,7 @@ class VerificationError(CryptoError):
"""Raised when verification fails."""
-def _pad_for_encryption(message, target_length):
+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
@@ -112,7 +126,7 @@ def _pad_for_encryption(message, target_length):
message])
-def _pad_for_signing(message, target_length):
+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.
@@ -146,7 +160,7 @@ def _pad_for_signing(message, target_length):
message])
-def encrypt(message, pub_key):
+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
@@ -178,7 +192,7 @@ def encrypt(message, pub_key):
return block
-def decrypt(crypto, priv_key):
+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
@@ -234,6 +248,12 @@ def decrypt(crypto, priv_key):
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:
+ raise DecryptionError('Decryption failed')
+
# If we can't find the cleartext marker, decryption failed.
if cleartext[0:2] != b'\x00\x02':
raise DecryptionError('Decryption failed')
@@ -247,14 +267,13 @@ def decrypt(crypto, priv_key):
return cleartext[sep_idx + 1:]
-def sign_hash(hash_value, priv_key, hash_method):
+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). Should be set to
- None if needing to hash and sign message.
+ :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'.
@@ -281,7 +300,7 @@ def sign_hash(hash_value, priv_key, hash_method):
return block
-def sign(message, priv_key, hash_method):
+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
@@ -303,7 +322,7 @@ def sign(message, priv_key, hash_method):
return sign_hash(msg_hash, priv_key, hash_method)
-def verify(message, signature, pub_key):
+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.
@@ -331,6 +350,9 @@ def verify(message, signature, pub_key):
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')
@@ -338,7 +360,7 @@ def verify(message, signature, pub_key):
return method_name
-def find_signature_hash(signature, pub_key):
+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.
@@ -357,7 +379,7 @@ def find_signature_hash(signature, pub_key):
return _find_method_hash(clearsig)
-def yield_fixedblocks(infile, blocksize):
+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.
@@ -378,7 +400,7 @@ def yield_fixedblocks(infile, blocksize):
break
-def compute_hash(message, method_name):
+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
@@ -395,18 +417,18 @@ def compute_hash(message, method_name):
method = HASH_METHODS[method_name]
hasher = method()
- if hasattr(message, 'read') and hasattr(message.read, '__call__'):
+ 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)
- else:
- # hash the message object itself.
- hasher.update(message)
return hasher.digest()
-def _find_method_hash(clearsig):
+def _find_method_hash(clearsig: bytes) -> str:
"""Finds the hash method.
:param clearsig: full padded ASN1 and hash.
diff --git a/rsa/pkcs1_v2.py b/rsa/pkcs1_v2.py
index 5f9c7dd..f780aff 100644
--- a/rsa/pkcs1_v2.py
+++ b/rsa/pkcs1_v2.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,7 +18,6 @@ This module implements certain functionality from PKCS#1 version 2. Main
documentation is RFC 2437: https://tools.ietf.org/html/rfc2437
"""
-from rsa._compat import range
from rsa import (
common,
pkcs1,
@@ -28,7 +25,7 @@ from rsa import (
)
-def mgf1(seed, length, hasher='SHA-1'):
+def mgf1(seed: bytes, length: int, hasher: str = 'SHA-1') -> bytes:
"""
MGF1 is a Mask Generation Function based on a hash function.
diff --git a/rsa/prime.py b/rsa/prime.py
index 3d63542..853aca5 100644
--- a/rsa/prime.py
+++ b/rsa/prime.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,14 +18,13 @@ Implementation based on the book Algorithm Design by Michael T. Goodrich and
Roberto Tamassia, 2002.
"""
-from rsa._compat import range
import rsa.common
import rsa.randnum
__all__ = ['getprime', 'are_relatively_prime']
-def gcd(p, q):
+def gcd(p: int, q: int) -> int:
"""Returns the greatest common divisor of p and q
>>> gcd(48, 180)
@@ -39,7 +36,7 @@ def gcd(p, q):
return p
-def get_primality_testing_rounds(number):
+def get_primality_testing_rounds(number: int) -> int:
"""Returns minimum number of rounds for Miller-Rabing primality testing,
based on number bitsize.
@@ -65,7 +62,7 @@ def get_primality_testing_rounds(number):
return 10
-def miller_rabin_primality_testing(n, k):
+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.
@@ -118,7 +115,7 @@ def miller_rabin_primality_testing(n, k):
return True
-def is_prime(number):
+def is_prime(number: int) -> bool:
"""Returns True if the number is prime, and False otherwise.
>>> is_prime(2)
@@ -144,7 +141,7 @@ def is_prime(number):
return miller_rabin_primality_testing(number, k + 1)
-def getprime(nbits):
+def getprime(nbits: int) -> int:
"""Returns a prime number that can be stored in 'nbits' bits.
>>> p = getprime(128)
@@ -172,7 +169,7 @@ def getprime(nbits):
# Retry if not prime
-def are_relatively_prime(a, b):
+def are_relatively_prime(a: int, b: int) -> bool:
"""Returns True if a and b are relatively prime, and False if they
are not.
diff --git a/rsa/randnum.py b/rsa/randnum.py
index 310acaa..a5bb850 100644
--- a/rsa/randnum.py
+++ b/rsa/randnum.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,12 +17,12 @@
# Source inspired by code by Yesudeep Mangalapilly <yesudeep@gmail.com>
import os
+import struct
from rsa import common, transform
-from rsa._compat import byte
-def read_random_bits(nbits):
+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
@@ -40,12 +38,12 @@ def read_random_bits(nbits):
if rbits > 0:
randomvalue = ord(os.urandom(1))
randomvalue >>= (8 - rbits)
- randomdata = byte(randomvalue) + randomdata
+ randomdata = struct.pack("B", randomvalue) + randomdata
return randomdata
-def read_random_int(nbits):
+def read_random_int(nbits: int) -> int:
"""Reads a random integer of approximately nbits bits.
"""
@@ -59,7 +57,7 @@ def read_random_int(nbits):
return value
-def read_random_odd_int(nbits):
+def read_random_odd_int(nbits: int) -> int:
"""Reads a random odd integer of approximately nbits bits.
>>> read_random_odd_int(512) & 1
@@ -72,7 +70,7 @@ def read_random_odd_int(nbits):
return value | 1
-def randint(maxvalue):
+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
diff --git a/rsa/transform.py b/rsa/transform.py
index 628d0af..03c4a77 100644
--- a/rsa/transform.py
+++ b/rsa/transform.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,16 +17,10 @@
From bytes to a number, number to bytes, etc.
"""
-from __future__ import absolute_import
-
-import binascii
-from struct import pack
+import math
-from rsa._compat import byte, is_integer
-from rsa import common, machine_size
-
-def bytes2int(raw_bytes):
+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.
@@ -39,110 +31,14 @@ def bytes2int(raw_bytes):
8405007
"""
+ return int.from_bytes(raw_bytes, 'big', signed=False)
- 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)
- b'\x07[\xcd\x15'
- >>> bytes2int(_int2bytes(123456789))
- 123456789
-
- >>> _int2bytes(123456789, 6)
- b'\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 = [b'\x00']
- 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) * b'\x00'
- else:
- padding = b''
-
- return padding + b''.join(raw_bytes)
-
-
-def bytes_leading(raw_bytes, needle=b'\x00'):
- """
- 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 \x00.
- :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):
+def int2bytes(number: int, fill_size: int = 0) -> bytes:
"""
- 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.
+ Convert an unsigned integer to bytes (big-endian)::
- .. NOTE:
- You must not specify both fill_size and chunk_size. Only one
- of them is allowed.
+ Does not preserve leading zeros if you don't specify a fill size.
:param number:
Integer value
@@ -150,15 +46,6 @@ def int2bytes(number, fill_size=None, chunk_size=None, overflow=False):
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:
@@ -171,42 +58,12 @@ def int2bytes(number, fill_size=None, chunk_size=None, overflow=False):
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 = machine_size.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 = b'\x00'
- # 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, b'\x00')
- 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, b'\x00')
- return raw_bytes
+ 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__':
diff --git a/rsa/util.py b/rsa/util.py
index 29d5eb1..cb31c46 100644
--- a/rsa/util.py
+++ b/rsa/util.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,15 +14,13 @@
"""Utility functions."""
-from __future__ import with_statement, print_function
-
import sys
from optparse import OptionParser
import rsa.key
-def private_to_public():
+def private_to_public() -> None:
"""Reads a private key and outputs the corresponding public key."""
# Parse the CLI options
diff --git a/setup.cfg b/setup.cfg
index ed8a958..e377bdb 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,3 +3,17 @@ universal = 1
[metadata]
license_file = LICENSE
+
+[flake8]
+max-line-length = 100
+max-complexity = 10
+
+[pep8]
+max-line-length = 100
+
+[mypy]
+python_version = 3.7
+warn_unused_ignores = True
+ignore_missing_imports = True
+follow_imports = skip
+incremental = True
diff --git a/setup.py b/setup.py
index 18f339e..2d22865 100755
--- a/setup.py
+++ b/setup.py
@@ -14,14 +14,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# io.open is needed for projects that support Python 2.7. It ensures open()
+# defaults to text mode with universal newlines, and accepts an argument to
+# specify the text encoding Python 3 only projects can skip this import.
+from io import open
from setuptools import setup
-with open('README.md') as f:
+with open('README.md', encoding='utf-8') as f:
long_description = f.read()
if __name__ == '__main__':
setup(name='rsa',
- version='4.0',
+ version='4.6',
description='Pure-Python RSA implementation',
long_description=long_description,
long_description_content_type='text/markdown',
@@ -40,17 +44,16 @@ if __name__ == '__main__':
'License :: OSI Approved :: Apache Software License',
'Operating System :: OS Independent',
'Programming Language :: Python',
- 'Programming Language :: Python :: 2',
- 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Security :: Cryptography',
],
+ python_requires='>=3.5, <4',
install_requires=[
'pyasn1 >= 0.1.3',
],
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 7ce57eb..1cd92c2 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -4,47 +4,37 @@ Unit tests for CLI entry points.
from __future__ import print_function
-import unittest
-import sys
import functools
-from contextlib import contextmanager
-
+import io
import os
-from io import StringIO, BytesIO
+import sys
+import typing
+import unittest
+from contextlib import contextmanager, redirect_stdout, redirect_stderr
import rsa
import rsa.cli
import rsa.util
-from rsa._compat import PY2
-def make_buffer():
- if PY2:
- return BytesIO()
- buf = StringIO()
- buf.buffer = BytesIO()
- return buf
+@contextmanager
+def captured_output() -> typing.Generator:
+ """Captures output to stdout and stderr"""
+ # According to mypy, we're not supposed to change buf_out.buffer.
+ # However, this is just a test, and it works, hence the 'type: ignore'.
+ buf_out = io.StringIO()
+ buf_out.buffer = io.BytesIO() # type: ignore
-def get_bytes_out(out):
- if PY2:
- # Python 2.x writes 'str' to stdout
- return out.getvalue()
- # Python 3.x writes 'bytes' to stdout.buffer
- return out.buffer.getvalue()
+ buf_err = io.StringIO()
+ buf_err.buffer = io.BytesIO() # type: ignore
+ with redirect_stdout(buf_out), redirect_stderr(buf_err):
+ yield buf_out, buf_err
-@contextmanager
-def captured_output():
- """Captures output to stdout and stderr"""
- new_out, new_err = make_buffer(), make_buffer()
- old_out, old_err = sys.stdout, sys.stderr
- try:
- sys.stdout, sys.stderr = new_out, new_err
- yield new_out, new_err
- finally:
- sys.stdout, sys.stderr = old_out, old_err
+def get_bytes_out(buf) -> bytes:
+ return buf.buffer.getvalue()
@contextmanager
diff --git a/tests/test_common.py b/tests/test_common.py
index af13695..71b81d0 100644
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -1,6 +1,4 @@
#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tests/test_compat.py b/tests/test_compat.py
index 62e933f..e74f046 100644
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,7 +15,7 @@
import unittest
import struct
-from rsa._compat import byte, is_bytes, range, xor_bytes
+from rsa._compat import byte, xor_bytes
class TestByte(unittest.TestCase):
@@ -26,7 +24,7 @@ class TestByte(unittest.TestCase):
def test_byte(self):
for i in range(256):
byt = byte(i)
- self.assertTrue(is_bytes(byt))
+ self.assertIsInstance(byt, bytes)
self.assertEqual(ord(byt), i)
def test_raises_StructError_on_overflow(self):
diff --git a/tests/test_integers.py b/tests/test_integers.py
index fb29ba4..2ca0a9a 100644
--- a/tests/test_integers.py
+++ b/tests/test_integers.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tests/test_load_save_keys.py b/tests/test_load_save_keys.py
index 55bd5a4..7892fb3 100644
--- a/tests/test_load_save_keys.py
+++ b/tests/test_load_save_keys.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,13 +15,12 @@
"""Unittest for saving and loading keys."""
import base64
-import mock
import os.path
import pickle
import unittest
import warnings
+from unittest import mock
-from rsa._compat import range
import rsa.key
B64PRIV_DER = b'MC4CAQACBQDeKYlRAgMBAAECBQDHn4npAgMA/icCAwDfxwIDANcXAgInbwIDAMZt'
diff --git a/tests/test_mypy.py b/tests/test_mypy.py
new file mode 100644
index 0000000..8258e7e
--- /dev/null
+++ b/tests/test_mypy.py
@@ -0,0 +1,27 @@
+import pathlib
+import unittest
+
+import mypy.api
+
+test_modules = ['rsa', 'tests']
+
+
+class MypyRunnerTest(unittest.TestCase):
+ def test_run_mypy(self):
+ proj_root = pathlib.Path(__file__).parent.parent
+ args = ['--incremental', '--ignore-missing-imports'] + [str(proj_root / dirname) for dirname
+ in test_modules]
+
+ result = mypy.api.run(args)
+
+ stdout, stderr, status = result
+
+ messages = []
+ if stderr:
+ messages.append(stderr)
+ if stdout:
+ messages.append(stdout)
+ if status:
+ messages.append('Mypy failed with status %d' % status)
+ if messages and not all('Success' in message for message in messages):
+ self.fail('\n'.join(['Mypy errors:'] + messages))
diff --git a/tests/test_pem.py b/tests/test_pem.py
index 5fb9600..dd03cca 100644
--- a/tests/test_pem.py
+++ b/tests/test_pem.py
@@ -1,6 +1,4 @@
#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,7 +15,6 @@
import unittest
-from rsa._compat import is_bytes
from rsa.pem import _markers
import rsa.key
@@ -79,13 +76,13 @@ class TestByteOutput(unittest.TestCase):
def test_bytes_public(self):
key = rsa.key.PublicKey.load_pkcs1_openssl_pem(public_key_pem)
- self.assertTrue(is_bytes(key.save_pkcs1(format='DER')))
- self.assertTrue(is_bytes(key.save_pkcs1(format='PEM')))
+ self.assertIsInstance(key.save_pkcs1(format='DER'), bytes)
+ self.assertIsInstance(key.save_pkcs1(format='PEM'), bytes)
def test_bytes_private(self):
key = rsa.key.PrivateKey.load_pkcs1(private_key_pem)
- self.assertTrue(is_bytes(key.save_pkcs1(format='DER')))
- self.assertTrue(is_bytes(key.save_pkcs1(format='PEM')))
+ self.assertIsInstance(key.save_pkcs1(format='DER'), bytes)
+ self.assertIsInstance(key.save_pkcs1(format='PEM'), bytes)
class TestByteInput(unittest.TestCase):
@@ -93,10 +90,10 @@ class TestByteInput(unittest.TestCase):
def test_bytes_public(self):
key = rsa.key.PublicKey.load_pkcs1_openssl_pem(public_key_pem.encode('ascii'))
- self.assertTrue(is_bytes(key.save_pkcs1(format='DER')))
- self.assertTrue(is_bytes(key.save_pkcs1(format='PEM')))
+ self.assertIsInstance(key.save_pkcs1(format='DER'), bytes)
+ self.assertIsInstance(key.save_pkcs1(format='PEM'), bytes)
def test_bytes_private(self):
key = rsa.key.PrivateKey.load_pkcs1(private_key_pem.encode('ascii'))
- self.assertTrue(is_bytes(key.save_pkcs1(format='DER')))
- self.assertTrue(is_bytes(key.save_pkcs1(format='PEM')))
+ self.assertIsInstance(key.save_pkcs1(format='DER'), bytes)
+ self.assertIsInstance(key.save_pkcs1(format='PEM'), bytes)
diff --git a/tests/test_pkcs1.py b/tests/test_pkcs1.py
index 5377b30..f7baf7f 100644
--- a/tests/test_pkcs1.py
+++ b/tests/test_pkcs1.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,11 +15,12 @@
"""Tests string operations."""
import struct
+import sys
import unittest
import rsa
from rsa import pkcs1
-from rsa._compat import byte, is_bytes
+from rsa._compat import byte
class BinaryTest(unittest.TestCase):
@@ -46,8 +45,8 @@ class BinaryTest(unittest.TestCase):
# Alter the encrypted stream
a = encrypted[5]
- if is_bytes(a):
- a = ord(a)
+ self.assertIsInstance(a, int)
+
altered_a = (a + 1) % 256
encrypted = encrypted[:5] + byte(altered_a) + encrypted[6:]
@@ -66,6 +65,32 @@ class BinaryTest(unittest.TestCase):
self.assertNotEqual(encrypted1, encrypted2)
+class ExtraZeroesTest(unittest.TestCase):
+ def setUp(self):
+ # Key, cyphertext, and plaintext taken from https://github.com/sybrenstuvel/python-rsa/issues/146
+ self.private_key = rsa.PrivateKey.load_pkcs1(
+ "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAs1EKK81M5kTFtZSuUFnhKy8FS2WNXaWVmi/fGHG4CLw98+Yo\n0nkuUarVwSS0O9pFPcpc3kvPKOe9Tv+6DLS3Qru21aATy2PRqjqJ4CYn71OYtSwM\n/ZfSCKvrjXybzgu+sBmobdtYm+sppbdL+GEHXGd8gdQw8DDCZSR6+dPJFAzLZTCd\nB+Ctwe/RXPF+ewVdfaOGjkZIzDoYDw7n+OHnsYCYozkbTOcWHpjVevipR+IBpGPi\n1rvKgFnlcG6d/tj0hWRl/6cS7RqhjoiNEtxqoJzpXs/Kg8xbCxXbCchkf11STA8u\ndiCjQWuWI8rcDwl69XMmHJjIQAqhKvOOQ8rYTQIDAQABAoIBABpQLQ7qbHtp4h1Y\nORAfcFRW7Q74UvtH/iEHH1TF8zyM6wZsYtcn4y0mxYE3Mp+J0xlTJbeVJkwZXYVH\nL3UH29CWHSlR+TWiazTwrCTRVJDhEoqbcTiRW8fb+o/jljVxMcVDrpyYUHNo2c6w\njBxhmKPtp66hhaDpds1Cwi0A8APZ8Z2W6kya/L/hRBzMgCz7Bon1nYBMak5PQEwV\nF0dF7Wy4vIjvCzO6DSqA415DvJDzUAUucgFudbANNXo4HJwNRnBpymYIh8mHdmNJ\n/MQ0YLSqUWvOB57dh7oWQwe3UsJ37ZUorTugvxh3NJ7Tt5ZqbCQBEECb9ND63gxo\n/a3YR/0CgYEA7BJc834xCi/0YmO5suBinWOQAF7IiRPU+3G9TdhWEkSYquupg9e6\nK9lC5k0iP+t6I69NYF7+6mvXDTmv6Z01o6oV50oXaHeAk74O3UqNCbLe9tybZ/+F\ndkYlwuGSNttMQBzjCiVy0+y0+Wm3rRnFIsAtd0RlZ24aN3bFTWJINIsCgYEAwnQq\nvNmJe9SwtnH5c/yCqPhKv1cF/4jdQZSGI6/p3KYNxlQzkHZ/6uvrU5V27ov6YbX8\nvKlKfO91oJFQxUD6lpTdgAStI3GMiJBJIZNpyZ9EWNSvwUj28H34cySpbZz3s4Xd\nhiJBShgy+fKURvBQwtWmQHZJ3EGrcOI7PcwiyYcCgYEAlql5jSUCY0ALtidzQogW\nJ+B87N+RGHsBuJ/0cxQYinwg+ySAAVbSyF1WZujfbO/5+YBN362A/1dn3lbswCnH\nK/bHF9+fZNqvwprPnceQj5oK1n4g6JSZNsy6GNAhosT+uwQ0misgR8SQE4W25dDG\nkdEYsz+BgCsyrCcu8J5C+tUCgYAFVPQbC4f2ikVyKzvgz0qx4WUDTBqRACq48p6e\n+eLatv7nskVbr7QgN+nS9+Uz80ihR0Ev1yCAvnwmM/XYAskcOea87OPmdeWZlQM8\nVXNwINrZ6LMNBLgorfuTBK1UoRo1pPUHCYdqxbEYI2unak18mikd2WB7Fp3h0YI4\nVpGZnwKBgBxkAYnZv+jGI4MyEKdsQgxvROXXYOJZkWzsKuKxVkVpYP2V4nR2YMOJ\nViJQ8FUEnPq35cMDlUk4SnoqrrHIJNOvcJSCqM+bWHAioAsfByLbUPM8sm3CDdIk\nXVJl32HuKYPJOMIWfc7hIfxLRHnCN+coz2M6tgqMDs0E/OfjuqVZ\n-----END RSA PRIVATE KEY-----",
+ format='PEM')
+ self.cyphertext = bytes.fromhex(
+ "4501b4d669e01b9ef2dc800aa1b06d49196f5a09fe8fbcd037323c60eaf027bfb98432be4e4a26c567ffec718bcbea977dd26812fa071c33808b4d5ebb742d9879806094b6fbeea63d25ea3141733b60e31c6912106e1b758a7fe0014f075193faa8b4622bfd5d3013f0a32190a95de61a3604711bc62945f95a6522bd4dfed0a994ef185b28c281f7b5e4c8ed41176d12d9fc1b837e6a0111d0132d08a6d6f0580de0c9eed8ed105531799482d1e466c68c23b0c222af7fc12ac279bc4ff57e7b4586d209371b38c4c1035edd418dc5f960441cb21ea2bedbfea86de0d7861e81021b650a1de51002c315f1e7c12debe4dcebf790caaa54a2f26b149cf9e77d"
+ )
+ self.plaintext = bytes.fromhex("54657374")
+
+ def test_unmodified(self):
+ message = rsa.decrypt(self.cyphertext, self.private_key)
+ self.assertEqual(message, self.plaintext)
+
+ def test_prepend_zeroes(self):
+ cyphertext = bytes.fromhex("0000") + self.cyphertext
+ with self.assertRaises(rsa.DecryptionError):
+ rsa.decrypt(cyphertext, self.private_key)
+
+ def test_append_zeroes(self):
+ cyphertext = self.cyphertext + bytes.fromhex("0000")
+ with self.assertRaises(rsa.DecryptionError):
+ rsa.decrypt(cyphertext, self.private_key)
+
+
class SignatureTest(unittest.TestCase):
def setUp(self):
(self.pub, self.priv) = rsa.newkeys(512)
@@ -75,9 +100,17 @@ class SignatureTest(unittest.TestCase):
message = b'je moeder'
signature = pkcs1.sign(message, self.priv, 'SHA-256')
-
self.assertEqual('SHA-256', pkcs1.verify(message, signature, self.pub))
+
+ @unittest.skipIf(sys.version_info < (3, 6), "SHA3 requires Python 3.6+")
+ def test_sign_verify_sha3(self):
+ """Test happy flow of sign and verify with SHA3-256"""
+
+ message = b'je moeder'
+ signature = pkcs1.sign(message, self.priv, 'SHA3-256')
+ self.assertEqual('SHA3-256', pkcs1.verify(message, signature, self.pub))
+
def test_find_signature_hash(self):
"""Test happy flow of sign and find_signature_hash"""
@@ -132,3 +165,21 @@ class SignatureTest(unittest.TestCase):
signature = pkcs1.sign_hash(msg_hash, self.priv, 'SHA-224')
self.assertTrue(pkcs1.verify(message, signature, self.pub))
+
+ def test_prepend_zeroes(self):
+ """Prepending the signature with zeroes should be detected."""
+
+ message = b'je moeder'
+ signature = pkcs1.sign(message, self.priv, 'SHA-256')
+ signature = bytes.fromhex('0000') + signature
+ with self.assertRaises(rsa.VerificationError):
+ pkcs1.verify(message, signature, self.pub)
+
+ def test_apppend_zeroes(self):
+ """Apppending the signature with zeroes should be detected."""
+
+ message = b'je moeder'
+ signature = pkcs1.sign(message, self.priv, 'SHA-256')
+ signature = signature + bytes.fromhex('0000')
+ with self.assertRaises(rsa.VerificationError):
+ pkcs1.verify(message, signature, self.pub)
diff --git a/tests/test_pkcs1_v2.py b/tests/test_pkcs1_v2.py
index 1d8f001..bba525e 100644
--- a/tests/test_pkcs1_v2.py
+++ b/tests/test_pkcs1_v2.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tests/test_prime.py b/tests/test_prime.py
index f3bda9b..5577f67 100644
--- a/tests/test_prime.py
+++ b/tests/test_prime.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,7 +16,6 @@
import unittest
-from rsa._compat import range
import rsa.prime
import rsa.randnum
diff --git a/tests/test_strings.py b/tests/test_strings.py
index 28fa091..1090a8e 100644
--- a/tests/test_strings.py
+++ b/tests/test_strings.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -31,12 +29,12 @@ class StringTest(unittest.TestCase):
def test_enc_dec(self):
message = unicode_string.encode('utf-8')
- print("\tMessage: %s" % message)
+ print("\tMessage: %r" % message)
encrypted = rsa.encrypt(message, self.pub)
- print("\tEncrypted: %s" % encrypted)
+ print("\tEncrypted: %r" % encrypted)
decrypted = rsa.decrypt(encrypted, self.priv)
- print("\tDecrypted: %s" % decrypted)
+ print("\tDecrypted: %r" % decrypted)
self.assertEqual(message, decrypted)
diff --git a/tests/test_transform.py b/tests/test_transform.py
index fe0970c..7b9335e 100644
--- a/tests/test_transform.py
+++ b/tests/test_transform.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,37 +13,26 @@
# limitations under the License.
import unittest
-from rsa.transform import int2bytes, bytes2int, _int2bytes
+from rsa.transform import int2bytes, bytes2int
class Test_int2bytes(unittest.TestCase):
def test_accuracy(self):
self.assertEqual(int2bytes(123456789), b'\x07[\xcd\x15')
- self.assertEqual(_int2bytes(123456789), b'\x07[\xcd\x15')
def test_codec_identity(self):
self.assertEqual(bytes2int(int2bytes(123456789, 128)), 123456789)
- self.assertEqual(bytes2int(_int2bytes(123456789, 128)), 123456789)
def test_chunk_size(self):
self.assertEqual(int2bytes(123456789, 6), b'\x00\x00\x07[\xcd\x15')
self.assertEqual(int2bytes(123456789, 7),
b'\x00\x00\x00\x07[\xcd\x15')
- self.assertEqual(_int2bytes(123456789, 6),
- b'\x00\x00\x07[\xcd\x15')
- self.assertEqual(_int2bytes(123456789, 7),
- b'\x00\x00\x00\x07[\xcd\x15')
-
def test_zero(self):
self.assertEqual(int2bytes(0, 4), b'\x00' * 4)
self.assertEqual(int2bytes(0, 7), b'\x00' * 7)
self.assertEqual(int2bytes(0), b'\x00')
- self.assertEqual(_int2bytes(0, 4), b'\x00' * 4)
- self.assertEqual(_int2bytes(0, 7), b'\x00' * 7)
- self.assertEqual(_int2bytes(0), b'\x00')
-
def test_correctness_against_base_implementation(self):
# Slow test.
values = [
@@ -54,26 +41,16 @@ class Test_int2bytes(unittest.TestCase):
1 << 77,
]
for value in values:
- self.assertEqual(int2bytes(value), _int2bytes(value),
- "Boom %d" % value)
self.assertEqual(bytes2int(int2bytes(value)),
value,
"Boom %d" % value)
- self.assertEqual(bytes2int(_int2bytes(value)),
- value,
- "Boom %d" % value)
def test_raises_OverflowError_when_chunk_size_is_insufficient(self):
self.assertRaises(OverflowError, int2bytes, 123456789, 3)
self.assertRaises(OverflowError, int2bytes, 299999999999, 4)
- self.assertRaises(OverflowError, _int2bytes, 123456789, 3)
- self.assertRaises(OverflowError, _int2bytes, 299999999999, 4)
-
def test_raises_ValueError_when_negative_integer(self):
self.assertRaises(ValueError, int2bytes, -1)
- self.assertRaises(ValueError, _int2bytes, -1)
def test_raises_TypeError_when_not_integer(self):
self.assertRaises(TypeError, int2bytes, None)
- self.assertRaises(TypeError, _int2bytes, None)
diff --git a/tox.ini b/tox.ini
index a3109e4..0552a17 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,20 +1,18 @@
[tox]
# Environment changes have to be manually synced with '.travis.yml'.
-envlist = py27,py34,py35,py36,p37,pypy
+envlist = py35,py36,p37,p38
[pytest]
addopts = -v --cov rsa --cov-report term-missing
[testenv]
deps = pipenv
-commands=
- pipenv install --dev
- pipenv run py.test tests
+commands =
+ pipenv install --dev --deploy
+ pipenv run py.test tests/
-[testenv:py36]
+[testenv:py37]
+whitelist_externals = pipenv
commands=
- pipenv install --dev --ignore-pipfile
- pipenv run py.test --doctest-modules rsa tests
-
-[pep8]
-max-line-length = 100
+ pipenv install -v
+ pipenv run py.test --doctest-modules rsa tests/
diff --git a/update_version.sh b/update_version.sh
new file mode 100755
index 0000000..89c5447
--- /dev/null
+++ b/update_version.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+if [ -z "$1" ]; then
+ echo "Usage: $0 new-version" >&2
+ exit 1
+fi
+
+DATE=$(date +'%Y-%m-%d')
+
+sed "s/__date__\s=\s'[^']*'/__date__ = '$DATE'/" -i rsa/__init__.py
+sed "s/__version__\s=\s'[^']*'/__version__ = '$1'/" -i rsa/__init__.py
+sed "s/version='[^']*'/version='$1'/" -i setup.py
+
+git diff
+echo
+echo "Don't forget to commit and tag:"
+echo git commit -m \'Bumped version to $1\' rsa/__init__.py setup.py
+echo git tag -a version-$1 -m \'Tagged version $1\'