pycryptodome/pct-speedtest.py

222 lines
7.7 KiB
Python
Raw Normal View History

2009-03-11 00:10:04 -04:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# pct-speedtest.py: Speed test for the Python Cryptography Toolkit
#
# Written in 2009 by Dwayne C. Litzenberger <dlitz@dlitz.net>
#
# ===================================================================
# The contents of this file are dedicated to the public domain. To
# the extent that dedication to the public domain is not available,
# everyone is granted a worldwide, perpetual, royalty-free,
# non-exclusive license to exercise all rights associated with the
# contents of this file for any purpose whatsoever.
# No rights are reserved.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# ===================================================================
import time
import os
import sys
getStrongPrime() implementation From http://lists.dlitz.net/pipermail/pycrypto/2009q4/000167.html, with the following explanation included in the email: === snip === Hi there! Here comes my monster patch. It includes a python and C version of getStrongPrime, rabinMillerTest and isPrime. there are also two small unit tests and some helper functions. They all take a randfunc and propagate them (or so I hope). The Rabin-Miller-Test uses random bases (non-deterministic). getStrongPrime and isPrime take an optional parameter "false_positive_prob" where one can specify the maximum probability that the prime is actually composite. Internally the functions calculate the Rabin-Miller rounds from this. It defaults to 1e-6 (1:1000000) which results in 10 rounds of Rabin-Miller testing. Please review this carefully. Even though I tried hard to get things right some bugs always slip through. maybe you could also review the way I acquire and release the GIL. It felt kind of ugly the way I did it but I don't see a better way just now. Concerning the public exponent e: I now know why it needs to be coprime to p-1 and q-1. The private exponent d is the inverse of e mod ((p-1)(q-1)). If e is not coprime to ((p-1)(q-1)) then the inverse does not exist [1]. The getStrongPrime take an optional argument e. if provided the function will make sure p-1 and e are coprime. if e is even (p-1)/2 will be coprime. if e is even then there is a additional constraint: p =/= q mod 8. I can't check for that in getStrongPrime of course but since we hardcoded e to be odd in _RSA.py this should pose no problem. The Baillie-PSW-Test is not included. I tried hard not to use any functionality new than 2.1 but if you find anything feel free to criticize. Also if I didn't get the coding style right either tell me or feel free to correct it yourself. have fun. //Lorenz [1] http://mathworld.wolfram.com/ModularInverse.html === snip ===
2010-06-10 21:02:07 -04:00
from Crypto.PublicKey import RSA
from Crypto.Cipher import AES, ARC2, ARC4, Blowfish, CAST, DES3, DES, XOR
2009-03-11 00:10:04 -04:00
from Crypto.Hash import MD2, MD4, MD5, SHA256, SHA
try:
from Crypto.Hash import RIPEMD
except ImportError: # Some builds of PyCrypto don't have the RIPEMD module
RIPEMD = None
class Benchmark:
def __init__(self):
self.__random_data = None
def random_keys(self, bytes):
"""Return random keys of the specified number of bytes.
If this function has been called before with the same number of bytes,
cached keys are used instead of randomly generating new ones.
"""
return self.random_blocks(bytes, 10**5) # 100k
def random_blocks(self, bytes_per_block, blocks):
bytes = bytes_per_block * blocks
data = self.random_data(bytes)
retval = []
for i in xrange(blocks):
p = i * bytes_per_block
retval.append(data[p:p+bytes_per_block])
return retval
def random_data(self, bytes):
if self.__random_data is None:
self.__random_data = self._random_bytes(bytes)
return self.__random_data
elif bytes == len(self.__random_data):
return self.__random_data
elif bytes < len(self.__random_data):
return self.__random_data[:bytes]
else:
self.__random_data += self._random_bytes(bytes - len(self.__random_data))
return self.__random_data
def _random_bytes(self, b):
return os.urandom(b)
def announce_start(self, test_name):
sys.stdout.write("%s: " % (test_name,))
sys.stdout.flush()
def announce_result(self, value, units):
sys.stdout.write("%.2f %s\n" % (value, units))
sys.stdout.flush()
getStrongPrime() implementation From http://lists.dlitz.net/pipermail/pycrypto/2009q4/000167.html, with the following explanation included in the email: === snip === Hi there! Here comes my monster patch. It includes a python and C version of getStrongPrime, rabinMillerTest and isPrime. there are also two small unit tests and some helper functions. They all take a randfunc and propagate them (or so I hope). The Rabin-Miller-Test uses random bases (non-deterministic). getStrongPrime and isPrime take an optional parameter "false_positive_prob" where one can specify the maximum probability that the prime is actually composite. Internally the functions calculate the Rabin-Miller rounds from this. It defaults to 1e-6 (1:1000000) which results in 10 rounds of Rabin-Miller testing. Please review this carefully. Even though I tried hard to get things right some bugs always slip through. maybe you could also review the way I acquire and release the GIL. It felt kind of ugly the way I did it but I don't see a better way just now. Concerning the public exponent e: I now know why it needs to be coprime to p-1 and q-1. The private exponent d is the inverse of e mod ((p-1)(q-1)). If e is not coprime to ((p-1)(q-1)) then the inverse does not exist [1]. The getStrongPrime take an optional argument e. if provided the function will make sure p-1 and e are coprime. if e is even (p-1)/2 will be coprime. if e is even then there is a additional constraint: p =/= q mod 8. I can't check for that in getStrongPrime of course but since we hardcoded e to be odd in _RSA.py this should pose no problem. The Baillie-PSW-Test is not included. I tried hard not to use any functionality new than 2.1 but if you find anything feel free to criticize. Also if I didn't get the coding style right either tell me or feel free to correct it yourself. have fun. //Lorenz [1] http://mathworld.wolfram.com/ModularInverse.html === snip ===
2010-06-10 21:02:07 -04:00
def test_pubkey_setup(self, pubkey_name, module, key_bytes):
self.announce_start("%s pubkey setup" % (pubkey_name,))
keys = self.random_keys(key_bytes)[:5]
t0 = time.time()
for k in keys:
module.generate(key_bytes*8)
t = time.time()
pubkey_setups_per_second = len(keys) / (t - t0)
self.announce_result(pubkey_setups_per_second, "Keys/sec")
2009-03-11 00:10:04 -04:00
def test_key_setup(self, cipher_name, module, key_bytes, mode):
self.announce_start("%s key setup" % (cipher_name,))
# Generate random keys for use with the tests
keys = self.random_keys(key_bytes)
# Perform key setups
if mode is None:
t0 = time.time()
for k in keys:
module.new(k)
t = time.time()
else:
t0 = time.time()
for k in keys:
module.new(k, module.MODE_ECB)
t = time.time()
key_setups_per_second = len(keys) / (t - t0)
self.announce_result(key_setups_per_second/1000, "kKeys/sec")
def test_encryption(self, cipher_name, module, key_bytes, mode):
self.announce_start("%s encryption" % (cipher_name,))
# Generate random keys for use with the tests
rand = self.random_data(key_bytes + module.block_size)
key, iv = rand[:key_bytes], rand[key_bytes:]
blocks = self.random_blocks(16384, 1000)
if mode is None:
cipher = module.new(key)
else:
cipher = module.new(key, mode, iv)
# Perform encryption
t0 = time.time()
for b in blocks:
cipher.encrypt(b)
t = time.time()
encryption_speed = (len(blocks) * len(blocks[0])) / (t - t0)
self.announce_result(encryption_speed / 10**6, "MBps")
def test_hash_small(self, hash_name, module):
self.announce_start("%s (%d-byte inputs)" % (hash_name, module.digest_size))
blocks = self.random_blocks(module.digest_size, 10000)
# Initialize hashes
t0 = time.time()
for b in blocks:
module.new(b).digest()
t = time.time()
hashes_per_second = len(blocks) / (t - t0)
self.announce_result(hashes_per_second / 1000, "kHashes/sec")
def test_hash_large(self, hash_name, module):
self.announce_start("%s (single large input)" % (hash_name,))
blocks = self.random_blocks(16384, 10000)
# Perform hashing
t0 = time.time()
h = module.new()
for b in blocks:
h.update(b)
h.digest()
t = time.time()
hash_speed = len(blocks) * len(blocks[0]) / (t - t0)
self.announce_result(hash_speed / 10**6, "MBps")
def run(self):
getStrongPrime() implementation From http://lists.dlitz.net/pipermail/pycrypto/2009q4/000167.html, with the following explanation included in the email: === snip === Hi there! Here comes my monster patch. It includes a python and C version of getStrongPrime, rabinMillerTest and isPrime. there are also two small unit tests and some helper functions. They all take a randfunc and propagate them (or so I hope). The Rabin-Miller-Test uses random bases (non-deterministic). getStrongPrime and isPrime take an optional parameter "false_positive_prob" where one can specify the maximum probability that the prime is actually composite. Internally the functions calculate the Rabin-Miller rounds from this. It defaults to 1e-6 (1:1000000) which results in 10 rounds of Rabin-Miller testing. Please review this carefully. Even though I tried hard to get things right some bugs always slip through. maybe you could also review the way I acquire and release the GIL. It felt kind of ugly the way I did it but I don't see a better way just now. Concerning the public exponent e: I now know why it needs to be coprime to p-1 and q-1. The private exponent d is the inverse of e mod ((p-1)(q-1)). If e is not coprime to ((p-1)(q-1)) then the inverse does not exist [1]. The getStrongPrime take an optional argument e. if provided the function will make sure p-1 and e are coprime. if e is even (p-1)/2 will be coprime. if e is even then there is a additional constraint: p =/= q mod 8. I can't check for that in getStrongPrime of course but since we hardcoded e to be odd in _RSA.py this should pose no problem. The Baillie-PSW-Test is not included. I tried hard not to use any functionality new than 2.1 but if you find anything feel free to criticize. Also if I didn't get the coding style right either tell me or feel free to correct it yourself. have fun. //Lorenz [1] http://mathworld.wolfram.com/ModularInverse.html === snip ===
2010-06-10 21:02:07 -04:00
pubkey_specs = [
("RSA(1024)", RSA, 1024/8),
("RSA(2048)", RSA, 2048/8),
("RSA(4096)", RSA, 4096/8),
]
2009-03-11 00:10:04 -04:00
block_specs = [
("DES", DES, 8),
("DES3", DES3, 24),
("AES128", AES, 16),
("AES192", AES, 24),
("AES256", AES, 32),
("Blowfish(256)", Blowfish, 32),
("CAST(40)", CAST, 5),
("CAST(80)", CAST, 10),
("CAST(128)", CAST, 16),
]
stream_specs = [
("ARC2(128)", ARC2, 16),
("ARC4(128)", ARC4, 16),
("XOR(24)", XOR, 3),
("XOR(256)", XOR, 32),
2009-03-11 00:10:04 -04:00
]
hash_specs = [
("MD2", MD2),
("MD4", MD4),
("MD5", MD5),
("SHA", SHA),
("SHA256", SHA256),
]
if RIPEMD is not None:
hash_specs += [("RIPEMD", RIPEMD)]
getStrongPrime() implementation From http://lists.dlitz.net/pipermail/pycrypto/2009q4/000167.html, with the following explanation included in the email: === snip === Hi there! Here comes my monster patch. It includes a python and C version of getStrongPrime, rabinMillerTest and isPrime. there are also two small unit tests and some helper functions. They all take a randfunc and propagate them (or so I hope). The Rabin-Miller-Test uses random bases (non-deterministic). getStrongPrime and isPrime take an optional parameter "false_positive_prob" where one can specify the maximum probability that the prime is actually composite. Internally the functions calculate the Rabin-Miller rounds from this. It defaults to 1e-6 (1:1000000) which results in 10 rounds of Rabin-Miller testing. Please review this carefully. Even though I tried hard to get things right some bugs always slip through. maybe you could also review the way I acquire and release the GIL. It felt kind of ugly the way I did it but I don't see a better way just now. Concerning the public exponent e: I now know why it needs to be coprime to p-1 and q-1. The private exponent d is the inverse of e mod ((p-1)(q-1)). If e is not coprime to ((p-1)(q-1)) then the inverse does not exist [1]. The getStrongPrime take an optional argument e. if provided the function will make sure p-1 and e are coprime. if e is even (p-1)/2 will be coprime. if e is even then there is a additional constraint: p =/= q mod 8. I can't check for that in getStrongPrime of course but since we hardcoded e to be odd in _RSA.py this should pose no problem. The Baillie-PSW-Test is not included. I tried hard not to use any functionality new than 2.1 but if you find anything feel free to criticize. Also if I didn't get the coding style right either tell me or feel free to correct it yourself. have fun. //Lorenz [1] http://mathworld.wolfram.com/ModularInverse.html === snip ===
2010-06-10 21:02:07 -04:00
for pubkey_name, module, key_bytes in pubkey_specs:
self.test_pubkey_setup(pubkey_name, module, key_bytes)
2009-03-11 00:10:04 -04:00
for cipher_name, module, key_bytes in block_specs:
self.test_key_setup(cipher_name, module, key_bytes, module.MODE_CBC)
self.test_encryption("%s-CBC" % (cipher_name,), module, key_bytes, module.MODE_CBC)
self.test_encryption("%s-CFB-8" % (cipher_name,), module, key_bytes, module.MODE_CFB)
2009-03-11 00:10:04 -04:00
self.test_encryption("%s-OFB" % (cipher_name,), module, key_bytes, module.MODE_OFB)
2012-01-04 16:58:55 +08:00
self.test_encryption("%s-ECB" % (cipher_name,), module, key_bytes, module.MODE_ECB)
2012-05-17 22:46:14 +02:00
self.test_encryption("%s-OPENPGP" % (cipher_name,), module, key_bytes, module.MODE_OPENPGP)
2009-03-11 00:10:04 -04:00
for cipher_name, module, key_bytes in stream_specs:
self.test_key_setup(cipher_name, module, key_bytes, None)
self.test_encryption(cipher_name, module, key_bytes, None)
for hash_name, module in hash_specs:
self.test_hash_small(hash_name, module)
self.test_hash_large(hash_name, module)
if __name__ == '__main__':
Benchmark().run()
# vim:set ts=4 sw=4 sts=4 expandtab: