Skip to content

Commit

Permalink
Merge pull request #415 from tomato42/rsa-gmpy
Browse files Browse the repository at this point in the history
Quicker RSA operations
  • Loading branch information
tomato42 committed Jun 23, 2020
2 parents c6c9112 + 49e6cb9 commit 4521c5c
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 41 deletions.
28 changes: 23 additions & 5 deletions .travis.yml
Expand Up @@ -17,6 +17,9 @@ addons:
- swig
# needed for GMPY
- libgmp-dev
# needed for GMPY2
- libmpfr-dev
- libmpc-dev
before_cache:
- rm -f $HOME/.cache/pip/log/debug.log

Expand Down Expand Up @@ -95,19 +98,33 @@ jobs:
sudo: true
env: GMPY=true
- python: 2.7
env: M2CRYPTO=true PYCRYPTO=true GMPY=true
env: GMPY2=true
- python: 3.5
env: M2CRYPTO=true PYCRYPTO=true GMPY=true
env: GMPY2=true
- python: 3.6
env: M2CRYPTO=true PYCRYPTO=true GMPY=true
env: GMPY2=true
- python: 3.7
dist: xenial
sudo: true
env: M2CRYPTO=true PYCRYPTO=true GMPY=true CC_COV=true
env: GMPY2=true
- python: 3.8
dist: xenial
sudo: true
env: M2CRYPTO=true GMPY=true
env: GMPY2=true
- python: 2.7
env: M2CRYPTO=true PYCRYPTO=true GMPY=true GMPY2=true
- python: 3.5
env: M2CRYPTO=true PYCRYPTO=true GMPY=true GMPY2=true
- python: 3.6
env: M2CRYPTO=true PYCRYPTO=true GMPY=true GMPY2=true
- python: 3.7
dist: xenial
sudo: true
env: M2CRYPTO=true PYCRYPTO=true GMPY=true GMPY2=true CC_COV=true
- python: 3.8
dist: xenial
sudo: true
env: M2CRYPTO=true GMPY=true GMPY2=true

before_install:
- |
Expand Down Expand Up @@ -138,6 +155,7 @@ install:
- if [[ $PYCRYPTO == 'true' ]]; then travis_retry pip install pycrypto; fi
- if [[ $PYCRYPTODOME == 'true' ]]; then travis_retry pip install pycryptodome; fi
- if [[ $GMPY == 'true' ]]; then travis_retry pip install gmpy; fi
- if [[ $GMPY2 == 'true' ]]; then travis_retry pip install gmpy2; fi
- travis_retry pip install -r requirements.txt
- if [[ $CC_COV == 'true' ]]; then ./cc-test-reporter before-build; fi

Expand Down
74 changes: 74 additions & 0 deletions scripts/speed.py
@@ -0,0 +1,74 @@
import timeit

from tlslite.utils.cryptomath import gmpyLoaded, GMPY2_LOADED

print("Acceleration backends loaded:")
print("gmpy: {0}".format(gmpyLoaded))
print("gmpy2: {0}".format(GMPY2_LOADED))
print("")

def do(setup_statements, statement):
# extracted from timeit.py
t = timeit.Timer(stmt=statement, setup="\n".join(setup_statements))
# determine number so that 0.2 <= total time < 2.0
for i in range(1, 10):
number = 10 ** i
x = t.timeit(number)
if x >= 0.2:
break
return x / number


prnt_form = (
"{name:>16}{sep:1} {keygen:>9{form}}{unit:1} "
"{keygen_inv:>9{form_inv}} {sign:>9{form}}{unit:1} "
"{sign_inv:>9{form_inv}} {verify:>9{form}}{unit:1} "
"{verify_inv:>9{form_inv}}"
)

print(
prnt_form.format(
keygen="keygen",
keygen_inv="keygen/s",
sign="sign",
sign_inv="sign/s",
verify="verify",
verify_inv="verify/s",
name="",
sep="",
unit="",
form="",
form_inv="",
)
)

for size in [1024, 2048, 3072, 4096]:
S1 = "from tlslite.utils.python_rsakey import Python_RSAKey"
S2 = "from tlslite.utils.cryptomath import secureHash"
S3 = "key = Python_RSAKey.generate(%s)" % size
S4 = "msg = b'msg'"
S5 = "msg_hash = secureHash(msg, 'sha1')"
S6 = "sig = key.sign(msg_hash)"
S7 = "key.verify(sig, msg_hash)"
keygen = do([S1, S2], S3)
sign = do([S1, S2, S3, S4, S5], S6)
verf = do([S1, S2, S3, S4, S5, S6], S7)

print(
prnt_form.format(
name="RSA {0} bits".format(size),
sep=":",
unit="s",
keygen=keygen,
keygen_inv=1.0 / keygen,
sign=sign,
sign_inv=1.0 / sign,
verify=verf,
verify_inv=1.0 / verf,
form=".5f",
form_inv=".2f",
)
)

print("")

6 changes: 5 additions & 1 deletion scripts/tls.py
Expand Up @@ -71,7 +71,11 @@ def printUsage(s=None):
print(" GMPY : Loaded")
else:
print(" GMPY : Not Loaded")

if GMPY2_LOADED:
print(" GMPY2 : Loaded")
else:
print(" GMPY2 : Not Loaded")

print("")
print("""Commands:
Expand Down
2 changes: 1 addition & 1 deletion tlslite/api.py
Expand Up @@ -25,7 +25,7 @@
MultiPathTLSXMLRPCServer

from .utils.cryptomath import m2cryptoLoaded, gmpyLoaded, \
pycryptoLoaded, prngName
pycryptoLoaded, prngName, GMPY2_LOADED
from .utils.keyfactory import generateRSAKey, parsePEMKey, \
parseAsPublicKey, parsePrivateKey
from .utils.tackwrapper import tackpyLoaded
Expand Down
63 changes: 45 additions & 18 deletions tlslite/utils/cryptomath.py
Expand Up @@ -54,10 +54,27 @@
#Try to load GMPY
try:
import gmpy
gmpy.mpz
gmpyLoaded = True
except ImportError:
gmpyLoaded = False


# Try to load GMPY2
try:
from gmpy2 import powmod
GMPY2_LOADED = True
except ImportError:
GMPY2_LOADED = False


# Use the faster mpz
if GMPY2_LOADED:
from gmpy2 import mpz
elif gmpyLoaded:
from gmpy import mpz


#Try to load pycrypto
# pylint: disable=invalid-name
try:
Expand Down Expand Up @@ -292,25 +309,35 @@ def gcd(a,b):
def lcm(a, b):
return (a * b) // gcd(a, b)

#Returns inverse of a mod b, zero if none
#Uses Extended Euclidean Algorithm
def invMod(a, b):
c, d = a, b
uc, ud = 1, 0
while c != 0:
q = d // c
c, d = d-(q*c), c
uc, ud = ud - (q * uc), uc
if d == 1:
return ud % b
return 0


if gmpyLoaded:
# pylint: disable=invalid-name
# disable pylint check as the (a, b) are part of the API
if GMPY2_LOADED:
def invMod(a, b):
"""Return inverse of a mod b, zero if none."""
if a == 0:
return 0
return powmod(a, -1, b)
else:
# Use Extended Euclidean Algorithm
def invMod(a, b):
"""Return inverse of a mod b, zero if none."""
c, d = a, b
uc, ud = 1, 0
while c != 0:
q = d // c
c, d = d-(q*c), c
uc, ud = ud - (q * uc), uc
if d == 1:
return ud % b
return 0
# pylint: enable=invalid-name


if gmpyLoaded or GMPY2_LOADED:
def powMod(base, power, modulus):
base = gmpy.mpz(base)
power = gmpy.mpz(power)
modulus = gmpy.mpz(modulus)
base = mpz(base)
power = mpz(power)
modulus = mpz(modulus)
result = pow(base, power, modulus)
return compatLong(result)
else:
Expand Down
53 changes: 37 additions & 16 deletions tlslite/utils/python_rsakey.py
Expand Up @@ -7,6 +7,10 @@
from .rsakey import *
from .pem import *
from .deprecations import deprecated_params
if GMPY2_LOADED:
from gmpy2 import mpz
elif gmpyLoaded:
from gmpy import mpz

class Python_RSAKey(RSAKey):
def __init__(self, n=0, e=0, d=0, p=0, q=0, dP=0, dQ=0, qInv=0,
Expand All @@ -16,6 +20,15 @@ def __init__(self, n=0, e=0, d=0, p=0, q=0, dP=0, dQ=0, qInv=0,
see also generate() and parsePEM()."""
if (n and not e) or (e and not n):
raise AssertionError()
if gmpyLoaded or GMPY2_LOADED:
n = mpz(n)
e = mpz(e)
d = mpz(d)
p = mpz(p)
q = mpz(q)
dP = mpz(dP)
dQ = mpz(dQ)
qInv = mpz(qInv)
self.n = n
self.e = e
if p and not q or not p and q:
Expand Down Expand Up @@ -48,44 +61,46 @@ def hasPrivateKey(self):
return self.d != 0

def _rawPrivateKeyOp(self, message):
n = self.n
with self._lock:
# Create blinding values, on the first pass:
if not self.blinder:
self.unblinder = getRandomNumber(2, self.n)
self.blinder = powMod(invMod(self.unblinder, self.n), self.e,
self.n)
self.unblinder = getRandomNumber(2, n)
self.blinder = powMod(invMod(self.unblinder, n), self.e,
n)
unblinder = self.unblinder
blinder = self.blinder

# Update blinding values
self.blinder = (self.blinder * self.blinder) % self.n
self.unblinder = (self.unblinder * self.unblinder) % self.n
self.blinder = (blinder * blinder) % n
self.unblinder = (unblinder * unblinder) % n

# Blind the input
message = (message * blinder) % self.n
message = (message * blinder) % n

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

# Unblind the output
cipher = (cipher * unblinder) % self.n
cipher = (cipher * unblinder) % n

# Return the output
return cipher

def _rawPrivateKeyOpHelper(self, m):
#Non-CRT version
#c = powMod(m, self.d, self.n)

#CRT version (~3x faster)
s1 = powMod(m, self.dP, self.p)
s2 = powMod(m, self.dQ, self.q)
h = ((s1 - s2) * self.qInv) % self.p
c = s2 + self.q * h
#c = pow(m, self.d, self.n)

#CRT version (~3x faster).
p, q = self.p, self.q
s1 = pow(m, self.dP, p)
s2 = pow(m, self.dQ, q)
h = ((s1 - s2) * self.qInv) % p
c = s2 + q * h
return c

def _rawPublicKeyOp(self, ciphertext):
msg = powMod(ciphertext, self.e, self.n)
msg = pow(ciphertext, self.e, self.n)
return msg

def acceptsPassword(self):
Expand All @@ -101,9 +116,15 @@ def generate(bits, key_type="rsa"):
key = Python_RSAKey()
p = getRandomPrime(bits//2, False)
q = getRandomPrime(bits//2, False)
if gmpyLoaded or GMPY2_LOADED:
p = mpz(p)
q = mpz(q)
t = lcm(p-1, q-1)
key.n = p * q
key.e = 65537
if gmpyLoaded or GMPY2_LOADED:
key.e = mpz(65537)
else:
key.e = 65537
key.d = invMod(key.e, t)
key.p = p
key.q = q
Expand Down

0 comments on commit 4521c5c

Please sign in to comment.