pct-speedtest.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # pct-speedtest.py: Speed test for the Python Cryptography Toolkit
  5. #
  6. # Written in 2009 by Dwayne C. Litzenberger <dlitz@dlitz.net>
  7. #
  8. # ===================================================================
  9. # The contents of this file are dedicated to the public domain. To
  10. # the extent that dedication to the public domain is not available,
  11. # everyone is granted a worldwide, perpetual, royalty-free,
  12. # non-exclusive license to exercise all rights associated with the
  13. # contents of this file for any purpose whatsoever.
  14. # No rights are reserved.
  15. #
  16. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  18. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  20. # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
  21. # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  22. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23. # SOFTWARE.
  24. # ===================================================================
  25. import time
  26. import os
  27. import sys
  28. from Crypto.PublicKey import RSA
  29. from Crypto.Cipher import PKCS1_OAEP, PKCS1_v1_5 as RSAES_PKCS1_v1_5
  30. from Crypto.Signature import PKCS1_PSS, PKCS1_v1_5 as RSASSA_PKCS1_v1_5
  31. from Crypto.Cipher import (AES, ARC2, ARC4, Blowfish, CAST, DES3, DES,
  32. Salsa20, ChaCha20)
  33. from Crypto.Hash import (HMAC, MD2, MD4, MD5, SHA224, SHA256, SHA384, SHA512,
  34. CMAC, SHA3_224, SHA3_256, SHA3_384, SHA3_512,
  35. BLAKE2b, BLAKE2s)
  36. from Crypto.Random import get_random_bytes
  37. import Crypto.Util.Counter
  38. from Crypto.Util.number import bytes_to_long
  39. try:
  40. from Crypto.Hash import SHA1
  41. except ImportError:
  42. # Maybe it's called SHA
  43. from Crypto.Hash import SHA as SHA1
  44. try:
  45. from Crypto.Hash import RIPEMD160
  46. except ImportError:
  47. # Maybe it's called RIPEMD
  48. try:
  49. from Crypto.Hash import RIPEMD as RIPEMD160
  50. except ImportError:
  51. # Some builds of PyCrypto don't have the RIPEMD module
  52. RIPEMD160 = None
  53. try:
  54. import hashlib
  55. import hmac
  56. except ImportError: # Some builds/versions of Python don't have a hashlib module
  57. hashlib = hmac = None
  58. # os.urandom() is less noisy when profiling, but it doesn't exist in Python < 2.4
  59. try:
  60. urandom = os.urandom
  61. except AttributeError:
  62. urandom = get_random_bytes
  63. from Crypto.Random import random as pycrypto_random
  64. import random as stdlib_random
  65. class BLAKE2b_512(object):
  66. digest_size = 512
  67. @staticmethod
  68. def new(data=None):
  69. return BLAKE2b.new(digest_bits=512, data=data)
  70. class BLAKE2s_256(object):
  71. digest_size = 256
  72. @staticmethod
  73. def new(data=None):
  74. return BLAKE2s.new(digest_bits=256, data=data)
  75. class ChaCha20_old_style(object):
  76. @staticmethod
  77. def new(key, nonce):
  78. return ChaCha20.new(key=key, nonce=nonce)
  79. class ModeNotAvailable(ValueError):
  80. pass
  81. rng = get_random_bytes
  82. class Benchmark:
  83. def __init__(self):
  84. self.__random_data = None
  85. def random_keys(self, bytes, n=10**5):
  86. """Return random keys of the specified number of bytes.
  87. If this function has been called before with the same number of bytes,
  88. cached keys are used instead of randomly generating new ones.
  89. """
  90. return self.random_blocks(bytes, n)
  91. def random_blocks(self, bytes_per_block, blocks):
  92. bytes = bytes_per_block * blocks
  93. data = self.random_data(bytes)
  94. retval = []
  95. for i in range(blocks):
  96. p = i * bytes_per_block
  97. retval.append(data[p:p+bytes_per_block])
  98. return retval
  99. def random_data(self, bytes):
  100. if self.__random_data is None:
  101. self.__random_data = self._random_bytes(bytes)
  102. return self.__random_data
  103. elif bytes == len(self.__random_data):
  104. return self.__random_data
  105. elif bytes < len(self.__random_data):
  106. return self.__random_data[:bytes]
  107. else:
  108. self.__random_data += self._random_bytes(bytes - len(self.__random_data))
  109. return self.__random_data
  110. def _random_bytes(self, b):
  111. return urandom(b)
  112. def announce_start(self, test_name):
  113. sys.stdout.write("%s: " % (test_name,))
  114. sys.stdout.flush()
  115. def announce_result(self, value, units):
  116. sys.stdout.write("%.2f %s\n" % (value, units))
  117. sys.stdout.flush()
  118. def test_random_module(self, module_name, module):
  119. self.announce_start("%s.choice" % (module_name,))
  120. alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  121. t0 = time.time()
  122. for i in range(5000):
  123. module.choice(alphabet)
  124. t = time.time()
  125. invocations_per_second = 5000 / (t - t0)
  126. self.announce_result(invocations_per_second, "invocations/sec")
  127. def test_pubkey_setup(self, pubkey_name, module, key_bytes):
  128. self.announce_start("%s pubkey setup" % (pubkey_name,))
  129. keys = self.random_keys(key_bytes)[:5]
  130. t0 = time.time()
  131. for k in keys:
  132. module.generate(key_bytes*8)
  133. t = time.time()
  134. pubkey_setups_per_second = len(keys) / (t - t0)
  135. self.announce_result(pubkey_setups_per_second, "Keys/sec")
  136. def test_key_setup(self, cipher_name, module, key_bytes, params):
  137. self.generate_cipher(module, key_bytes, params)
  138. self.announce_start("%s key setup" % (cipher_name,))
  139. for x in xrange(5000):
  140. t0 = time.time()
  141. self.generate_cipher(module, key_bytes, params)
  142. t = time.time()
  143. key_setups_per_second = 5000 / (t - t0)
  144. self.announce_result(key_setups_per_second/1000, "kKeys/sec")
  145. def test_encryption(self, cipher_name, module, key_bytes, params):
  146. self.announce_start("%s encryption" % (cipher_name,))
  147. pt_size = 16384000L
  148. pt = rng(pt_size)
  149. cipher = self.generate_cipher(module, key_bytes, params)
  150. # Perform encryption
  151. t0 = time.time()
  152. cipher.encrypt(pt)
  153. t = time.time()
  154. encryption_speed = pt_size / (t - t0)
  155. self.announce_result(encryption_speed / 10**6, "MBps")
  156. def test_hash_small(self, hash_name, hash_constructor, digest_size):
  157. self.announce_start("%s (%d-byte inputs)" % (hash_name, digest_size))
  158. blocks = self.random_blocks(digest_size, 10000)
  159. # Initialize hashes
  160. t0 = time.time()
  161. for b in blocks:
  162. hash_constructor(b).digest()
  163. t = time.time()
  164. hashes_per_second = len(blocks) / (t - t0)
  165. self.announce_result(hashes_per_second / 1000, "kHashes/sec")
  166. def test_hash_large(self, hash_name, hash_constructor, digest_size):
  167. self.announce_start("%s (single large input)" % (hash_name,))
  168. blocks = self.random_blocks(16384, 10000)
  169. # Perform hashing
  170. t0 = time.time()
  171. h = hash_constructor()
  172. for b in blocks:
  173. h.update(b)
  174. h.digest()
  175. t = time.time()
  176. hash_speed = len(blocks) * len(blocks[0]) / (t - t0)
  177. self.announce_result(hash_speed / 10**6, "MBps")
  178. def test_hmac_small(self, mac_name, hmac_constructor, digestmod, digest_size):
  179. keys = iter(self.random_keys(digest_size))
  180. if sys.version_info[0] == 2:
  181. mac_constructor = lambda data=None: hmac_constructor(keys.next(), data, digestmod)
  182. else:
  183. mac_constructor = lambda data=None: hmac_constructor(keys.__next__(), data, digestmod)
  184. self.test_hash_small(mac_name, mac_constructor, digest_size)
  185. def test_hmac_large(self, mac_name, hmac_constructor, digestmod, digest_size):
  186. key = self.random_keys(digest_size)[0]
  187. mac_constructor = lambda data=None: hmac_constructor(key, data, digestmod)
  188. self.test_hash_large(mac_name, mac_constructor, digest_size)
  189. def test_cmac_small(self, mac_name, cmac_constructor, ciphermod, key_size):
  190. keys = iter(self.random_keys(key_size))
  191. if sys.version_info[0] == 2:
  192. mac_constructor = lambda data=None: cmac_constructor(keys.next(), data, ciphermod)
  193. else:
  194. mac_constructor = lambda data=None: cmac_constructor(keys.__next__(), data, ciphermod)
  195. self.test_hash_small(mac_name, mac_constructor, ciphermod.block_size)
  196. def test_cmac_large(self, mac_name, cmac_constructor, ciphermod, key_size):
  197. key = self.random_keys(key_size)[0]
  198. mac_constructor = lambda data=None: cmac_constructor(key, data, ciphermod)
  199. self.test_hash_large(mac_name, mac_constructor, ciphermod.block_size)
  200. def test_pkcs1_sign(self, scheme_name, scheme_constructor, hash_name, hash_constructor, digest_size):
  201. self.announce_start("%s signing %s (%d-byte inputs)" % (scheme_name, hash_name, digest_size))
  202. # Make a key
  203. k = RSA.generate(2048)
  204. sigscheme = scheme_constructor(k)
  205. # Make some hashes
  206. blocks = self.random_blocks(digest_size, 50)
  207. hashes = []
  208. for b in blocks:
  209. hashes.append(hash_constructor(b))
  210. # Perform signing
  211. t0 = time.time()
  212. for h in hashes:
  213. sigscheme.sign(h)
  214. t = time.time()
  215. speed = len(hashes) / (t - t0)
  216. self.announce_result(speed, "sigs/sec")
  217. def test_pkcs1_verify(self, scheme_name, scheme_constructor, hash_name, hash_constructor, digest_size):
  218. self.announce_start("%s verification %s (%d-byte inputs)" % (scheme_name, hash_name, digest_size))
  219. # Make a key
  220. k = RSA.generate(2048)
  221. sigscheme = scheme_constructor(k)
  222. # Make some hashes
  223. blocks = self.random_blocks(digest_size, 50)
  224. hashes = []
  225. for b in blocks:
  226. hashes.append(hash_constructor(b))
  227. # Make some signatures
  228. signatures = []
  229. for h in hashes:
  230. signatures.append(sigscheme.sign(h))
  231. # Double the list, to make timing better
  232. hashes = hashes + hashes
  233. signatures = signatures + signatures
  234. # Perform verification
  235. t0 = time.time()
  236. for h, s in zip(hashes, signatures):
  237. sigscheme.verify(h, s)
  238. t = time.time()
  239. speed = len(hashes) / (t - t0)
  240. self.announce_result(speed, "sigs/sec")
  241. def generate_cipher(self, module, key_size, params):
  242. params_dict = {}
  243. if params:
  244. params_dict = dict([x.split("=") for x in params.split(" ")])
  245. gen_tuple = []
  246. gen_dict = {}
  247. # 1st parameter (mandatory): key
  248. if params_dict.get('ks') == "x2":
  249. key = rng(2 * key_size)
  250. else:
  251. key = rng(key_size)
  252. gen_tuple.append(key)
  253. # 2nd parameter: mode
  254. mode = params_dict.get("mode")
  255. if mode:
  256. mode_value = getattr(module, mode, None)
  257. if mode_value is None:
  258. # Mode not available for this cipher
  259. raise ModeNotAvailable()
  260. gen_tuple.append(getattr(module, mode))
  261. # 3rd parameter: IV/nonce
  262. iv_length = params_dict.get("iv")
  263. if iv_length is None:
  264. iv_length = params_dict.get("nonce")
  265. if iv_length:
  266. if iv_length == "bs":
  267. iv_length = module.block_size
  268. iv = rng(int(iv_length))
  269. gen_tuple.append(iv)
  270. # Specific to CTR mode
  271. le = params_dict.get("little_endian")
  272. if le:
  273. if le == "True":
  274. le = True
  275. else:
  276. le = False
  277. # Remove iv from parameters
  278. gen_tuple = gen_tuple[:-1]
  279. ctr = Crypto.Util.Counter.new(module.block_size*8,
  280. initial_value=bytes_to_long(iv),
  281. little_endian=le,
  282. allow_wraparound=True)
  283. gen_dict['counter'] = ctr
  284. # Generate cipher
  285. return module.new(*gen_tuple, **gen_dict)
  286. def run(self):
  287. pubkey_specs = [
  288. ("RSA(1024)", RSA, int(1024/8)),
  289. ("RSA(2048)", RSA, int(2048/8)),
  290. ("RSA(4096)", RSA, int(4096/8)),
  291. ]
  292. block_cipher_modes = [
  293. # Mode name, key setup, parameters
  294. ("CBC", True, "mode=MODE_CBC iv=bs"),
  295. ("CFB-8", False, "mode=MODE_CFB iv=bs"),
  296. ("OFB", False, "mode=MODE_OFB iv=bs"),
  297. ("ECB", False, "mode=MODE_ECB"),
  298. ("CTR-LE", True, "mode=MODE_CTR iv=bs little_endian=True"),
  299. ("CTR-BE", False, "mode=MODE_CTR iv=bs little_endian=False"),
  300. ("OPENPGP", False, "mode=MODE_OPENPGP iv=bs"),
  301. ("CCM", True, "mode=MODE_CCM nonce=12"),
  302. ("GCM", True, "mode=MODE_GCM nonce=16"),
  303. ("EAX", True, "mode=MODE_EAX nonce=16"),
  304. ("SIV", True, "mode=MODE_SIV ks=x2 nonce=16"),
  305. ("OCB", True, "mode=MODE_OCB nonce=15"),
  306. ]
  307. block_specs = [
  308. # Cipher name, module, key size
  309. ("DES", DES, 8),
  310. ("DES3", DES3, 24),
  311. ("AES128", AES, 16),
  312. ("AES192", AES, 24),
  313. ("AES256", AES, 32),
  314. ("Blowfish(256)", Blowfish, 32),
  315. ("CAST(128)", CAST, 16),
  316. ("ARC2(128)", ARC2, 16),
  317. ]
  318. stream_specs = [
  319. # Cipher name, module, key size, nonce size
  320. ("ARC4(128)", ARC4, 16, 0),
  321. ("Salsa20(16)", Salsa20, 16, 8),
  322. ("Salsa20(32)", Salsa20, 32, 8),
  323. ("ChaCha20", ChaCha20_old_style, 32, 8),
  324. ]
  325. hash_specs = [
  326. ("MD2", MD2),
  327. ("MD4", MD4),
  328. ("MD5", MD5),
  329. ("SHA1", SHA1),
  330. ("SHA224", SHA224),
  331. ("SHA256", SHA256),
  332. ("SHA384", SHA384),
  333. ("SHA512", SHA512),
  334. ("SHA3_224", SHA3_224),
  335. ("SHA3_256", SHA3_256),
  336. ("SHA3_384", SHA3_384),
  337. ("SHA3_512", SHA3_512),
  338. ("BLAKE2b", BLAKE2b_512),
  339. ("BLAKE2s", BLAKE2s_256),
  340. ]
  341. if RIPEMD160 is not None:
  342. hash_specs += [("RIPEMD160", RIPEMD160)]
  343. hashlib_specs = []
  344. if hashlib is not None:
  345. if hasattr(hashlib, 'md5'): hashlib_specs.append(("hashlib.md5", hashlib.md5))
  346. if hasattr(hashlib, 'sha1'): hashlib_specs.append(("hashlib.sha1", hashlib.sha1))
  347. if hasattr(hashlib, 'sha224'): hashlib_specs.append(("hashlib.sha224", hashlib.sha224))
  348. if hasattr(hashlib, 'sha256'): hashlib_specs.append(("hashlib.sha256", hashlib.sha256))
  349. if hasattr(hashlib, 'sha384'): hashlib_specs.append(("hashlib.sha384", hashlib.sha384))
  350. if hasattr(hashlib, 'sha512'): hashlib_specs.append(("hashlib.sha512", hashlib.sha512))
  351. # stdlib random
  352. self.test_random_module("stdlib random", stdlib_random)
  353. # Crypto.Random.random
  354. self.test_random_module("Crypto.Random.random", pycrypto_random)
  355. # Crypto.PublicKey
  356. for pubkey_name, module, key_bytes in pubkey_specs:
  357. self.test_pubkey_setup(pubkey_name, module, key_bytes)
  358. # Crypto.Cipher (block ciphers)
  359. for cipher_name, module, key_bytes in block_specs:
  360. # Benchmark each cipher in each of the various modes (CBC, etc)
  361. for mode_name, test_ks, params in block_cipher_modes:
  362. mode_text = "%s-%s" % (cipher_name, mode_name)
  363. try:
  364. if test_ks:
  365. self.test_key_setup(mode_text, module, key_bytes, params)
  366. self.test_encryption(mode_text, module, key_bytes, params)
  367. except ModeNotAvailable as e:
  368. pass
  369. # Crypto.Cipher (stream ciphers)
  370. for cipher_name, module, key_bytes, nonce_bytes in stream_specs:
  371. params = ""
  372. if nonce_bytes:
  373. params = "nonce=" + str(nonce_bytes)
  374. self.test_key_setup(cipher_name, module, key_bytes, params)
  375. self.test_encryption(cipher_name, module, key_bytes, params)
  376. # Crypto.Hash
  377. for hash_name, module in hash_specs:
  378. self.test_hash_small(hash_name, module.new, module.digest_size)
  379. self.test_hash_large(hash_name, module.new, module.digest_size)
  380. # standard hashlib
  381. for hash_name, func in hashlib_specs:
  382. self.test_hash_small(hash_name, func, func().digest_size)
  383. self.test_hash_large(hash_name, func, func().digest_size)
  384. # PyCrypto HMAC
  385. for hash_name, module in hash_specs:
  386. if not hasattr(module, "block_size"):
  387. continue
  388. self.test_hmac_small("HMAC-"+hash_name, HMAC.new, module, module.digest_size)
  389. self.test_hmac_large("HMAC-"+hash_name, HMAC.new, module, module.digest_size)
  390. # standard hmac + hashlib
  391. for hash_name, func in hashlib_specs:
  392. if not hasattr(module, "block_size"):
  393. continue
  394. self.test_hmac_small("hmac+"+hash_name, hmac.HMAC, func, func().digest_size)
  395. self.test_hmac_large("hmac+"+hash_name, hmac.HMAC, func, func().digest_size)
  396. # CMAC
  397. for cipher_name, module, key_size in (("AES128", AES, 16),):
  398. self.test_cmac_small(cipher_name+"-CMAC", CMAC.new, module, key_size)
  399. self.test_cmac_large(cipher_name+"-CMAC", CMAC.new, module, key_size)
  400. # PKCS1_v1_5 (sign) + Crypto.Hash
  401. for hash_name, module in hash_specs:
  402. self.test_pkcs1_sign("PKCS#1-v1.5", RSASSA_PKCS1_v1_5.new, hash_name, module.new, module.digest_size)
  403. # PKCS1_PSS (sign) + Crypto.Hash
  404. for hash_name, module in hash_specs:
  405. self.test_pkcs1_sign("PKCS#1-PSS", PKCS1_PSS.new, hash_name, module.new, module.digest_size)
  406. # PKCS1_v1_5 (verify) + Crypto.Hash
  407. for hash_name, module in hash_specs:
  408. self.test_pkcs1_verify("PKCS#1-v1.5", RSASSA_PKCS1_v1_5.new, hash_name, module.new, module.digest_size)
  409. # PKCS1_PSS (verify) + Crypto.Hash
  410. for hash_name, module in hash_specs:
  411. self.test_pkcs1_verify("PKCS#1-PSS", PKCS1_PSS.new, hash_name, module.new, module.digest_size)
  412. if __name__ == '__main__':
  413. Benchmark().run()
  414. # vim:set ts=4 sw=4 sts=4 expandtab: