Skip to content

Commit 4521c5c

Browse files
authored
Merge pull request #415 from tomato42/rsa-gmpy
Quicker RSA operations
2 parents c6c9112 + 49e6cb9 commit 4521c5c

File tree

6 files changed

+185
-41
lines changed

6 files changed

+185
-41
lines changed

.travis.yml

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ addons:
1717
- swig
1818
# needed for GMPY
1919
- libgmp-dev
20+
# needed for GMPY2
21+
- libmpfr-dev
22+
- libmpc-dev
2023
before_cache:
2124
- rm -f $HOME/.cache/pip/log/debug.log
2225

@@ -95,19 +98,33 @@ jobs:
9598
sudo: true
9699
env: GMPY=true
97100
- python: 2.7
98-
env: M2CRYPTO=true PYCRYPTO=true GMPY=true
101+
env: GMPY2=true
99102
- python: 3.5
100-
env: M2CRYPTO=true PYCRYPTO=true GMPY=true
103+
env: GMPY2=true
101104
- python: 3.6
102-
env: M2CRYPTO=true PYCRYPTO=true GMPY=true
105+
env: GMPY2=true
103106
- python: 3.7
104107
dist: xenial
105108
sudo: true
106-
env: M2CRYPTO=true PYCRYPTO=true GMPY=true CC_COV=true
109+
env: GMPY2=true
107110
- python: 3.8
108111
dist: xenial
109112
sudo: true
110-
env: M2CRYPTO=true GMPY=true
113+
env: GMPY2=true
114+
- python: 2.7
115+
env: M2CRYPTO=true PYCRYPTO=true GMPY=true GMPY2=true
116+
- python: 3.5
117+
env: M2CRYPTO=true PYCRYPTO=true GMPY=true GMPY2=true
118+
- python: 3.6
119+
env: M2CRYPTO=true PYCRYPTO=true GMPY=true GMPY2=true
120+
- python: 3.7
121+
dist: xenial
122+
sudo: true
123+
env: M2CRYPTO=true PYCRYPTO=true GMPY=true GMPY2=true CC_COV=true
124+
- python: 3.8
125+
dist: xenial
126+
sudo: true
127+
env: M2CRYPTO=true GMPY=true GMPY2=true
111128

112129
before_install:
113130
- |
@@ -138,6 +155,7 @@ install:
138155
- if [[ $PYCRYPTO == 'true' ]]; then travis_retry pip install pycrypto; fi
139156
- if [[ $PYCRYPTODOME == 'true' ]]; then travis_retry pip install pycryptodome; fi
140157
- if [[ $GMPY == 'true' ]]; then travis_retry pip install gmpy; fi
158+
- if [[ $GMPY2 == 'true' ]]; then travis_retry pip install gmpy2; fi
141159
- travis_retry pip install -r requirements.txt
142160
- if [[ $CC_COV == 'true' ]]; then ./cc-test-reporter before-build; fi
143161

scripts/speed.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import timeit
2+
3+
from tlslite.utils.cryptomath import gmpyLoaded, GMPY2_LOADED
4+
5+
print("Acceleration backends loaded:")
6+
print("gmpy: {0}".format(gmpyLoaded))
7+
print("gmpy2: {0}".format(GMPY2_LOADED))
8+
print("")
9+
10+
def do(setup_statements, statement):
11+
# extracted from timeit.py
12+
t = timeit.Timer(stmt=statement, setup="\n".join(setup_statements))
13+
# determine number so that 0.2 <= total time < 2.0
14+
for i in range(1, 10):
15+
number = 10 ** i
16+
x = t.timeit(number)
17+
if x >= 0.2:
18+
break
19+
return x / number
20+
21+
22+
prnt_form = (
23+
"{name:>16}{sep:1} {keygen:>9{form}}{unit:1} "
24+
"{keygen_inv:>9{form_inv}} {sign:>9{form}}{unit:1} "
25+
"{sign_inv:>9{form_inv}} {verify:>9{form}}{unit:1} "
26+
"{verify_inv:>9{form_inv}}"
27+
)
28+
29+
print(
30+
prnt_form.format(
31+
keygen="keygen",
32+
keygen_inv="keygen/s",
33+
sign="sign",
34+
sign_inv="sign/s",
35+
verify="verify",
36+
verify_inv="verify/s",
37+
name="",
38+
sep="",
39+
unit="",
40+
form="",
41+
form_inv="",
42+
)
43+
)
44+
45+
for size in [1024, 2048, 3072, 4096]:
46+
S1 = "from tlslite.utils.python_rsakey import Python_RSAKey"
47+
S2 = "from tlslite.utils.cryptomath import secureHash"
48+
S3 = "key = Python_RSAKey.generate(%s)" % size
49+
S4 = "msg = b'msg'"
50+
S5 = "msg_hash = secureHash(msg, 'sha1')"
51+
S6 = "sig = key.sign(msg_hash)"
52+
S7 = "key.verify(sig, msg_hash)"
53+
keygen = do([S1, S2], S3)
54+
sign = do([S1, S2, S3, S4, S5], S6)
55+
verf = do([S1, S2, S3, S4, S5, S6], S7)
56+
57+
print(
58+
prnt_form.format(
59+
name="RSA {0} bits".format(size),
60+
sep=":",
61+
unit="s",
62+
keygen=keygen,
63+
keygen_inv=1.0 / keygen,
64+
sign=sign,
65+
sign_inv=1.0 / sign,
66+
verify=verf,
67+
verify_inv=1.0 / verf,
68+
form=".5f",
69+
form_inv=".2f",
70+
)
71+
)
72+
73+
print("")
74+

scripts/tls.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,11 @@ def printUsage(s=None):
7171
print(" GMPY : Loaded")
7272
else:
7373
print(" GMPY : Not Loaded")
74-
74+
if GMPY2_LOADED:
75+
print(" GMPY2 : Loaded")
76+
else:
77+
print(" GMPY2 : Not Loaded")
78+
7579
print("")
7680
print("""Commands:
7781

tlslite/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
MultiPathTLSXMLRPCServer
2626

2727
from .utils.cryptomath import m2cryptoLoaded, gmpyLoaded, \
28-
pycryptoLoaded, prngName
28+
pycryptoLoaded, prngName, GMPY2_LOADED
2929
from .utils.keyfactory import generateRSAKey, parsePEMKey, \
3030
parseAsPublicKey, parsePrivateKey
3131
from .utils.tackwrapper import tackpyLoaded

tlslite/utils/cryptomath.py

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,27 @@
5454
#Try to load GMPY
5555
try:
5656
import gmpy
57+
gmpy.mpz
5758
gmpyLoaded = True
5859
except ImportError:
5960
gmpyLoaded = False
6061

62+
63+
# Try to load GMPY2
64+
try:
65+
from gmpy2 import powmod
66+
GMPY2_LOADED = True
67+
except ImportError:
68+
GMPY2_LOADED = False
69+
70+
71+
# Use the faster mpz
72+
if GMPY2_LOADED:
73+
from gmpy2 import mpz
74+
elif gmpyLoaded:
75+
from gmpy import mpz
76+
77+
6178
#Try to load pycrypto
6279
# pylint: disable=invalid-name
6380
try:
@@ -292,25 +309,35 @@ def gcd(a,b):
292309
def lcm(a, b):
293310
return (a * b) // gcd(a, b)
294311

295-
#Returns inverse of a mod b, zero if none
296-
#Uses Extended Euclidean Algorithm
297-
def invMod(a, b):
298-
c, d = a, b
299-
uc, ud = 1, 0
300-
while c != 0:
301-
q = d // c
302-
c, d = d-(q*c), c
303-
uc, ud = ud - (q * uc), uc
304-
if d == 1:
305-
return ud % b
306-
return 0
307-
308-
309-
if gmpyLoaded:
312+
# pylint: disable=invalid-name
313+
# disable pylint check as the (a, b) are part of the API
314+
if GMPY2_LOADED:
315+
def invMod(a, b):
316+
"""Return inverse of a mod b, zero if none."""
317+
if a == 0:
318+
return 0
319+
return powmod(a, -1, b)
320+
else:
321+
# Use Extended Euclidean Algorithm
322+
def invMod(a, b):
323+
"""Return inverse of a mod b, zero if none."""
324+
c, d = a, b
325+
uc, ud = 1, 0
326+
while c != 0:
327+
q = d // c
328+
c, d = d-(q*c), c
329+
uc, ud = ud - (q * uc), uc
330+
if d == 1:
331+
return ud % b
332+
return 0
333+
# pylint: enable=invalid-name
334+
335+
336+
if gmpyLoaded or GMPY2_LOADED:
310337
def powMod(base, power, modulus):
311-
base = gmpy.mpz(base)
312-
power = gmpy.mpz(power)
313-
modulus = gmpy.mpz(modulus)
338+
base = mpz(base)
339+
power = mpz(power)
340+
modulus = mpz(modulus)
314341
result = pow(base, power, modulus)
315342
return compatLong(result)
316343
else:

tlslite/utils/python_rsakey.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
from .rsakey import *
88
from .pem import *
99
from .deprecations import deprecated_params
10+
if GMPY2_LOADED:
11+
from gmpy2 import mpz
12+
elif gmpyLoaded:
13+
from gmpy import mpz
1014

1115
class Python_RSAKey(RSAKey):
1216
def __init__(self, n=0, e=0, d=0, p=0, q=0, dP=0, dQ=0, qInv=0,
@@ -16,6 +20,15 @@ def __init__(self, n=0, e=0, d=0, p=0, q=0, dP=0, dQ=0, qInv=0,
1620
see also generate() and parsePEM()."""
1721
if (n and not e) or (e and not n):
1822
raise AssertionError()
23+
if gmpyLoaded or GMPY2_LOADED:
24+
n = mpz(n)
25+
e = mpz(e)
26+
d = mpz(d)
27+
p = mpz(p)
28+
q = mpz(q)
29+
dP = mpz(dP)
30+
dQ = mpz(dQ)
31+
qInv = mpz(qInv)
1932
self.n = n
2033
self.e = e
2134
if p and not q or not p and q:
@@ -48,44 +61,46 @@ def hasPrivateKey(self):
4861
return self.d != 0
4962

5063
def _rawPrivateKeyOp(self, message):
64+
n = self.n
5165
with self._lock:
5266
# Create blinding values, on the first pass:
5367
if not self.blinder:
54-
self.unblinder = getRandomNumber(2, self.n)
55-
self.blinder = powMod(invMod(self.unblinder, self.n), self.e,
56-
self.n)
68+
self.unblinder = getRandomNumber(2, n)
69+
self.blinder = powMod(invMod(self.unblinder, n), self.e,
70+
n)
5771
unblinder = self.unblinder
5872
blinder = self.blinder
5973

6074
# Update blinding values
61-
self.blinder = (self.blinder * self.blinder) % self.n
62-
self.unblinder = (self.unblinder * self.unblinder) % self.n
75+
self.blinder = (blinder * blinder) % n
76+
self.unblinder = (unblinder * unblinder) % n
6377

6478
# Blind the input
65-
message = (message * blinder) % self.n
79+
message = (message * blinder) % n
6680

6781
# Perform the RSA operation
6882
cipher = self._rawPrivateKeyOpHelper(message)
6983

7084
# Unblind the output
71-
cipher = (cipher * unblinder) % self.n
85+
cipher = (cipher * unblinder) % n
7286

7387
# Return the output
7488
return cipher
7589

7690
def _rawPrivateKeyOpHelper(self, m):
7791
#Non-CRT version
78-
#c = powMod(m, self.d, self.n)
79-
80-
#CRT version (~3x faster)
81-
s1 = powMod(m, self.dP, self.p)
82-
s2 = powMod(m, self.dQ, self.q)
83-
h = ((s1 - s2) * self.qInv) % self.p
84-
c = s2 + self.q * h
92+
#c = pow(m, self.d, self.n)
93+
94+
#CRT version (~3x faster).
95+
p, q = self.p, self.q
96+
s1 = pow(m, self.dP, p)
97+
s2 = pow(m, self.dQ, q)
98+
h = ((s1 - s2) * self.qInv) % p
99+
c = s2 + q * h
85100
return c
86101

87102
def _rawPublicKeyOp(self, ciphertext):
88-
msg = powMod(ciphertext, self.e, self.n)
103+
msg = pow(ciphertext, self.e, self.n)
89104
return msg
90105

91106
def acceptsPassword(self):
@@ -101,9 +116,15 @@ def generate(bits, key_type="rsa"):
101116
key = Python_RSAKey()
102117
p = getRandomPrime(bits//2, False)
103118
q = getRandomPrime(bits//2, False)
119+
if gmpyLoaded or GMPY2_LOADED:
120+
p = mpz(p)
121+
q = mpz(q)
104122
t = lcm(p-1, q-1)
105123
key.n = p * q
106-
key.e = 65537
124+
if gmpyLoaded or GMPY2_LOADED:
125+
key.e = mpz(65537)
126+
else:
127+
key.e = 65537
107128
key.d = invMod(key.e, t)
108129
key.p = p
109130
key.q = q

0 commit comments

Comments
 (0)