From d957b241c792b089334abf2bb87c4a1e0576bd86 Mon Sep 17 00:00:00 2001 From: Paul Scarrone Date: Tue, 15 Jan 2013 11:15:34 -0500 Subject: [PATCH 01/12] Added local paramiko and pycrypto. Began threading paramiko into the connect process Now connects using paramiko added password tracking. Need to interface overtop the send methods for paramiko General implementation works with login CD and LS. Some hooks after initial LS do not function Allowed for correct closing of transport and sftp threads Added way to force display of directory on each connection. SSH transports are no longer lost Created new cleanls to work from paramiko Path corrections Opening file does not update search path Deleted some ugly logging statementsw Refactored and corrected folder file identification for paramiko ls results assured that thread is stopped on disconnect Almost fully functional demo First Testing version working on osx Split logic for posix in download localpath variable removed a bunch of print statements --- Mote.py | 214 +- lib/Crypto/Cipher/AES.py | 114 + lib/Crypto/Cipher/ARC2.py | 129 + lib/Crypto/Cipher/ARC4.py | 141 ++ lib/Crypto/Cipher/Blowfish.py | 120 + lib/Crypto/Cipher/CAST.py | 122 + lib/Crypto/Cipher/DES.py | 117 + lib/Crypto/Cipher/DES3.py | 132 ++ lib/Crypto/Cipher/PKCS1_OAEP.py | 255 ++ lib/Crypto/Cipher/PKCS1_v1_5.py | 226 ++ lib/Crypto/Cipher/XOR.py | 86 + lib/Crypto/Cipher/_AES.so | Bin 0 -> 68712 bytes lib/Crypto/Cipher/_ARC2.so | Bin 0 -> 43896 bytes lib/Crypto/Cipher/_ARC4.so | Bin 0 -> 26676 bytes lib/Crypto/Cipher/_Blowfish.so | Bin 0 -> 52272 bytes lib/Crypto/Cipher/_CAST.so | Bin 0 -> 60360 bytes lib/Crypto/Cipher/_DES.so | Bin 0 -> 114012 bytes lib/Crypto/Cipher/_DES3.so | Bin 0 -> 118108 bytes lib/Crypto/Cipher/_XOR.so | Bin 0 -> 26692 bytes lib/Crypto/Cipher/__init__.py | 83 + lib/Crypto/Cipher/blockalgo.py | 296 +++ lib/Crypto/Hash/HMAC.py | 212 ++ lib/Crypto/Hash/MD2.py | 91 + lib/Crypto/Hash/MD4.py | 91 + lib/Crypto/Hash/MD5.py | 97 + lib/Crypto/Hash/RIPEMD.py | 94 + lib/Crypto/Hash/SHA.py | 98 + lib/Crypto/Hash/SHA224.py | 95 + lib/Crypto/Hash/SHA256.py | 95 + lib/Crypto/Hash/SHA384.py | 96 + lib/Crypto/Hash/SHA512.py | 95 + lib/Crypto/Hash/_MD2.so | Bin 0 -> 30620 bytes lib/Crypto/Hash/_MD4.so | Bin 0 -> 34780 bytes lib/Crypto/Hash/_RIPEMD160.so | Bin 0 -> 35168 bytes lib/Crypto/Hash/_SHA224.so | Bin 0 -> 34812 bytes lib/Crypto/Hash/_SHA256.so | Bin 0 -> 34812 bytes lib/Crypto/Hash/_SHA384.so | Bin 0 -> 34860 bytes lib/Crypto/Hash/_SHA512.so | Bin 0 -> 34860 bytes lib/Crypto/Hash/__init__.py | 56 + lib/Crypto/Hash/hashalgo.py | 116 + lib/Crypto/Protocol/AllOrNothing.py | 319 +++ lib/Crypto/Protocol/Chaffing.py | 245 ++ lib/Crypto/Protocol/KDF.py | 123 + lib/Crypto/Protocol/__init__.py | 41 + lib/Crypto/PublicKey/DSA.py | 379 +++ lib/Crypto/PublicKey/ElGamal.py | 373 +++ lib/Crypto/PublicKey/RSA.py | 719 ++++++ lib/Crypto/PublicKey/_DSA.py | 115 + lib/Crypto/PublicKey/_RSA.py | 81 + lib/Crypto/PublicKey/__init__.py | 41 + lib/Crypto/PublicKey/_slowmath.py | 187 ++ lib/Crypto/PublicKey/pubkey.py | 240 ++ .../Random/Fortuna/FortunaAccumulator.py | 145 ++ lib/Crypto/Random/Fortuna/FortunaGenerator.py | 132 ++ lib/Crypto/Random/Fortuna/SHAd256.py | 98 + lib/Crypto/Random/Fortuna/__init__.py | 0 lib/Crypto/Random/OSRNG/__init__.py | 40 + lib/Crypto/Random/OSRNG/fallback.py | 46 + lib/Crypto/Random/OSRNG/nt.py | 74 + lib/Crypto/Random/OSRNG/posix.py | 86 + lib/Crypto/Random/OSRNG/rng_base.py | 88 + lib/Crypto/Random/_UserFriendlyRNG.py | 215 ++ lib/Crypto/Random/__init__.py | 43 + lib/Crypto/Random/random.py | 142 ++ lib/Crypto/SelfTest/Cipher/__init__.py | 48 + lib/Crypto/SelfTest/Cipher/common.py | 399 ++++ lib/Crypto/SelfTest/Cipher/test_AES.py | 1433 +++++++++++ lib/Crypto/SelfTest/Cipher/test_ARC2.py | 124 + lib/Crypto/SelfTest/Cipher/test_ARC4.py | 457 ++++ lib/Crypto/SelfTest/Cipher/test_Blowfish.py | 113 + lib/Crypto/SelfTest/Cipher/test_CAST.py | 57 + lib/Crypto/SelfTest/Cipher/test_DES.py | 339 +++ lib/Crypto/SelfTest/Cipher/test_DES3.py | 333 +++ lib/Crypto/SelfTest/Cipher/test_XOR.py | 72 + lib/Crypto/SelfTest/Cipher/test_pkcs1_15.py | 174 ++ lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py | 373 +++ lib/Crypto/SelfTest/Hash/__init__.py | 52 + lib/Crypto/SelfTest/Hash/common.py | 197 ++ lib/Crypto/SelfTest/Hash/test_HMAC.py | 223 ++ lib/Crypto/SelfTest/Hash/test_MD2.py | 64 + lib/Crypto/SelfTest/Hash/test_MD4.py | 64 + lib/Crypto/SelfTest/Hash/test_MD5.py | 64 + lib/Crypto/SelfTest/Hash/test_RIPEMD.py | 73 + lib/Crypto/SelfTest/Hash/test_SHA.py | 64 + lib/Crypto/SelfTest/Hash/test_SHA224.py | 65 + lib/Crypto/SelfTest/Hash/test_SHA256.py | 96 + lib/Crypto/SelfTest/Hash/test_SHA384.py | 63 + lib/Crypto/SelfTest/Hash/test_SHA512.py | 60 + lib/Crypto/SelfTest/Protocol/__init__.py | 41 + .../SelfTest/Protocol/test_AllOrNothing.py | 76 + lib/Crypto/SelfTest/Protocol/test_KDF.py | 98 + lib/Crypto/SelfTest/Protocol/test_chaffing.py | 74 + lib/Crypto/SelfTest/Protocol/test_rfc1751.py | 62 + lib/Crypto/SelfTest/PublicKey/__init__.py | 44 + lib/Crypto/SelfTest/PublicKey/test_DSA.py | 244 ++ lib/Crypto/SelfTest/PublicKey/test_ElGamal.py | 210 ++ lib/Crypto/SelfTest/PublicKey/test_RSA.py | 415 ++++ .../SelfTest/PublicKey/test_importKey.py | 349 +++ .../SelfTest/Random/Fortuna/__init__.py | 44 + .../Random/Fortuna/test_FortunaAccumulator.py | 189 ++ .../Random/Fortuna/test_FortunaGenerator.py | 83 + .../SelfTest/Random/Fortuna/test_SHAd256.py | 55 + lib/Crypto/SelfTest/Random/OSRNG/__init__.py | 49 + .../SelfTest/Random/OSRNG/test_fallback.py | 48 + .../SelfTest/Random/OSRNG/test_generic.py | 48 + lib/Crypto/SelfTest/Random/OSRNG/test_nt.py | 48 + .../SelfTest/Random/OSRNG/test_posix.py | 48 + .../SelfTest/Random/OSRNG/test_winrandom.py | 48 + lib/Crypto/SelfTest/Random/__init__.py | 42 + lib/Crypto/SelfTest/Random/test_random.py | 171 ++ .../SelfTest/Random/test_rpoolcompat.py | 55 + lib/Crypto/SelfTest/Signature/__init__.py | 40 + .../SelfTest/Signature/test_pkcs1_15.py | 219 ++ .../SelfTest/Signature/test_pkcs1_pss.py | 446 ++++ lib/Crypto/SelfTest/Util/__init__.py | 44 + lib/Crypto/SelfTest/Util/test_Counter.py | 165 ++ lib/Crypto/SelfTest/Util/test_asn1.py | 293 +++ lib/Crypto/SelfTest/Util/test_number.py | 343 +++ lib/Crypto/SelfTest/Util/test_winrandom.py | 48 + lib/Crypto/SelfTest/__init__.py | 92 + lib/Crypto/SelfTest/st_common.py | 62 + lib/Crypto/Signature/PKCS1_PSS.py | 355 +++ lib/Crypto/Signature/PKCS1_v1_5.py | 236 ++ lib/Crypto/Signature/__init__.py | 31 + lib/Crypto/Util/Counter.py | 128 + lib/Crypto/Util/RFC1751.py | 364 +++ lib/Crypto/Util/__init__.py | 38 + lib/Crypto/Util/_counter.so | Bin 0 -> 35072 bytes lib/Crypto/Util/_number_new.py | 119 + lib/Crypto/Util/asn1.py | 286 +++ lib/Crypto/Util/number.py | 1456 ++++++++++++ lib/Crypto/Util/py21compat.py | 84 + lib/Crypto/Util/py3compat.py | 107 + lib/Crypto/Util/randpool.py | 82 + lib/Crypto/Util/strxor.so | Bin 0 -> 25724 bytes lib/Crypto/Util/winrandom.py | 28 + lib/Crypto/__init__.py | 51 + lib/Crypto/pct_warnings.py | 60 + lib/paramiko/__init__.py | 141 ++ lib/paramiko/agent.py | 153 ++ lib/paramiko/auth_handler.py | 426 ++++ lib/paramiko/ber.py | 129 + lib/paramiko/buffered_pipe.py | 200 ++ lib/paramiko/channel.py | 1234 ++++++++++ lib/paramiko/client.py | 498 ++++ lib/paramiko/common.py | 126 + lib/paramiko/compress.py | 39 + lib/paramiko/config.py | 110 + lib/paramiko/dsskey.py | 196 ++ lib/paramiko/file.py | 456 ++++ lib/paramiko/hostkeys.py | 316 +++ lib/paramiko/kex_gex.py | 243 ++ lib/paramiko/kex_group1.py | 135 ++ lib/paramiko/logging22.py | 66 + lib/paramiko/message.py | 301 +++ lib/paramiko/packet.py | 485 ++++ lib/paramiko/pipe.py | 147 ++ lib/paramiko/pkey.py | 381 +++ lib/paramiko/primes.py | 151 ++ lib/paramiko/resource.py | 72 + lib/paramiko/rsakey.py | 185 ++ lib/paramiko/server.py | 632 +++++ lib/paramiko/sftp.py | 188 ++ lib/paramiko/sftp_attr.py | 223 ++ lib/paramiko/sftp_client.py | 733 ++++++ lib/paramiko/sftp_file.py | 476 ++++ lib/paramiko/sftp_handle.py | 202 ++ lib/paramiko/sftp_server.py | 444 ++++ lib/paramiko/sftp_si.py | 310 +++ lib/paramiko/ssh_exception.py | 115 + lib/paramiko/transport.py | 2098 +++++++++++++++++ lib/paramiko/util.py | 302 +++ lib/paramiko/win_pageant.py | 148 ++ 173 files changed, 31987 insertions(+), 42 deletions(-) create mode 100644 lib/Crypto/Cipher/AES.py create mode 100644 lib/Crypto/Cipher/ARC2.py create mode 100644 lib/Crypto/Cipher/ARC4.py create mode 100644 lib/Crypto/Cipher/Blowfish.py create mode 100644 lib/Crypto/Cipher/CAST.py create mode 100644 lib/Crypto/Cipher/DES.py create mode 100644 lib/Crypto/Cipher/DES3.py create mode 100644 lib/Crypto/Cipher/PKCS1_OAEP.py create mode 100644 lib/Crypto/Cipher/PKCS1_v1_5.py create mode 100644 lib/Crypto/Cipher/XOR.py create mode 100755 lib/Crypto/Cipher/_AES.so create mode 100755 lib/Crypto/Cipher/_ARC2.so create mode 100755 lib/Crypto/Cipher/_ARC4.so create mode 100755 lib/Crypto/Cipher/_Blowfish.so create mode 100755 lib/Crypto/Cipher/_CAST.so create mode 100755 lib/Crypto/Cipher/_DES.so create mode 100755 lib/Crypto/Cipher/_DES3.so create mode 100755 lib/Crypto/Cipher/_XOR.so create mode 100644 lib/Crypto/Cipher/__init__.py create mode 100644 lib/Crypto/Cipher/blockalgo.py create mode 100644 lib/Crypto/Hash/HMAC.py create mode 100644 lib/Crypto/Hash/MD2.py create mode 100644 lib/Crypto/Hash/MD4.py create mode 100644 lib/Crypto/Hash/MD5.py create mode 100644 lib/Crypto/Hash/RIPEMD.py create mode 100644 lib/Crypto/Hash/SHA.py create mode 100644 lib/Crypto/Hash/SHA224.py create mode 100644 lib/Crypto/Hash/SHA256.py create mode 100644 lib/Crypto/Hash/SHA384.py create mode 100644 lib/Crypto/Hash/SHA512.py create mode 100755 lib/Crypto/Hash/_MD2.so create mode 100755 lib/Crypto/Hash/_MD4.so create mode 100755 lib/Crypto/Hash/_RIPEMD160.so create mode 100755 lib/Crypto/Hash/_SHA224.so create mode 100755 lib/Crypto/Hash/_SHA256.so create mode 100755 lib/Crypto/Hash/_SHA384.so create mode 100755 lib/Crypto/Hash/_SHA512.so create mode 100644 lib/Crypto/Hash/__init__.py create mode 100644 lib/Crypto/Hash/hashalgo.py create mode 100644 lib/Crypto/Protocol/AllOrNothing.py create mode 100644 lib/Crypto/Protocol/Chaffing.py create mode 100644 lib/Crypto/Protocol/KDF.py create mode 100644 lib/Crypto/Protocol/__init__.py create mode 100644 lib/Crypto/PublicKey/DSA.py create mode 100644 lib/Crypto/PublicKey/ElGamal.py create mode 100644 lib/Crypto/PublicKey/RSA.py create mode 100644 lib/Crypto/PublicKey/_DSA.py create mode 100644 lib/Crypto/PublicKey/_RSA.py create mode 100644 lib/Crypto/PublicKey/__init__.py create mode 100644 lib/Crypto/PublicKey/_slowmath.py create mode 100644 lib/Crypto/PublicKey/pubkey.py create mode 100644 lib/Crypto/Random/Fortuna/FortunaAccumulator.py create mode 100644 lib/Crypto/Random/Fortuna/FortunaGenerator.py create mode 100644 lib/Crypto/Random/Fortuna/SHAd256.py create mode 100644 lib/Crypto/Random/Fortuna/__init__.py create mode 100644 lib/Crypto/Random/OSRNG/__init__.py create mode 100644 lib/Crypto/Random/OSRNG/fallback.py create mode 100644 lib/Crypto/Random/OSRNG/nt.py create mode 100644 lib/Crypto/Random/OSRNG/posix.py create mode 100644 lib/Crypto/Random/OSRNG/rng_base.py create mode 100644 lib/Crypto/Random/_UserFriendlyRNG.py create mode 100644 lib/Crypto/Random/__init__.py create mode 100644 lib/Crypto/Random/random.py create mode 100644 lib/Crypto/SelfTest/Cipher/__init__.py create mode 100644 lib/Crypto/SelfTest/Cipher/common.py create mode 100644 lib/Crypto/SelfTest/Cipher/test_AES.py create mode 100644 lib/Crypto/SelfTest/Cipher/test_ARC2.py create mode 100644 lib/Crypto/SelfTest/Cipher/test_ARC4.py create mode 100644 lib/Crypto/SelfTest/Cipher/test_Blowfish.py create mode 100644 lib/Crypto/SelfTest/Cipher/test_CAST.py create mode 100644 lib/Crypto/SelfTest/Cipher/test_DES.py create mode 100644 lib/Crypto/SelfTest/Cipher/test_DES3.py create mode 100644 lib/Crypto/SelfTest/Cipher/test_XOR.py create mode 100644 lib/Crypto/SelfTest/Cipher/test_pkcs1_15.py create mode 100644 lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py create mode 100644 lib/Crypto/SelfTest/Hash/__init__.py create mode 100644 lib/Crypto/SelfTest/Hash/common.py create mode 100644 lib/Crypto/SelfTest/Hash/test_HMAC.py create mode 100644 lib/Crypto/SelfTest/Hash/test_MD2.py create mode 100644 lib/Crypto/SelfTest/Hash/test_MD4.py create mode 100644 lib/Crypto/SelfTest/Hash/test_MD5.py create mode 100644 lib/Crypto/SelfTest/Hash/test_RIPEMD.py create mode 100644 lib/Crypto/SelfTest/Hash/test_SHA.py create mode 100644 lib/Crypto/SelfTest/Hash/test_SHA224.py create mode 100644 lib/Crypto/SelfTest/Hash/test_SHA256.py create mode 100644 lib/Crypto/SelfTest/Hash/test_SHA384.py create mode 100644 lib/Crypto/SelfTest/Hash/test_SHA512.py create mode 100644 lib/Crypto/SelfTest/Protocol/__init__.py create mode 100644 lib/Crypto/SelfTest/Protocol/test_AllOrNothing.py create mode 100644 lib/Crypto/SelfTest/Protocol/test_KDF.py create mode 100644 lib/Crypto/SelfTest/Protocol/test_chaffing.py create mode 100644 lib/Crypto/SelfTest/Protocol/test_rfc1751.py create mode 100644 lib/Crypto/SelfTest/PublicKey/__init__.py create mode 100644 lib/Crypto/SelfTest/PublicKey/test_DSA.py create mode 100644 lib/Crypto/SelfTest/PublicKey/test_ElGamal.py create mode 100644 lib/Crypto/SelfTest/PublicKey/test_RSA.py create mode 100644 lib/Crypto/SelfTest/PublicKey/test_importKey.py create mode 100644 lib/Crypto/SelfTest/Random/Fortuna/__init__.py create mode 100644 lib/Crypto/SelfTest/Random/Fortuna/test_FortunaAccumulator.py create mode 100644 lib/Crypto/SelfTest/Random/Fortuna/test_FortunaGenerator.py create mode 100644 lib/Crypto/SelfTest/Random/Fortuna/test_SHAd256.py create mode 100644 lib/Crypto/SelfTest/Random/OSRNG/__init__.py create mode 100644 lib/Crypto/SelfTest/Random/OSRNG/test_fallback.py create mode 100644 lib/Crypto/SelfTest/Random/OSRNG/test_generic.py create mode 100644 lib/Crypto/SelfTest/Random/OSRNG/test_nt.py create mode 100644 lib/Crypto/SelfTest/Random/OSRNG/test_posix.py create mode 100644 lib/Crypto/SelfTest/Random/OSRNG/test_winrandom.py create mode 100644 lib/Crypto/SelfTest/Random/__init__.py create mode 100644 lib/Crypto/SelfTest/Random/test_random.py create mode 100644 lib/Crypto/SelfTest/Random/test_rpoolcompat.py create mode 100644 lib/Crypto/SelfTest/Signature/__init__.py create mode 100644 lib/Crypto/SelfTest/Signature/test_pkcs1_15.py create mode 100644 lib/Crypto/SelfTest/Signature/test_pkcs1_pss.py create mode 100644 lib/Crypto/SelfTest/Util/__init__.py create mode 100644 lib/Crypto/SelfTest/Util/test_Counter.py create mode 100644 lib/Crypto/SelfTest/Util/test_asn1.py create mode 100644 lib/Crypto/SelfTest/Util/test_number.py create mode 100644 lib/Crypto/SelfTest/Util/test_winrandom.py create mode 100644 lib/Crypto/SelfTest/__init__.py create mode 100644 lib/Crypto/SelfTest/st_common.py create mode 100644 lib/Crypto/Signature/PKCS1_PSS.py create mode 100644 lib/Crypto/Signature/PKCS1_v1_5.py create mode 100644 lib/Crypto/Signature/__init__.py create mode 100644 lib/Crypto/Util/Counter.py create mode 100644 lib/Crypto/Util/RFC1751.py create mode 100644 lib/Crypto/Util/__init__.py create mode 100755 lib/Crypto/Util/_counter.so create mode 100644 lib/Crypto/Util/_number_new.py create mode 100644 lib/Crypto/Util/asn1.py create mode 100644 lib/Crypto/Util/number.py create mode 100644 lib/Crypto/Util/py21compat.py create mode 100644 lib/Crypto/Util/py3compat.py create mode 100644 lib/Crypto/Util/randpool.py create mode 100755 lib/Crypto/Util/strxor.so create mode 100644 lib/Crypto/Util/winrandom.py create mode 100644 lib/Crypto/__init__.py create mode 100644 lib/Crypto/pct_warnings.py create mode 100644 lib/paramiko/__init__.py create mode 100644 lib/paramiko/agent.py create mode 100644 lib/paramiko/auth_handler.py create mode 100644 lib/paramiko/ber.py create mode 100644 lib/paramiko/buffered_pipe.py create mode 100644 lib/paramiko/channel.py create mode 100644 lib/paramiko/client.py create mode 100644 lib/paramiko/common.py create mode 100644 lib/paramiko/compress.py create mode 100644 lib/paramiko/config.py create mode 100644 lib/paramiko/dsskey.py create mode 100644 lib/paramiko/file.py create mode 100644 lib/paramiko/hostkeys.py create mode 100644 lib/paramiko/kex_gex.py create mode 100644 lib/paramiko/kex_group1.py create mode 100644 lib/paramiko/logging22.py create mode 100644 lib/paramiko/message.py create mode 100644 lib/paramiko/packet.py create mode 100644 lib/paramiko/pipe.py create mode 100644 lib/paramiko/pkey.py create mode 100644 lib/paramiko/primes.py create mode 100644 lib/paramiko/resource.py create mode 100644 lib/paramiko/rsakey.py create mode 100644 lib/paramiko/server.py create mode 100644 lib/paramiko/sftp.py create mode 100644 lib/paramiko/sftp_attr.py create mode 100644 lib/paramiko/sftp_client.py create mode 100644 lib/paramiko/sftp_file.py create mode 100644 lib/paramiko/sftp_handle.py create mode 100644 lib/paramiko/sftp_server.py create mode 100644 lib/paramiko/sftp_si.py create mode 100644 lib/paramiko/ssh_exception.py create mode 100644 lib/paramiko/transport.py create mode 100644 lib/paramiko/util.py create mode 100644 lib/paramiko/win_pageant.py diff --git a/Mote.py b/Mote.py index f73d925..64febbf 100644 --- a/Mote.py +++ b/Mote.py @@ -1,7 +1,7 @@ import sublime, sublime_plugin import subprocess -import os, time +import os, time, inspect, sys import threading import json import posixpath @@ -9,6 +9,13 @@ import shutil from collections import deque +# use this if you want to include modules from a subforder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"lib"))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import paramiko + MOTES = {} def main(): @@ -103,6 +110,7 @@ def run(self): print MOTES[server]['thread'].sftp print MOTES[server]['thread'].results + class MoteDisconnectCommand(sublime_plugin.WindowCommand): def run(self, server=''): MOTES[server]['thread'].add_command('exit','') @@ -125,8 +133,26 @@ def on_post_save(self, view): class MoteSearchThread(threading.Thread): def __init__(self, server, search_path='', connection_string='', password=None, idle_recursive=False, private_key=None, port=None): self.server = server - self.search_path = '' + self.search_path = search_path + self.hostname = '' + self.password = password #didn't see this in the namespace so added it self.connection_string = connection_string + self.os_mode = os.name + self.base_dir = '' + + #Identify if this is a username@hostname string + connection_string_parts = connection_string.split('@') + if len(connection_string_parts) > 1: + self.hostname = connection_string_parts[1] + self.username = connection_string_parts[0] + else: + self.hostname = connection_string + + #lovely debugging + print "Hostname: " + self.hostname + print "Username: " + self.username + print self.connection_string + if ('-pw' not in connection_string) and password: @@ -142,8 +168,8 @@ def __init__(self, server, search_path='', connection_string='', password=None, self.idle_recursive = idle_recursive - self.results = {} + self.transport = None self.sftp = None self.results_lock = threading.Condition() @@ -153,11 +179,40 @@ def __init__(self, server, search_path='', connection_string='', password=None, threading.Thread.__init__(self) + def is_os_mode(self, mode): + return self.os_mode == mode + def connect(self): - if not self.sftp: - self.sftp = psftp(self.connection_string) - self.sftp.next() - return self + if not self.is_os_mode('posix'): + if not self.sftp: + self.sftp = psftp(self.connection_string) + self.sftp.next() + return self + else: + try: + host_keys = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts')) + except IOError: + try: + # try ~/ssh/ too, because windows can't have a folder named ~/.ssh/ + host_keys = paramiko.util.load_host_keys(os.path.expanduser('~/ssh/known_hosts')) + except IOError: + print '*** Unable to open host keys file' + host_keys = {} + + if host_keys.has_key(self.hostname): + hostkeytype = host_keys[self.hostname].keys()[0] + hostkey = host_keys[self.hostname][hostkeytype] + print 'Using host key of type %s' % hostkeytype + try: + self.transport = t = paramiko.Transport((self.hostname, 22)) + t.connect(username=self.username, password=self.password, hostkey=hostkey) + self.sftp = paramiko.SFTPClient.from_transport(t) + print "SFTP INIT: "+ str(self.transport.is_active()) + self.add_command('cd',self.search_path, True) + except Exception: + print "transport Failed" + return self + def disconnect(self): self.add_command('exit','') @@ -178,14 +233,45 @@ def get_front_command(self): else: return (None,None) + def send_cmd(self, command, path, show_panel): + print "Command: " + command + print "Path: " + path + if command == 'ls': + if show_panel == True: + sublime.set_timeout(lambda:sublime.status_message('Opening %s' % path),0) + self.ls(path) + if show_panel == True: + self.showfilepanel() + sublime.set_timeout(lambda:sublime.status_message('Finished opening %s' % path),0) + elif command == 'open': + sublime.set_timeout(lambda:sublime.status_message('Downloading %s' % path),0) + self.download(path) + sublime.set_timeout(lambda:sublime.status_message('Finished downloading %s' % path),0) + elif command == 'save': + sublime.set_timeout(lambda:sublime.status_message('Uploading %s' % path),0) + self.upload(path) + sublime.set_timeout(lambda:sublime.status_message('Finished uploading %s' % path),0) + elif command == 'cd': + self.cd(path, show_panel) + + + def cd(self, path, show_panel): + if not self.is_os_mode('posix'): + self.sftp.send('cd "%s"' % (path) ) + self.add_command('ls','', show_panel) + else: + print "changing dir: " + path + print self.sftp.getcwd() + self.sftp.chdir(path) + self.search_path = self.sftp.getcwd() + self.add_command('ls',path, show_panel) + def run(self): sublime.set_timeout(lambda:sublime.status_message('Connecting to %s' % self.server),0) self.connect() while True: - - self.results_lock.acquire() if len(self.command_deque) == 0: self.results_lock.wait() @@ -197,59 +283,68 @@ def run(self): print command, path, show_panel - if command == 'ls': - if show_panel == True: - sublime.set_timeout(lambda:sublime.status_message('Opening %s' % path),0) - self.ls(path) - if show_panel == True: - self.showfilepanel() - sublime.set_timeout(lambda:sublime.status_message('Finished opening %s' % path),0) - elif command == 'open': - sublime.set_timeout(lambda:sublime.status_message('Downloading %s' % path),0) - self.download(path) - sublime.set_timeout(lambda:sublime.status_message('Finished downloading %s' % path),0) - elif command == 'save': - sublime.set_timeout(lambda:sublime.status_message('Uploading %s' % path),0) - self.upload(path) - sublime.set_timeout(lambda:sublime.status_message('Finished uploading %s' % path),0) - elif command == 'cd': - self.sftp.send('cd "%s"' % (path) ) - self.add_command('ls','', show_panel) - elif command == 'exit': + self.send_cmd(command, path, show_panel) + + if command == 'exit': break else: pass - sublime.set_timeout(lambda:sublime.status_message('Disconnectin from %s' % self.server),0) + sublime.set_timeout(lambda:sublime.status_message('Disconnecting from %s' % self.server),0) try: - self.sftp.send('exit') + if not self.is_os_mode('posix'): + self.sftp.send('exit') + else: + self.sftp.close() + print self.transport.is_active() + self.transport.close() + print self.transport.is_active() + print self.transport.stop_thread() + self.sftp = None + self._Thread__stop() + print threading.enumerate() + #if os.path.exists(os.path.join(sublime.packages_path(),'Mote','temp',self.server)): + #print os.path.join(sublime.packages_path(),'Mote','temp',self.server) + #self.rm_rf(str(os.path.join(sublime.packages_path(),'Mote','temp',self.server))) + except StopIteration: pass - self.sftp = None threading.Thread.__init__(self) - + def ls(self, search_path = ''): fullpath = cleanpath(self.search_path,search_path) - - results = self.cleanls(fullpath, self.sftp.send('ls "%s"' % fullpath)) + if not self.is_os_mode('posix'): + results = self.sftp.send('ls "%s"' % fullpath) + results = self.cleanls(fullpath, results) + self.results.update(results) + else: + file_list = {} + file_list = dict(zip(self.sftp.listdir(self.sftp.getcwd()), self.sftp.listdir_attr(self.sftp.getcwd()))) + #results = self.cleanls(fullpath, results) + results = self.cleanlsposix(fullpath, file_list) + self.results = results if self.idle_recursive: subfolders = dict((k,v) for k,v in results.items() if v['type'] == 'folder') for recur_folder in subfolders: self.add_command('ls',results[recur_folder]['path']) - #print results - self.results.update(results) - def download(self, path): - localpath = os.path.normpath(os.path.join(sublime.packages_path(),'Mote','temp',self.server,path)) + if not self.is_os_mode('posix'): + localpath = os.path.normpath(os.path.join(sublime.packages_path(),'Mote','temp',self.server,path)) + else: + localpath = os.path.normpath(os.path.join(sublime.packages_path(),'Mote','temp',self.server,path[1:])) if not os.path.exists(os.path.dirname(localpath)): os.makedirs(os.path.dirname(localpath)) - self.sftp.send('get "%s" "%s"' % (path,localpath) ) + if not self.is_os_mode('posix'): + self.sftp.send('get "%s" "%s"' % (path,localpath) ) + else: + print "preget: "+ path+" "+localpath + self.sftp.get(path, localpath) sublime.set_timeout(lambda:self.window.open_file(localpath), 0) @@ -258,7 +353,10 @@ def download(self, path): def upload(self, path): localpath = os.path.normpath(os.path.join(sublime.packages_path(),'Mote','temp',self.server,path)) - self.sftp.send('put "%s" "%s"' % (localpath,path) ) + if not self.is_os_mode('posix'): + self.sftp.send('put "%s" "%s"' % (localpath,path) ) + else: + self.sftp.put(localpath, '/'+path) def showfilepanel(self): self.keys = sorted(self.results.keys()) @@ -266,6 +364,27 @@ def show_quick_panel(): self.window.show_quick_panel(self.keys, self.on_select) sublime.set_timeout(show_quick_panel, 10) + def cleanlsposix(self, fullpath, file_list): + paths = {} + paths['..'] = {} + paths['..']['path'] = '/'.join(self.sftp.getcwd().split('/')[0:-1]) + paths['..']['type'] = 'folder' + for path, attr in file_list.items(): + dflag = oct(attr.st_mode) + named_path = cleanpath(fullpath, path) + if str(dflag[0:2]) == '04': + path_key = named_path + '/..' + else: + path_key = named_path + '-' + + paths[path_key] = {} + paths[path_key]['path'] = named_path + if str(dflag[0:2]) == '04': + paths[path_key]['type'] = 'folder' + else: + paths[path_key]['type'] = 'file' + return paths + def cleanls(self,fullpath, out): paths = {} for path in out.split('\n')[2:-1]: @@ -275,6 +394,7 @@ def cleanls(self,fullpath, out): named_path = cleanpath(fullpath,raw_path) path_key = named_path + ('' if path[0] == '-' else '/..') + print named_path+" " +path_key + " "+path[0] #print named_path paths[path_key] = {} @@ -292,10 +412,21 @@ def on_select(self, picked): key = self.keys[picked] if self.results[key]['type'] == 'folder': - self.add_command('ls',self.results[key]['path'], True) + if not self.is_os_mode('posix'): + self.add_command('ls',self.results[key]['path'], True) + else: + self.add_command('cd',self.results[key]['path'], True) elif self.results[key]['type'] == 'file': self.add_command('open',self.results[key]['path']) + def rm_rf(dir): + for path in (os.path.join(d,f) for f in os.listdir(d)): + if os.path.isdir(path): + rm_rf(path) + else: + os.unlink(path) + os.rmdir(d) + def cleanpath(*args): return posixpath.normpath(posixpath.join(*args)) @@ -316,7 +447,6 @@ def psftp(connection_string): untilprompt(p,'exit') return - def untilprompt(proc, strinput = None): if strinput: proc.stdin.write(strinput+'\n') diff --git a/lib/Crypto/Cipher/AES.py b/lib/Crypto/Cipher/AES.py new file mode 100644 index 0000000..eb91c5f --- /dev/null +++ b/lib/Crypto/Cipher/AES.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +# +# Cipher/AES.py : AES +# +# =================================================================== +# 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. +# =================================================================== +"""AES symmetric cipher + +AES `(Advanced Encryption Standard)`__ is a symmetric block cipher standardized +by NIST_ . It has a fixed data block size of 16 bytes. +Its keys can be 128, 192, or 256 bits long. + +AES is very fast and secure, and it is the de facto standard for symmetric +encryption. + +As an example, encryption can be done as follows: + + >>> from Crypto.Cipher import AES + >>> from Crypto import Random + >>> + >>> key = b'Sixteen byte key' + >>> iv = Random.new().read(AES.block_size) + >>> cipher = AES.new(key, AES.MODE_CFB, iv) + >>> msg = iv + cipher.encrypt(b'Attack at dawn') + +.. __: http://en.wikipedia.org/wiki/Advanced_Encryption_Standard +.. _NIST: http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf + +:undocumented: __revision__, __package__ +""" + +__revision__ = "$Id$" + +from Crypto.Cipher import blockalgo +from Crypto.Cipher import _AES + +class AESCipher (blockalgo.BlockAlgo): + """AES cipher object""" + + def __init__(self, key, *args, **kwargs): + """Initialize an AES cipher object + + See also `new()` at the module level.""" + blockalgo.BlockAlgo.__init__(self, _AES, key, *args, **kwargs) + +def new(key, *args, **kwargs): + """Create a new AES cipher + + :Parameters: + key : byte string + The secret key to use in the symmetric cipher. + It must be 16 (*AES-128*), 24 (*AES-192*), or 32 (*AES-256*) bytes long. + :Keywords: + mode : a *MODE_** constant + The chaining mode to use for encryption or decryption. + Default is `MODE_ECB`. + IV : byte string + The initialization vector to use for encryption or decryption. + + It is ignored for `MODE_ECB` and `MODE_CTR`. + + For `MODE_OPENPGP`, IV must be `block_size` bytes long for encryption + and `block_size` +2 bytes for decryption (in the latter case, it is + actually the *encrypted* IV which was prefixed to the ciphertext). + It is mandatory. + + For all other modes, it must be `block_size` bytes longs. + counter : callable + (*Only* `MODE_CTR`). A stateful function that returns the next + *counter block*, which is a byte string of `block_size` bytes. + For better performance, use `Crypto.Util.Counter`. + segment_size : integer + (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext + are segmented in. + It must be a multiple of 8. If 0 or not specified, it will be assumed to be 8. + + :Return: an `AESCipher` object + """ + return AESCipher(key, *args, **kwargs) + +#: Electronic Code Book (ECB). See `blockalgo.MODE_ECB`. +MODE_ECB = 1 +#: Cipher-Block Chaining (CBC). See `blockalgo.MODE_CBC`. +MODE_CBC = 2 +#: Cipher FeedBack (CFB). See `blockalgo.MODE_CFB`. +MODE_CFB = 3 +#: This mode should not be used. +MODE_PGP = 4 +#: Output FeedBack (OFB). See `blockalgo.MODE_OFB`. +MODE_OFB = 5 +#: CounTer Mode (CTR). See `blockalgo.MODE_CTR`. +MODE_CTR = 6 +#: OpenPGP Mode. See `blockalgo.MODE_OPENPGP`. +MODE_OPENPGP = 7 +#: Size of a data block (in bytes) +block_size = 16 +#: Size of a key (in bytes) +key_size = ( 16, 24, 32 ) + diff --git a/lib/Crypto/Cipher/ARC2.py b/lib/Crypto/Cipher/ARC2.py new file mode 100644 index 0000000..ddcca47 --- /dev/null +++ b/lib/Crypto/Cipher/ARC2.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- +# +# Cipher/ARC2.py : ARC2.py +# +# =================================================================== +# 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. +# =================================================================== +"""RC2 symmetric cipher + +RC2_ (Rivest's Cipher version 2) is a symmetric block cipher designed +by Ron Rivest in 1987. The cipher started as a proprietary design, +that was reverse engineered and anonymously posted on Usenet in 1996. +For this reason, the algorithm was first called *Alleged* RC2 (ARC2), +since the company that owned RC2 (RSA Data Inc.) did not confirm whether +the details leaked into public domain were really correct. + +The company eventually published its full specification in RFC2268_. + +RC2 has a fixed data block size of 8 bytes. Length of its keys can vary from +8 to 128 bits. One particular property of RC2 is that the actual +cryptographic strength of the key (*effective key length*) can be reduced +via a parameter. + +Even though RC2 is not cryptographically broken, it has not been analyzed as +thoroughly as AES, which is also faster than RC2. + +New designs should not use RC2. + +As an example, encryption can be done as follows: + + >>> from Crypto.Cipher import ARC2 + >>> from Crypto import Random + >>> + >>> key = b'Sixteen byte key' + >>> iv = Random.new().read(ARC2.block_size) + >>> cipher = ARC2.new(key, ARC2.MODE_CFB, iv) + >>> msg = iv + cipher.encrypt(b'Attack at dawn') + +.. _RC2: http://en.wikipedia.org/wiki/RC2 +.. _RFC2268: http://tools.ietf.org/html/rfc2268 + +:undocumented: __revision__, __package__ +""" + +__revision__ = "$Id$" + +from Crypto.Cipher import blockalgo +from Crypto.Cipher import _ARC2 + +class RC2Cipher (blockalgo.BlockAlgo): + """RC2 cipher object""" + + def __init__(self, key, *args, **kwargs): + """Initialize an ARC2 cipher object + + See also `new()` at the module level.""" + blockalgo.BlockAlgo.__init__(self, _ARC2, key, *args, **kwargs) + +def new(key, *args, **kwargs): + """Create a new RC2 cipher + + :Parameters: + key : byte string + The secret key to use in the symmetric cipher. + Its length can vary from 1 to 128 bytes. + :Keywords: + mode : a *MODE_** constant + The chaining mode to use for encryption or decryption. + Default is `MODE_ECB`. + IV : byte string + The initialization vector to use for encryption or decryption. + + It is ignored for `MODE_ECB` and `MODE_CTR`. + + For `MODE_OPENPGP`, IV must be `block_size` bytes long for encryption + and `block_size` +2 bytes for decryption (in the latter case, it is + actually the *encrypted* IV which was prefixed to the ciphertext). + It is mandatory. + + For all other modes, it must be `block_size` bytes longs. + counter : callable + (*Only* `MODE_CTR`). A stateful function that returns the next + *counter block*, which is a byte string of `block_size` bytes. + For better performance, use `Crypto.Util.Counter`. + segment_size : integer + (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext + are segmented in. + It must be a multiple of 8. If 0 or not specified, it will be assumed to be 8. + effective_keylen : integer + Maximum cryptographic strength of the key, in bits. + It can vary from 0 to 1024. The default value is 1024. + + :Return: an `RC2Cipher` object + """ + return RC2Cipher(key, *args, **kwargs) + +#: Electronic Code Book (ECB). See `blockalgo.MODE_ECB`. +MODE_ECB = 1 +#: Cipher-Block Chaining (CBC). See `blockalgo.MODE_CBC`. +MODE_CBC = 2 +#: Cipher FeedBack (CFB). See `blockalgo.MODE_CFB`. +MODE_CFB = 3 +#: This mode should not be used. +MODE_PGP = 4 +#: Output FeedBack (OFB). See `blockalgo.MODE_OFB`. +MODE_OFB = 5 +#: CounTer Mode (CTR). See `blockalgo.MODE_CTR`. +MODE_CTR = 6 +#: OpenPGP Mode. See `blockalgo.MODE_OPENPGP`. +MODE_OPENPGP = 7 +#: Size of a data block (in bytes) +block_size = 8 +#: Size of a key (in bytes) +key_size = xrange(1,16+1) + diff --git a/lib/Crypto/Cipher/ARC4.py b/lib/Crypto/Cipher/ARC4.py new file mode 100644 index 0000000..48d39f1 --- /dev/null +++ b/lib/Crypto/Cipher/ARC4.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +# +# Cipher/ARC4.py : ARC4 +# +# =================================================================== +# 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. +# =================================================================== +"""ARC4 symmetric cipher + +ARC4_ (Alleged RC4) is an implementation of RC4 (Rivest's Cipher version 4), +a symmetric stream cipher designed by Ron Rivest in 1987. + +The cipher started as a proprietary design, that was reverse engineered and +anonymously posted on Usenet in 1994. The company that owns RC4 (RSA Data +Inc.) never confirmed the correctness of the leaked algorithm. + +Unlike RC2, the company has never published the full specification of RC4, +of whom it still holds the trademark. + +ARC4 keys can vary in length from 40 to 2048 bits. + +One problem of ARC4 is that it does not take a nonce or an IV. If it is required +to encrypt multiple messages with the same long-term key, a distinct +independent nonce must be created for each message, and a short-term key must +be derived from the combination of the long-term key and the nonce. +Due to the weak key scheduling algorithm of RC2, the combination must be carried +out with a complex function (e.g. a cryptographic hash) and not by simply +concatenating key and nonce. + +New designs should not use ARC4. A good alternative is AES +(`Crypto.Cipher.AES`) in any of the modes that turn it into a stream cipher (OFB, CFB, or CTR). + +As an example, encryption can be done as follows: + + >>> from Crypto.Cipher import ARC4 + >>> from Crypto.Hash import SHA + >>> from Crypto import Random + >>> + >>> key = b'Very long and confidential key' + >>> nonce = Random.new().read(16) + >>> tempkey = SHA.new(key+nonce).digest() + >>> cipher = ARC4.new(tempkey) + >>> msg = nonce + cipher.encrypt(b'Open the pod bay doors, HAL') + +.. _ARC4: http://en.wikipedia.org/wiki/RC4 + +:undocumented: __revision__, __package__ +""" + +__revision__ = "$Id$" + +from Crypto.Util.py3compat import * +from Crypto.Cipher import _ARC4 + +class ARC4Cipher: + """ARC4 cipher object""" + + + def __init__(self, key, *args, **kwargs): + """Initialize an ARC4 cipher object + + See also `new()` at the module level.""" + + if len(args)>0: + ndrop = args[0] + args = args[1:] + else: + ndrop = kwargs.get('drop', 0) + if ndrop: del kwargs['drop'] + self._cipher = _ARC4.new(key, *args, **kwargs) + if ndrop: + # This is OK even if the cipher is used for decryption, since encrypt + # and decrypt are actually the same thing with ARC4. + self._cipher.encrypt(b('\x00')*ndrop) + + self.block_size = self._cipher.block_size + self.key_size = self._cipher.key_size + + def encrypt(self, plaintext): + """Encrypt a piece of data. + + :Parameters: + plaintext : byte string + The piece of data to encrypt. It can be of any size. + :Return: the encrypted data (byte string, as long as the + plaintext). + """ + return self._cipher.encrypt(plaintext) + + def decrypt(self, ciphertext): + """Decrypt a piece of data. + + :Parameters: + ciphertext : byte string + The piece of data to decrypt. It can be of any size. + :Return: the decrypted data (byte string, as long as the + ciphertext). + """ + return self._cipher.decrypt(ciphertext) + +def new(key, *args, **kwargs): + """Create a new ARC4 cipher + + :Parameters: + key : byte string + The secret key to use in the symmetric cipher. + It can have any length, with a minimum of 40 bytes. + Its cryptograpic strength is always capped to 2048 bits (256 bytes). + :Keywords: + drop : integer + The amount of bytes to discard from the initial part of the keystream. + In fact, such part has been found to be distinguishable from random + data (while it shouldn't) and also correlated to key. + + The recommended value is 3072_ bytes. The default value is 0. + + :Return: an `ARC4Cipher` object + + .. _3072: http://eprint.iacr.org/2002/067.pdf + """ + return ARC4Cipher(key, *args, **kwargs) + +#: Size of a data block (in bytes) +block_size = 1 +#: Size of a key (in bytes) +key_size = xrange(1,256+1) + diff --git a/lib/Crypto/Cipher/Blowfish.py b/lib/Crypto/Cipher/Blowfish.py new file mode 100644 index 0000000..2ce78e9 --- /dev/null +++ b/lib/Crypto/Cipher/Blowfish.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# +# Cipher/Blowfish.py : Blowfish +# +# =================================================================== +# 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. +# =================================================================== +"""Blowfish symmetric cipher + +Blowfish_ is a symmetric block cipher designed by Bruce Schneier. + +It has a fixed data block size of 8 bytes and its keys can vary in length +from 32 to 448 bits (4 to 56 bytes). + +Blowfish is deemed secure and it is fast. However, its keys should be chosen +to be big enough to withstand a brute force attack (e.g. at least 16 bytes). + +As an example, encryption can be done as follows: + + >>> from Crypto.Cipher import Blowfish + >>> from Crypto import Random + >>> from struct import pack + >>> + >>> bs = Blowfish.block_size + >>> key = b'An arbitrarily long key' + >>> iv = Random.new().read(bs) + >>> cipher = Blowfish.new(key, Blowfish.MODE_CBC, iv) + >>> plaintext = b'docendo discimus ' + >>> plen = bs - divmod(len(plaintext),bs)[1] + >>> padding = [plen]*plen + >>> padding = pack('b'*plen, *padding) + >>> msg = iv + cipher.encrypt(plaintext + padding) + +.. _Blowfish: http://www.schneier.com/blowfish.html + +:undocumented: __revision__, __package__ +""" + +__revision__ = "$Id$" + +from Crypto.Cipher import blockalgo +from Crypto.Cipher import _Blowfish + +class BlowfishCipher (blockalgo.BlockAlgo): + """Blowfish cipher object""" + + def __init__(self, key, *args, **kwargs): + """Initialize a Blowfish cipher object + + See also `new()` at the module level.""" + blockalgo.BlockAlgo.__init__(self, _Blowfish, key, *args, **kwargs) + +def new(key, *args, **kwargs): + """Create a new Blowfish cipher + + :Parameters: + key : byte string + The secret key to use in the symmetric cipher. + Its length can vary from 4 to 56 bytes. + :Keywords: + mode : a *MODE_** constant + The chaining mode to use for encryption or decryption. + Default is `MODE_ECB`. + IV : byte string + The initialization vector to use for encryption or decryption. + + It is ignored for `MODE_ECB` and `MODE_CTR`. + + For `MODE_OPENPGP`, IV must be `block_size` bytes long for encryption + and `block_size` +2 bytes for decryption (in the latter case, it is + actually the *encrypted* IV which was prefixed to the ciphertext). + It is mandatory. + + For all other modes, it must be `block_size` bytes longs. + counter : callable + (*Only* `MODE_CTR`). A stateful function that returns the next + *counter block*, which is a byte string of `block_size` bytes. + For better performance, use `Crypto.Util.Counter`. + segment_size : integer + (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext + are segmented in. + It must be a multiple of 8. If 0 or not specified, it will be assumed to be 8. + + :Return: a `BlowfishCipher` object + """ + return BlowfishCipher(key, *args, **kwargs) + +#: Electronic Code Book (ECB). See `blockalgo.MODE_ECB`. +MODE_ECB = 1 +#: Cipher-Block Chaining (CBC). See `blockalgo.MODE_CBC`. +MODE_CBC = 2 +#: Cipher FeedBack (CFB). See `blockalgo.MODE_CFB`. +MODE_CFB = 3 +#: This mode should not be used. +MODE_PGP = 4 +#: Output FeedBack (OFB). See `blockalgo.MODE_OFB`. +MODE_OFB = 5 +#: CounTer Mode (CTR). See `blockalgo.MODE_CTR`. +MODE_CTR = 6 +#: OpenPGP Mode. See `blockalgo.MODE_OPENPGP`. +MODE_OPENPGP = 7 +#: Size of a data block (in bytes) +block_size = 8 +#: Size of a key (in bytes) +key_size = xrange(4,56+1) + diff --git a/lib/Crypto/Cipher/CAST.py b/lib/Crypto/Cipher/CAST.py new file mode 100644 index 0000000..5f009a7 --- /dev/null +++ b/lib/Crypto/Cipher/CAST.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +# +# Cipher/CAST.py : CAST +# +# =================================================================== +# 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. +# =================================================================== +"""CAST-128 symmetric cipher + +CAST-128_ (or CAST5) is a symmetric block cipher specified in RFC2144_. + +It has a fixed data block size of 8 bytes. Its key can vary in length +from 40 to 128 bits. + +CAST is deemed to be cryptographically secure, but its usage is not widespread. +Keys of sufficient length should be used to prevent brute force attacks +(128 bits are recommended). + +As an example, encryption can be done as follows: + + >>> from Crypto.Cipher import CAST + >>> from Crypto import Random + >>> + >>> key = b'Sixteen byte key' + >>> iv = Random.new().read(CAST.block_size) + >>> cipher = CAST.new(key, CAST.MODE_OPENPGP, iv) + >>> plaintext = b'sona si latine loqueris ' + >>> msg = cipher.encrypt(plaintext) + >>> + ... + >>> eiv = msg[:CAST.block_size+2] + >>> ciphertext = msg[CAST.block_size+2:] + >>> cipher = CAST.new(key, CAST.MODE_OPENPGP, eiv) + >>> print cipher.decrypt(ciphertext) + +.. _CAST-128: http://en.wikipedia.org/wiki/CAST-128 +.. _RFC2144: http://tools.ietf.org/html/rfc2144 + +:undocumented: __revision__, __package__ +""" + +__revision__ = "$Id$" + +from Crypto.Cipher import blockalgo +from Crypto.Cipher import _CAST + +class CAST128Cipher(blockalgo.BlockAlgo): + """CAST-128 cipher object""" + + def __init__(self, key, *args, **kwargs): + """Initialize a CAST-128 cipher object + + See also `new()` at the module level.""" + blockalgo.BlockAlgo.__init__(self, _CAST, key, *args, **kwargs) + +def new(key, *args, **kwargs): + """Create a new CAST-128 cipher + + :Parameters: + key : byte string + The secret key to use in the symmetric cipher. + Its length may vary from 5 to 16 bytes. + :Keywords: + mode : a *MODE_** constant + The chaining mode to use for encryption or decryption. + Default is `MODE_ECB`. + IV : byte string + The initialization vector to use for encryption or decryption. + + It is ignored for `MODE_ECB` and `MODE_CTR`. + + For `MODE_OPENPGP`, IV must be `block_size` bytes long for encryption + and `block_size` +2 bytes for decryption (in the latter case, it is + actually the *encrypted* IV which was prefixed to the ciphertext). + It is mandatory. + + For all other modes, it must be `block_size` bytes longs. + counter : callable + (*Only* `MODE_CTR`). A stateful function that returns the next + *counter block*, which is a byte string of `block_size` bytes. + For better performance, use `Crypto.Util.Counter`. + segment_size : integer + (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext + are segmented in. + It must be a multiple of 8. If 0 or not specified, it will be assumed to be 8. + + :Return: an `CAST128Cipher` object + """ + return CAST128Cipher(key, *args, **kwargs) + +#: Electronic Code Book (ECB). See `blockalgo.MODE_ECB`. +MODE_ECB = 1 +#: Cipher-Block Chaining (CBC). See `blockalgo.MODE_CBC`. +MODE_CBC = 2 +#: Cipher FeedBack (CFB). See `blockalgo.MODE_CFB`. +MODE_CFB = 3 +#: This mode should not be used. +MODE_PGP = 4 +#: Output FeedBack (OFB). See `blockalgo.MODE_OFB`. +MODE_OFB = 5 +#: CounTer Mode (CTR). See `blockalgo.MODE_CTR`. +MODE_CTR = 6 +#: OpenPGP Mode. See `blockalgo.MODE_OPENPGP`. +MODE_OPENPGP = 7 +#: Size of a data block (in bytes) +block_size = 8 +#: Size of a key (in bytes) +key_size = xrange(5,16+1) diff --git a/lib/Crypto/Cipher/DES.py b/lib/Crypto/Cipher/DES.py new file mode 100644 index 0000000..1062d23 --- /dev/null +++ b/lib/Crypto/Cipher/DES.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +# +# Cipher/DES.py : DES +# +# =================================================================== +# 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. +# =================================================================== +"""DES symmetric cipher + +DES `(Data Encryption Standard)`__ is a symmetric block cipher standardized +by NIST_ . It has a fixed data block size of 8 bytes. +Its keys are 64 bits long, even though 8 bits were used for integrity (now they +are ignored) and do not contribute to securty. + +DES is cryptographically secure, but its key length is too short by nowadays +standards and it could be brute forced with some effort. + +DES should not be used for new designs. Use `AES`. + +As an example, encryption can be done as follows: + + >>> from Crypto.Cipher import DES + >>> from Crypto import Random + >>> + >>> key = b'-8B key-' + >>> iv = Random.new().read(DES.block_size) + >>> cipher = DES.new(key, DES.MODE_OFB, iv) + >>> plaintext = b'sona si latine loqueris ' + >>> msg = iv + cipher.encrypt(plaintext) + +.. __: http://en.wikipedia.org/wiki/Data_Encryption_Standard +.. _NIST: http://csrc.nist.gov/publications/fips/fips46-3/fips46-3.pdf + +:undocumented: __revision__, __package__ +""" + +__revision__ = "$Id$" + +from Crypto.Cipher import blockalgo +from Crypto.Cipher import _DES + +class DESCipher(blockalgo.BlockAlgo): + """DES cipher object""" + + def __init__(self, key, *args, **kwargs): + """Initialize a DES cipher object + + See also `new()` at the module level.""" + blockalgo.BlockAlgo.__init__(self, _DES, key, *args, **kwargs) + +def new(key, *args, **kwargs): + """Create a new DES cipher + + :Parameters: + key : byte string + The secret key to use in the symmetric cipher. + It must be 8 byte long. The parity bits will be ignored. + :Keywords: + mode : a *MODE_** constant + The chaining mode to use for encryption or decryption. + Default is `MODE_ECB`. + IV : byte string + The initialization vector to use for encryption or decryption. + + It is ignored for `MODE_ECB` and `MODE_CTR`. + + For `MODE_OPENPGP`, IV must be `block_size` bytes long for encryption + and `block_size` +2 bytes for decryption (in the latter case, it is + actually the *encrypted* IV which was prefixed to the ciphertext). + It is mandatory. + + For all other modes, it must be `block_size` bytes longs. + counter : callable + (*Only* `MODE_CTR`). A stateful function that returns the next + *counter block*, which is a byte string of `block_size` bytes. + For better performance, use `Crypto.Util.Counter`. + segment_size : integer + (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext + are segmented in. + It must be a multiple of 8. If 0 or not specified, it will be assumed to be 8. + + :Return: an `DESCipher` object + """ + return DESCipher(key, *args, **kwargs) + +#: Electronic Code Book (ECB). See `blockalgo.MODE_ECB`. +MODE_ECB = 1 +#: Cipher-Block Chaining (CBC). See `blockalgo.MODE_CBC`. +MODE_CBC = 2 +#: Cipher FeedBack (CFB). See `blockalgo.MODE_CFB`. +MODE_CFB = 3 +#: This mode should not be used. +MODE_PGP = 4 +#: Output FeedBack (OFB). See `blockalgo.MODE_OFB`. +MODE_OFB = 5 +#: CounTer Mode (CTR). See `blockalgo.MODE_CTR`. +MODE_CTR = 6 +#: OpenPGP Mode. See `blockalgo.MODE_OPENPGP`. +MODE_OPENPGP = 7 +#: Size of a data block (in bytes) +block_size = 8 +#: Size of a key (in bytes) +key_size = 8 diff --git a/lib/Crypto/Cipher/DES3.py b/lib/Crypto/Cipher/DES3.py new file mode 100644 index 0000000..0265c0c --- /dev/null +++ b/lib/Crypto/Cipher/DES3.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +# +# Cipher/DES3.py : DES3 +# +# =================================================================== +# 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. +# =================================================================== +"""Triple DES symmetric cipher + +`Triple DES`__ (or TDES or TDEA or 3DES) is a symmetric block cipher standardized by NIST_. +It has a fixed data block size of 8 bytes. Its keys are 128 (*Option 1*) or 192 +bits (*Option 2*) long. +However, 1 out of 8 bits is used for redundancy and do not contribute to +security. The effective key length is respectively 112 or 168 bits. + +TDES consists of the concatenation of 3 simple `DES` ciphers. + +The plaintext is first DES encrypted with *K1*, then decrypted with *K2*, +and finally encrypted again with *K3*. The ciphertext is decrypted in the reverse manner. + +The 192 bit key is a bundle of three 64 bit independent subkeys: *K1*, *K2*, and *K3*. + +The 128 bit key is split into *K1* and *K2*, whereas *K1=K3*. + +It is important that all subkeys are different, otherwise TDES would degrade to +single `DES`. + +TDES is cryptographically secure, even though it is neither as secure nor as fast +as `AES`. + +As an example, encryption can be done as follows: + + >>> from Crypto.Cipher import DES3 + >>> from Crypto import Random + >>> from Crypto.Util import Counter + >>> + >>> key = b'Sixteen byte key' + >>> nonce = Random.new().read(DES3.block_size/2) + >>> ctr = Counter.new(DES3.block_size*8/2, prefix=nonce) + >>> cipher = DES3.new(key, DES3.MODE_CTR, counter=ctr) + >>> plaintext = b'We are no longer the knights who say ni!' + >>> msg = nonce + cipher.encrypt(plaintext) + +.. __: http://en.wikipedia.org/wiki/Triple_DES +.. _NIST: http://csrc.nist.gov/publications/nistpubs/800-67/SP800-67.pdf + +:undocumented: __revision__, __package__ +""" + +__revision__ = "$Id$" + +from Crypto.Cipher import blockalgo +from Crypto.Cipher import _DES3 + +class DES3Cipher(blockalgo.BlockAlgo): + """TDES cipher object""" + + def __init__(self, key, *args, **kwargs): + """Initialize a TDES cipher object + + See also `new()` at the module level.""" + blockalgo.BlockAlgo.__init__(self, _DES3, key, *args, **kwargs) + +def new(key, *args, **kwargs): + """Create a new TDES cipher + + :Parameters: + key : byte string + The secret key to use in the symmetric cipher. + It must be 16 or 24 bytes long. The parity bits will be ignored. + :Keywords: + mode : a *MODE_** constant + The chaining mode to use for encryption or decryption. + Default is `MODE_ECB`. + IV : byte string + The initialization vector to use for encryption or decryption. + + It is ignored for `MODE_ECB` and `MODE_CTR`. + + For `MODE_OPENPGP`, IV must be `block_size` bytes long for encryption + and `block_size` +2 bytes for decryption (in the latter case, it is + actually the *encrypted* IV which was prefixed to the ciphertext). + It is mandatory. + + For all other modes, it must be `block_size` bytes longs. + counter : callable + (*Only* `MODE_CTR`). A stateful function that returns the next + *counter block*, which is a byte string of `block_size` bytes. + For better performance, use `Crypto.Util.Counter`. + segment_size : integer + (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext + are segmented in. + It must be a multiple of 8. If 0 or not specified, it will be assumed to be 8. + + :Attention: it is important that all 8 byte subkeys are different, + otherwise TDES would degrade to single `DES`. + :Return: an `DES3Cipher` object + """ + return DES3Cipher(key, *args, **kwargs) + +#: Electronic Code Book (ECB). See `blockalgo.MODE_ECB`. +MODE_ECB = 1 +#: Cipher-Block Chaining (CBC). See `blockalgo.MODE_CBC`. +MODE_CBC = 2 +#: Cipher FeedBack (CFB). See `blockalgo.MODE_CFB`. +MODE_CFB = 3 +#: This mode should not be used. +MODE_PGP = 4 +#: Output FeedBack (OFB). See `blockalgo.MODE_OFB`. +MODE_OFB = 5 +#: CounTer Mode (CTR). See `blockalgo.MODE_CTR`. +MODE_CTR = 6 +#: OpenPGP Mode. See `blockalgo.MODE_OPENPGP`. +MODE_OPENPGP = 7 +#: Size of a data block (in bytes) +block_size = 8 +#: Size of a key (in bytes) +key_size = ( 16, 24 ) diff --git a/lib/Crypto/Cipher/PKCS1_OAEP.py b/lib/Crypto/Cipher/PKCS1_OAEP.py new file mode 100644 index 0000000..9afe176 --- /dev/null +++ b/lib/Crypto/Cipher/PKCS1_OAEP.py @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- +# +# Cipher/PKCS1_OAEP.py : PKCS#1 OAEP +# +# =================================================================== +# 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. +# =================================================================== + +"""RSA encryption protocol according to PKCS#1 OAEP + +See RFC3447__ or the `original RSA Labs specification`__ . + +This scheme is more properly called ``RSAES-OAEP``. + +As an example, a sender may encrypt a message in this way: + + >>> from Crypto.Cipher import PKCS1_OAEP + >>> from Crypto.PublicKey import RSA + >>> + >>> message = 'To be encrypted' + >>> key = RSA.importKey(open('pubkey.der').read()) + >>> cipher = PKCS1_OAEP.new(key) + >>> ciphertext = cipher.encrypt(message) + +At the receiver side, decryption can be done using the private part of +the RSA key: + + >>> key = RSA.importKey(open('privkey.der').read()) + >>> cipher = PKCS1_OAP.new(key) + >>> message = cipher.decrypt(ciphertext) + +:undocumented: __revision__, __package__ + +.. __: http://www.ietf.org/rfc/rfc3447.txt +.. __: http://www.rsa.com/rsalabs/node.asp?id=2125. +""" + +from __future__ import nested_scopes + +__revision__ = "$Id$" +__all__ = [ 'new', 'PKCS1OAEP_Cipher' ] + +import Crypto.Signature.PKCS1_PSS +import Crypto.Hash.SHA + +from Crypto.Util.py3compat import * +import Crypto.Util.number +from Crypto.Util.number import ceil_div +from Crypto.Util.strxor import strxor + +class PKCS1OAEP_Cipher: + """This cipher can perform PKCS#1 v1.5 OAEP encryption or decryption.""" + + def __init__(self, key, hashAlgo, mgfunc, label): + """Initialize this PKCS#1 OAEP cipher object. + + :Parameters: + key : an RSA key object + If a private half is given, both encryption and decryption are possible. + If a public half is given, only encryption is possible. + hashAlgo : hash object + The hash function to use. This can be a module under `Crypto.Hash` + or an existing hash object created from any of such modules. If not specified, + `Crypto.Hash.SHA` (that is, SHA-1) is used. + mgfunc : callable + A mask generation function that accepts two parameters: a string to + use as seed, and the lenth of the mask to generate, in bytes. + If not specified, the standard MGF1 is used (a safe choice). + label : string + A label to apply to this particular encryption. If not specified, + an empty string is used. Specifying a label does not improve + security. + + :attention: Modify the mask generation function only if you know what you are doing. + Sender and receiver must use the same one. + """ + self._key = key + + if hashAlgo: + self._hashObj = hashAlgo + else: + self._hashObj = Crypto.Hash.SHA + + if mgfunc: + self._mgf = mgfunc + else: + self._mgf = lambda x,y: Crypto.Signature.PKCS1_PSS.MGF1(x,y,self._hashObj) + + self._label = label + + def can_encrypt(self): + """Return True/1 if this cipher object can be used for encryption.""" + return self._key.can_encrypt() + + def can_decrypt(self): + """Return True/1 if this cipher object can be used for decryption.""" + return self._key.can_decrypt() + + def encrypt(self, message): + """Produce the PKCS#1 OAEP encryption of a message. + + This function is named ``RSAES-OAEP-ENCRYPT``, and is specified in + section 7.1.1 of RFC3447. + + :Parameters: + message : string + The message to encrypt, also known as plaintext. It can be of + variable length, but not longer than the RSA modulus (in bytes) + minus 2, minus twice the hash output size. + + :Return: A string, the ciphertext in which the message is encrypted. + It is as long as the RSA modulus (in bytes). + :Raise ValueError: + If the RSA key length is not sufficiently long to deal with the given + message. + """ + # TODO: Verify the key is RSA + + randFunc = self._key._randfunc + + # See 7.1.1 in RFC3447 + modBits = Crypto.Util.number.size(self._key.n) + k = ceil_div(modBits,8) # Convert from bits to bytes + hLen = self._hashObj.digest_size + mLen = len(message) + + # Step 1b + ps_len = k-mLen-2*hLen-2 + if ps_len<0: + raise ValueError("Plaintext is too long.") + # Step 2a + lHash = self._hashObj.new(self._label).digest() + # Step 2b + ps = bchr(0x00)*ps_len + # Step 2c + db = lHash + ps + bchr(0x01) + message + # Step 2d + ros = randFunc(hLen) + # Step 2e + dbMask = self._mgf(ros, k-hLen-1) + # Step 2f + maskedDB = strxor(db, dbMask) + # Step 2g + seedMask = self._mgf(maskedDB, hLen) + # Step 2h + maskedSeed = strxor(ros, seedMask) + # Step 2i + em = bchr(0x00) + maskedSeed + maskedDB + # Step 3a (OS2IP), step 3b (RSAEP), part of step 3c (I2OSP) + m = self._key.encrypt(em, 0)[0] + # Complete step 3c (I2OSP) + c = bchr(0x00)*(k-len(m)) + m + return c + + def decrypt(self, ct): + """Decrypt a PKCS#1 OAEP ciphertext. + + This function is named ``RSAES-OAEP-DECRYPT``, and is specified in + section 7.1.2 of RFC3447. + + :Parameters: + ct : string + The ciphertext that contains the message to recover. + + :Return: A string, the original message. + :Raise ValueError: + If the ciphertext length is incorrect, or if the decryption does not + succeed. + :Raise TypeError: + If the RSA key has no private half. + """ + # TODO: Verify the key is RSA + + # See 7.1.2 in RFC3447 + modBits = Crypto.Util.number.size(self._key.n) + k = ceil_div(modBits,8) # Convert from bits to bytes + hLen = self._hashObj.digest_size + + # Step 1b and 1c + if len(ct) != k or k>> from Crypto.Cipher import PKCS1_v1_5 + >>> from Crypto.PublicKey import RSA + >>> from Crypto.Hash import SHA + >>> + >>> message = 'To be encrypted' + >>> h = SHA.new(message) + >>> + >>> key = RSA.importKey(open('pubkey.der').read()) + >>> cipher = PKCS1_v1_5.new(key) + >>> ciphertext = cipher.encrypt(message+h.digest()) + +At the receiver side, decryption can be done using the private part of +the RSA key: + + >>> From Crypto.Hash import SHA + >>> from Crypto import Random + >>> + >>> key = RSA.importKey(open('privkey.der').read()) + >>> + >>> dsize = SHA.digest_size + >>> sentinel = Random.new().read(15+dsize) # Let's assume that average data length is 15 + >>> + >>> cipher = PKCS1_v1_5.new(key) + >>> message = cipher.decrypt(ciphertext, sentinel) + >>> + >>> digest = SHA.new(message[:-dsize]).digest() + >>> if digest==message[-dsize:]: # Note how we DO NOT look for the sentinel + >>> print "Encryption was correct." + >>> else: + >>> print "Encryption was not correct." + +:undocumented: __revision__, __package__ + +.. __: http://www.ietf.org/rfc/rfc3447.txt +.. __: http://www.rsa.com/rsalabs/node.asp?id=2125. +""" + +__revision__ = "$Id$" +__all__ = [ 'new', 'PKCS115_Cipher' ] + +from Crypto.Util.number import ceil_div +from Crypto.Util.py3compat import * +import Crypto.Util.number + +class PKCS115_Cipher: + """This cipher can perform PKCS#1 v1.5 RSA encryption or decryption.""" + + def __init__(self, key): + """Initialize this PKCS#1 v1.5 cipher object. + + :Parameters: + key : an RSA key object + If a private half is given, both encryption and decryption are possible. + If a public half is given, only encryption is possible. + """ + self._key = key + + def can_encrypt(self): + """Return True if this cipher object can be used for encryption.""" + return self._key.can_encrypt() + + def can_decrypt(self): + """Return True if this cipher object can be used for decryption.""" + return self._key.can_decrypt() + + def encrypt(self, message): + """Produce the PKCS#1 v1.5 encryption of a message. + + This function is named ``RSAES-PKCS1-V1_5-ENCRYPT``, and is specified in + section 7.2.1 of RFC3447. + For a complete example see `Crypto.Cipher.PKCS1_v1_5`. + + :Parameters: + message : byte string + The message to encrypt, also known as plaintext. It can be of + variable length, but not longer than the RSA modulus (in bytes) minus 11. + + :Return: A byte string, the ciphertext in which the message is encrypted. + It is as long as the RSA modulus (in bytes). + :Raise ValueError: + If the RSA key length is not sufficiently long to deal with the given + message. + + """ + # TODO: Verify the key is RSA + + randFunc = self._key._randfunc + + # See 7.2.1 in RFC3447 + modBits = Crypto.Util.number.size(self._key.n) + k = ceil_div(modBits,8) # Convert from bits to bytes + mLen = len(message) + + # Step 1 + if mLen > k-11: + raise ValueError("Plaintext is too long.") + # Step 2a + class nonZeroRandByte: + def __init__(self, rf): self.rf=rf + def __call__(self, c): + while bord(c)==0x00: c=self.rf(1)[0] + return c + ps = tobytes(map(nonZeroRandByte(randFunc), randFunc(k-mLen-3))) + # Step 2b + em = b('\x00\x02') + ps + bchr(0x00) + message + # Step 3a (OS2IP), step 3b (RSAEP), part of step 3c (I2OSP) + m = self._key.encrypt(em, 0)[0] + # Complete step 3c (I2OSP) + c = bchr(0x00)*(k-len(m)) + m + return c + + def decrypt(self, ct, sentinel): + """Decrypt a PKCS#1 v1.5 ciphertext. + + This function is named ``RSAES-PKCS1-V1_5-DECRYPT``, and is specified in + section 7.2.2 of RFC3447. + For a complete example see `Crypto.Cipher.PKCS1_v1_5`. + + :Parameters: + ct : byte string + The ciphertext that contains the message to recover. + sentinel : any type + The object to return to indicate that an error was detected during decryption. + + :Return: A byte string. It is either the original message or the ``sentinel`` (in case of an error). + :Raise ValueError: + If the ciphertext length is incorrect + :Raise TypeError: + If the RSA key has no private half. + + :attention: + You should **never** let the party who submitted the ciphertext know that + this function returned the ``sentinel`` value. + Armed with such knowledge (for a fair amount of carefully crafted but invalid ciphertexts), + an attacker is able to recontruct the plaintext of any other encryption that were carried out + with the same RSA public key (see `Bleichenbacher's`__ attack). + + In general, it should not be possible for the other party to distinguish + whether processing at the server side failed because the value returned + was a ``sentinel`` as opposed to a random, invalid message. + + In fact, the second option is not that unlikely: encryption done according to PKCS#1 v1.5 + embeds no good integrity check. There is roughly one chance + in 2^16 for a random ciphertext to be returned as a valid message + (although random looking). + + It is therefore advisabled to: + + 1. Select as ``sentinel`` a value that resembles a plausable random, invalid message. + 2. Not report back an error as soon as you detect a ``sentinel`` value. + Put differently, you should not explicitly check if the returned value is the ``sentinel`` or not. + 3. Cover all possible errors with a single, generic error indicator. + 4. Embed into the definition of ``message`` (at the protocol level) a digest (e.g. ``SHA-1``). + It is recommended for it to be the rightmost part ``message``. + 5. Where possible, monitor the number of errors due to ciphertexts originating from the same party, + and slow down the rate of the requests from such party (or even blacklist it altogether). + + **If you are designing a new protocol, consider using the more robust PKCS#1 OAEP.** + + .. __: http://www.bell-labs.com/user/bleichen/papers/pkcs.ps + + """ + + # TODO: Verify the key is RSA + + # See 7.2.1 in RFC3447 + modBits = Crypto.Util.number.size(self._key.n) + k = ceil_div(modBits,8) # Convert from bits to bytes + + # Step 1 + if len(ct) != k: + raise ValueError("Ciphertext with incorrect length.") + # Step 2a (O2SIP), 2b (RSADP), and part of 2c (I2OSP) + m = self._key.decrypt(ct) + # Complete step 2c (I2OSP) + em = bchr(0x00)*(k-len(m)) + m + # Step 3 + sep = em.find(bchr(0x00),2) + if not em.startswith(b('\x00\x02')) or sep<10: + return sentinel + # Step 4 + return em[sep+1:] + +def new(key): + """Return a cipher object `PKCS115_Cipher` that can be used to perform PKCS#1 v1.5 encryption or decryption. + + :Parameters: + key : RSA key object + The key to use to encrypt or decrypt the message. This is a `Crypto.PublicKey.RSA` object. + Decryption is only possible if *key* is a private RSA key. + + """ + return PKCS115_Cipher(key) + diff --git a/lib/Crypto/Cipher/XOR.py b/lib/Crypto/Cipher/XOR.py new file mode 100644 index 0000000..26ec1b1 --- /dev/null +++ b/lib/Crypto/Cipher/XOR.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# +# Cipher/XOR.py : XOR +# +# =================================================================== +# 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. +# =================================================================== +"""XOR toy cipher + +XOR is one the simplest stream ciphers. Encryption and decryption are +performed by XOR-ing data with a keystream made by contatenating +the key. + +Do not use it for real applications! + +:undocumented: __revision__, __package__ +""" + +__revision__ = "$Id$" + +from Crypto.Cipher import _XOR + +class XORCipher: + """XOR cipher object""" + + def __init__(self, key, *args, **kwargs): + """Initialize a XOR cipher object + + See also `new()` at the module level.""" + self._cipher = _XOR.new(key, *args, **kwargs) + self.block_size = self._cipher.block_size + self.key_size = self._cipher.key_size + + def encrypt(self, plaintext): + """Encrypt a piece of data. + + :Parameters: + plaintext : byte string + The piece of data to encrypt. It can be of any size. + :Return: the encrypted data (byte string, as long as the + plaintext). + """ + return self._cipher.encrypt(plaintext) + + def decrypt(self, ciphertext): + """Decrypt a piece of data. + + :Parameters: + ciphertext : byte string + The piece of data to decrypt. It can be of any size. + :Return: the decrypted data (byte string, as long as the + ciphertext). + """ + return self._cipher.decrypt(ciphertext) + +def new(key, *args, **kwargs): + """Create a new XOR cipher + + :Parameters: + key : byte string + The secret key to use in the symmetric cipher. + Its length may vary from 1 to 32 bytes. + + :Return: an `XORCipher` object + """ + return XORCipher(key, *args, **kwargs) + +#: Size of a data block (in bytes) +block_size = 1 +#: Size of a key (in bytes) +key_size = xrange(1,32+1) + diff --git a/lib/Crypto/Cipher/_AES.so b/lib/Crypto/Cipher/_AES.so new file mode 100755 index 0000000000000000000000000000000000000000..4a3c7280272459df6dde74a52e7af6ef9e972802 GIT binary patch literal 68712 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%0A(_ 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 literal 0 HcmV?d00001 diff --git a/lib/Crypto/Cipher/_ARC2.so b/lib/Crypto/Cipher/_ARC2.so new file mode 100755 index 0000000000000000000000000000000000000000..47bca8884f8cd0be7ce1e785a1f66b3d2f5f3445 GIT binary patch literal 43896 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 OlGS`@8UNVV6#fT=nYk$d literal 0 HcmV?d00001 diff --git a/lib/Crypto/Cipher/_Blowfish.so b/lib/Crypto/Cipher/_Blowfish.so new file mode 100755 index 0000000000000000000000000000000000000000..c00c96edd42b27ce0190be4b5c966002663640c5 GIT binary patch literal 52272 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 WAKrmLE1Mcin(AvAg#K&SApC#SFHST7 literal 0 HcmV?d00001 diff --git a/lib/Crypto/Cipher/_CAST.so b/lib/Crypto/Cipher/_CAST.so new file mode 100755 index 0000000000000000000000000000000000000000..b52862ac14d8e72ed4fa2417cfb246853c6f3f8f GIT binary patch literal 60360 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 literal 0 HcmV?d00001 diff --git a/lib/Crypto/Cipher/_DES3.so b/lib/Crypto/Cipher/_DES3.so new file mode 100755 index 0000000000000000000000000000000000000000..b5dbf3d8842bcbbdccdb53039c7175af249f1928 GIT binary patch literal 118108 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 Ir_40{Pa2K0761SM literal 0 HcmV?d00001 diff --git a/lib/Crypto/Cipher/_XOR.so b/lib/Crypto/Cipher/_XOR.so new file mode 100755 index 0000000000000000000000000000000000000000..ee62396259565c822d36a46d5bd714dee5c4b230 GIT binary patch literal 26692 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 X;NDZindex: + if param: + raise ValueError("Parameter '%s' is specified twice" % name) + param = args[index] + return param or default + +class BlockAlgo: + """Class modelling an abstract block cipher.""" + + def __init__(self, factory, key, *args, **kwargs): + self.mode = _getParameter('mode', 0, args, kwargs, default=MODE_ECB) + self.block_size = factory.block_size + + if self.mode != MODE_OPENPGP: + self._cipher = factory.new(key, *args, **kwargs) + self.IV = self._cipher.IV + else: + # OPENPGP mode. For details, see 13.9 in RCC4880. + # + # A few members are specifically created for this mode: + # - _encrypted_iv, set in this constructor + # - _done_first_block, set to True after the first encryption + # - _done_last_block, set to True after a partial block is processed + + self._done_first_block = False + self._done_last_block = False + self.IV = _getParameter('iv', 1, args, kwargs) + if not self.IV: + raise ValueError("MODE_OPENPGP requires an IV") + + # Instantiate a temporary cipher to process the IV + IV_cipher = factory.new(key, MODE_CFB, + b('\x00')*self.block_size, # IV for CFB + segment_size=self.block_size*8) + + # The cipher will be used for... + if len(self.IV) == self.block_size: + # ... encryption + self._encrypted_IV = IV_cipher.encrypt( + self.IV + self.IV[-2:] + # Plaintext + b('\x00')*(self.block_size-2) # Padding + )[:self.block_size+2] + elif len(self.IV) == self.block_size+2: + # ... decryption + self._encrypted_IV = self.IV + self.IV = IV_cipher.decrypt(self.IV + # Ciphertext + b('\x00')*(self.block_size-2) # Padding + )[:self.block_size+2] + if self.IV[-2:] != self.IV[-4:-2]: + raise ValueError("Failed integrity check for OPENPGP IV") + self.IV = self.IV[:-2] + else: + raise ValueError("Length of IV must be %d or %d bytes for MODE_OPENPGP" + % (self.block_size, self.block_size+2)) + + # Instantiate the cipher for the real PGP data + self._cipher = factory.new(key, MODE_CFB, + self._encrypted_IV[-self.block_size:], + segment_size=self.block_size*8) + + def encrypt(self, plaintext): + """Encrypt data with the key and the parameters set at initialization. + + The cipher object is stateful; encryption of a long block + of data can be broken up in two or more calls to `encrypt()`. + That is, the statement: + + >>> c.encrypt(a) + c.encrypt(b) + + is always equivalent to: + + >>> c.encrypt(a+b) + + That also means that you cannot reuse an object for encrypting + or decrypting other data with the same key. + + This function does not perform any padding. + + - For `MODE_ECB`, `MODE_CBC`, and `MODE_OFB`, *plaintext* length + (in bytes) must be a multiple of *block_size*. + + - For `MODE_CFB`, *plaintext* length (in bytes) must be a multiple + of *segment_size*/8. + + - For `MODE_CTR`, *plaintext* can be of any length. + + - For `MODE_OPENPGP`, *plaintext* must be a multiple of *block_size*, + unless it is the last chunk of the message. + + :Parameters: + plaintext : byte string + The piece of data to encrypt. + :Return: + the encrypted data, as a byte string. It is as long as + *plaintext* with one exception: when encrypting the first message + chunk with `MODE_OPENPGP`, the encypted IV is prepended to the + returned ciphertext. + """ + + if self.mode == MODE_OPENPGP: + padding_length = (self.block_size - len(plaintext) % self.block_size) % self.block_size + if padding_length>0: + # CFB mode requires ciphertext to have length multiple of block size, + # but PGP mode allows the last block to be shorter + if self._done_last_block: + raise ValueError("Only the last chunk is allowed to have length not multiple of %d bytes", + self.block_size) + self._done_last_block = True + padded = plaintext + b('\x00')*padding_length + res = self._cipher.encrypt(padded)[:len(plaintext)] + else: + res = self._cipher.encrypt(plaintext) + if not self._done_first_block: + res = self._encrypted_IV + res + self._done_first_block = True + return res + + return self._cipher.encrypt(plaintext) + + def decrypt(self, ciphertext): + """Decrypt data with the key and the parameters set at initialization. + + The cipher object is stateful; decryption of a long block + of data can be broken up in two or more calls to `decrypt()`. + That is, the statement: + + >>> c.decrypt(a) + c.decrypt(b) + + is always equivalent to: + + >>> c.decrypt(a+b) + + That also means that you cannot reuse an object for encrypting + or decrypting other data with the same key. + + This function does not perform any padding. + + - For `MODE_ECB`, `MODE_CBC`, and `MODE_OFB`, *ciphertext* length + (in bytes) must be a multiple of *block_size*. + + - For `MODE_CFB`, *ciphertext* length (in bytes) must be a multiple + of *segment_size*/8. + + - For `MODE_CTR`, *ciphertext* can be of any length. + + - For `MODE_OPENPGP`, *plaintext* must be a multiple of *block_size*, + unless it is the last chunk of the message. + + :Parameters: + ciphertext : byte string + The piece of data to decrypt. + :Return: the decrypted data (byte string, as long as *ciphertext*). + """ + if self.mode == MODE_OPENPGP: + padding_length = (self.block_size - len(ciphertext) % self.block_size) % self.block_size + if padding_length>0: + # CFB mode requires ciphertext to have length multiple of block size, + # but PGP mode allows the last block to be shorter + if self._done_last_block: + raise ValueError("Only the last chunk is allowed to have length not multiple of %d bytes", + self.block_size) + self._done_last_block = True + padded = ciphertext + b('\x00')*padding_length + res = self._cipher.decrypt(padded)[:len(ciphertext)] + else: + res = self._cipher.decrypt(ciphertext) + return res + + return self._cipher.decrypt(ciphertext) + diff --git a/lib/Crypto/Hash/HMAC.py b/lib/Crypto/Hash/HMAC.py new file mode 100644 index 0000000..6244db4 --- /dev/null +++ b/lib/Crypto/Hash/HMAC.py @@ -0,0 +1,212 @@ +# HMAC.py - Implements the HMAC algorithm as described by RFC 2104. +# +# =================================================================== +# Portions Copyright (c) 2001, 2002, 2003 Python Software Foundation; +# All Rights Reserved +# +# This file contains code from the Python 2.2 hmac.py module (the +# "Original Code"), with modifications made after it was incorporated +# into PyCrypto (the "Modifications"). +# +# To the best of our knowledge, the Python Software Foundation is the +# copyright holder of the Original Code, and has licensed it under the +# Python 2.2 license. See the file LEGAL/copy/LICENSE.python-2.2 for +# details. +# +# The Modifications to 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. +# =================================================================== + + +"""HMAC (Hash-based Message Authentication Code) algorithm + +HMAC is a MAC defined in RFC2104_ and FIPS-198_ and constructed using +a cryptograpic hash algorithm. +It is usually named *HMAC-X*, where *X* is the hash algorithm; for +instance *HMAC-SHA1* or *HMAC-MD5*. + +The strength of an HMAC depends on: + + - the strength of the hash algorithm + - the length and entropy of the secret key + +An example of possible usage is the following: + + >>> from Crypto.Hash import HMAC + >>> + >>> secret = b'Swordfish' + >>> h = HMAC.new(secret) + >>> h.update(b'Hello') + >>> print h.hexdigest() + +.. _RFC2104: http://www.ietf.org/rfc/rfc2104.txt +.. _FIPS-198: http://csrc.nist.gov/publications/fips/fips198/fips-198a.pdf +""" + +# This is just a copy of the Python 2.2 HMAC module, modified to work when +# used on versions of Python before 2.2. + +__revision__ = "$Id$" + +__all__ = ['new', 'digest_size', 'HMAC' ] + +from Crypto.Util.strxor import strxor_c +from Crypto.Util.py3compat import * + +#: The size of the authentication tag produced by the MAC. +#: It matches the digest size on the underlying +#: hashing module used. +digest_size = None + +class HMAC: + """Class that implements HMAC""" + + #: The size of the authentication tag produced by the MAC. + #: It matches the digest size on the underlying + #: hashing module used. + digest_size = None + + def __init__(self, key, msg = None, digestmod = None): + """Create a new HMAC object. + + :Parameters: + key : byte string + secret key for the MAC object. + It must be long enough to match the expected security level of the + MAC. However, there is no benefit in using keys longer than the + `digest_size` of the underlying hash algorithm. + msg : byte string + The very first chunk of the message to authenticate. + It is equivalent to an early call to `update()`. Optional. + :Parameter digestmod: + The hash algorithm the HMAC is based on. + Default is `Crypto.Hash.MD5`. + :Type digestmod: + A hash module or object instantiated from `Crypto.Hash` + """ + if digestmod is None: + import MD5 + digestmod = MD5 + + self.digestmod = digestmod + self.outer = digestmod.new() + self.inner = digestmod.new() + try: + self.digest_size = digestmod.digest_size + except AttributeError: + self.digest_size = len(self.outer.digest()) + + try: + # The block size is 128 bytes for SHA384 and SHA512 and 64 bytes + # for the others hash function + blocksize = digestmod.block_size + except AttributeError: + blocksize = 64 + + ipad = 0x36 + opad = 0x5C + + if len(key) > blocksize: + key = digestmod.new(key).digest() + + key = key + bchr(0) * (blocksize - len(key)) + self.outer.update(strxor_c(key, opad)) + self.inner.update(strxor_c(key, ipad)) + if (msg): + self.update(msg) + + def update(self, msg): + """Continue authentication of a message by consuming the next chunk of data. + + Repeated calls are equivalent to a single call with the concatenation + of all the arguments. In other words: + + >>> m.update(a); m.update(b) + + is equivalent to: + + >>> m.update(a+b) + + :Parameters: + msg : byte string + The next chunk of the message being authenticated + """ + + self.inner.update(msg) + + def copy(self): + """Return a copy ("clone") of the MAC object. + + The copy will have the same internal state as the original MAC + object. + This can be used to efficiently compute the MAC of strings that + share a common initial substring. + + :Returns: An `HMAC` object + """ + other = HMAC(b("")) + other.digestmod = self.digestmod + other.inner = self.inner.copy() + other.outer = self.outer.copy() + return other + + def digest(self): + """Return the **binary** (non-printable) MAC of the message that has + been authenticated so far. + + This method does not change the state of the MAC object. + You can continue updating the object after calling this function. + + :Return: A byte string of `digest_size` bytes. It may contain non-ASCII + characters, including null bytes. + """ + h = self.outer.copy() + h.update(self.inner.digest()) + return h.digest() + + def hexdigest(self): + """Return the **printable** MAC of the message that has been + authenticated so far. + + This method does not change the state of the MAC object. + + :Return: A string of 2* `digest_size` bytes. It contains only + hexadecimal ASCII digits. + """ + return "".join(["%02x" % bord(x) + for x in tuple(self.digest())]) + +def new(key, msg = None, digestmod = None): + """Create a new HMAC object. + + :Parameters: + key : byte string + key for the MAC object. + It must be long enough to match the expected security level of the + MAC. However, there is no benefit in using keys longer than the + `digest_size` of the underlying hash algorithm. + msg : byte string + The very first chunk of the message to authenticate. + It is equivalent to an early call to `HMAC.update()`. + Optional. + :Parameter digestmod: + The hash to use to implement the HMAC. Default is `Crypto.Hash.MD5`. + :Type digestmod: + A hash module or instantiated object from `Crypto.Hash` + :Returns: An `HMAC` object + """ + return HMAC(key, msg, digestmod) + diff --git a/lib/Crypto/Hash/MD2.py b/lib/Crypto/Hash/MD2.py new file mode 100644 index 0000000..dac959e --- /dev/null +++ b/lib/Crypto/Hash/MD2.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +# +# =================================================================== +# 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. +# =================================================================== + +"""MD2 cryptographic hash algorithm. + +MD2 is specified in RFC1319_ and it produces the 128 bit digest of a message. + + >>> from Crypto.Hash import MD2 + >>> + >>> h = MD2.new() + >>> h.update(b'Hello') + >>> print h.hexdigest() + +MD2 stand for Message Digest version 2, and it was invented by Rivest in 1989. + +This algorithm is both slow and insecure. Do not use it for new designs. + +.. _RFC1319: http://tools.ietf.org/html/rfc1319 +""" + +_revision__ = "$Id$" + +__all__ = ['new', 'digest_size', 'MD2Hash' ] + +from Crypto.Util.py3compat import * +from Crypto.Hash.hashalgo import HashAlgo + +import Crypto.Hash._MD2 as _MD2 +hashFactory = _MD2 + +class MD2Hash(HashAlgo): + """Class that implements an MD2 hash + + :undocumented: block_size + """ + + #: ASN.1 Object identifier (OID):: + #: + #: id-md2 OBJECT IDENTIFIER ::= { + #: iso(1) member-body(2) us(840) rsadsi(113549) + #: digestAlgorithm(2) 2 + #: } + #: + #: This value uniquely identifies the MD2 algorithm. + oid = b('\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x02') + + digest_size = 16 + block_size = 16 + + def __init__(self, data=None): + HashAlgo.__init__(self, hashFactory, data) + + def new(self, data=None): + return MD2Hash(data) + +def new(data=None): + """Return a fresh instance of the hash object. + + :Parameters: + data : byte string + The very first chunk of the message to hash. + It is equivalent to an early call to `MD2Hash.update()`. + Optional. + + :Return: An `MD2Hash` object + """ + return MD2Hash().new(data) + +#: The size of the resulting hash in bytes. +digest_size = MD2Hash.digest_size + +#: The internal block size of the hash algorithm in bytes. +block_size = MD2Hash.block_size + diff --git a/lib/Crypto/Hash/MD4.py b/lib/Crypto/Hash/MD4.py new file mode 100644 index 0000000..e28a201 --- /dev/null +++ b/lib/Crypto/Hash/MD4.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +# +# =================================================================== +# 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. +# =================================================================== + +"""MD4 cryptographic hash algorithm. + +MD4 is specified in RFC1320_ and produces the 128 bit digest of a message. + + >>> from Crypto.Hash import MD4 + >>> + >>> h = MD4.new() + >>> h.update(b'Hello') + >>> print h.hexdigest() + +MD4 stand for Message Digest version 4, and it was invented by Rivest in 1990. + +This algorithm is insecure. Do not use it for new designs. + +.. _RFC1320: http://tools.ietf.org/html/rfc1320 +""" + +_revision__ = "$Id$" + +__all__ = ['new', 'digest_size', 'MD4Hash' ] + +from Crypto.Util.py3compat import * +from Crypto.Hash.hashalgo import HashAlgo + +import Crypto.Hash._MD4 as _MD4 +hashFactory = _MD4 + +class MD4Hash(HashAlgo): + """Class that implements an MD4 hash + + :undocumented: block_size + """ + + #: ASN.1 Object identifier (OID):: + #: + #: id-md2 OBJECT IDENTIFIER ::= { + #: iso(1) member-body(2) us(840) rsadsi(113549) + #: digestAlgorithm(2) 4 + #: } + #: + #: This value uniquely identifies the MD4 algorithm. + oid = b('\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x04') + + digest_size = 16 + block_size = 64 + + def __init__(self, data=None): + HashAlgo.__init__(self, hashFactory, data) + + def new(self, data=None): + return MD4Hash(data) + +def new(data=None): + """Return a fresh instance of the hash object. + + :Parameters: + data : byte string + The very first chunk of the message to hash. + It is equivalent to an early call to `MD4Hash.update()`. + Optional. + + :Return: A `MD4Hash` object + """ + return MD4Hash().new(data) + +#: The size of the resulting hash in bytes. +digest_size = MD4Hash.digest_size + +#: The internal block size of the hash algorithm in bytes. +block_size = MD4Hash.block_size + diff --git a/lib/Crypto/Hash/MD5.py b/lib/Crypto/Hash/MD5.py new file mode 100644 index 0000000..18e9e7b --- /dev/null +++ b/lib/Crypto/Hash/MD5.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +# +# =================================================================== +# 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. +# =================================================================== + +"""MD5 cryptographic hash algorithm. + +MD5 is specified in RFC1321_ and produces the 128 bit digest of a message. + + >>> from Crypto.Hash import MD5 + >>> + >>> h = MD5.new() + >>> h.update(b'Hello') + >>> print h.hexdigest() + +MD5 stand for Message Digest version 5, and it was invented by Rivest in 1991. + +This algorithm is insecure. Do not use it for new designs. + +.. _RFC1321: http://tools.ietf.org/html/rfc1321 +""" + +_revision__ = "$Id$" + +__all__ = ['new', 'digest_size', 'MD5Hash' ] + +from Crypto.Util.py3compat import * +from Crypto.Hash.hashalgo import HashAlgo + +try: + # The md5 module is deprecated in Python 2.6, so use hashlib when possible. + import hashlib + hashFactory = hashlib.md5 + +except ImportError: + import md5 + hashFactory = md5 + +class MD5Hash(HashAlgo): + """Class that implements an MD5 hash + + :undocumented: block_size + """ + + #: ASN.1 Object identifier (OID):: + #: + #: id-md5 OBJECT IDENTIFIER ::= { + #: iso(1) member-body(2) us(840) rsadsi(113549) + #: digestAlgorithm(2) 5 + #: } + #: + #: This value uniquely identifies the MD5 algorithm. + oid = b('\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05') + + digest_size = 16 + block_size = 64 + + def __init__(self, data=None): + HashAlgo.__init__(self, hashFactory, data) + + def new(self, data=None): + return MD5Hash(data) + +def new(data=None): + """Return a fresh instance of the hash object. + + :Parameters: + data : byte string + The very first chunk of the message to hash. + It is equivalent to an early call to `MD5Hash.update()`. + Optional. + + :Return: A `MD5Hash` object + """ + return MD5Hash().new(data) + +#: The size of the resulting hash in bytes. +digest_size = MD5Hash.digest_size + +#: The internal block size of the hash algorithm in bytes. +block_size = MD5Hash.block_size + diff --git a/lib/Crypto/Hash/RIPEMD.py b/lib/Crypto/Hash/RIPEMD.py new file mode 100644 index 0000000..33099cb --- /dev/null +++ b/lib/Crypto/Hash/RIPEMD.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# +# =================================================================== +# 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. +# =================================================================== + +"""RIPEMD-160 cryptographic hash algorithm. + +RIPEMD-160_ produces the 160 bit digest of a message. + + >>> from Crypto.Hash import RIPEMD + >>> + >>> h = RIPEMD.new() + >>> h.update(b'Hello') + >>> print h.hexdigest() + +RIPEMD-160 stands for RACE Integrity Primitives Evaluation Message Digest +with a 160 bit digest. It was invented by Dobbertin, Bosselaers, and Preneel. + +This algorithm is considered secure, although it has not been scrutinized as +extensively as SHA-1. Moreover, it provides an informal security level of just +80bits. + +.. _RIPEMD-160: http://homes.esat.kuleuven.be/~bosselae/ripemd160.html +""" + +_revision__ = "$Id$" + +__all__ = ['new', 'digest_size', 'RIPEMD160Hash' ] + +from Crypto.Util.py3compat import * +from Crypto.Hash.hashalgo import HashAlgo + +import Crypto.Hash._RIPEMD160 as _RIPEMD160 +hashFactory = _RIPEMD160 + +class RIPEMD160Hash(HashAlgo): + """Class that implements a RIPMD-160 hash + + :undocumented: block_size + """ + + #: ASN.1 Object identifier (OID):: + #: + #: id-ripemd160 OBJECT IDENTIFIER ::= { + #: iso(1) identified-organization(3) teletrust(36) + #: algorithm(3) hashAlgorithm(2) ripemd160(1) + #: } + #: + #: This value uniquely identifies the RIPMD-160 algorithm. + oid = b("\x06\x05\x2b\x24\x03\x02\x01") + + digest_size = 20 + block_size = 64 + + def __init__(self, data=None): + HashAlgo.__init__(self, hashFactory, data) + + def new(self, data=None): + return RIPEMD160Hash(data) + +def new(data=None): + """Return a fresh instance of the hash object. + + :Parameters: + data : byte string + The very first chunk of the message to hash. + It is equivalent to an early call to `RIPEMD160Hash.update()`. + Optional. + + :Return: A `RIPEMD160Hash` object + """ + return RIPEMD160Hash().new(data) + +#: The size of the resulting hash in bytes. +digest_size = RIPEMD160Hash.digest_size + +#: The internal block size of the hash algorithm in bytes. +block_size = RIPEMD160Hash.block_size + diff --git a/lib/Crypto/Hash/SHA.py b/lib/Crypto/Hash/SHA.py new file mode 100644 index 0000000..0bc5917 --- /dev/null +++ b/lib/Crypto/Hash/SHA.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# +# =================================================================== +# 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. +# =================================================================== + +"""SHA-1 cryptographic hash algorithm. + +SHA-1_ produces the 160 bit digest of a message. + + >>> from Crypto.Hash import SHA + >>> + >>> h = SHA.new() + >>> h.update(b'Hello') + >>> print h.hexdigest() + +*SHA* stands for Secure Hash Algorithm. + +This algorithm is not considered secure. Do not use it for new designs. + +.. _SHA-1: http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf +""" + +_revision__ = "$Id$" + +__all__ = ['new', 'digest_size', 'SHA1Hash' ] + +from Crypto.Util.py3compat import * +from Crypto.Hash.hashalgo import HashAlgo + +try: + # The sha module is deprecated in Python 2.6, so use hashlib when possible. + import hashlib + hashFactory = hashlib.sha1 + +except ImportError: + import sha + hashFactory = sha + +class SHA1Hash(HashAlgo): + """Class that implements a SHA-1 hash + + :undocumented: block_size + """ + + #: ASN.1 Object identifier (OID):: + #: + #: id-sha1 OBJECT IDENTIFIER ::= { + #: iso(1) identified-organization(3) oiw(14) secsig(3) + #: algorithms(2) 26 + #: } + #: + #: This value uniquely identifies the SHA-1 algorithm. + oid = b('\x06\x05\x2b\x0e\x03\x02\x1a') + + digest_size = 20 + block_size = 64 + + def __init__(self, data=None): + HashAlgo.__init__(self, hashFactory, data) + + def new(self, data=None): + return SHA1Hash(data) + +def new(data=None): + """Return a fresh instance of the hash object. + + :Parameters: + data : byte string + The very first chunk of the message to hash. + It is equivalent to an early call to `SHA1Hash.update()`. + Optional. + + :Return: A `SHA1Hash` object + """ + return SHA1Hash().new(data) + +#: The size of the resulting hash in bytes. +digest_size = SHA1Hash.digest_size + +#: The internal block size of the hash algorithm in bytes. +block_size = SHA1Hash.block_size + + diff --git a/lib/Crypto/Hash/SHA224.py b/lib/Crypto/Hash/SHA224.py new file mode 100644 index 0000000..959b56d --- /dev/null +++ b/lib/Crypto/Hash/SHA224.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +# +# =================================================================== +# 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. +# =================================================================== + +"""SHA-224 cryptographic hash algorithm. + +SHA-224 belongs to the SHA-2_ family of cryptographic hashes. +It produces the 224 bit digest of a message. + + >>> from Crypto.Hash import SHA224 + >>> + >>> h = SHA224.new() + >>> h.update(b'Hello') + >>> print h.hexdigest() + +*SHA* stands for Secure Hash Algorithm. + +.. _SHA-2: http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf +""" + +_revision__ = "$Id$" + +__all__ = ['new', 'digest_size', 'SHA224Hash' ] + +from Crypto.Util.py3compat import * +from Crypto.Hash.hashalgo import HashAlgo + +try: + import hashlib + hashFactory = hashlib.sha224 + +except ImportError: + from Crypto.Hash import _SHA224 + hashFactory = _SHA224 + +class SHA224Hash(HashAlgo): + """Class that implements a SHA-224 hash + + :undocumented: block_size + """ + + #: ASN.1 Object identifier (OID):: + #: + #: id-sha224 OBJECT IDENTIFIER ::= { + #: joint-iso-itu-t(2) country(16) us(840) organization(1) gov(101) csor(3) + #: nistalgorithm(4) hashalgs(2) 4 + #: } + #: + #: This value uniquely identifies the SHA-224 algorithm. + oid = b('\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04') + + digest_size = 28 + block_size = 64 + + def __init__(self, data=None): + HashAlgo.__init__(self, hashFactory, data) + + def new(self, data=None): + return SHA224Hash(data) + +def new(data=None): + """Return a fresh instance of the hash object. + + :Parameters: + data : byte string + The very first chunk of the message to hash. + It is equivalent to an early call to `SHA224Hash.update()`. + Optional. + + :Return: A `SHA224Hash` object + """ + return SHA224Hash().new(data) + +#: The size of the resulting hash in bytes. +digest_size = SHA224Hash.digest_size + +#: The internal block size of the hash algorithm in bytes. +block_size = SHA224Hash.block_size + diff --git a/lib/Crypto/Hash/SHA256.py b/lib/Crypto/Hash/SHA256.py new file mode 100644 index 0000000..b0a99b3 --- /dev/null +++ b/lib/Crypto/Hash/SHA256.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +# +# =================================================================== +# 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. +# =================================================================== + +"""SHA-256 cryptographic hash algorithm. + +SHA-256 belongs to the SHA-2_ family of cryptographic hashes. +It produces the 256 bit digest of a message. + + >>> from Crypto.Hash import SHA256 + >>> + >>> h = SHA256.new() + >>> h.update(b'Hello') + >>> print h.hexdigest() + +*SHA* stands for Secure Hash Algorithm. + +.. _SHA-2: http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf +""" + +_revision__ = "$Id$" + +__all__ = ['new', 'digest_size', 'SHA256Hash' ] + +from Crypto.Util.py3compat import * +from Crypto.Hash.hashalgo import HashAlgo + +try: + import hashlib + hashFactory = hashlib.sha256 + +except ImportError: + from Crypto.Hash import _SHA256 + hashFactory = _SHA256 + +class SHA256Hash(HashAlgo): + """Class that implements a SHA-256 hash + + :undocumented: block_size + """ + + #: ASN.1 Object identifier (OID):: + #: + #: id-sha256 OBJECT IDENTIFIER ::= { + #: joint-iso-itu-t(2) country(16) us(840) organization(1) + #: gov(101) csor(3) nistalgorithm(4) hashalgs(2) 1 + #: } + #: + #: This value uniquely identifies the SHA-256 algorithm. + oid = b('\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01') + + digest_size = 32 + block_size = 64 + + def __init__(self, data=None): + HashAlgo.__init__(self, hashFactory, data) + + def new(self, data=None): + return SHA256Hash(data) + +def new(data=None): + """Return a fresh instance of the hash object. + + :Parameters: + data : byte string + The very first chunk of the message to hash. + It is equivalent to an early call to `SHA256Hash.update()`. + Optional. + + :Return: A `SHA256Hash` object + """ + return SHA256Hash().new(data) + +#: The size of the resulting hash in bytes. +digest_size = SHA256Hash.digest_size + +#: The internal block size of the hash algorithm in bytes. +block_size = SHA256Hash.block_size + diff --git a/lib/Crypto/Hash/SHA384.py b/lib/Crypto/Hash/SHA384.py new file mode 100644 index 0000000..3490b02 --- /dev/null +++ b/lib/Crypto/Hash/SHA384.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +# +# =================================================================== +# 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. +# =================================================================== + +"""SHA-384 cryptographic hash algorithm. + +SHA-384 belongs to the SHA-2_ family of cryptographic hashes. +It produces the 384 bit digest of a message. + + >>> from Crypto.Hash import SHA384 + >>> + >>> h = SHA384.new() + >>> h.update(b'Hello') + >>> print h.hexdigest() + +*SHA* stands for Secure Hash Algorithm. + +.. _SHA-2: http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf +""" + +_revision__ = "$Id$" + +__all__ = ['new', 'digest_size', 'SHA384Hash' ] + +from Crypto.Util.py3compat import * +from Crypto.Hash.hashalgo import HashAlgo + +try: + import hashlib + hashFactory = hashlib.sha384 + +except ImportError: + from Crypto.Hash import _SHA384 + hashFactory = _SHA384 + +class SHA384Hash(HashAlgo): + """Class that implements a SHA-384 hash + + :undocumented: block_size + """ + + #: ASN.1 Object identifier (OID):: + #: + #: id-sha384 OBJECT IDENTIFIER ::= { + #: joint-iso-itu-t(2) country(16) us(840) organization(1) gov(101) csor(3) + #: nistalgorithm(4) hashalgs(2) 2 + #: } + #: + #: This value uniquely identifies the SHA-384 algorithm. + oid = b('\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02') + + digest_size = 48 + block_size = 128 + + def __init__(self, data=None): + HashAlgo.__init__(self, hashFactory, data) + + def new(self, data=None): + return SHA384Hash(data) + +def new(data=None): + """Return a fresh instance of the hash object. + + :Parameters: + data : byte string + The very first chunk of the message to hash. + It is equivalent to an early call to `SHA384Hash.update()`. + Optional. + + :Return: A `SHA384Hash` object + """ + return SHA384Hash().new(data) + +#: The size of the resulting hash in bytes. +digest_size = SHA384Hash.digest_size + +#: The internal block size of the hash algorithm in bytes. +block_size = SHA384Hash.block_size + + diff --git a/lib/Crypto/Hash/SHA512.py b/lib/Crypto/Hash/SHA512.py new file mode 100644 index 0000000..d57548d --- /dev/null +++ b/lib/Crypto/Hash/SHA512.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +# +# =================================================================== +# 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. +# =================================================================== + +"""SHA-512 cryptographic hash algorithm. + +SHA-512 belongs to the SHA-2_ family of cryptographic hashes. +It produces the 512 bit digest of a message. + + >>> from Crypto.Hash import SHA512 + >>> + >>> h = SHA512.new() + >>> h.update(b'Hello') + >>> print h.hexdigest() + +*SHA* stands for Secure Hash Algorithm. + +.. _SHA-2: http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf +""" + +_revision__ = "$Id$" + +__all__ = ['new', 'digest_size', 'SHA512Hash' ] + +from Crypto.Util.py3compat import * +from Crypto.Hash.hashalgo import HashAlgo + +try: + import hashlib + hashFactory = hashlib.sha512 + +except ImportError: + from Crypto.Hash import _SHA512 + hashFactory = _SHA512 + +class SHA512Hash(HashAlgo): + """Class that implements a SHA-512 hash + + :undocumented: block_size + """ + + #: ASN.1 Object identifier (OID):: + #: + #: id-sha512 OBJECT IDENTIFIER ::= { + #: joint-iso-itu-t(2) + #: country(16) us(840) organization(1) gov(101) csor(3) nistalgorithm(4) hashalgs(2) 3 + #: } + #: + #: This value uniquely identifies the SHA-512 algorithm. + oid = b('\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03') + + digest_size = 64 + block_size = 128 + + def __init__(self, data=None): + HashAlgo.__init__(self, hashFactory, data) + + def new(self, data=None): + return SHA512Hash(data) + +def new(data=None): + """Return a fresh instance of the hash object. + + :Parameters: + data : byte string + The very first chunk of the message to hash. + It is equivalent to an early call to `SHA512Hash.update()`. + Optional. + + :Return: A `SHA512Hash` object + """ + return SHA512Hash().new(data) + +#: The size of the resulting hash in bytes. +digest_size = SHA512Hash.digest_size + +#: The internal block size of the hash algorithm in bytes. +block_size = SHA512Hash.block_size + diff --git a/lib/Crypto/Hash/_MD2.so b/lib/Crypto/Hash/_MD2.so new file mode 100755 index 0000000000000000000000000000000000000000..359abf2211aa4f4a117d5ed3db41b1bc2d00d9ca GIT binary patch literal 30620 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 M3wGQ8tIres9-mgyS^xk5 literal 0 HcmV?d00001 diff --git a/lib/Crypto/Hash/_MD4.so b/lib/Crypto/Hash/_MD4.so new file mode 100755 index 0000000000000000000000000000000000000000..e70630adfdfeda74c8db59cacca0de5d6e829fa8 GIT binary patch literal 34780 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 literal 0 HcmV?d00001 diff --git a/lib/Crypto/Hash/_SHA224.so b/lib/Crypto/Hash/_SHA224.so new file mode 100755 index 0000000000000000000000000000000000000000..e5d41ed336f8a25fcbf2b35e91a99dcca0ae076d GIT binary patch literal 34812 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 literal 0 HcmV?d00001 diff --git a/lib/Crypto/Hash/_SHA256.so b/lib/Crypto/Hash/_SHA256.so new file mode 100755 index 0000000000000000000000000000000000000000..93a90c0df42ff28c2cb9ec7e4e3b0b3311f9302d GIT binary patch literal 34812 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 OXys2?^8XvIEcg!`k^o}> literal 0 HcmV?d00001 diff --git a/lib/Crypto/Hash/_SHA512.so b/lib/Crypto/Hash/_SHA512.so new file mode 100755 index 0000000000000000000000000000000000000000..d5a2313c8fbcb3edbea62ac59c09b1323898fe7b GIT binary patch literal 34860 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 VpilO9-d8{?f6|iwKX7Hie*ng-2uA<_ literal 0 HcmV?d00001 diff --git a/lib/Crypto/Hash/__init__.py b/lib/Crypto/Hash/__init__.py new file mode 100644 index 0000000..4582c66 --- /dev/null +++ b/lib/Crypto/Hash/__init__.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# +# =================================================================== +# 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. +# =================================================================== + +"""Hashing algorithms + +Hash functions take arbitrary binary strings as input, and produce a random-like output +of fixed size that is dependent on the input; it should be practically infeasible +to derive the original input data given only the hash function's +output. In other words, the hash function is *one-way*. + +It should also not be practically feasible to find a second piece of data +(a *second pre-image*) whose hash is the same as the original message +(*weak collision resistance*). + +Finally, it should not be feasible to find two arbitrary messages with the +same hash (*strong collision resistance*). + +The output of the hash function is called the *digest* of the input message. +In general, the security of a hash function is related to the length of the +digest. If the digest is *n* bits long, its security level is roughly comparable +to the the one offered by an *n/2* bit encryption algorithm. + +Hash functions can be used simply as a integrity check, or, in +association with a public-key algorithm, can be used to implement +digital signatures. + +The hashing modules here all support the interface described in `PEP +247`_ , "API for Cryptographic Hash Functions". + +.. _`PEP 247` : http://www.python.org/dev/peps/pep-0247/ + +:undocumented: _MD2, _MD4, _RIPEMD160, _SHA224, _SHA256, _SHA384, _SHA512 +""" + +__all__ = ['HMAC', 'MD2', 'MD4', 'MD5', 'RIPEMD', 'SHA', + 'SHA224', 'SHA256', 'SHA384', 'SHA512'] +__revision__ = "$Id$" + + diff --git a/lib/Crypto/Hash/hashalgo.py b/lib/Crypto/Hash/hashalgo.py new file mode 100644 index 0000000..b38b3a6 --- /dev/null +++ b/lib/Crypto/Hash/hashalgo.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +# +# =================================================================== +# 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. +# =================================================================== + +from binascii import hexlify + +class HashAlgo: + """A generic class for an abstract cryptographic hash algorithm. + + :undocumented: block_size + """ + + #: The size of the resulting hash in bytes. + digest_size = None + #: The internal block size of the hash algorithm in bytes. + block_size = None + + def __init__(self, hashFactory, data=None): + """Initialize the hash object. + + :Parameters: + hashFactory : callable + An object that will generate the actual hash implementation. + *hashFactory* must have a *new()* method, or must be directly + callable. + data : byte string + The very first chunk of the message to hash. + It is equivalent to an early call to `update()`. + """ + if hasattr(hashFactory, 'new'): + self._hash = hashFactory.new() + else: + self._hash = hashFactory() + if data: + self.update(data) + + def update(self, data): + """Continue hashing of a message by consuming the next chunk of data. + + Repeated calls are equivalent to a single call with the concatenation + of all the arguments. In other words: + + >>> m.update(a); m.update(b) + + is equivalent to: + + >>> m.update(a+b) + + :Parameters: + data : byte string + The next chunk of the message being hashed. + """ + return self._hash.update(data) + + def digest(self): + """Return the **binary** (non-printable) digest of the message that has been hashed so far. + + This method does not change the state of the hash object. + You can continue updating the object after calling this function. + + :Return: A byte string of `digest_size` bytes. It may contain non-ASCII + characters, including null bytes. + """ + return self._hash.digest() + + def hexdigest(self): + """Return the **printable** digest of the message that has been hashed so far. + + This method does not change the state of the hash object. + + :Return: A string of 2* `digest_size` characters. It contains only + hexadecimal ASCII digits. + """ + return self._hash.hexdigest() + + def copy(self): + """Return a copy ("clone") of the hash object. + + The copy will have the same internal state as the original hash + object. + This can be used to efficiently compute the digests of strings that + share a common initial substring. + + :Return: A hash object of the same type + """ + return self._hash.copy() + + def new(self, data=None): + """Return a fresh instance of the hash object. + + Unlike the `copy` method, the internal state of the object is empty. + + :Parameters: + data : byte string + The next chunk of the message being hashed. + + :Return: A hash object of the same type + """ + pass + diff --git a/lib/Crypto/Protocol/AllOrNothing.py b/lib/Crypto/Protocol/AllOrNothing.py new file mode 100644 index 0000000..acc92d5 --- /dev/null +++ b/lib/Crypto/Protocol/AllOrNothing.py @@ -0,0 +1,319 @@ +# +# AllOrNothing.py : all-or-nothing package transformations +# +# Part of the Python Cryptography Toolkit +# +# Written by Andrew M. Kuchling and others +# +# =================================================================== +# 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. +# =================================================================== + +"""This file implements all-or-nothing package transformations. + +An all-or-nothing package transformation is one in which some text is +transformed into message blocks, such that all blocks must be obtained before +the reverse transformation can be applied. Thus, if any blocks are corrupted +or lost, the original message cannot be reproduced. + +An all-or-nothing package transformation is not encryption, although a block +cipher algorithm is used. The encryption key is randomly generated and is +extractable from the message blocks. + +This class implements the All-Or-Nothing package transformation algorithm +described in: + +Ronald L. Rivest. "All-Or-Nothing Encryption and The Package Transform" +http://theory.lcs.mit.edu/~rivest/fusion.pdf + +""" + +__revision__ = "$Id$" + +import operator +import sys +from Crypto.Util.number import bytes_to_long, long_to_bytes +from Crypto.Util.py3compat import * + +def isInt(x): + test = 0 + try: + test += x + except TypeError: + return 0 + return 1 + +class AllOrNothing: + """Class implementing the All-or-Nothing package transform. + + Methods for subclassing: + + _inventkey(key_size): + Returns a randomly generated key. Subclasses can use this to + implement better random key generating algorithms. The default + algorithm is probably not very cryptographically secure. + + """ + + def __init__(self, ciphermodule, mode=None, IV=None): + """AllOrNothing(ciphermodule, mode=None, IV=None) + + ciphermodule is a module implementing the cipher algorithm to + use. It must provide the PEP272 interface. + + Note that the encryption key is randomly generated + automatically when needed. Optional arguments mode and IV are + passed directly through to the ciphermodule.new() method; they + are the feedback mode and initialization vector to use. All + three arguments must be the same for the object used to create + the digest, and to undigest'ify the message blocks. + """ + + self.__ciphermodule = ciphermodule + self.__mode = mode + self.__IV = IV + self.__key_size = ciphermodule.key_size + if not isInt(self.__key_size) or self.__key_size==0: + self.__key_size = 16 + + __K0digit = bchr(0x69) + + def digest(self, text): + """digest(text:string) : [string] + + Perform the All-or-Nothing package transform on the given + string. Output is a list of message blocks describing the + transformed text, where each block is a string of bit length equal + to the ciphermodule's block_size. + """ + + # generate a random session key and K0, the key used to encrypt the + # hash blocks. Rivest calls this a fixed, publically-known encryption + # key, but says nothing about the security implications of this key or + # how to choose it. + key = self._inventkey(self.__key_size) + K0 = self.__K0digit * self.__key_size + + # we need two cipher objects here, one that is used to encrypt the + # message blocks and one that is used to encrypt the hashes. The + # former uses the randomly generated key, while the latter uses the + # well-known key. + mcipher = self.__newcipher(key) + hcipher = self.__newcipher(K0) + + # Pad the text so that its length is a multiple of the cipher's + # block_size. Pad with trailing spaces, which will be eliminated in + # the undigest() step. + block_size = self.__ciphermodule.block_size + padbytes = block_size - (len(text) % block_size) + text = text + b(' ') * padbytes + + # Run through the algorithm: + # s: number of message blocks (size of text / block_size) + # input sequence: m1, m2, ... ms + # random key K' (`key' in the code) + # Compute output sequence: m'1, m'2, ... m's' for s' = s + 1 + # Let m'i = mi ^ E(K', i) for i = 1, 2, 3, ..., s + # Let m's' = K' ^ h1 ^ h2 ^ ... hs + # where hi = E(K0, m'i ^ i) for i = 1, 2, ... s + # + # The one complication I add is that the last message block is hard + # coded to the number of padbytes added, so that these can be stripped + # during the undigest() step + s = divmod(len(text), block_size)[0] + blocks = [] + hashes = [] + for i in range(1, s+1): + start = (i-1) * block_size + end = start + block_size + mi = text[start:end] + assert len(mi) == block_size + cipherblock = mcipher.encrypt(long_to_bytes(i, block_size)) + mticki = bytes_to_long(mi) ^ bytes_to_long(cipherblock) + blocks.append(mticki) + # calculate the hash block for this block + hi = hcipher.encrypt(long_to_bytes(mticki ^ i, block_size)) + hashes.append(bytes_to_long(hi)) + + # Add the padbytes length as a message block + i = i + 1 + cipherblock = mcipher.encrypt(long_to_bytes(i, block_size)) + mticki = padbytes ^ bytes_to_long(cipherblock) + blocks.append(mticki) + + # calculate this block's hash + hi = hcipher.encrypt(long_to_bytes(mticki ^ i, block_size)) + hashes.append(bytes_to_long(hi)) + + # Now calculate the last message block of the sequence 1..s'. This + # will contain the random session key XOR'd with all the hash blocks, + # so that for undigest(), once all the hash blocks are calculated, the + # session key can be trivially extracted. Calculating all the hash + # blocks requires that all the message blocks be received, thus the + # All-or-Nothing algorithm succeeds. + mtick_stick = bytes_to_long(key) ^ reduce(operator.xor, hashes) + blocks.append(mtick_stick) + + # we convert the blocks to strings since in Python, byte sequences are + # always represented as strings. This is more consistent with the + # model that encryption and hash algorithms always operate on strings. + return [long_to_bytes(i,self.__ciphermodule.block_size) for i in blocks] + + + def undigest(self, blocks): + """undigest(blocks : [string]) : string + + Perform the reverse package transformation on a list of message + blocks. Note that the ciphermodule used for both transformations + must be the same. blocks is a list of strings of bit length + equal to the ciphermodule's block_size. + """ + + # better have at least 2 blocks, for the padbytes package and the hash + # block accumulator + if len(blocks) < 2: + raise ValueError, "List must be at least length 2." + + # blocks is a list of strings. We need to deal with them as long + # integers + blocks = map(bytes_to_long, blocks) + + # Calculate the well-known key, to which the hash blocks are + # encrypted, and create the hash cipher. + K0 = self.__K0digit * self.__key_size + hcipher = self.__newcipher(K0) + block_size = self.__ciphermodule.block_size + + # Since we have all the blocks (or this method would have been called + # prematurely), we can calculate all the hash blocks. + hashes = [] + for i in range(1, len(blocks)): + mticki = blocks[i-1] ^ i + hi = hcipher.encrypt(long_to_bytes(mticki, block_size)) + hashes.append(bytes_to_long(hi)) + + # now we can calculate K' (key). remember the last block contains + # m's' which we don't include here + key = blocks[-1] ^ reduce(operator.xor, hashes) + + # and now we can create the cipher object + mcipher = self.__newcipher(long_to_bytes(key, self.__key_size)) + + # And we can now decode the original message blocks + parts = [] + for i in range(1, len(blocks)): + cipherblock = mcipher.encrypt(long_to_bytes(i, block_size)) + mi = blocks[i-1] ^ bytes_to_long(cipherblock) + parts.append(mi) + + # The last message block contains the number of pad bytes appended to + # the original text string, such that its length was an even multiple + # of the cipher's block_size. This number should be small enough that + # the conversion from long integer to integer should never overflow + padbytes = int(parts[-1]) + text = b('').join(map(long_to_bytes, parts[:-1])) + return text[:-padbytes] + + def _inventkey(self, key_size): + # Return key_size random bytes + from Crypto import Random + return Random.new().read(key_size) + + def __newcipher(self, key): + if self.__mode is None and self.__IV is None: + return self.__ciphermodule.new(key) + elif self.__IV is None: + return self.__ciphermodule.new(key, self.__mode) + else: + return self.__ciphermodule.new(key, self.__mode, self.__IV) + + + +if __name__ == '__main__': + import sys + import getopt + import base64 + + usagemsg = '''\ +Test module usage: %(program)s [-c cipher] [-l] [-h] + +Where: + --cipher module + -c module + Cipher module to use. Default: %(ciphermodule)s + + --aslong + -l + Print the encoded message blocks as long integers instead of base64 + encoded strings + + --help + -h + Print this help message +''' + + ciphermodule = 'AES' + aslong = 0 + + def usage(code, msg=None): + if msg: + print msg + print usagemsg % {'program': sys.argv[0], + 'ciphermodule': ciphermodule} + sys.exit(code) + + try: + opts, args = getopt.getopt(sys.argv[1:], + 'c:l', ['cipher=', 'aslong']) + except getopt.error, msg: + usage(1, msg) + + if args: + usage(1, 'Too many arguments') + + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-c', '--cipher'): + ciphermodule = arg + elif opt in ('-l', '--aslong'): + aslong = 1 + + # ugly hack to force __import__ to give us the end-path module + module = __import__('Crypto.Cipher.'+ciphermodule, None, None, ['new']) + + x = AllOrNothing(module) + print 'Original text:\n==========' + print __doc__ + print '==========' + msgblocks = x.digest(b(__doc__)) + print 'message blocks:' + for i, blk in zip(range(len(msgblocks)), msgblocks): + # base64 adds a trailing newline + print ' %3d' % i, + if aslong: + print bytes_to_long(blk) + else: + print base64.encodestring(blk)[:-1] + # + # get a new undigest-only object so there's no leakage + y = AllOrNothing(module) + text = y.undigest(msgblocks) + if text == b(__doc__): + print 'They match!' + else: + print 'They differ!' diff --git a/lib/Crypto/Protocol/Chaffing.py b/lib/Crypto/Protocol/Chaffing.py new file mode 100644 index 0000000..c19e037 --- /dev/null +++ b/lib/Crypto/Protocol/Chaffing.py @@ -0,0 +1,245 @@ +# +# Chaffing.py : chaffing & winnowing support +# +# Part of the Python Cryptography Toolkit +# +# Written by Andrew M. Kuchling, Barry A. Warsaw, and others +# +# =================================================================== +# 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. +# =================================================================== +# +"""This file implements the chaffing algorithm. + +Winnowing and chaffing is a technique for enhancing privacy without requiring +strong encryption. In short, the technique takes a set of authenticated +message blocks (the wheat) and adds a number of chaff blocks which have +randomly chosen data and MAC fields. This means that to an adversary, the +chaff blocks look as valid as the wheat blocks, and so the authentication +would have to be performed on every block. By tailoring the number of chaff +blocks added to the message, the sender can make breaking the message +computationally infeasible. There are many other interesting properties of +the winnow/chaff technique. + +For example, say Alice is sending a message to Bob. She packetizes the +message and performs an all-or-nothing transformation on the packets. Then +she authenticates each packet with a message authentication code (MAC). The +MAC is a hash of the data packet, and there is a secret key which she must +share with Bob (key distribution is an exercise left to the reader). She then +adds a serial number to each packet, and sends the packets to Bob. + +Bob receives the packets, and using the shared secret authentication key, +authenticates the MACs for each packet. Those packets that have bad MACs are +simply discarded. The remainder are sorted by serial number, and passed +through the reverse all-or-nothing transform. The transform means that an +eavesdropper (say Eve) must acquire all the packets before any of the data can +be read. If even one packet is missing, the data is useless. + +There's one twist: by adding chaff packets, Alice and Bob can make Eve's job +much harder, since Eve now has to break the shared secret key, or try every +combination of wheat and chaff packet to read any of the message. The cool +thing is that Bob doesn't need to add any additional code; the chaff packets +are already filtered out because their MACs don't match (in all likelihood -- +since the data and MACs for the chaff packets are randomly chosen it is +possible, but very unlikely that a chaff MAC will match the chaff data). And +Alice need not even be the party adding the chaff! She could be completely +unaware that a third party, say Charles, is adding chaff packets to her +messages as they are transmitted. + +For more information on winnowing and chaffing see this paper: + +Ronald L. Rivest, "Chaffing and Winnowing: Confidentiality without Encryption" +http://theory.lcs.mit.edu/~rivest/chaffing.txt + +""" + +__revision__ = "$Id$" + +from Crypto.Util.number import bytes_to_long + +class Chaff: + """Class implementing the chaff adding algorithm. + + Methods for subclasses: + + _randnum(size): + Returns a randomly generated number with a byte-length equal + to size. Subclasses can use this to implement better random + data and MAC generating algorithms. The default algorithm is + probably not very cryptographically secure. It is most + important that the chaff data does not contain any patterns + that can be used to discern it from wheat data without running + the MAC. + + """ + + def __init__(self, factor=1.0, blocksper=1): + """Chaff(factor:float, blocksper:int) + + factor is the number of message blocks to add chaff to, + expressed as a percentage between 0.0 and 1.0. blocksper is + the number of chaff blocks to include for each block being + chaffed. Thus the defaults add one chaff block to every + message block. By changing the defaults, you can adjust how + computationally difficult it could be for an adversary to + brute-force crack the message. The difficulty is expressed + as: + + pow(blocksper, int(factor * number-of-blocks)) + + For ease of implementation, when factor < 1.0, only the first + int(factor*number-of-blocks) message blocks are chaffed. + """ + + if not (0.0<=factor<=1.0): + raise ValueError, "'factor' must be between 0.0 and 1.0" + if blocksper < 0: + raise ValueError, "'blocksper' must be zero or more" + + self.__factor = factor + self.__blocksper = blocksper + + + def chaff(self, blocks): + """chaff( [(serial-number:int, data:string, MAC:string)] ) + : [(int, string, string)] + + Add chaff to message blocks. blocks is a list of 3-tuples of the + form (serial-number, data, MAC). + + Chaff is created by choosing a random number of the same + byte-length as data, and another random number of the same + byte-length as MAC. The message block's serial number is + placed on the chaff block and all the packet's chaff blocks + are randomly interspersed with the single wheat block. This + method then returns a list of 3-tuples of the same form. + Chaffed blocks will contain multiple instances of 3-tuples + with the same serial number, but the only way to figure out + which blocks are wheat and which are chaff is to perform the + MAC hash and compare values. + """ + + chaffedblocks = [] + + # count is the number of blocks to add chaff to. blocksper is the + # number of chaff blocks to add per message block that is being + # chaffed. + count = len(blocks) * self.__factor + blocksper = range(self.__blocksper) + for i, wheat in zip(range(len(blocks)), blocks): + # it shouldn't matter which of the n blocks we add chaff to, so for + # ease of implementation, we'll just add them to the first count + # blocks + if i < count: + serial, data, mac = wheat + datasize = len(data) + macsize = len(mac) + addwheat = 1 + # add chaff to this block + for j in blocksper: + import sys + chaffdata = self._randnum(datasize) + chaffmac = self._randnum(macsize) + chaff = (serial, chaffdata, chaffmac) + # mix up the order, if the 5th bit is on then put the + # wheat on the list + if addwheat and bytes_to_long(self._randnum(16)) & 0x40: + chaffedblocks.append(wheat) + addwheat = 0 + chaffedblocks.append(chaff) + if addwheat: + chaffedblocks.append(wheat) + else: + # just add the wheat + chaffedblocks.append(wheat) + return chaffedblocks + + def _randnum(self, size): + from Crypto import Random + return Random.new().read(size) + + +if __name__ == '__main__': + text = """\ +We hold these truths to be self-evident, that all men are created equal, that +they are endowed by their Creator with certain unalienable Rights, that among +these are Life, Liberty, and the pursuit of Happiness. That to secure these +rights, Governments are instituted among Men, deriving their just powers from +the consent of the governed. That whenever any Form of Government becomes +destructive of these ends, it is the Right of the People to alter or to +abolish it, and to institute new Government, laying its foundation on such +principles and organizing its powers in such form, as to them shall seem most +likely to effect their Safety and Happiness. +""" + print 'Original text:\n==========' + print text + print '==========' + + # first transform the text into packets + blocks = [] ; size = 40 + for i in range(0, len(text), size): + blocks.append( text[i:i+size] ) + + # now get MACs for all the text blocks. The key is obvious... + print 'Calculating MACs...' + from Crypto.Hash import HMAC, SHA + key = 'Jefferson' + macs = [HMAC.new(key, block, digestmod=SHA).digest() + for block in blocks] + + assert len(blocks) == len(macs) + + # put these into a form acceptable as input to the chaffing procedure + source = [] + m = zip(range(len(blocks)), blocks, macs) + print m + for i, data, mac in m: + source.append((i, data, mac)) + + # now chaff these + print 'Adding chaff...' + c = Chaff(factor=0.5, blocksper=2) + chaffed = c.chaff(source) + + from base64 import encodestring + + # print the chaffed message blocks. meanwhile, separate the wheat from + # the chaff + + wheat = [] + print 'chaffed message blocks:' + for i, data, mac in chaffed: + # do the authentication + h = HMAC.new(key, data, digestmod=SHA) + pmac = h.digest() + if pmac == mac: + tag = '-->' + wheat.append(data) + else: + tag = ' ' + # base64 adds a trailing newline + print tag, '%3d' % i, \ + repr(data), encodestring(mac)[:-1] + + # now decode the message packets and check it against the original text + print 'Undigesting wheat...' + # PY3K: This is meant to be text, do not change to bytes (data) + newtext = "".join(wheat) + if newtext == text: + print 'They match!' + else: + print 'They differ!' diff --git a/lib/Crypto/Protocol/KDF.py b/lib/Crypto/Protocol/KDF.py new file mode 100644 index 0000000..973b7af --- /dev/null +++ b/lib/Crypto/Protocol/KDF.py @@ -0,0 +1,123 @@ +# +# KDF.py : a collection of Key Derivation Functions +# +# Part of the Python Cryptography Toolkit +# +# =================================================================== +# 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. +# =================================================================== + +"""This file contains a collection of standard key derivation functions. + +A key derivation function derives one or more secondary secret keys from +one primary secret (a master key or a pass phrase). + +This is typically done to insulate the secondary keys from each other, +to avoid that leakage of a secondary key compromises the security of the +master key, or to thwart attacks on pass phrases (e.g. via rainbow tables). + +:undocumented: __revision__ +""" + +__revision__ = "$Id$" + +import math +import struct + +from Crypto.Util.py3compat import * +from Crypto.Hash import SHA as SHA1, HMAC +from Crypto.Util.strxor import strxor + +def PBKDF1(password, salt, dkLen, count=1000, hashAlgo=None): + """Derive one key from a password (or passphrase). + + This function performs key derivation according an old version of + the PKCS#5 standard (v1.5). + + This algorithm is called ``PBKDF1``. Even though it is still described + in the latest version of the PKCS#5 standard (version 2, or RFC2898), + newer applications should use the more secure and versatile `PBKDF2` instead. + + :Parameters: + password : string + The secret password or pass phrase to generate the key from. + salt : byte string + An 8 byte string to use for better protection from dictionary attacks. + This value does not need to be kept secret, but it should be randomly + chosen for each derivation. + dkLen : integer + The length of the desired key. Default is 16 bytes, suitable for instance for `Crypto.Cipher.AES`. + count : integer + The number of iterations to carry out. It's recommended to use at least 1000. + hashAlgo : module + The hash algorithm to use, as a module or an object from the `Crypto.Hash` package. + The digest length must be no shorter than ``dkLen``. + The default algorithm is `SHA1`. + + :Return: A byte string of length `dkLen` that can be used as key. + """ + if not hashAlgo: + hashAlgo = SHA1 + password = tobytes(password) + pHash = hashAlgo.new(password+salt) + digest = pHash.digest_size + if dkLen>digest: + raise ValueError("Selected hash algorithm has a too short digest (%d bytes)." % digest) + if len(salt)!=8: + raise ValueError("Salt is not 8 bytes long.") + for i in xrange(count-1): + pHash = pHash.new(pHash.digest()) + return pHash.digest()[:dkLen] + +def PBKDF2(password, salt, dkLen=16, count=1000, prf=None): + """Derive one or more keys from a password (or passphrase). + + This performs key derivation according to the PKCS#5 standard (v2.0), + by means of the ``PBKDF2`` algorithm. + + :Parameters: + password : string + The secret password or pass phrase to generate the key from. + salt : string + A string to use for better protection from dictionary attacks. + This value does not need to be kept secret, but it should be randomly + chosen for each derivation. It is recommended to be at least 8 bytes long. + dkLen : integer + The cumulative length of the desired keys. Default is 16 bytes, suitable for instance for `Crypto.Cipher.AES`. + count : integer + The number of iterations to carry out. It's recommended to use at least 1000. + prf : callable + A pseudorandom function. It must be a function that returns a pseudorandom string + from two parameters: a secret and a salt. If not specified, HMAC-SHA1 is used. + + :Return: A byte string of length `dkLen` that can be used as key material. + If you wanted multiple keys, just break up this string into segments of the desired length. +""" + password = tobytes(password) + if prf is None: + prf = lambda p,s: HMAC.new(p,s,SHA1).digest() + key = b('') + i = 1 + while len(key)I", i)) + for j in xrange(count-1): + previousU = t = prf(password,previousU) + U = strxor(U,t) + key += U + i = i + 1 + return key[:dkLen] + diff --git a/lib/Crypto/Protocol/__init__.py b/lib/Crypto/Protocol/__init__.py new file mode 100644 index 0000000..cacc685 --- /dev/null +++ b/lib/Crypto/Protocol/__init__.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# +# =================================================================== +# 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. +# =================================================================== + +"""Cryptographic protocols + +Implements various cryptographic protocols. (Don't expect to find +network protocols here.) + +Crypto.Protocol.AllOrNothing + Transforms a message into a set of message blocks, such that the blocks + can be recombined to get the message back. + +Crypto.Protocol.Chaffing + Takes a set of authenticated message blocks (the wheat) and adds a number + of randomly generated blocks (the chaff). + +Crypto.Protocol.KDF + A collection of standard key derivation functions. + +:undocumented: __revision__ +""" + +__all__ = ['AllOrNothing', 'Chaffing', 'KDF'] +__revision__ = "$Id$" diff --git a/lib/Crypto/PublicKey/DSA.py b/lib/Crypto/PublicKey/DSA.py new file mode 100644 index 0000000..d6bffd6 --- /dev/null +++ b/lib/Crypto/PublicKey/DSA.py @@ -0,0 +1,379 @@ +# -*- coding: utf-8 -*- +# +# PublicKey/DSA.py : DSA signature primitive +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""DSA public-key signature algorithm. + +DSA_ is a widespread public-key signature algorithm. Its security is +based on the discrete logarithm problem (DLP_). Given a cyclic +group, a generator *g*, and an element *h*, it is hard +to find an integer *x* such that *g^x = h*. The problem is believed +to be difficult, and it has been proved such (and therefore secure) for +more than 30 years. + +The group is actually a sub-group over the integers modulo *p*, with *p* prime. +The sub-group order is *q*, which is prime too; it always holds that *(p-1)* is a multiple of *q*. +The cryptographic strength is linked to the magnitude of *p* and *q*. +The signer holds a value *x* (*0>> from Crypto.Random import random + >>> from Crypto.PublicKey import DSA + >>> from Crypto.Hash import SHA + >>> + >>> message = "Hello" + >>> key = DSA.generate(1024) + >>> h = SHA.new(message).digest() + >>> k = random.StrongRandom().randint(1,key.q-1) + >>> sig = key.sign(h,k) + >>> ... + >>> if key.verify(h,sig): + >>> print "OK" + >>> else: + >>> print "Incorrect signature" + +.. _DSA: http://en.wikipedia.org/wiki/Digital_Signature_Algorithm +.. _DLP: http://www.cosic.esat.kuleuven.be/publications/talk-78.pdf +.. _ECRYPT: http://www.ecrypt.eu.org/documents/D.SPA.17.pdf +""" + +__revision__ = "$Id$" + +__all__ = ['generate', 'construct', 'error', 'DSAImplementation', '_DSAobj'] + +import sys +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * + +from Crypto.PublicKey import _DSA, _slowmath, pubkey +from Crypto import Random + +try: + from Crypto.PublicKey import _fastmath +except ImportError: + _fastmath = None + +class _DSAobj(pubkey.pubkey): + """Class defining an actual DSA key. + + :undocumented: __getstate__, __setstate__, __repr__, __getattr__ + """ + #: Dictionary of DSA parameters. + #: + #: A public key will only have the following entries: + #: + #: - **y**, the public key. + #: - **g**, the generator. + #: - **p**, the modulus. + #: - **q**, the order of the sub-group. + #: + #: A private key will also have: + #: + #: - **x**, the private key. + keydata = ['y', 'g', 'p', 'q', 'x'] + + def __init__(self, implementation, key): + self.implementation = implementation + self.key = key + + def __getattr__(self, attrname): + if attrname in self.keydata: + # For backward compatibility, allow the user to get (not set) the + # DSA key parameters directly from this object. + return getattr(self.key, attrname) + else: + raise AttributeError("%s object has no %r attribute" % (self.__class__.__name__, attrname,)) + + def sign(self, M, K): + """Sign a piece of data with DSA. + + :Parameter M: The piece of data to sign with DSA. It may + not be longer in bit size than the sub-group order (*q*). + :Type M: byte string or long + + :Parameter K: A secret number, chosen randomly in the closed + range *[1,q-1]*. + :Type K: long (recommended) or byte string (not recommended) + + :attention: selection of *K* is crucial for security. Generating a + random number larger than *q* and taking the modulus by *q* is + **not** secure, since smaller values will occur more frequently. + Generating a random number systematically smaller than *q-1* + (e.g. *floor((q-1)/8)* random bytes) is also **not** secure. In general, + it shall not be possible for an attacker to know the value of `any + bit of K`__. + + :attention: The number *K* shall not be reused for any other + operation and shall be discarded immediately. + + :attention: M must be a digest cryptographic hash, otherwise + an attacker may mount an existential forgery attack. + + :Return: A tuple with 2 longs. + + .. __: http://www.di.ens.fr/~pnguyen/pub_NgSh00.htm + """ + return pubkey.pubkey.sign(self, M, K) + + def verify(self, M, signature): + """Verify the validity of a DSA signature. + + :Parameter M: The expected message. + :Type M: byte string or long + + :Parameter signature: The DSA signature to verify. + :Type signature: A tuple with 2 longs as return by `sign` + + :Return: True if the signature is correct, False otherwise. + """ + return pubkey.pubkey.verify(self, M, signature) + + def _encrypt(self, c, K): + raise TypeError("DSA cannot encrypt") + + def _decrypt(self, c): + raise TypeError("DSA cannot decrypt") + + def _blind(self, m, r): + raise TypeError("DSA cannot blind") + + def _unblind(self, m, r): + raise TypeError("DSA cannot unblind") + + def _sign(self, m, k): + return self.key._sign(m, k) + + def _verify(self, m, sig): + (r, s) = sig + return self.key._verify(m, r, s) + + def has_private(self): + return self.key.has_private() + + def size(self): + return self.key.size() + + def can_blind(self): + return False + + def can_encrypt(self): + return False + + def can_sign(self): + return True + + def publickey(self): + return self.implementation.construct((self.key.y, self.key.g, self.key.p, self.key.q)) + + def __getstate__(self): + d = {} + for k in self.keydata: + try: + d[k] = getattr(self.key, k) + except AttributeError: + pass + return d + + def __setstate__(self, d): + if not hasattr(self, 'implementation'): + self.implementation = DSAImplementation() + t = [] + for k in self.keydata: + if not d.has_key(k): + break + t.append(d[k]) + self.key = self.implementation._math.dsa_construct(*tuple(t)) + + def __repr__(self): + attrs = [] + for k in self.keydata: + if k == 'p': + attrs.append("p(%d)" % (self.size()+1,)) + elif hasattr(self.key, k): + attrs.append(k) + if self.has_private(): + attrs.append("private") + # PY3K: This is meant to be text, do not change to bytes (data) + return "<%s @0x%x %s>" % (self.__class__.__name__, id(self), ",".join(attrs)) + +class DSAImplementation(object): + """ + A DSA key factory. + + This class is only internally used to implement the methods of the + `Crypto.PublicKey.DSA` module. + """ + + def __init__(self, **kwargs): + """Create a new DSA key factory. + + :Keywords: + use_fast_math : bool + Specify which mathematic library to use: + + - *None* (default). Use fastest math available. + - *True* . Use fast math. + - *False* . Use slow math. + default_randfunc : callable + Specify how to collect random data: + + - *None* (default). Use Random.new().read(). + - not *None* . Use the specified function directly. + :Raise RuntimeError: + When **use_fast_math** =True but fast math is not available. + """ + use_fast_math = kwargs.get('use_fast_math', None) + if use_fast_math is None: # Automatic + if _fastmath is not None: + self._math = _fastmath + else: + self._math = _slowmath + + elif use_fast_math: # Explicitly select fast math + if _fastmath is not None: + self._math = _fastmath + else: + raise RuntimeError("fast math module not available") + + else: # Explicitly select slow math + self._math = _slowmath + + self.error = self._math.error + + # 'default_randfunc' parameter: + # None (default) - use Random.new().read + # not None - use the specified function + self._default_randfunc = kwargs.get('default_randfunc', None) + self._current_randfunc = None + + def _get_randfunc(self, randfunc): + if randfunc is not None: + return randfunc + elif self._current_randfunc is None: + self._current_randfunc = Random.new().read + return self._current_randfunc + + def generate(self, bits, randfunc=None, progress_func=None): + """Randomly generate a fresh, new DSA key. + + :Parameters: + bits : int + Key length, or size (in bits) of the DSA modulus + *p*. + It must be a multiple of 64, in the closed + interval [512,1024]. + randfunc : callable + Random number generation function; it should accept + a single integer N and return a string of random data + N bytes long. + If not specified, a new one will be instantiated + from ``Crypto.Random``. + progress_func : callable + Optional function that will be called with a short string + containing the key parameter currently being generated; + it's useful for interactive applications where a user is + waiting for a key to be generated. + + :attention: You should always use a cryptographically secure random number generator, + such as the one defined in the ``Crypto.Random`` module; **don't** just use the + current time and the ``random`` module. + + :Return: A DSA key object (`_DSAobj`). + + :Raise ValueError: + When **bits** is too little, too big, or not a multiple of 64. + """ + + # Check against FIPS 186-2, which says that the size of the prime p + # must be a multiple of 64 bits between 512 and 1024 + for i in (0, 1, 2, 3, 4, 5, 6, 7, 8): + if bits == 512 + 64*i: + return self._generate(bits, randfunc, progress_func) + + # The March 2006 draft of FIPS 186-3 also allows 2048 and 3072-bit + # primes, but only with longer q values. Since the current DSA + # implementation only supports a 160-bit q, we don't support larger + # values. + raise ValueError("Number of bits in p must be a multiple of 64 between 512 and 1024, not %d bits" % (bits,)) + + def _generate(self, bits, randfunc=None, progress_func=None): + rf = self._get_randfunc(randfunc) + obj = _DSA.generate_py(bits, rf, progress_func) # TODO: Don't use legacy _DSA module + key = self._math.dsa_construct(obj.y, obj.g, obj.p, obj.q, obj.x) + return _DSAobj(self, key) + + def construct(self, tup): + """Construct a DSA key from a tuple of valid DSA components. + + The modulus *p* must be a prime. + + The following equations must apply: + + - p-1 = 0 mod q + - g^x = y mod p + - 0 < x < q + - 1 < g < p + + :Parameters: + tup : tuple + A tuple of long integers, with 4 or 5 items + in the following order: + + 1. Public key (*y*). + 2. Sub-group generator (*g*). + 3. Modulus, finite field order (*p*). + 4. Sub-group order (*q*). + 5. Private key (*x*). Optional. + + :Return: A DSA key object (`_DSAobj`). + """ + key = self._math.dsa_construct(*tup) + return _DSAobj(self, key) + +_impl = DSAImplementation() +generate = _impl.generate +construct = _impl.construct +error = _impl.error + +# vim:set ts=4 sw=4 sts=4 expandtab: + diff --git a/lib/Crypto/PublicKey/ElGamal.py b/lib/Crypto/PublicKey/ElGamal.py new file mode 100644 index 0000000..99af71c --- /dev/null +++ b/lib/Crypto/PublicKey/ElGamal.py @@ -0,0 +1,373 @@ +# +# ElGamal.py : ElGamal encryption/decryption and signatures +# +# Part of the Python Cryptography Toolkit +# +# Originally written by: A.M. Kuchling +# +# =================================================================== +# 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. +# =================================================================== + +"""ElGamal public-key algorithm (randomized encryption and signature). + +Signature algorithm +------------------- +The security of the ElGamal signature scheme is based (like DSA) on the discrete +logarithm problem (DLP_). Given a cyclic group, a generator *g*, +and an element *h*, it is hard to find an integer *x* such that *g^x = h*. + +The group is the largest multiplicative sub-group of the integers modulo *p*, +with *p* prime. +The signer holds a value *x* (*0>> from Crypto import Random + >>> from Crypto.Random import random + >>> from Crypto.PublicKey import ElGamal + >>> from Crypto.Util.number import GCD + >>> from Crypto.Hash import SHA + >>> + >>> message = "Hello" + >>> key = ElGamal.generate(1024, Random.new().read) + >>> h = SHA.new(message).digest() + >>> while 1: + >>> k = random.StrongRandom().randint(1,key.p-1) + >>> if GCD(k,key.p-1)==1: break + >>> sig = key.sign(h,k) + >>> ... + >>> if key.verify(h,sig): + >>> print "OK" + >>> else: + >>> print "Incorrect signature" + +.. _DLP: http://www.cosic.esat.kuleuven.be/publications/talk-78.pdf +.. _CDH: http://en.wikipedia.org/wiki/Computational_Diffie%E2%80%93Hellman_assumption +.. _ECRYPT: http://www.ecrypt.eu.org/documents/D.SPA.17.pdf +""" + +__revision__ = "$Id$" + +__all__ = ['generate', 'construct', 'error', 'ElGamalobj'] + +from Crypto.PublicKey.pubkey import * +from Crypto.Util import number + +class error (Exception): + pass + +# Generate an ElGamal key with N bits +def generate(bits, randfunc, progress_func=None): + """Randomly generate a fresh, new ElGamal key. + + The key will be safe for use for both encryption and signature + (although it should be used for **only one** purpose). + + :Parameters: + bits : int + Key length, or size (in bits) of the modulus *p*. + Recommended value is 2048. + randfunc : callable + Random number generation function; it should accept + a single integer N and return a string of random data + N bytes long. + progress_func : callable + Optional function that will be called with a short string + containing the key parameter currently being generated; + it's useful for interactive applications where a user is + waiting for a key to be generated. + + :attention: You should always use a cryptographically secure random number generator, + such as the one defined in the ``Crypto.Random`` module; **don't** just use the + current time and the ``random`` module. + + :Return: An ElGamal key object (`ElGamalobj`). + """ + obj=ElGamalobj() + # Generate a safe prime p + # See Algorithm 4.86 in Handbook of Applied Cryptography + if progress_func: + progress_func('p\n') + while 1: + q = bignum(getPrime(bits-1, randfunc)) + obj.p = 2*q+1 + if number.isPrime(obj.p, randfunc=randfunc): + break + # Generate generator g + # See Algorithm 4.80 in Handbook of Applied Cryptography + # Note that the order of the group is n=p-1=2q, where q is prime + if progress_func: + progress_func('g\n') + while 1: + # We must avoid g=2 because of Bleichenbacher's attack described + # in "Generating ElGamal signatures without knowning the secret key", + # 1996 + # + obj.g = number.getRandomRange(3, obj.p, randfunc) + safe = 1 + if pow(obj.g, 2, obj.p)==1: + safe=0 + if safe and pow(obj.g, q, obj.p)==1: + safe=0 + # Discard g if it divides p-1 because of the attack described + # in Note 11.67 (iii) in HAC + if safe and divmod(obj.p-1, obj.g)[1]==0: + safe=0 + # g^{-1} must not divide p-1 because of Khadir's attack + # described in "Conditions of the generator for forging ElGamal + # signature", 2011 + ginv = number.inverse(obj.g, obj.p) + if safe and divmod(obj.p-1, ginv)[1]==0: + safe=0 + if safe: + break + # Generate private key x + if progress_func: + progress_func('x\n') + obj.x=number.getRandomRange(2, obj.p-1, randfunc) + # Generate public key y + if progress_func: + progress_func('y\n') + obj.y = pow(obj.g, obj.x, obj.p) + return obj + +def construct(tup): + """Construct an ElGamal key from a tuple of valid ElGamal components. + + The modulus *p* must be a prime. + + The following conditions must apply: + + - 1 < g < p-1 + - g^{p-1} = 1 mod p + - 1 < x < p-1 + - g^x = y mod p + + :Parameters: + tup : tuple + A tuple of long integers, with 3 or 4 items + in the following order: + + 1. Modulus (*p*). + 2. Generator (*g*). + 3. Public key (*y*). + 4. Private key (*x*). Optional. + + :Return: An ElGamal key object (`ElGamalobj`). + """ + + obj=ElGamalobj() + if len(tup) not in [3,4]: + raise ValueError('argument for construct() wrong length') + for i in range(len(tup)): + field = obj.keydata[i] + setattr(obj, field, tup[i]) + return obj + +class ElGamalobj(pubkey): + """Class defining an ElGamal key. + + :undocumented: __getstate__, __setstate__, __repr__, __getattr__ + """ + + #: Dictionary of ElGamal parameters. + #: + #: A public key will only have the following entries: + #: + #: - **y**, the public key. + #: - **g**, the generator. + #: - **p**, the modulus. + #: + #: A private key will also have: + #: + #: - **x**, the private key. + keydata=['p', 'g', 'y', 'x'] + + def encrypt(self, plaintext, K): + """Encrypt a piece of data with ElGamal. + + :Parameter plaintext: The piece of data to encrypt with ElGamal. + It must be numerically smaller than the module (*p*). + :Type plaintext: byte string or long + + :Parameter K: A secret number, chosen randomly in the closed + range *[1,p-2]*. + :Type K: long (recommended) or byte string (not recommended) + + :Return: A tuple with two items. Each item is of the same type as the + plaintext (string or long). + + :attention: selection of *K* is crucial for security. Generating a + random number larger than *p-1* and taking the modulus by *p-1* is + **not** secure, since smaller values will occur more frequently. + Generating a random number systematically smaller than *p-1* + (e.g. *floor((p-1)/8)* random bytes) is also **not** secure. + In general, it shall not be possible for an attacker to know + the value of any bit of K. + + :attention: The number *K* shall not be reused for any other + operation and shall be discarded immediately. + """ + return pubkey.encrypt(self, plaintext, K) + + def decrypt(self, ciphertext): + """Decrypt a piece of data with ElGamal. + + :Parameter ciphertext: The piece of data to decrypt with ElGamal. + :Type ciphertext: byte string, long or a 2-item tuple as returned + by `encrypt` + + :Return: A byte string if ciphertext was a byte string or a tuple + of byte strings. A long otherwise. + """ + return pubkey.decrypt(self, ciphertext) + + def sign(self, M, K): + """Sign a piece of data with ElGamal. + + :Parameter M: The piece of data to sign with ElGamal. It may + not be longer in bit size than *p-1*. + :Type M: byte string or long + + :Parameter K: A secret number, chosen randomly in the closed + range *[1,p-2]* and such that *gcd(k,p-1)=1*. + :Type K: long (recommended) or byte string (not recommended) + + :attention: selection of *K* is crucial for security. Generating a + random number larger than *p-1* and taking the modulus by *p-1* is + **not** secure, since smaller values will occur more frequently. + Generating a random number systematically smaller than *p-1* + (e.g. *floor((p-1)/8)* random bytes) is also **not** secure. + In general, it shall not be possible for an attacker to know + the value of any bit of K. + + :attention: The number *K* shall not be reused for any other + operation and shall be discarded immediately. + + :attention: M must be be a cryptographic hash, otherwise an + attacker may mount an existential forgery attack. + + :Return: A tuple with 2 longs. + """ + return pubkey.sign(self, M, K) + + def verify(self, M, signature): + """Verify the validity of an ElGamal signature. + + :Parameter M: The expected message. + :Type M: byte string or long + + :Parameter signature: The ElGamal signature to verify. + :Type signature: A tuple with 2 longs as return by `sign` + + :Return: True if the signature is correct, False otherwise. + """ + return pubkey.verify(self, M, signature) + + def _encrypt(self, M, K): + a=pow(self.g, K, self.p) + b=( M*pow(self.y, K, self.p) ) % self.p + return ( a,b ) + + def _decrypt(self, M): + if (not hasattr(self, 'x')): + raise TypeError('Private key not available in this object') + ax=pow(M[0], self.x, self.p) + plaintext=(M[1] * inverse(ax, self.p ) ) % self.p + return plaintext + + def _sign(self, M, K): + if (not hasattr(self, 'x')): + raise TypeError('Private key not available in this object') + p1=self.p-1 + if (GCD(K, p1)!=1): + raise ValueError('Bad K value: GCD(K,p-1)!=1') + a=pow(self.g, K, self.p) + t=(M-self.x*a) % p1 + while t<0: t=t+p1 + b=(t*inverse(K, p1)) % p1 + return (a, b) + + def _verify(self, M, sig): + if sig[0]<1 or sig[0]>self.p-1: + return 0 + v1=pow(self.y, sig[0], self.p) + v1=(v1*pow(sig[0], sig[1], self.p)) % self.p + v2=pow(self.g, M, self.p) + if v1==v2: + return 1 + return 0 + + def size(self): + return number.size(self.p) - 1 + + def has_private(self): + if hasattr(self, 'x'): + return 1 + else: + return 0 + + def publickey(self): + return construct((self.p, self.g, self.y)) + + +object=ElGamalobj diff --git a/lib/Crypto/PublicKey/RSA.py b/lib/Crypto/PublicKey/RSA.py new file mode 100644 index 0000000..bab9288 --- /dev/null +++ b/lib/Crypto/PublicKey/RSA.py @@ -0,0 +1,719 @@ +# -*- coding: utf-8 -*- +# +# PublicKey/RSA.py : RSA public key primitive +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""RSA public-key cryptography algorithm (signature and encryption). + +RSA_ is the most widespread and used public key algorithm. Its security is +based on the difficulty of factoring large integers. The algorithm has +withstood attacks for 30 years, and it is therefore considered reasonably +secure for new designs. + +The algorithm can be used for both confidentiality (encryption) and +authentication (digital signature). It is worth noting that signing and +decryption are significantly slower than verification and encryption. +The cryptograhic strength is primarily linked to the length of the modulus *n*. +In 2012, a sufficient length is deemed to be 2048 bits. For more information, +see the most recent ECRYPT_ report. + +Both RSA ciphertext and RSA signature are as big as the modulus *n* (256 +bytes if *n* is 2048 bit long). + +This module provides facilities for generating fresh, new RSA keys, constructing +them from known components, exporting them, and importing them. + + >>> from Crypto.PublicKey import RSA + >>> + >>> key = RSA.generate(2048) + >>> f = open('mykey.pem','w') + >>> f.write(RSA.exportKey('PEM')) + >>> f.close() + ... + >>> f = open('mykey.pem','r') + >>> key = RSA.importKey(f.read()) + +Even though you may choose to directly use the methods of an RSA key object +to perform the primitive cryptographic operations (e.g. `_RSAobj.encrypt`), +it is recommended to use one of the standardized schemes instead (like +`Crypto.Cipher.PKCS1_v1_5` or `Crypto.Signature.PKCS1_v1_5`). + +.. _RSA: http://en.wikipedia.org/wiki/RSA_%28algorithm%29 +.. _ECRYPT: http://www.ecrypt.eu.org/documents/D.SPA.17.pdf + +:sort: generate,construct,importKey,error +""" + +__revision__ = "$Id$" + +__all__ = ['generate', 'construct', 'error', 'importKey', 'RSAImplementation', '_RSAobj'] + +import sys +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * +from Crypto.Util.py3compat import * +#from Crypto.Util.python_compat import * +from Crypto.Util.number import getRandomRange, bytes_to_long, long_to_bytes + +from Crypto.PublicKey import _RSA, _slowmath, pubkey +from Crypto import Random + +from Crypto.Util.asn1 import DerObject, DerSequence, DerNull +import binascii +import struct + +from Crypto.Util.number import inverse + +from Crypto.Util.number import inverse + +try: + from Crypto.PublicKey import _fastmath +except ImportError: + _fastmath = None + +class _RSAobj(pubkey.pubkey): + """Class defining an actual RSA key. + + :undocumented: __getstate__, __setstate__, __repr__, __getattr__ + """ + #: Dictionary of RSA parameters. + #: + #: A public key will only have the following entries: + #: + #: - **n**, the modulus. + #: - **e**, the public exponent. + #: + #: A private key will also have: + #: + #: - **d**, the private exponent. + #: - **p**, the first factor of n. + #: - **q**, the second factor of n. + #: - **u**, the CRT coefficient (1/p) mod q. + keydata = ['n', 'e', 'd', 'p', 'q', 'u'] + + def __init__(self, implementation, key, randfunc=None): + self.implementation = implementation + self.key = key + if randfunc is None: + randfunc = Random.new().read + self._randfunc = randfunc + + def __getattr__(self, attrname): + if attrname in self.keydata: + # For backward compatibility, allow the user to get (not set) the + # RSA key parameters directly from this object. + return getattr(self.key, attrname) + else: + raise AttributeError("%s object has no %r attribute" % (self.__class__.__name__, attrname,)) + + def encrypt(self, plaintext, K): + """Encrypt a piece of data with RSA. + + :Parameter plaintext: The piece of data to encrypt with RSA. It may not + be numerically larger than the RSA module (**n**). + :Type plaintext: byte string or long + + :Parameter K: A random parameter (*for compatibility only. This + value will be ignored*) + :Type K: byte string or long + + :attention: this function performs the plain, primitive RSA encryption + (*textbook*). In real applications, you always need to use proper + cryptographic padding, and you should not directly encrypt data with + this method. Failure to do so may lead to security vulnerabilities. + It is recommended to use modules + `Crypto.Cipher.PKCS1_OAEP` or `Crypto.Cipher.PKCS1_v1_5` instead. + + :Return: A tuple with two items. The first item is the ciphertext + of the same type as the plaintext (string or long). The second item + is always None. + """ + return pubkey.pubkey.encrypt(self, plaintext, K) + + def decrypt(self, ciphertext): + """Decrypt a piece of data with RSA. + + Decryption always takes place with blinding. + + :attention: this function performs the plain, primitive RSA decryption + (*textbook*). In real applications, you always need to use proper + cryptographic padding, and you should not directly decrypt data with + this method. Failure to do so may lead to security vulnerabilities. + It is recommended to use modules + `Crypto.Cipher.PKCS1_OAEP` or `Crypto.Cipher.PKCS1_v1_5` instead. + + :Parameter ciphertext: The piece of data to decrypt with RSA. It may + not be numerically larger than the RSA module (**n**). If a tuple, + the first item is the actual ciphertext; the second item is ignored. + + :Type ciphertext: byte string, long or a 2-item tuple as returned by + `encrypt` + + :Return: A byte string if ciphertext was a byte string or a tuple + of byte strings. A long otherwise. + """ + return pubkey.pubkey.decrypt(self, ciphertext) + + def sign(self, M, K): + """Sign a piece of data with RSA. + + Signing always takes place with blinding. + + :attention: this function performs the plain, primitive RSA decryption + (*textbook*). In real applications, you always need to use proper + cryptographic padding, and you should not directly sign data with + this method. Failure to do so may lead to security vulnerabilities. + It is recommended to use modules + `Crypto.Signature.PKCS1_PSS` or `Crypto.Signature.PKCS1_v1_5` instead. + + :Parameter M: The piece of data to sign with RSA. It may + not be numerically larger than the RSA module (**n**). + :Type M: byte string or long + + :Parameter K: A random parameter (*for compatibility only. This + value will be ignored*) + :Type K: byte string or long + + :Return: A 2-item tuple. The first item is the actual signature (a + long). The second item is always None. + """ + return pubkey.pubkey.sign(self, M, K) + + def verify(self, M, signature): + """Verify the validity of an RSA signature. + + :attention: this function performs the plain, primitive RSA encryption + (*textbook*). In real applications, you always need to use proper + cryptographic padding, and you should not directly verify data with + this method. Failure to do so may lead to security vulnerabilities. + It is recommended to use modules + `Crypto.Signature.PKCS1_PSS` or `Crypto.Signature.PKCS1_v1_5` instead. + + :Parameter M: The expected message. + :Type M: byte string or long + + :Parameter signature: The RSA signature to verify. The first item of + the tuple is the actual signature (a long not larger than the modulus + **n**), whereas the second item is always ignored. + :Type signature: A 2-item tuple as return by `sign` + + :Return: True if the signature is correct, False otherwise. + """ + return pubkey.pubkey.verify(self, M, signature) + + def _encrypt(self, c, K): + return (self.key._encrypt(c),) + + def _decrypt(self, c): + #(ciphertext,) = c + (ciphertext,) = c[:1] # HACK - We should use the previous line + # instead, but this is more compatible and we're + # going to replace the Crypto.PublicKey API soon + # anyway. + + # Blinded RSA decryption (to prevent timing attacks): + # Step 1: Generate random secret blinding factor r, such that 0 < r < n-1 + r = getRandomRange(1, self.key.n-1, randfunc=self._randfunc) + # Step 2: Compute c' = c * r**e mod n + cp = self.key._blind(ciphertext, r) + # Step 3: Compute m' = c'**d mod n (ordinary RSA decryption) + mp = self.key._decrypt(cp) + # Step 4: Compute m = m**(r-1) mod n + return self.key._unblind(mp, r) + + def _blind(self, m, r): + return self.key._blind(m, r) + + def _unblind(self, m, r): + return self.key._unblind(m, r) + + def _sign(self, m, K=None): + return (self.key._sign(m),) + + def _verify(self, m, sig): + #(s,) = sig + (s,) = sig[:1] # HACK - We should use the previous line instead, but + # this is more compatible and we're going to replace + # the Crypto.PublicKey API soon anyway. + return self.key._verify(m, s) + + def has_private(self): + return self.key.has_private() + + def size(self): + return self.key.size() + + def can_blind(self): + return True + + def can_encrypt(self): + return True + + def can_sign(self): + return True + + def publickey(self): + return self.implementation.construct((self.key.n, self.key.e)) + + def __getstate__(self): + d = {} + for k in self.keydata: + try: + d[k] = getattr(self.key, k) + except AttributeError: + pass + return d + + def __setstate__(self, d): + if not hasattr(self, 'implementation'): + self.implementation = RSAImplementation() + t = [] + for k in self.keydata: + if not d.has_key(k): + break + t.append(d[k]) + self.key = self.implementation._math.rsa_construct(*tuple(t)) + + def __repr__(self): + attrs = [] + for k in self.keydata: + if k == 'n': + attrs.append("n(%d)" % (self.size()+1,)) + elif hasattr(self.key, k): + attrs.append(k) + if self.has_private(): + attrs.append("private") + # PY3K: This is meant to be text, do not change to bytes (data) + return "<%s @0x%x %s>" % (self.__class__.__name__, id(self), ",".join(attrs)) + + def exportKey(self, format='PEM', passphrase=None, pkcs=1): + """Export this RSA key. + + :Parameter format: The format to use for wrapping the key. + + - *'DER'*. Binary encoding, always unencrypted. + - *'PEM'*. Textual encoding, done according to `RFC1421`_/`RFC1423`_. + Unencrypted (default) or encrypted. + - *'OpenSSH'*. Textual encoding, done according to OpenSSH specification. + Only suitable for public keys (not private keys). + :Type format: string + + :Parameter passphrase: In case of PEM, the pass phrase to derive the encryption key from. + :Type passphrase: string + + :Parameter pkcs: The PKCS standard to follow for assembling the key. + You have two choices: + + - with **1**, the public key is embedded into an X.509 `SubjectPublicKeyInfo` DER SEQUENCE. + The private key is embedded into a `PKCS#1`_ `RSAPrivateKey` DER SEQUENCE. + This mode is the default. + - with **8**, the private key is embedded into a `PKCS#8`_ `PrivateKeyInfo` DER SEQUENCE. + This mode is not available for public keys. + + PKCS standards are not relevant for the *OpenSSH* format. + :Type pkcs: integer + + :Return: A byte string with the encoded public or private half. + :Raise ValueError: + When the format is unknown. + + .. _RFC1421: http://www.ietf.org/rfc/rfc1421.txt + .. _RFC1423: http://www.ietf.org/rfc/rfc1423.txt + .. _`PKCS#1`: http://www.ietf.org/rfc/rfc3447.txt + .. _`PKCS#8`: http://www.ietf.org/rfc/rfc5208.txt + """ + if passphrase is not None: + passphrase = tobytes(passphrase) + if format=='OpenSSH': + eb = long_to_bytes(self.e) + nb = long_to_bytes(self.n) + if bord(eb[0]) & 0x80: eb=bchr(0x00)+eb + if bord(nb[0]) & 0x80: nb=bchr(0x00)+nb + keyparts = [ b('ssh-rsa'), eb, nb ] + keystring = b('').join([ struct.pack(">I",len(kp))+kp for kp in keyparts]) + return b('ssh-rsa ')+binascii.b2a_base64(keystring)[:-1] + + # DER format is always used, even in case of PEM, which simply + # encodes it into BASE64. + der = DerSequence() + if self.has_private(): + keyType= { 1: 'RSA PRIVATE', 8: 'PRIVATE' }[pkcs] + der[:] = [ 0, self.n, self.e, self.d, self.p, self.q, + self.d % (self.p-1), self.d % (self.q-1), + inverse(self.q, self.p) ] + if pkcs==8: + derkey = der.encode() + der = DerSequence([0]) + der.append(algorithmIdentifier) + der.append(DerObject('OCTET STRING', derkey).encode()) + else: + keyType = "PUBLIC" + der.append(algorithmIdentifier) + bitmap = DerObject('BIT STRING') + derPK = DerSequence( [ self.n, self.e ] ) + bitmap.payload = bchr(0x00) + derPK.encode() + der.append(bitmap.encode()) + if format=='DER': + return der.encode() + if format=='PEM': + pem = b("-----BEGIN " + keyType + " KEY-----\n") + objenc = None + if passphrase and keyType.endswith('PRIVATE'): + # We only support 3DES for encryption + import Crypto.Hash.MD5 + from Crypto.Cipher import DES3 + from Crypto.Protocol.KDF import PBKDF1 + salt = self._randfunc(8) + key = PBKDF1(passphrase, salt, 16, 1, Crypto.Hash.MD5) + key += PBKDF1(key+passphrase, salt, 8, 1, Crypto.Hash.MD5) + objenc = DES3.new(key, Crypto.Cipher.DES3.MODE_CBC, salt) + pem += b('Proc-Type: 4,ENCRYPTED\n') + pem += b('DEK-Info: DES-EDE3-CBC,') + binascii.b2a_hex(salt).upper() + b('\n\n') + + binaryKey = der.encode() + if objenc: + # Add PKCS#7-like padding + padding = objenc.block_size-len(binaryKey)%objenc.block_size + binaryKey = objenc.encrypt(binaryKey+bchr(padding)*padding) + + # Each BASE64 line can take up to 64 characters (=48 bytes of data) + chunks = [ binascii.b2a_base64(binaryKey[i:i+48]) for i in range(0, len(binaryKey), 48) ] + pem += b('').join(chunks) + pem += b("-----END " + keyType + " KEY-----") + return pem + raise ValueError("Unknown key format '%s'. Cannot export the RSA key." % format) + +class RSAImplementation(object): + """ + An RSA key factory. + + This class is only internally used to implement the methods of the `Crypto.PublicKey.RSA` module. + + :sort: __init__,generate,construct,importKey + :undocumented: _g*, _i* + """ + + def __init__(self, **kwargs): + """Create a new RSA key factory. + + :Keywords: + use_fast_math : bool + Specify which mathematic library to use: + + - *None* (default). Use fastest math available. + - *True* . Use fast math. + - *False* . Use slow math. + default_randfunc : callable + Specify how to collect random data: + + - *None* (default). Use Random.new().read(). + - not *None* . Use the specified function directly. + :Raise RuntimeError: + When **use_fast_math** =True but fast math is not available. + """ + use_fast_math = kwargs.get('use_fast_math', None) + if use_fast_math is None: # Automatic + if _fastmath is not None: + self._math = _fastmath + else: + self._math = _slowmath + + elif use_fast_math: # Explicitly select fast math + if _fastmath is not None: + self._math = _fastmath + else: + raise RuntimeError("fast math module not available") + + else: # Explicitly select slow math + self._math = _slowmath + + self.error = self._math.error + + self._default_randfunc = kwargs.get('default_randfunc', None) + self._current_randfunc = None + + def _get_randfunc(self, randfunc): + if randfunc is not None: + return randfunc + elif self._current_randfunc is None: + self._current_randfunc = Random.new().read + return self._current_randfunc + + def generate(self, bits, randfunc=None, progress_func=None, e=65537): + """Randomly generate a fresh, new RSA key. + + :Parameters: + bits : int + Key length, or size (in bits) of the RSA modulus. + It must be a multiple of 256, and no smaller than 1024. + + randfunc : callable + Random number generation function; it should accept + a single integer N and return a string of random data + N bytes long. + If not specified, a new one will be instantiated + from ``Crypto.Random``. + + progress_func : callable + Optional function that will be called with a short string + containing the key parameter currently being generated; + it's useful for interactive applications where a user is + waiting for a key to be generated. + + e : int + Public RSA exponent. It must be an odd positive integer. + It is typically a small number with very few ones in its + binary representation. + The default value 65537 (= ``0b10000000000000001`` ) is a safe + choice: other common values are 5, 7, 17, and 257. + + :attention: You should always use a cryptographically secure random number generator, + such as the one defined in the ``Crypto.Random`` module; **don't** just use the + current time and the ``random`` module. + + :attention: Exponent 3 is also widely used, but it requires very special care when padding + the message. + + :Return: An RSA key object (`_RSAobj`). + + :Raise ValueError: + When **bits** is too little or not a multiple of 256, or when + **e** is not odd or smaller than 2. + """ + if bits < 1024 or (bits & 0xff) != 0: + # pubkey.getStrongPrime doesn't like anything that's not a multiple of 256 and >= 1024 + raise ValueError("RSA modulus length must be a multiple of 256 and >= 1024") + if e%2==0 or e<3: + raise ValueError("RSA public exponent must be a positive, odd integer larger than 2.") + rf = self._get_randfunc(randfunc) + obj = _RSA.generate_py(bits, rf, progress_func, e) # TODO: Don't use legacy _RSA module + key = self._math.rsa_construct(obj.n, obj.e, obj.d, obj.p, obj.q, obj.u) + return _RSAobj(self, key) + + def construct(self, tup): + """Construct an RSA key from a tuple of valid RSA components. + + The modulus **n** must be the product of two primes. + The public exponent **e** must be odd and larger than 1. + + In case of a private key, the following equations must apply: + + - e != 1 + - p*q = n + - e*d = 1 mod (p-1)(q-1) + - p*u = 1 mod q + + :Parameters: + tup : tuple + A tuple of long integers, with at least 2 and no + more than 6 items. The items come in the following order: + + 1. RSA modulus (n). + 2. Public exponent (e). + 3. Private exponent (d). Only required if the key is private. + 4. First factor of n (p). Optional. + 5. Second factor of n (q). Optional. + 6. CRT coefficient, (1/p) mod q (u). Optional. + + :Return: An RSA key object (`_RSAobj`). + """ + key = self._math.rsa_construct(*tup) + return _RSAobj(self, key) + + def _importKeyDER(self, externKey): + """Import an RSA key (public or private half), encoded in DER form.""" + + try: + + der = DerSequence() + der.decode(externKey, True) + + # Try PKCS#1 first, for a private key + if len(der)==9 and der.hasOnlyInts() and der[0]==0: + # ASN.1 RSAPrivateKey element + del der[6:] # Remove d mod (p-1), d mod (q-1), and q^{-1} mod p + der.append(inverse(der[4],der[5])) # Add p^{-1} mod q + del der[0] # Remove version + return self.construct(der[:]) + + # Keep on trying PKCS#1, but now for a public key + if len(der)==2: + # The DER object is an RSAPublicKey SEQUENCE with two elements + if der.hasOnlyInts(): + return self.construct(der[:]) + # The DER object is a SubjectPublicKeyInfo SEQUENCE with two elements: + # an 'algorithm' (or 'algorithmIdentifier') SEQUENCE and a 'subjectPublicKey' BIT STRING. + # 'algorithm' takes the value given a few lines above. + # 'subjectPublicKey' encapsulates the actual ASN.1 RSAPublicKey element. + if der[0]==algorithmIdentifier: + bitmap = DerObject() + bitmap.decode(der[1], True) + if bitmap.isType('BIT STRING') and bord(bitmap.payload[0])==0x00: + der.decode(bitmap.payload[1:], True) + if len(der)==2 and der.hasOnlyInts(): + return self.construct(der[:]) + + # Try unencrypted PKCS#8 + if der[0]==0: + # The second element in the SEQUENCE is algorithmIdentifier. + # It must say RSA (see above for description). + if der[1]==algorithmIdentifier: + privateKey = DerObject() + privateKey.decode(der[2], True) + if privateKey.isType('OCTET STRING'): + return self._importKeyDER(privateKey.payload) + + except (ValueError, IndexError): + pass + + raise ValueError("RSA key format is not supported") + + def importKey(self, externKey, passphrase=None): + """Import an RSA key (public or private half), encoded in standard form. + + :Parameter externKey: + The RSA key to import, encoded as a string. + + An RSA public key can be in any of the following formats: + + - X.509 `subjectPublicKeyInfo` DER SEQUENCE (binary or PEM encoding) + - `PKCS#1`_ `RSAPublicKey` DER SEQUENCE (binary or PEM encoding) + - OpenSSH (textual public key only) + + An RSA private key can be in any of the following formats: + + - PKCS#1 `RSAPrivateKey` DER SEQUENCE (binary or PEM encoding) + - `PKCS#8`_ `PrivateKeyInfo` DER SEQUENCE (binary or PEM encoding) + - OpenSSH (textual public key only) + + For details about the PEM encoding, see `RFC1421`_/`RFC1423`_. + + In case of PEM encoding, the private key can be encrypted with DES or 3TDES according to a certain ``pass phrase``. + Only OpenSSL-compatible pass phrases are supported. + :Type externKey: string + + :Parameter passphrase: + In case of an encrypted PEM key, this is the pass phrase from which the encryption key is derived. + :Type passphrase: string + + :Return: An RSA key object (`_RSAobj`). + + :Raise ValueError/IndexError/TypeError: + When the given key cannot be parsed (possibly because the pass phrase is wrong). + + .. _RFC1421: http://www.ietf.org/rfc/rfc1421.txt + .. _RFC1423: http://www.ietf.org/rfc/rfc1423.txt + .. _`PKCS#1`: http://www.ietf.org/rfc/rfc3447.txt + .. _`PKCS#8`: http://www.ietf.org/rfc/rfc5208.txt + """ + externKey = tobytes(externKey) + if passphrase is not None: + passphrase = tobytes(passphrase) + + if externKey.startswith(b('-----')): + # This is probably a PEM encoded key + lines = externKey.replace(b(" "),b('')).split() + keyobj = None + + # The encrypted PEM format + if lines[1].startswith(b('Proc-Type:4,ENCRYPTED')): + DEK = lines[2].split(b(':')) + if len(DEK)!=2 or DEK[0]!=b('DEK-Info') or not passphrase: + raise ValueError("PEM encryption format not supported.") + algo, salt = DEK[1].split(b(',')) + salt = binascii.a2b_hex(salt) + import Crypto.Hash.MD5 + from Crypto.Cipher import DES, DES3 + from Crypto.Protocol.KDF import PBKDF1 + if algo==b("DES-CBC"): + # This is EVP_BytesToKey in OpenSSL + key = PBKDF1(passphrase, salt, 8, 1, Crypto.Hash.MD5) + keyobj = DES.new(key, Crypto.Cipher.DES.MODE_CBC, salt) + elif algo==b("DES-EDE3-CBC"): + # Note that EVP_BytesToKey is note exactly the same as PBKDF1 + key = PBKDF1(passphrase, salt, 16, 1, Crypto.Hash.MD5) + key += PBKDF1(key+passphrase, salt, 8, 1, Crypto.Hash.MD5) + keyobj = DES3.new(key, Crypto.Cipher.DES3.MODE_CBC, salt) + else: + raise ValueError("Unsupport PEM encryption algorithm.") + lines = lines[2:] + + der = binascii.a2b_base64(b('').join(lines[1:-1])) + if keyobj: + der = keyobj.decrypt(der) + padding = bord(der[-1]) + der = der[:-padding] + return self._importKeyDER(der) + + if externKey.startswith(b('ssh-rsa ')): + # This is probably an OpenSSH key + keystring = binascii.a2b_base64(externKey.split(b(' '))[1]) + keyparts = [] + while len(keystring)>4: + l = struct.unpack(">I",keystring[:4])[0] + keyparts.append(keystring[4:4+l]) + keystring = keystring[4+l:] + e = bytes_to_long(keyparts[1]) + n = bytes_to_long(keyparts[2]) + return self.construct([n, e]) + if bord(externKey[0])==0x30: + # This is probably a DER encoded key + return self._importKeyDER(externKey) + + raise ValueError("RSA key format is not supported") + +#: This is the ASN.1 DER object that qualifies an algorithm as +#: compliant to PKCS#1 (that is, the standard RSA). +# It is found in all 'algorithm' fields (also called 'algorithmIdentifier'). +# It is a SEQUENCE with the oid assigned to RSA and with its parameters (none). +# 0x06 0x09 OBJECT IDENTIFIER, 9 bytes of payload +# 0x2A 0x86 0x48 0x86 0xF7 0x0D 0x01 0x01 0x01 +# rsaEncryption (1 2 840 113549 1 1 1) (PKCS #1) +# 0x05 0x00 NULL +algorithmIdentifier = DerSequence( + [ b('\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01'), + DerNull().encode() ] + ).encode() + +_impl = RSAImplementation() +#: +#: Randomly generate a fresh, new RSA key object. +#: +#: See `RSAImplementation.generate`. +#: +generate = _impl.generate +#: +#: Construct an RSA key object from a tuple of valid RSA components. +#: +#: See `RSAImplementation.construct`. +#: +construct = _impl.construct +#: +#: Import an RSA key (public or private half), encoded in standard form. +#: +#: See `RSAImplementation.importKey`. +#: +importKey = _impl.importKey +error = _impl.error + +# vim:set ts=4 sw=4 sts=4 expandtab: + diff --git a/lib/Crypto/PublicKey/_DSA.py b/lib/Crypto/PublicKey/_DSA.py new file mode 100644 index 0000000..6b7a964 --- /dev/null +++ b/lib/Crypto/PublicKey/_DSA.py @@ -0,0 +1,115 @@ + +# +# DSA.py : Digital Signature Algorithm +# +# Part of the Python Cryptography Toolkit +# +# Written by Andrew Kuchling, Paul Swartz, and others +# +# =================================================================== +# 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. +# =================================================================== +# + +__revision__ = "$Id$" + +from Crypto.PublicKey.pubkey import * +from Crypto.Util import number +from Crypto.Util.number import bytes_to_long, long_to_bytes +from Crypto.Hash import SHA +from Crypto.Util.py3compat import * + +class error (Exception): + pass + +def generateQ(randfunc): + S=randfunc(20) + hash1=SHA.new(S).digest() + hash2=SHA.new(long_to_bytes(bytes_to_long(S)+1)).digest() + q = bignum(0) + for i in range(0,20): + c=bord(hash1[i])^bord(hash2[i]) + if i==0: + c=c | 128 + if i==19: + c= c | 1 + q=q*256+c + while (not isPrime(q)): + q=q+2 + if pow(2,159L) < q < pow(2,160L): + return S, q + raise RuntimeError('Bad q value generated') + +def generate_py(bits, randfunc, progress_func=None): + """generate(bits:int, randfunc:callable, progress_func:callable) + + Generate a DSA key of length 'bits', using 'randfunc' to get + random data and 'progress_func', if present, to display + the progress of the key generation. + """ + + if bits<160: + raise ValueError('Key length < 160 bits') + obj=DSAobj() + # Generate string S and prime q + if progress_func: + progress_func('p,q\n') + while (1): + S, obj.q = generateQ(randfunc) + n=divmod(bits-1, 160)[0] + C, N, V = 0, 2, {} + b=(obj.q >> 5) & 15 + powb=pow(bignum(2), b) + powL1=pow(bignum(2), bits-1) + while C<4096: + for k in range(0, n+1): + V[k]=bytes_to_long(SHA.new(S+bstr(N)+bstr(k)).digest()) + W=V[n] % powb + for k in range(n-1, -1, -1): + W=(W<<160L)+V[k] + X=W+powL1 + p=X-(X%(2*obj.q)-1) + if powL1<=p and isPrime(p): + break + C, N = C+1, N+n+1 + if C<4096: + break + if progress_func: + progress_func('4096 multiples failed\n') + + obj.p = p + power=divmod(p-1, obj.q)[0] + if progress_func: + progress_func('h,g\n') + while (1): + h=bytes_to_long(randfunc(bits)) % (p-1) + g=pow(h, power, p) + if 11: + break + obj.g=g + if progress_func: + progress_func('x,y\n') + while (1): + x=bytes_to_long(randfunc(20)) + if 0 < x < obj.q: + break + obj.x, obj.y = x, pow(g, x, p) + return obj + +class DSAobj: + pass + diff --git a/lib/Crypto/PublicKey/_RSA.py b/lib/Crypto/PublicKey/_RSA.py new file mode 100644 index 0000000..9366d19 --- /dev/null +++ b/lib/Crypto/PublicKey/_RSA.py @@ -0,0 +1,81 @@ +# +# RSA.py : RSA encryption/decryption +# +# Part of the Python Cryptography Toolkit +# +# Written by Andrew Kuchling, Paul Swartz, and others +# +# =================================================================== +# 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. +# =================================================================== +# + +__revision__ = "$Id$" + +from Crypto.PublicKey import pubkey +from Crypto.Util import number + +def generate_py(bits, randfunc, progress_func=None, e=65537): + """generate(bits:int, randfunc:callable, progress_func:callable, e:int) + + Generate an RSA key of length 'bits', public exponent 'e'(which must be + odd), using 'randfunc' to get random data and 'progress_func', + if present, to display the progress of the key generation. + """ + obj=RSAobj() + obj.e = long(e) + + # Generate the prime factors of n + if progress_func: + progress_func('p,q\n') + p = q = 1L + while number.size(p*q) < bits: + # Note that q might be one bit longer than p if somebody specifies an odd + # number of bits for the key. (Why would anyone do that? You don't get + # more security.) + p = pubkey.getStrongPrime(bits>>1, obj.e, 1e-12, randfunc) + q = pubkey.getStrongPrime(bits - (bits>>1), obj.e, 1e-12, randfunc) + + # It's OK for p to be larger than q, but let's be + # kind to the function that will invert it for + # th calculation of u. + if p > q: + (p, q)=(q, p) + obj.p = p + obj.q = q + + if progress_func: + progress_func('u\n') + obj.u = pubkey.inverse(obj.p, obj.q) + obj.n = obj.p*obj.q + + if progress_func: + progress_func('d\n') + obj.d=pubkey.inverse(obj.e, (obj.p-1)*(obj.q-1)) + + assert bits <= 1+obj.size(), "Generated key is too small" + + return obj + +class RSAobj(pubkey.pubkey): + + def size(self): + """size() : int + Return the maximum number of bits that can be handled by this key. + """ + return number.size(self.n) - 1 + diff --git a/lib/Crypto/PublicKey/__init__.py b/lib/Crypto/PublicKey/__init__.py new file mode 100644 index 0000000..503809f --- /dev/null +++ b/lib/Crypto/PublicKey/__init__.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# +# =================================================================== +# 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. +# =================================================================== + +"""Public-key encryption and signature algorithms. + +Public-key encryption uses two different keys, one for encryption and +one for decryption. The encryption key can be made public, and the +decryption key is kept private. Many public-key algorithms can also +be used to sign messages, and some can *only* be used for signatures. + +======================== ============================================= +Module Description +======================== ============================================= +Crypto.PublicKey.DSA Digital Signature Algorithm (Signature only) +Crypto.PublicKey.ElGamal (Signing and encryption) +Crypto.PublicKey.RSA (Signing, encryption, and blinding) +======================== ============================================= + +:undocumented: _DSA, _RSA, _fastmath, _slowmath, pubkey +""" + +__all__ = ['RSA', 'DSA', 'ElGamal'] +__revision__ = "$Id$" + diff --git a/lib/Crypto/PublicKey/_slowmath.py b/lib/Crypto/PublicKey/_slowmath.py new file mode 100644 index 0000000..d926596 --- /dev/null +++ b/lib/Crypto/PublicKey/_slowmath.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +# +# PubKey/RSA/_slowmath.py : Pure Python implementation of the RSA portions of _fastmath +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Pure Python implementation of the RSA-related portions of Crypto.PublicKey._fastmath.""" + +__revision__ = "$Id$" + +__all__ = ['rsa_construct'] + +import sys + +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * +from Crypto.Util.number import size, inverse, GCD + +class error(Exception): + pass + +class _RSAKey(object): + def _blind(self, m, r): + # compute r**e * m (mod n) + return m * pow(r, self.e, self.n) + + def _unblind(self, m, r): + # compute m / r (mod n) + return inverse(r, self.n) * m % self.n + + def _decrypt(self, c): + # compute c**d (mod n) + if not self.has_private(): + raise TypeError("No private key") + if (hasattr(self,'p') and hasattr(self,'q') and hasattr(self,'u')): + m1 = pow(c, self.d % (self.p-1), self.p) + m2 = pow(c, self.d % (self.q-1), self.q) + h = m2 - m1 + if (h<0): + h = h + self.q + h = h*self.u % self.q + return h*self.p+m1 + return pow(c, self.d, self.n) + + def _encrypt(self, m): + # compute m**d (mod n) + return pow(m, self.e, self.n) + + def _sign(self, m): # alias for _decrypt + if not self.has_private(): + raise TypeError("No private key") + return self._decrypt(m) + + def _verify(self, m, sig): + return self._encrypt(sig) == m + + def has_private(self): + return hasattr(self, 'd') + + def size(self): + """Return the maximum number of bits that can be encrypted""" + return size(self.n) - 1 + +def rsa_construct(n, e, d=None, p=None, q=None, u=None): + """Construct an RSAKey object""" + assert isinstance(n, long) + assert isinstance(e, long) + assert isinstance(d, (long, type(None))) + assert isinstance(p, (long, type(None))) + assert isinstance(q, (long, type(None))) + assert isinstance(u, (long, type(None))) + obj = _RSAKey() + obj.n = n + obj.e = e + if d is None: + return obj + obj.d = d + if p is not None and q is not None: + obj.p = p + obj.q = q + else: + # Compute factors p and q from the private exponent d. + # We assume that n has no more than two factors. + # See 8.2.2(i) in Handbook of Applied Cryptography. + ktot = d*e-1 + # The quantity d*e-1 is a multiple of phi(n), even, + # and can be represented as t*2^s. + t = ktot + while t%2==0: + t=divmod(t,2)[0] + # Cycle through all multiplicative inverses in Zn. + # The algorithm is non-deterministic, but there is a 50% chance + # any candidate a leads to successful factoring. + # See "Digitalized Signatures and Public Key Functions as Intractable + # as Factorization", M. Rabin, 1979 + spotted = 0 + a = 2 + while not spotted and a<100: + k = t + # Cycle through all values a^{t*2^i}=a^k + while k +# +# =================================================================== +# 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. +# =================================================================== + +__revision__ = "$Id$" + +import sys +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * +from Crypto.Util.py3compat import * + +from binascii import b2a_hex +import time +import warnings + +from Crypto.pct_warnings import ClockRewindWarning +import SHAd256 + +import FortunaGenerator + +class FortunaPool(object): + """Fortuna pool type + + This object acts like a hash object, with the following differences: + + - It keeps a count (the .length attribute) of the number of bytes that + have been added to the pool + - It supports a .reset() method for in-place reinitialization + - The method to add bytes to the pool is .append(), not .update(). + """ + + digest_size = SHAd256.digest_size + + def __init__(self): + self.reset() + + def append(self, data): + self._h.update(data) + self.length += len(data) + + def digest(self): + return self._h.digest() + + def hexdigest(self): + if sys.version_info[0] == 2: + return b2a_hex(self.digest()) + else: + return b2a_hex(self.digest()).decode() + + def reset(self): + self._h = SHAd256.new() + self.length = 0 + +def which_pools(r): + """Return a list of pools indexes (in range(32)) that are to be included during reseed number r. + + According to _Practical Cryptography_, chapter 10.5.2 "Pools": + + "Pool P_i is included if 2**i is a divisor of r. Thus P_0 is used + every reseed, P_1 every other reseed, P_2 every fourth reseed, etc." + """ + # This is a separate function so that it can be unit-tested. + assert r >= 1 + retval = [] + mask = 0 + for i in range(32): + # "Pool P_i is included if 2**i is a divisor of [reseed_count]" + if (r & mask) == 0: + retval.append(i) + else: + break # optimization. once this fails, it always fails + mask = (mask << 1) | 1L + return retval + +class FortunaAccumulator(object): + + min_pool_size = 64 # TODO: explain why + reseed_interval = 0.100 # 100 ms TODO: explain why + + def __init__(self): + self.reseed_count = 0 + self.generator = FortunaGenerator.AESGenerator() + self.last_reseed = None + + # Initialize 32 FortunaPool instances. + # NB: This is _not_ equivalent to [FortunaPool()]*32, which would give + # us 32 references to the _same_ FortunaPool instance (and cause the + # assertion below to fail). + self.pools = [FortunaPool() for i in range(32)] # 32 pools + assert(self.pools[0] is not self.pools[1]) + + def random_data(self, bytes): + current_time = time.time() + if (self.last_reseed is not None and self.last_reseed > current_time): # Avoid float comparison to None to make Py3k happy + warnings.warn("Clock rewind detected. Resetting last_reseed.", ClockRewindWarning) + self.last_reseed = None + if (self.pools[0].length >= self.min_pool_size and + (self.last_reseed is None or + current_time > self.last_reseed + self.reseed_interval)): + self._reseed(current_time) + # The following should fail if we haven't seeded the pool yet. + return self.generator.pseudo_random_data(bytes) + + def _reseed(self, current_time=None): + if current_time is None: + current_time = time.time() + seed = [] + self.reseed_count += 1 + self.last_reseed = current_time + for i in which_pools(self.reseed_count): + seed.append(self.pools[i].digest()) + self.pools[i].reset() + + seed = b("").join(seed) + self.generator.reseed(seed) + + def add_random_event(self, source_number, pool_number, data): + assert 1 <= len(data) <= 32 + assert 0 <= source_number <= 255 + assert 0 <= pool_number <= 31 + self.pools[pool_number].append(bchr(source_number)) + self.pools[pool_number].append(bchr(len(data))) + self.pools[pool_number].append(data) + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/Random/Fortuna/FortunaGenerator.py b/lib/Crypto/Random/Fortuna/FortunaGenerator.py new file mode 100644 index 0000000..723fa63 --- /dev/null +++ b/lib/Crypto/Random/Fortuna/FortunaGenerator.py @@ -0,0 +1,132 @@ +# -*- coding: ascii -*- +# +# FortunaGenerator.py : Fortuna's internal PRNG +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +__revision__ = "$Id$" + +import sys +if sys.version_info[0] is 2 and sys.version_info[1] is 1: + from Crypto.Util.py21compat import * +from Crypto.Util.py3compat import * + +import struct + +from Crypto.Util.number import ceil_shift, exact_log2, exact_div +from Crypto.Util import Counter +from Crypto.Cipher import AES + +import SHAd256 + +class AESGenerator(object): + """The Fortuna "generator" + + This is used internally by the Fortuna PRNG to generate arbitrary amounts + of pseudorandom data from a smaller amount of seed data. + + The output is generated by running AES-256 in counter mode and re-keying + after every mebibyte (2**16 blocks) of output. + """ + + block_size = AES.block_size # output block size in octets (128 bits) + key_size = 32 # key size in octets (256 bits) + + # Because of the birthday paradox, we expect to find approximately one + # collision for every 2**64 blocks of output from a real random source. + # However, this code generates pseudorandom data by running AES in + # counter mode, so there will be no collisions until the counter + # (theoretically) wraps around at 2**128 blocks. Thus, in order to prevent + # Fortuna's pseudorandom output from deviating perceptibly from a true + # random source, Ferguson and Schneier specify a limit of 2**16 blocks + # without rekeying. + max_blocks_per_request = 2**16 # Allow no more than this number of blocks per _pseudo_random_data request + + _four_kiblocks_of_zeros = b("\0") * block_size * 4096 + + def __init__(self): + self.counter = Counter.new(nbits=self.block_size*8, initial_value=0, little_endian=True) + self.key = None + + # Set some helper constants + self.block_size_shift = exact_log2(self.block_size) + assert (1 << self.block_size_shift) == self.block_size + + self.blocks_per_key = exact_div(self.key_size, self.block_size) + assert self.key_size == self.blocks_per_key * self.block_size + + self.max_bytes_per_request = self.max_blocks_per_request * self.block_size + + def reseed(self, seed): + if self.key is None: + self.key = b("\0") * self.key_size + + self._set_key(SHAd256.new(self.key + seed).digest()) + self.counter() # increment counter + assert len(self.key) == self.key_size + + def pseudo_random_data(self, bytes): + assert bytes >= 0 + + num_full_blocks = bytes >> 20 + remainder = bytes & ((1<<20)-1) + + retval = [] + for i in xrange(num_full_blocks): + retval.append(self._pseudo_random_data(1<<20)) + retval.append(self._pseudo_random_data(remainder)) + + return b("").join(retval) + + def _set_key(self, key): + self.key = key + self._cipher = AES.new(key, AES.MODE_CTR, counter=self.counter) + + def _pseudo_random_data(self, bytes): + if not (0 <= bytes <= self.max_bytes_per_request): + raise AssertionError("You cannot ask for more than 1 MiB of data per request") + + num_blocks = ceil_shift(bytes, self.block_size_shift) # num_blocks = ceil(bytes / self.block_size) + + # Compute the output + retval = self._generate_blocks(num_blocks)[:bytes] + + # Switch to a new key to avoid later compromises of this output (i.e. + # state compromise extension attacks) + self._set_key(self._generate_blocks(self.blocks_per_key)) + + assert len(retval) == bytes + assert len(self.key) == self.key_size + + return retval + + def _generate_blocks(self, num_blocks): + if self.key is None: + raise AssertionError("generator must be seeded before use") + assert 0 <= num_blocks <= self.max_blocks_per_request + retval = [] + for i in xrange(num_blocks >> 12): # xrange(num_blocks / 4096) + retval.append(self._cipher.encrypt(self._four_kiblocks_of_zeros)) + remaining_bytes = (num_blocks & 4095) << self.block_size_shift # (num_blocks % 4095) * self.block_size + retval.append(self._cipher.encrypt(self._four_kiblocks_of_zeros[:remaining_bytes])) + return b("").join(retval) + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/Random/Fortuna/SHAd256.py b/lib/Crypto/Random/Fortuna/SHAd256.py new file mode 100644 index 0000000..2e135c9 --- /dev/null +++ b/lib/Crypto/Random/Fortuna/SHAd256.py @@ -0,0 +1,98 @@ +# -*- coding: ascii -*- +# +# Random/Fortuna/SHAd256.py : SHA_d-256 hash function implementation +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""\ +SHA_d-256 hash function implementation. + +This module should comply with PEP 247. +""" + +__revision__ = "$Id$" +__all__ = ['new', 'digest_size'] + +import sys +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * +from Crypto.Util.py3compat import * + +from binascii import b2a_hex + +from Crypto.Hash import SHA256 + +assert SHA256.digest_size == 32 + +class _SHAd256(object): + """SHA-256, doubled. + + Returns SHA-256(SHA-256(data)). + """ + + digest_size = SHA256.digest_size + + _internal = object() + + def __init__(self, internal_api_check, sha256_hash_obj): + if internal_api_check is not self._internal: + raise AssertionError("Do not instantiate this class directly. Use %s.new()" % (__name__,)) + self._h = sha256_hash_obj + + # PEP 247 "copy" method + def copy(self): + """Return a copy of this hashing object""" + return _SHAd256(SHAd256._internal, self._h.copy()) + + # PEP 247 "digest" method + def digest(self): + """Return the hash value of this object as a binary string""" + retval = SHA256.new(self._h.digest()).digest() + assert len(retval) == 32 + return retval + + # PEP 247 "hexdigest" method + def hexdigest(self): + """Return the hash value of this object as a (lowercase) hexadecimal string""" + retval = b2a_hex(self.digest()) + assert len(retval) == 64 + if sys.version_info[0] == 2: + return retval + else: + return retval.decode() + + # PEP 247 "update" method + def update(self, data): + self._h.update(data) + +# PEP 247 module-level "digest_size" variable +digest_size = _SHAd256.digest_size + +# PEP 247 module-level "new" function +def new(data=None): + """Return a new SHAd256 hashing object""" + if not data: + data=b("") + sha = _SHAd256(_SHAd256._internal, SHA256.new(data)) + sha.new = globals()['new'] + return sha + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/Random/Fortuna/__init__.py b/lib/Crypto/Random/Fortuna/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/Crypto/Random/OSRNG/__init__.py b/lib/Crypto/Random/OSRNG/__init__.py new file mode 100644 index 0000000..2fbbecb --- /dev/null +++ b/lib/Crypto/Random/OSRNG/__init__.py @@ -0,0 +1,40 @@ +# +# Random/OSRNG/__init__.py : Platform-independent OS RNG API +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Provides a platform-independent interface to the random number generators +supplied by various operating systems.""" + +__revision__ = "$Id$" + +import os + +if os.name == 'posix': + from Crypto.Random.OSRNG.posix import new +elif os.name == 'nt': + from Crypto.Random.OSRNG.nt import new +elif hasattr(os, 'urandom'): + from Crypto.Random.OSRNG.fallback import new +else: + raise ImportError("Not implemented") + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/Random/OSRNG/fallback.py b/lib/Crypto/Random/OSRNG/fallback.py new file mode 100644 index 0000000..5bb6126 --- /dev/null +++ b/lib/Crypto/Random/OSRNG/fallback.py @@ -0,0 +1,46 @@ +# +# Random/OSRNG/fallback.py : Fallback entropy source for systems with os.urandom +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + + +__revision__ = "$Id$" +__all__ = ['PythonOSURandomRNG'] + +import os + +from rng_base import BaseRNG + +class PythonOSURandomRNG(BaseRNG): + + name = "" + + def __init__(self): + self._read = os.urandom + BaseRNG.__init__(self) + + def _close(self): + self._read = None + +def new(*args, **kwargs): + return PythonOSURandomRNG(*args, **kwargs) + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/Random/OSRNG/nt.py b/lib/Crypto/Random/OSRNG/nt.py new file mode 100644 index 0000000..c1c2f44 --- /dev/null +++ b/lib/Crypto/Random/OSRNG/nt.py @@ -0,0 +1,74 @@ +# +# Random/OSRNG/nt.py : OS entropy source for MS Windows +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + + +__revision__ = "$Id$" +__all__ = ['WindowsRNG'] + +import winrandom +from rng_base import BaseRNG + +class WindowsRNG(BaseRNG): + + name = "" + + def __init__(self): + self.__winrand = winrandom.new() + BaseRNG.__init__(self) + + def flush(self): + """Work around weakness in Windows RNG. + + The CryptGenRandom mechanism in some versions of Windows allows an + attacker to learn 128 KiB of past and future output. As a workaround, + this function reads 128 KiB of 'random' data from Windows and discards + it. + + For more information about the weaknesses in CryptGenRandom, see + _Cryptanalysis of the Random Number Generator of the Windows Operating + System_, by Leo Dorrendorf and Zvi Gutterman and Benny Pinkas + http://eprint.iacr.org/2007/419 + """ + if self.closed: + raise ValueError("I/O operation on closed file") + data = self.__winrand.get_bytes(128*1024) + assert (len(data) == 128*1024) + BaseRNG.flush(self) + + def _close(self): + self.__winrand = None + + def _read(self, N): + # Unfortunately, research shows that CryptGenRandom doesn't provide + # forward secrecy and fails the next-bit test unless we apply a + # workaround, which we do here. See http://eprint.iacr.org/2007/419 + # for information on the vulnerability. + self.flush() + data = self.__winrand.get_bytes(N) + self.flush() + return data + +def new(*args, **kwargs): + return WindowsRNG(*args, **kwargs) + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/Random/OSRNG/posix.py b/lib/Crypto/Random/OSRNG/posix.py new file mode 100644 index 0000000..ca6ac05 --- /dev/null +++ b/lib/Crypto/Random/OSRNG/posix.py @@ -0,0 +1,86 @@ +# +# Random/OSRNG/posix.py : OS entropy source for POSIX systems +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + + +__revision__ = "$Id$" +__all__ = ['DevURandomRNG'] + +import errno +import os +import stat + +from rng_base import BaseRNG +from Crypto.Util.py3compat import b + +class DevURandomRNG(BaseRNG): + + def __init__(self, devname=None): + if devname is None: + self.name = "/dev/urandom" + else: + self.name = devname + + # Test that /dev/urandom is a character special device + f = open(self.name, "rb", 0) + fmode = os.fstat(f.fileno())[stat.ST_MODE] + if not stat.S_ISCHR(fmode): + f.close() + raise TypeError("%r is not a character special device" % (self.name,)) + + self.__file = f + + BaseRNG.__init__(self) + + def _close(self): + self.__file.close() + + def _read(self, N): + # Starting with Python 3 open with buffering=0 returns a FileIO object. + # FileIO.read behaves like read(2) and not like fread(3) and thus we + # have to handle the case that read returns less data as requested here + # more carefully. + data = b("") + while len(data) < N: + try: + d = self.__file.read(N - len(data)) + except IOError, e: + # read(2) has been interrupted by a signal; redo the read + if e.errno == errno.EINTR: + continue + raise + + if d is None: + # __file is in non-blocking mode and no data is available + return data + if len(d) == 0: + # __file is in blocking mode and arrived at EOF + return data + + data += d + return data + +def new(*args, **kwargs): + return DevURandomRNG(*args, **kwargs) + + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/Random/OSRNG/rng_base.py b/lib/Crypto/Random/OSRNG/rng_base.py new file mode 100644 index 0000000..54c3aa0 --- /dev/null +++ b/lib/Crypto/Random/OSRNG/rng_base.py @@ -0,0 +1,88 @@ +# +# Random/OSRNG/rng_base.py : Base class for OSRNG +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +__revision__ = "$Id$" + +import sys +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * + +class BaseRNG(object): + + def __init__(self): + self.closed = False + self._selftest() + + def __del__(self): + self.close() + + def _selftest(self): + # Test that urandom can return data + data = self.read(16) + if len(data) != 16: + raise AssertionError("read truncated") + + # Test that we get different data every time (if we don't, the RNG is + # probably malfunctioning) + data2 = self.read(16) + if data == data2: + raise AssertionError("OS RNG returned duplicate data") + + # PEP 343: Support for the "with" statement + def __enter__(self): + pass + def __exit__(self): + """PEP 343 support""" + self.close() + + def close(self): + if not self.closed: + self._close() + self.closed = True + + def flush(self): + pass + + def read(self, N=-1): + """Return N bytes from the RNG.""" + if self.closed: + raise ValueError("I/O operation on closed file") + if not isinstance(N, (long, int)): + raise TypeError("an integer is required") + if N < 0: + raise ValueError("cannot read to end of infinite stream") + elif N == 0: + return "" + data = self._read(N) + if len(data) != N: + raise AssertionError("%s produced truncated output (requested %d, got %d)" % (self.name, N, len(data))) + return data + + def _close(self): + raise NotImplementedError("child class must implement this") + + def _read(self, N): + raise NotImplementedError("child class must implement this") + + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/Random/_UserFriendlyRNG.py b/lib/Crypto/Random/_UserFriendlyRNG.py new file mode 100644 index 0000000..c2a2eae --- /dev/null +++ b/lib/Crypto/Random/_UserFriendlyRNG.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 -*- +# +# Random/_UserFriendlyRNG.py : A user-friendly random number generator +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +__revision__ = "$Id$" + +import sys +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * + +import os +import threading +import struct +import time +from math import floor + +from Crypto.Random import OSRNG +from Crypto.Random.Fortuna import FortunaAccumulator + +class _EntropySource(object): + def __init__(self, accumulator, src_num): + self._fortuna = accumulator + self._src_num = src_num + self._pool_num = 0 + + def feed(self, data): + self._fortuna.add_random_event(self._src_num, self._pool_num, data) + self._pool_num = (self._pool_num + 1) & 31 + +class _EntropyCollector(object): + + def __init__(self, accumulator): + self._osrng = OSRNG.new() + self._osrng_es = _EntropySource(accumulator, 255) + self._time_es = _EntropySource(accumulator, 254) + self._clock_es = _EntropySource(accumulator, 253) + + def reinit(self): + # Add 256 bits to each of the 32 pools, twice. (For a total of 16384 + # bits collected from the operating system.) + for i in range(2): + block = self._osrng.read(32*32) + for p in range(32): + self._osrng_es.feed(block[p*32:(p+1)*32]) + block = None + self._osrng.flush() + + def collect(self): + # Collect 64 bits of entropy from the operating system and feed it to Fortuna. + self._osrng_es.feed(self._osrng.read(8)) + + # Add the fractional part of time.time() + t = time.time() + self._time_es.feed(struct.pack("@I", int(2**30 * (t - floor(t))))) + + # Add the fractional part of time.clock() + t = time.clock() + self._clock_es.feed(struct.pack("@I", int(2**30 * (t - floor(t))))) + + +class _UserFriendlyRNG(object): + + def __init__(self): + self.closed = False + self._fa = FortunaAccumulator.FortunaAccumulator() + self._ec = _EntropyCollector(self._fa) + self.reinit() + + def reinit(self): + """Initialize the random number generator and seed it with entropy from + the operating system. + """ + self._pid = os.getpid() + self._ec.reinit() + + def close(self): + self.closed = True + self._osrng = None + self._fa = None + + def flush(self): + pass + + def read(self, N): + """Return N bytes from the RNG.""" + if self.closed: + raise ValueError("I/O operation on closed file") + if not isinstance(N, (long, int)): + raise TypeError("an integer is required") + if N < 0: + raise ValueError("cannot read to end of infinite stream") + + # Collect some entropy and feed it to Fortuna + self._ec.collect() + + # Ask Fortuna to generate some bytes + retval = self._fa.random_data(N) + + # Check that we haven't forked in the meantime. (If we have, we don't + # want to use the data, because it might have been duplicated in the + # parent process. + self._check_pid() + + # Return the random data. + return retval + + def _check_pid(self): + # Lame fork detection to remind developers to invoke Random.atfork() + # after every call to os.fork(). Note that this check is not reliable, + # since process IDs can be reused on most operating systems. + # + # You need to do Random.atfork() in the child process after every call + # to os.fork() to avoid reusing PRNG state. If you want to avoid + # leaking PRNG state to child processes (for example, if you are using + # os.setuid()) then you should also invoke Random.atfork() in the + # *parent* process. + if os.getpid() != self._pid: + raise AssertionError("PID check failed. RNG must be re-initialized after fork(). Hint: Try Random.atfork()") + + +class _LockingUserFriendlyRNG(_UserFriendlyRNG): + def __init__(self): + self._lock = threading.Lock() + _UserFriendlyRNG.__init__(self) + + def close(self): + self._lock.acquire() + try: + return _UserFriendlyRNG.close(self) + finally: + self._lock.release() + + def reinit(self): + self._lock.acquire() + try: + return _UserFriendlyRNG.reinit(self) + finally: + self._lock.release() + + def read(self, bytes): + self._lock.acquire() + try: + return _UserFriendlyRNG.read(self, bytes) + finally: + self._lock.release() + +class RNGFile(object): + def __init__(self, singleton): + self.closed = False + self._singleton = singleton + + # PEP 343: Support for the "with" statement + def __enter__(self): + """PEP 343 support""" + def __exit__(self): + """PEP 343 support""" + self.close() + + def close(self): + # Don't actually close the singleton, just close this RNGFile instance. + self.closed = True + self._singleton = None + + def read(self, bytes): + if self.closed: + raise ValueError("I/O operation on closed file") + return self._singleton.read(bytes) + + def flush(self): + if self.closed: + raise ValueError("I/O operation on closed file") + +_singleton_lock = threading.Lock() +_singleton = None +def _get_singleton(): + global _singleton + _singleton_lock.acquire() + try: + if _singleton is None: + _singleton = _LockingUserFriendlyRNG() + return _singleton + finally: + _singleton_lock.release() + +def new(): + return RNGFile(_get_singleton()) + +def reinit(): + _get_singleton().reinit() + +def get_random_bytes(n): + """Return the specified number of cryptographically-strong random bytes.""" + return _get_singleton().read(n) + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/Random/__init__.py b/lib/Crypto/Random/__init__.py new file mode 100644 index 0000000..659ffee --- /dev/null +++ b/lib/Crypto/Random/__init__.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# +# Random/__init__.py : PyCrypto random number generation +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +__revision__ = "$Id$" +__all__ = ['new'] + +from Crypto.Random import OSRNG +from Crypto.Random import _UserFriendlyRNG + +def new(*args, **kwargs): + """Return a file-like object that outputs cryptographically random bytes.""" + return _UserFriendlyRNG.new(*args, **kwargs) + +def atfork(): + """Call this whenever you call os.fork()""" + _UserFriendlyRNG.reinit() + +def get_random_bytes(n): + """Return the specified number of cryptographically-strong random bytes.""" + return _UserFriendlyRNG.get_random_bytes(n) + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/Random/random.py b/lib/Crypto/Random/random.py new file mode 100644 index 0000000..bef02e6 --- /dev/null +++ b/lib/Crypto/Random/random.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- +# +# Random/random.py : Strong alternative for the standard 'random' module +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""A cryptographically strong version of Python's standard "random" module.""" + +__revision__ = "$Id$" +__all__ = ['StrongRandom', 'getrandbits', 'randrange', 'randint', 'choice', 'shuffle', 'sample'] + +from Crypto import Random +import sys +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * + +class StrongRandom(object): + def __init__(self, rng=None, randfunc=None): + if randfunc is None and rng is None: + self._randfunc = None + elif randfunc is not None and rng is None: + self._randfunc = randfunc + elif randfunc is None and rng is not None: + self._randfunc = rng.read + else: + raise ValueError("Cannot specify both 'rng' and 'randfunc'") + + def getrandbits(self, k): + """Return a python long integer with k random bits.""" + if self._randfunc is None: + self._randfunc = Random.new().read + mask = (1L << k) - 1 + return mask & bytes_to_long(self._randfunc(ceil_div(k, 8))) + + def randrange(self, *args): + """randrange([start,] stop[, step]): + Return a randomly-selected element from range(start, stop, step).""" + if len(args) == 3: + (start, stop, step) = args + elif len(args) == 2: + (start, stop) = args + step = 1 + elif len(args) == 1: + (stop,) = args + start = 0 + step = 1 + else: + raise TypeError("randrange expected at most 3 arguments, got %d" % (len(args),)) + if (not isinstance(start, (int, long)) + or not isinstance(stop, (int, long)) + or not isinstance(step, (int, long))): + raise TypeError("randrange requires integer arguments") + if step == 0: + raise ValueError("randrange step argument must not be zero") + + num_choices = ceil_div(stop - start, step) + if num_choices < 0: + num_choices = 0 + if num_choices < 1: + raise ValueError("empty range for randrange(%r, %r, %r)" % (start, stop, step)) + + # Pick a random number in the range of possible numbers + r = num_choices + while r >= num_choices: + r = self.getrandbits(size(num_choices)) + + return start + (step * r) + + def randint(self, a, b): + """Return a random integer N such that a <= N <= b.""" + if not isinstance(a, (int, long)) or not isinstance(b, (int, long)): + raise TypeError("randint requires integer arguments") + N = self.randrange(a, b+1) + assert a <= N <= b + return N + + def choice(self, seq): + """Return a random element from a (non-empty) sequence. + + If the seqence is empty, raises IndexError. + """ + if len(seq) == 0: + raise IndexError("empty sequence") + return seq[self.randrange(len(seq))] + + def shuffle(self, x): + """Shuffle the sequence in place.""" + # Make a (copy) of the list of objects we want to shuffle + items = list(x) + + # Choose a random item (without replacement) until all the items have been + # chosen. + for i in xrange(len(x)): + x[i] = items.pop(self.randrange(len(items))) + + def sample(self, population, k): + """Return a k-length list of unique elements chosen from the population sequence.""" + + num_choices = len(population) + if k > num_choices: + raise ValueError("sample larger than population") + + retval = [] + selected = {} # we emulate a set using a dict here + for i in xrange(k): + r = None + while r is None or selected.has_key(r): + r = self.randrange(num_choices) + retval.append(population[r]) + selected[r] = 1 + return retval + +_r = StrongRandom() +getrandbits = _r.getrandbits +randrange = _r.randrange +randint = _r.randint +choice = _r.choice +shuffle = _r.shuffle +sample = _r.sample + +# These are at the bottom to avoid problems with recursive imports +from Crypto.Util.number import ceil_div, bytes_to_long, long_to_bytes, size + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Cipher/__init__.py b/lib/Crypto/SelfTest/Cipher/__init__.py new file mode 100644 index 0000000..63e9c57 --- /dev/null +++ b/lib/Crypto/SelfTest/Cipher/__init__.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Cipher/__init__.py: Self-test for cipher modules +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test for cipher modules""" + +__revision__ = "$Id$" + +def get_tests(config={}): + tests = [] + from Crypto.SelfTest.Cipher import test_AES; tests += test_AES.get_tests(config=config) + from Crypto.SelfTest.Cipher import test_ARC2; tests += test_ARC2.get_tests(config=config) + from Crypto.SelfTest.Cipher import test_ARC4; tests += test_ARC4.get_tests(config=config) + from Crypto.SelfTest.Cipher import test_Blowfish; tests += test_Blowfish.get_tests(config=config) + from Crypto.SelfTest.Cipher import test_CAST; tests += test_CAST.get_tests(config=config) + from Crypto.SelfTest.Cipher import test_DES3; tests += test_DES3.get_tests(config=config) + from Crypto.SelfTest.Cipher import test_DES; tests += test_DES.get_tests(config=config) + from Crypto.SelfTest.Cipher import test_XOR; tests += test_XOR.get_tests(config=config) + from Crypto.SelfTest.Cipher import test_pkcs1_15; tests += test_pkcs1_15.get_tests(config=config) + from Crypto.SelfTest.Cipher import test_pkcs1_oaep; tests += test_pkcs1_oaep.get_tests(config=config) + return tests + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Cipher/common.py b/lib/Crypto/SelfTest/Cipher/common.py new file mode 100644 index 0000000..8bebed9 --- /dev/null +++ b/lib/Crypto/SelfTest/Cipher/common.py @@ -0,0 +1,399 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Hash/common.py: Common code for Crypto.SelfTest.Hash +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-testing for PyCrypto hash modules""" + +__revision__ = "$Id$" + +import sys +import unittest +from binascii import a2b_hex, b2a_hex +from Crypto.Util.py3compat import * + +# For compatibility with Python 2.1 and Python 2.2 +if sys.hexversion < 0x02030000: + # Python 2.1 doesn't have a dict() function + # Python 2.2 dict() function raises TypeError if you do dict(MD5='blah') + def dict(**kwargs): + return kwargs.copy() +else: + dict = dict + +class _NoDefault: pass # sentinel object +def _extract(d, k, default=_NoDefault): + """Get an item from a dictionary, and remove it from the dictionary.""" + try: + retval = d[k] + except KeyError: + if default is _NoDefault: + raise + return default + del d[k] + return retval + +# Generic cipher test case +class CipherSelfTest(unittest.TestCase): + + def __init__(self, module, params): + unittest.TestCase.__init__(self) + self.module = module + + # Extract the parameters + params = params.copy() + self.description = _extract(params, 'description') + self.key = b(_extract(params, 'key')) + self.plaintext = b(_extract(params, 'plaintext')) + self.ciphertext = b(_extract(params, 'ciphertext')) + self.module_name = _extract(params, 'module_name', None) + + mode = _extract(params, 'mode', None) + self.mode_name = str(mode) + if mode is not None: + # Block cipher + self.mode = getattr(self.module, "MODE_" + mode) + self.iv = _extract(params, 'iv', None) + if self.iv is not None: self.iv = b(self.iv) + + # Only relevant for OPENPGP mode + self.encrypted_iv = _extract(params, 'encrypted_iv', None) + if self.encrypted_iv is not None: + self.encrypted_iv = b(self.encrypted_iv) + else: + # Stream cipher + self.mode = None + self.iv = None + + self.extra_params = params + + def shortDescription(self): + return self.description + + def _new(self, do_decryption=0): + params = self.extra_params.copy() + + # Handle CTR mode parameters. By default, we use Counter.new(self.module.block_size) + if hasattr(self.module, "MODE_CTR") and self.mode == self.module.MODE_CTR: + from Crypto.Util import Counter + ctr_class = _extract(params, 'ctr_class', Counter.new) + ctr_params = _extract(params, 'ctr_params', {}).copy() + if ctr_params.has_key('prefix'): ctr_params['prefix'] = a2b_hex(b(ctr_params['prefix'])) + if ctr_params.has_key('suffix'): ctr_params['suffix'] = a2b_hex(b(ctr_params['suffix'])) + if not ctr_params.has_key('nbits'): + ctr_params['nbits'] = 8*(self.module.block_size - len(ctr_params.get('prefix', '')) - len(ctr_params.get('suffix', ''))) + params['counter'] = ctr_class(**ctr_params) + + if self.mode is None: + # Stream cipher + return self.module.new(a2b_hex(self.key), **params) + elif self.iv is None: + # Block cipher without iv + return self.module.new(a2b_hex(self.key), self.mode, **params) + else: + # Block cipher with iv + if do_decryption and self.mode == self.module.MODE_OPENPGP: + # In PGP mode, the IV to feed for decryption is the *encrypted* one + return self.module.new(a2b_hex(self.key), self.mode, a2b_hex(self.encrypted_iv), **params) + else: + return self.module.new(a2b_hex(self.key), self.mode, a2b_hex(self.iv), **params) + + def runTest(self): + plaintext = a2b_hex(self.plaintext) + ciphertext = a2b_hex(self.ciphertext) + + ct1 = b2a_hex(self._new().encrypt(plaintext)) + pt1 = b2a_hex(self._new(1).decrypt(ciphertext)) + ct2 = b2a_hex(self._new().encrypt(plaintext)) + pt2 = b2a_hex(self._new(1).decrypt(ciphertext)) + + if hasattr(self.module, "MODE_OPENPGP") and self.mode == self.module.MODE_OPENPGP: + # In PGP mode, data returned by the first encrypt() + # is prefixed with the encrypted IV. + # Here we check it and then remove it from the ciphertexts. + eilen = len(self.encrypted_iv) + self.assertEqual(self.encrypted_iv, ct1[:eilen]) + self.assertEqual(self.encrypted_iv, ct2[:eilen]) + ct1 = ct1[eilen:] + ct2 = ct2[eilen:] + + self.assertEqual(self.ciphertext, ct1) # encrypt + self.assertEqual(self.ciphertext, ct2) # encrypt (second time) + self.assertEqual(self.plaintext, pt1) # decrypt + self.assertEqual(self.plaintext, pt2) # decrypt (second time) + +class CipherStreamingSelfTest(CipherSelfTest): + + def shortDescription(self): + desc = self.module_name + if self.mode is not None: + desc += " in %s mode" % (self.mode_name,) + return "%s should behave like a stream cipher" % (desc,) + + def runTest(self): + plaintext = a2b_hex(self.plaintext) + ciphertext = a2b_hex(self.ciphertext) + + # The cipher should work like a stream cipher + + # Test counter mode encryption, 3 bytes at a time + ct3 = [] + cipher = self._new() + for i in range(0, len(plaintext), 3): + ct3.append(cipher.encrypt(plaintext[i:i+3])) + ct3 = b2a_hex(b("").join(ct3)) + self.assertEqual(self.ciphertext, ct3) # encryption (3 bytes at a time) + + # Test counter mode decryption, 3 bytes at a time + pt3 = [] + cipher = self._new() + for i in range(0, len(ciphertext), 3): + pt3.append(cipher.encrypt(ciphertext[i:i+3])) + # PY3K: This is meant to be text, do not change to bytes (data) + pt3 = b2a_hex(b("").join(pt3)) + self.assertEqual(self.plaintext, pt3) # decryption (3 bytes at a time) + +class CTRSegfaultTest(unittest.TestCase): + + def __init__(self, module, params): + unittest.TestCase.__init__(self) + self.module = module + self.key = b(params['key']) + self.module_name = params.get('module_name', None) + + def shortDescription(self): + return """Regression test: %s.new(key, %s.MODE_CTR) should raise TypeError, not segfault""" % (self.module_name, self.module_name) + + def runTest(self): + self.assertRaises(TypeError, self.module.new, a2b_hex(self.key), self.module.MODE_CTR) + +class CTRWraparoundTest(unittest.TestCase): + + def __init__(self, module, params): + unittest.TestCase.__init__(self) + self.module = module + self.key = b(params['key']) + self.module_name = params.get('module_name', None) + + def shortDescription(self): + return """Regression test: %s with MODE_CTR should raise OverflowError on wraparound when shortcut used""" % (self.module_name,) + + def runTest(self): + from Crypto.Util import Counter + + for disable_shortcut in (0, 1): # (False, True) Test CTR-mode shortcut and PyObject_CallObject code paths + for little_endian in (0, 1): # (False, True) Test both endiannesses + ctr = Counter.new(8*self.module.block_size, initial_value=2L**(8*self.module.block_size)-1, little_endian=little_endian, disable_shortcut=disable_shortcut) + cipher = self.module.new(a2b_hex(self.key), self.module.MODE_CTR, counter=ctr) + block = b("\x00") * self.module.block_size + cipher.encrypt(block) + self.assertRaises(OverflowError, cipher.encrypt, block) + +class CFBSegmentSizeTest(unittest.TestCase): + + def __init__(self, module, params): + unittest.TestCase.__init__(self) + self.module = module + self.key = b(params['key']) + self.description = params['description'] + + def shortDescription(self): + return self.description + + def runTest(self): + """Regression test: m.new(key, m.MODE_CFB, segment_size=N) should require segment_size to be a multiple of 8 bits""" + for i in range(1, 8): + self.assertRaises(ValueError, self.module.new, a2b_hex(self.key), self.module.MODE_CFB, segment_size=i) + self.module.new(a2b_hex(self.key), self.module.MODE_CFB, "\0"*self.module.block_size, segment_size=8) # should succeed + +class RoundtripTest(unittest.TestCase): + def __init__(self, module, params): + from Crypto import Random + unittest.TestCase.__init__(self) + self.module = module + self.iv = Random.get_random_bytes(module.block_size) + self.key = b(params['key']) + self.plaintext = 100 * b(params['plaintext']) + self.module_name = params.get('module_name', None) + + def shortDescription(self): + return """%s .decrypt() output of .encrypt() should not be garbled""" % (self.module_name,) + + def runTest(self): + for mode in (self.module.MODE_ECB, self.module.MODE_CBC, self.module.MODE_CFB, self.module.MODE_OFB, self.module.MODE_OPENPGP): + encryption_cipher = self.module.new(a2b_hex(self.key), mode, self.iv) + ciphertext = encryption_cipher.encrypt(self.plaintext) + + if mode != self.module.MODE_OPENPGP: + decryption_cipher = self.module.new(a2b_hex(self.key), mode, self.iv) + else: + eiv = ciphertext[:self.module.block_size+2] + ciphertext = ciphertext[self.module.block_size+2:] + decryption_cipher = self.module.new(a2b_hex(self.key), mode, eiv) + decrypted_plaintext = decryption_cipher.decrypt(ciphertext) + self.assertEqual(self.plaintext, decrypted_plaintext) + +class PGPTest(unittest.TestCase): + def __init__(self, module, params): + unittest.TestCase.__init__(self) + self.module = module + self.key = b(params['key']) + + def shortDescription(self): + return "MODE_PGP was implemented incorrectly and insecurely. It's completely banished now." + + def runTest(self): + self.assertRaises(ValueError, self.module.new, a2b_hex(self.key), + self.module.MODE_PGP) + +class IVLengthTest(unittest.TestCase): + def __init__(self, module, params): + unittest.TestCase.__init__(self) + self.module = module + self.key = b(params['key']) + + def shortDescription(self): + return "Check that all modes except MODE_ECB and MODE_CTR require an IV of the proper length" + + def runTest(self): + self.assertRaises(ValueError, self.module.new, a2b_hex(self.key), + self.module.MODE_CBC, "") + self.assertRaises(ValueError, self.module.new, a2b_hex(self.key), + self.module.MODE_CFB, "") + self.assertRaises(ValueError, self.module.new, a2b_hex(self.key), + self.module.MODE_OFB, "") + self.assertRaises(ValueError, self.module.new, a2b_hex(self.key), + self.module.MODE_OPENPGP, "") + self.module.new(a2b_hex(self.key), self.module.MODE_ECB, "") + self.module.new(a2b_hex(self.key), self.module.MODE_CTR, "", counter=self._dummy_counter) + + def _dummy_counter(self): + return "\0" * self.module.block_size + +def make_block_tests(module, module_name, test_data): + tests = [] + extra_tests_added = 0 + for i in range(len(test_data)): + row = test_data[i] + + # Build the "params" dictionary + params = {'mode': 'ECB'} + if len(row) == 3: + (params['plaintext'], params['ciphertext'], params['key']) = row + elif len(row) == 4: + (params['plaintext'], params['ciphertext'], params['key'], params['description']) = row + elif len(row) == 5: + (params['plaintext'], params['ciphertext'], params['key'], params['description'], extra_params) = row + params.update(extra_params) + else: + raise AssertionError("Unsupported tuple size %d" % (len(row),)) + + # Build the display-name for the test + p2 = params.copy() + p_key = _extract(p2, 'key') + p_plaintext = _extract(p2, 'plaintext') + p_ciphertext = _extract(p2, 'ciphertext') + p_description = _extract(p2, 'description', None) + p_mode = p2.get('mode', 'ECB') + if p_mode == 'ECB': + _extract(p2, 'mode', 'ECB') + + if p_description is not None: + description = p_description + elif p_mode == 'ECB' and not p2: + description = "p=%s, k=%s" % (p_plaintext, p_key) + else: + description = "p=%s, k=%s, %r" % (p_plaintext, p_key, p2) + name = "%s #%d: %s" % (module_name, i+1, description) + params['description'] = name + params['module_name'] = module_name + + # Add extra test(s) to the test suite before the current test + if not extra_tests_added: + tests += [ + CTRSegfaultTest(module, params), + CTRWraparoundTest(module, params), + CFBSegmentSizeTest(module, params), + RoundtripTest(module, params), + PGPTest(module, params), + IVLengthTest(module, params), + ] + extra_tests_added = 1 + + # Add the current test to the test suite + tests.append(CipherSelfTest(module, params)) + + # When using CTR mode, test that the interface behaves like a stream cipher + if p_mode == 'CTR': + tests.append(CipherStreamingSelfTest(module, params)) + + # When using CTR mode, test the non-shortcut code path. + if p_mode == 'CTR' and not params.has_key('ctr_class'): + params2 = params.copy() + params2['description'] += " (shortcut disabled)" + ctr_params2 = params.get('ctr_params', {}).copy() + params2['ctr_params'] = ctr_params2 + if not params2['ctr_params'].has_key('disable_shortcut'): + params2['ctr_params']['disable_shortcut'] = 1 + tests.append(CipherSelfTest(module, params2)) + return tests + +def make_stream_tests(module, module_name, test_data): + tests = [] + for i in range(len(test_data)): + row = test_data[i] + + # Build the "params" dictionary + params = {} + if len(row) == 3: + (params['plaintext'], params['ciphertext'], params['key']) = row + elif len(row) == 4: + (params['plaintext'], params['ciphertext'], params['key'], params['description']) = row + elif len(row) == 5: + (params['plaintext'], params['ciphertext'], params['key'], params['description'], extra_params) = row + params.update(extra_params) + else: + raise AssertionError("Unsupported tuple size %d" % (len(row),)) + + # Build the display-name for the test + p2 = params.copy() + p_key = _extract(p2, 'key') + p_plaintext = _extract(p2, 'plaintext') + p_ciphertext = _extract(p2, 'ciphertext') + p_description = _extract(p2, 'description', None) + + if p_description is not None: + description = p_description + elif not p2: + description = "p=%s, k=%s" % (p_plaintext, p_key) + else: + description = "p=%s, k=%s, %r" % (p_plaintext, p_key, p2) + name = "%s #%d: %s" % (module_name, i+1, description) + params['description'] = name + params['module_name'] = module_name + + # Add the test to the test suite + tests.append(CipherSelfTest(module, params)) + tests.append(CipherStreamingSelfTest(module, params)) + return tests + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Cipher/test_AES.py b/lib/Crypto/SelfTest/Cipher/test_AES.py new file mode 100644 index 0000000..ea7c323 --- /dev/null +++ b/lib/Crypto/SelfTest/Cipher/test_AES.py @@ -0,0 +1,1433 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Cipher/AES.py: Self-test for the AES cipher +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Cipher.AES""" + +__revision__ = "$Id$" + +from common import dict # For compatibility with Python 2.1 and 2.2 +from Crypto.Util.py3compat import * +from binascii import hexlify + +# This is a list of (plaintext, ciphertext, key[, description[, params]]) tuples. +test_data = [ + # FIPS PUB 197 test vectors + # http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf + + ('00112233445566778899aabbccddeeff', '69c4e0d86a7b0430d8cdb78070b4c55a', + '000102030405060708090a0b0c0d0e0f', 'FIPS 197 C.1 (AES-128)'), + + ('00112233445566778899aabbccddeeff', 'dda97ca4864cdfe06eaf70a0ec0d7191', + '000102030405060708090a0b0c0d0e0f1011121314151617', + 'FIPS 197 C.2 (AES-192)'), + + ('00112233445566778899aabbccddeeff', '8ea2b7ca516745bfeafc49904b496089', + '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', + 'FIPS 197 C.3 (AES-256)'), + + # Rijndael128 test vectors + # Downloaded 2008-09-13 from + # http://www.iaik.tugraz.at/Research/krypto/AES/old/~rijmen/rijndael/testvalues.tar.gz + + # ecb_tbl.txt, KEYSIZE=128 + ('506812a45f08c889b97f5980038b8359', 'd8f532538289ef7d06b506a4fd5be9c9', + '00010203050607080a0b0c0d0f101112', + 'ecb-tbl-128: I=1'), + ('5c6d71ca30de8b8b00549984d2ec7d4b', '59ab30f4d4ee6e4ff9907ef65b1fb68c', + '14151617191a1b1c1e1f202123242526', + 'ecb-tbl-128: I=2'), + ('53f3f4c64f8616e4e7c56199f48f21f6', 'bf1ed2fcb2af3fd41443b56d85025cb1', + '28292a2b2d2e2f30323334353738393a', + 'ecb-tbl-128: I=3'), + ('a1eb65a3487165fb0f1c27ff9959f703', '7316632d5c32233edcb0780560eae8b2', + '3c3d3e3f41424344464748494b4c4d4e', + 'ecb-tbl-128: I=4'), + ('3553ecf0b1739558b08e350a98a39bfa', '408c073e3e2538072b72625e68b8364b', + '50515253555657585a5b5c5d5f606162', + 'ecb-tbl-128: I=5'), + ('67429969490b9711ae2b01dc497afde8', 'e1f94dfa776597beaca262f2f6366fea', + '64656667696a6b6c6e6f707173747576', + 'ecb-tbl-128: I=6'), + ('93385c1f2aec8bed192f5a8e161dd508', 'f29e986c6a1c27d7b29ffd7ee92b75f1', + '78797a7b7d7e7f80828384858788898a', + 'ecb-tbl-128: I=7'), + ('b5bf946be19beb8db3983b5f4c6e8ddb', '131c886a57f8c2e713aba6955e2b55b5', + '8c8d8e8f91929394969798999b9c9d9e', + 'ecb-tbl-128: I=8'), + ('41321ee10e21bd907227c4450ff42324', 'd2ab7662df9b8c740210e5eeb61c199d', + 'a0a1a2a3a5a6a7a8aaabacadafb0b1b2', + 'ecb-tbl-128: I=9'), + ('00a82f59c91c8486d12c0a80124f6089', '14c10554b2859c484cab5869bbe7c470', + 'b4b5b6b7b9babbbcbebfc0c1c3c4c5c6', + 'ecb-tbl-128: I=10'), + ('7ce0fd076754691b4bbd9faf8a1372fe', 'db4d498f0a49cf55445d502c1f9ab3b5', + 'c8c9cacbcdcecfd0d2d3d4d5d7d8d9da', + 'ecb-tbl-128: I=11'), + ('23605a8243d07764541bc5ad355b3129', '6d96fef7d66590a77a77bb2056667f7f', + 'dcdddedfe1e2e3e4e6e7e8e9ebecedee', + 'ecb-tbl-128: I=12'), + ('12a8cfa23ea764fd876232b4e842bc44', '316fb68edba736c53e78477bf913725c', + 'f0f1f2f3f5f6f7f8fafbfcfdfe010002', + 'ecb-tbl-128: I=13'), + ('bcaf32415e8308b3723e5fdd853ccc80', '6936f2b93af8397fd3a771fc011c8c37', + '04050607090a0b0c0e0f101113141516', + 'ecb-tbl-128: I=14'), + ('89afae685d801ad747ace91fc49adde0', 'f3f92f7a9c59179c1fcc2c2ba0b082cd', + '2c2d2e2f31323334363738393b3c3d3e', + 'ecb-tbl-128: I=15'), + ('f521d07b484357c4a69e76124a634216', '6a95ea659ee3889158e7a9152ff04ebc', + '40414243454647484a4b4c4d4f505152', + 'ecb-tbl-128: I=16'), + ('3e23b3bc065bcc152407e23896d77783', '1959338344e945670678a5d432c90b93', + '54555657595a5b5c5e5f606163646566', + 'ecb-tbl-128: I=17'), + ('79f0fba002be1744670e7e99290d8f52', 'e49bddd2369b83ee66e6c75a1161b394', + '68696a6b6d6e6f70727374757778797a', + 'ecb-tbl-128: I=18'), + ('da23fe9d5bd63e1d72e3dafbe21a6c2a', 'd3388f19057ff704b70784164a74867d', + '7c7d7e7f81828384868788898b8c8d8e', + 'ecb-tbl-128: I=19'), + ('e3f5698ba90b6a022efd7db2c7e6c823', '23aa03e2d5e4cd24f3217e596480d1e1', + 'a4a5a6a7a9aaabacaeafb0b1b3b4b5b6', + 'ecb-tbl-128: I=20'), + ('bdc2691d4f1b73d2700679c3bcbf9c6e', 'c84113d68b666ab2a50a8bdb222e91b9', + 'e0e1e2e3e5e6e7e8eaebecedeff0f1f2', + 'ecb-tbl-128: I=21'), + ('ba74e02093217ee1ba1b42bd5624349a', 'ac02403981cd4340b507963db65cb7b6', + '08090a0b0d0e0f10121314151718191a', + 'ecb-tbl-128: I=22'), + ('b5c593b5851c57fbf8b3f57715e8f680', '8d1299236223359474011f6bf5088414', + '6c6d6e6f71727374767778797b7c7d7e', + 'ecb-tbl-128: I=23'), + ('3da9bd9cec072381788f9387c3bbf4ee', '5a1d6ab8605505f7977e55b9a54d9b90', + '80818283858687888a8b8c8d8f909192', + 'ecb-tbl-128: I=24'), + ('4197f3051121702ab65d316b3c637374', '72e9c2d519cf555e4208805aabe3b258', + '94959697999a9b9c9e9fa0a1a3a4a5a6', + 'ecb-tbl-128: I=25'), + ('9f46c62ec4f6ee3f6e8c62554bc48ab7', 'a8f3e81c4a23a39ef4d745dffe026e80', + 'a8a9aaabadaeafb0b2b3b4b5b7b8b9ba', + 'ecb-tbl-128: I=26'), + ('0220673fe9e699a4ebc8e0dbeb6979c8', '546f646449d31458f9eb4ef5483aee6c', + 'bcbdbebfc1c2c3c4c6c7c8c9cbcccdce', + 'ecb-tbl-128: I=27'), + ('b2b99171337ded9bc8c2c23ff6f18867', '4dbe4bc84ac797c0ee4efb7f1a07401c', + 'd0d1d2d3d5d6d7d8dadbdcdddfe0e1e2', + 'ecb-tbl-128: I=28'), + ('a7facf4e301e984e5efeefd645b23505', '25e10bfb411bbd4d625ac8795c8ca3b3', + 'e4e5e6e7e9eaebeceeeff0f1f3f4f5f6', + 'ecb-tbl-128: I=29'), + ('f7c762e4a9819160fd7acfb6c4eedcdd', '315637405054ec803614e43def177579', + 'f8f9fafbfdfefe00020304050708090a', + 'ecb-tbl-128: I=30'), + ('9b64fc21ea08709f4915436faa70f1be', '60c5bc8a1410247295c6386c59e572a8', + '0c0d0e0f11121314161718191b1c1d1e', + 'ecb-tbl-128: I=31'), + ('52af2c3de07ee6777f55a4abfc100b3f', '01366fc8ca52dfe055d6a00a76471ba6', + '20212223252627282a2b2c2d2f303132', + 'ecb-tbl-128: I=32'), + ('2fca001224386c57aa3f968cbe2c816f', 'ecc46595516ec612449c3f581e7d42ff', + '34353637393a3b3c3e3f404143444546', + 'ecb-tbl-128: I=33'), + ('4149c73658a4a9c564342755ee2c132f', '6b7ffe4c602a154b06ee9c7dab5331c9', + '48494a4b4d4e4f50525354555758595a', + 'ecb-tbl-128: I=34'), + ('af60005a00a1772f7c07a48a923c23d2', '7da234c14039a240dd02dd0fbf84eb67', + '5c5d5e5f61626364666768696b6c6d6e', + 'ecb-tbl-128: I=35'), + ('6fccbc28363759914b6f0280afaf20c6', 'c7dc217d9e3604ffe7e91f080ecd5a3a', + '70717273757677787a7b7c7d7f808182', + 'ecb-tbl-128: I=36'), + ('7d82a43ddf4fefa2fc5947499884d386', '37785901863f5c81260ea41e7580cda5', + '84858687898a8b8c8e8f909193949596', + 'ecb-tbl-128: I=37'), + ('5d5a990eaab9093afe4ce254dfa49ef9', 'a07b9338e92ed105e6ad720fccce9fe4', + '98999a9b9d9e9fa0a2a3a4a5a7a8a9aa', + 'ecb-tbl-128: I=38'), + ('4cd1e2fd3f4434b553aae453f0ed1a02', 'ae0fb9722418cc21a7da816bbc61322c', + 'acadaeafb1b2b3b4b6b7b8b9bbbcbdbe', + 'ecb-tbl-128: I=39'), + ('5a2c9a9641d4299125fa1b9363104b5e', 'c826a193080ff91ffb21f71d3373c877', + 'c0c1c2c3c5c6c7c8cacbcccdcfd0d1d2', + 'ecb-tbl-128: I=40'), + ('b517fe34c0fa217d341740bfd4fe8dd4', '1181b11b0e494e8d8b0aa6b1d5ac2c48', + 'd4d5d6d7d9dadbdcdedfe0e1e3e4e5e6', + 'ecb-tbl-128: I=41'), + ('014baf2278a69d331d5180103643e99a', '6743c3d1519ab4f2cd9a78ab09a511bd', + 'e8e9eaebedeeeff0f2f3f4f5f7f8f9fa', + 'ecb-tbl-128: I=42'), + ('b529bd8164f20d0aa443d4932116841c', 'dc55c076d52bacdf2eefd952946a439d', + 'fcfdfeff01020304060708090b0c0d0e', + 'ecb-tbl-128: I=43'), + ('2e596dcbb2f33d4216a1176d5bd1e456', '711b17b590ffc72b5c8e342b601e8003', + '10111213151617181a1b1c1d1f202122', + 'ecb-tbl-128: I=44'), + ('7274a1ea2b7ee2424e9a0e4673689143', '19983bb0950783a537e1339f4aa21c75', + '24252627292a2b2c2e2f303133343536', + 'ecb-tbl-128: I=45'), + ('ae20020bd4f13e9d90140bee3b5d26af', '3ba7762e15554169c0f4fa39164c410c', + '38393a3b3d3e3f40424344454748494a', + 'ecb-tbl-128: I=46'), + ('baac065da7ac26e855e79c8849d75a02', 'a0564c41245afca7af8aa2e0e588ea89', + '4c4d4e4f51525354565758595b5c5d5e', + 'ecb-tbl-128: I=47'), + ('7c917d8d1d45fab9e2540e28832540cc', '5e36a42a2e099f54ae85ecd92e2381ed', + '60616263656667686a6b6c6d6f707172', + 'ecb-tbl-128: I=48'), + ('bde6f89e16daadb0e847a2a614566a91', '770036f878cd0f6ca2268172f106f2fe', + '74757677797a7b7c7e7f808183848586', + 'ecb-tbl-128: I=49'), + ('c9de163725f1f5be44ebb1db51d07fbc', '7e4e03908b716116443ccf7c94e7c259', + '88898a8b8d8e8f90929394959798999a', + 'ecb-tbl-128: I=50'), + ('3af57a58f0c07dffa669572b521e2b92', '482735a48c30613a242dd494c7f9185d', + '9c9d9e9fa1a2a3a4a6a7a8a9abacadae', + 'ecb-tbl-128: I=51'), + ('3d5ebac306dde4604f1b4fbbbfcdae55', 'b4c0f6c9d4d7079addf9369fc081061d', + 'b0b1b2b3b5b6b7b8babbbcbdbfc0c1c2', + 'ecb-tbl-128: I=52'), + ('c2dfa91bceb76a1183c995020ac0b556', 'd5810fe0509ac53edcd74f89962e6270', + 'c4c5c6c7c9cacbcccecfd0d1d3d4d5d6', + 'ecb-tbl-128: I=53'), + ('c70f54305885e9a0746d01ec56c8596b', '03f17a16b3f91848269ecdd38ebb2165', + 'd8d9dadbdddedfe0e2e3e4e5e7e8e9ea', + 'ecb-tbl-128: I=54'), + ('c4f81b610e98012ce000182050c0c2b2', 'da1248c3180348bad4a93b4d9856c9df', + 'ecedeeeff1f2f3f4f6f7f8f9fbfcfdfe', + 'ecb-tbl-128: I=55'), + ('eaab86b1d02a95d7404eff67489f97d4', '3d10d7b63f3452c06cdf6cce18be0c2c', + '00010203050607080a0b0c0d0f101112', + 'ecb-tbl-128: I=56'), + ('7c55bdb40b88870b52bec3738de82886', '4ab823e7477dfddc0e6789018fcb6258', + '14151617191a1b1c1e1f202123242526', + 'ecb-tbl-128: I=57'), + ('ba6eaa88371ff0a3bd875e3f2a975ce0', 'e6478ba56a77e70cfdaa5c843abde30e', + '28292a2b2d2e2f30323334353738393a', + 'ecb-tbl-128: I=58'), + ('08059130c4c24bd30cf0575e4e0373dc', '1673064895fbeaf7f09c5429ff75772d', + '3c3d3e3f41424344464748494b4c4d4e', + 'ecb-tbl-128: I=59'), + ('9a8eab004ef53093dfcf96f57e7eda82', '4488033ae9f2efd0ca9383bfca1a94e9', + '50515253555657585a5b5c5d5f606162', + 'ecb-tbl-128: I=60'), + ('0745b589e2400c25f117b1d796c28129', '978f3b8c8f9d6f46626cac3c0bcb9217', + '64656667696a6b6c6e6f707173747576', + 'ecb-tbl-128: I=61'), + ('2f1777781216cec3f044f134b1b92bbe', 'e08c8a7e582e15e5527f1d9e2eecb236', + '78797a7b7d7e7f80828384858788898a', + 'ecb-tbl-128: I=62'), + ('353a779ffc541b3a3805d90ce17580fc', 'cec155b76ac5ffda4cf4f9ca91e49a7a', + '8c8d8e8f91929394969798999b9c9d9e', + 'ecb-tbl-128: I=63'), + ('1a1eae4415cefcf08c4ac1c8f68bea8f', 'd5ac7165763225dd2a38cdc6862c29ad', + 'a0a1a2a3a5a6a7a8aaabacadafb0b1b2', + 'ecb-tbl-128: I=64'), + ('e6e7e4e5b0b3b2b5d4d5aaab16111013', '03680fe19f7ce7275452020be70e8204', + 'b4b5b6b7b9babbbcbebfc0c1c3c4c5c6', + 'ecb-tbl-128: I=65'), + ('f8f9fafbfbf8f9e677767170efe0e1e2', '461df740c9781c388e94bb861ceb54f6', + 'c8c9cacbcdcecfd0d2d3d4d5d7d8d9da', + 'ecb-tbl-128: I=66'), + ('63626160a1a2a3a445444b4a75727370', '451bd60367f96483042742219786a074', + 'dcdddedfe1e2e3e4e6e7e8e9ebecedee', + 'ecb-tbl-128: I=67'), + ('717073720605040b2d2c2b2a05fafbf9', 'e4dfa42671a02e57ef173b85c0ea9f2b', + 'f0f1f2f3f5f6f7f8fafbfcfdfe010002', + 'ecb-tbl-128: I=68'), + ('78797a7beae9e8ef3736292891969794', 'ed11b89e76274282227d854700a78b9e', + '04050607090a0b0c0e0f101113141516', + 'ecb-tbl-128: I=69'), + ('838281803231300fdddcdbdaa0afaead', '433946eaa51ea47af33895f2b90b3b75', + '18191a1b1d1e1f20222324252728292a', + 'ecb-tbl-128: I=70'), + ('18191a1bbfbcbdba75747b7a7f78797a', '6bc6d616a5d7d0284a5910ab35022528', + '2c2d2e2f31323334363738393b3c3d3e', + 'ecb-tbl-128: I=71'), + ('848586879b989996a3a2a5a4849b9a99', 'd2a920ecfe919d354b5f49eae9719c98', + '40414243454647484a4b4c4d4f505152', + 'ecb-tbl-128: I=72'), + ('0001020322212027cacbf4f551565754', '3a061b17f6a92885efbd0676985b373d', + '54555657595a5b5c5e5f606163646566', + 'ecb-tbl-128: I=73'), + ('cecfcccdafacadb2515057564a454447', 'fadeec16e33ea2f4688499d157e20d8f', + '68696a6b6d6e6f70727374757778797a', + 'ecb-tbl-128: I=74'), + ('92939091cdcecfc813121d1c80878685', '5cdefede59601aa3c3cda36fa6b1fa13', + '7c7d7e7f81828384868788898b8c8d8e', + 'ecb-tbl-128: I=75'), + ('d2d3d0d16f6c6d6259585f5ed1eeefec', '9574b00039844d92ebba7ee8719265f8', + '90919293959697989a9b9c9d9fa0a1a2', + 'ecb-tbl-128: I=76'), + ('acadaeaf878485820f0e1110d5d2d3d0', '9a9cf33758671787e5006928188643fa', + 'a4a5a6a7a9aaabacaeafb0b1b3b4b5b6', + 'ecb-tbl-128: I=77'), + ('9091929364676619e6e7e0e1757a7b78', '2cddd634c846ba66bb46cbfea4a674f9', + 'b8b9babbbdbebfc0c2c3c4c5c7c8c9ca', + 'ecb-tbl-128: I=78'), + ('babbb8b98a89888f74757a7b92959497', 'd28bae029393c3e7e26e9fafbbb4b98f', + 'cccdcecfd1d2d3d4d6d7d8d9dbdcddde', + 'ecb-tbl-128: I=79'), + ('8d8c8f8e6e6d6c633b3a3d3ccad5d4d7', 'ec27529b1bee0a9ab6a0d73ebc82e9b7', + 'e0e1e2e3e5e6e7e8eaebecedeff0f1f2', + 'ecb-tbl-128: I=80'), + ('86878485010203040808f7f767606162', '3cb25c09472aff6ee7e2b47ccd7ccb17', + 'f4f5f6f7f9fafbfcfefe010103040506', + 'ecb-tbl-128: I=81'), + ('8e8f8c8d656667788a8b8c8d010e0f0c', 'dee33103a7283370d725e44ca38f8fe5', + '08090a0b0d0e0f10121314151718191a', + 'ecb-tbl-128: I=82'), + ('c8c9cacb858687807a7b7475e7e0e1e2', '27f9bcd1aac64bffc11e7815702c1a69', + '1c1d1e1f21222324262728292b2c2d2e', + 'ecb-tbl-128: I=83'), + ('6d6c6f6e5053525d8c8d8a8badd2d3d0', '5df534ffad4ed0749a9988e9849d0021', + '30313233353637383a3b3c3d3f404142', + 'ecb-tbl-128: I=84'), + ('28292a2b393a3b3c0607181903040506', 'a48bee75db04fb60ca2b80f752a8421b', + '44454647494a4b4c4e4f505153545556', + 'ecb-tbl-128: I=85'), + ('a5a4a7a6b0b3b28ddbdadddcbdb2b3b0', '024c8cf70bc86ee5ce03678cb7af45f9', + '58595a5b5d5e5f60626364656768696a', + 'ecb-tbl-128: I=86'), + ('323330316467666130313e3f2c2b2a29', '3c19ac0f8a3a3862ce577831301e166b', + '6c6d6e6f71727374767778797b7c7d7e', + 'ecb-tbl-128: I=87'), + ('27262524080b0a05171611100b141516', 'c5e355b796a57421d59ca6be82e73bca', + '80818283858687888a8b8c8d8f909192', + 'ecb-tbl-128: I=88'), + ('040506074142434435340b0aa3a4a5a6', 'd94033276417abfb05a69d15b6e386e2', + '94959697999a9b9c9e9fa0a1a3a4a5a6', + 'ecb-tbl-128: I=89'), + ('242526271112130c61606766bdb2b3b0', '24b36559ea3a9b9b958fe6da3e5b8d85', + 'a8a9aaabadaeafb0b2b3b4b5b7b8b9ba', + 'ecb-tbl-128: I=90'), + ('4b4a4948252627209e9f9091cec9c8cb', '20fd4feaa0e8bf0cce7861d74ef4cb72', + 'bcbdbebfc1c2c3c4c6c7c8c9cbcccdce', + 'ecb-tbl-128: I=91'), + ('68696a6b6665646b9f9e9998d9e6e7e4', '350e20d5174277b9ec314c501570a11d', + 'd0d1d2d3d5d6d7d8dadbdcdddfe0e1e2', + 'ecb-tbl-128: I=92'), + ('34353637c5c6c7c0f0f1eeef7c7b7a79', '87a29d61b7c604d238fe73045a7efd57', + 'e4e5e6e7e9eaebeceeeff0f1f3f4f5f6', + 'ecb-tbl-128: I=93'), + ('32333031c2c1c13f0d0c0b0a050a0b08', '2c3164c1cc7d0064816bdc0faa362c52', + 'f8f9fafbfdfefe00020304050708090a', + 'ecb-tbl-128: I=94'), + ('cdcccfcebebdbcbbabaaa5a4181f1e1d', '195fe5e8a05a2ed594f6e4400eee10b3', + '0c0d0e0f11121314161718191b1c1d1e', + 'ecb-tbl-128: I=95'), + ('212023223635343ba0a1a6a7445b5a59', 'e4663df19b9a21a5a284c2bd7f905025', + '20212223252627282a2b2c2d2f303132', + 'ecb-tbl-128: I=96'), + ('0e0f0c0da8abaaad2f2e515002050407', '21b88714cfb4e2a933bd281a2c4743fd', + '34353637393a3b3c3e3f404143444546', + 'ecb-tbl-128: I=97'), + ('070605042a2928378e8f8889bdb2b3b0', 'cbfc3980d704fd0fc54378ab84e17870', + '48494a4b4d4e4f50525354555758595a', + 'ecb-tbl-128: I=98'), + ('cbcac9c893909196a9a8a7a6a5a2a3a0', 'bc5144baa48bdeb8b63e22e03da418ef', + '5c5d5e5f61626364666768696b6c6d6e', + 'ecb-tbl-128: I=99'), + ('80818283c1c2c3cc9c9d9a9b0cf3f2f1', '5a1dbaef1ee2984b8395da3bdffa3ccc', + '70717273757677787a7b7c7d7f808182', + 'ecb-tbl-128: I=100'), + ('1213101125262720fafbe4e5b1b6b7b4', 'f0b11cd0729dfcc80cec903d97159574', + '84858687898a8b8c8e8f909193949596', + 'ecb-tbl-128: I=101'), + ('7f7e7d7c3033320d97969190222d2c2f', '9f95314acfddc6d1914b7f19a9cc8209', + '98999a9b9d9e9fa0a2a3a4a5a7a8a9aa', + 'ecb-tbl-128: I=102'), + ('4e4f4c4d484b4a4d81808f8e53545556', '595736f6f0f70914a94e9e007f022519', + 'acadaeafb1b2b3b4b6b7b8b9bbbcbdbe', + 'ecb-tbl-128: I=103'), + ('dcdddedfb0b3b2bd15141312a1bebfbc', '1f19f57892cae586fcdfb4c694deb183', + 'c0c1c2c3c5c6c7c8cacbcccdcfd0d1d2', + 'ecb-tbl-128: I=104'), + ('93929190282b2a2dc4c5fafb92959497', '540700ee1f6f3dab0b3eddf6caee1ef5', + 'd4d5d6d7d9dadbdcdedfe0e1e3e4e5e6', + 'ecb-tbl-128: I=105'), + ('f5f4f7f6c4c7c6d9373631307e717073', '14a342a91019a331687a2254e6626ca2', + 'e8e9eaebedeeeff0f2f3f4f5f7f8f9fa', + 'ecb-tbl-128: I=106'), + ('93929190b6b5b4b364656a6b05020300', '7b25f3c3b2eea18d743ef283140f29ff', + 'fcfdfeff01020304060708090b0c0d0e', + 'ecb-tbl-128: I=107'), + ('babbb8b90d0e0f00a4a5a2a3043b3a39', '46c2587d66e5e6fa7f7ca6411ad28047', + '10111213151617181a1b1c1d1f202122', + 'ecb-tbl-128: I=108'), + ('d8d9dadb7f7c7d7a10110e0f787f7e7d', '09470e72229d954ed5ee73886dfeeba9', + '24252627292a2b2c2e2f303133343536', + 'ecb-tbl-128: I=109'), + ('fefffcfdefeced923b3a3d3c6768696a', 'd77c03de92d4d0d79ef8d4824ef365eb', + '38393a3b3d3e3f40424344454748494a', + 'ecb-tbl-128: I=110'), + ('d6d7d4d58a89888f96979899a5a2a3a0', '1d190219f290e0f1715d152d41a23593', + '4c4d4e4f51525354565758595b5c5d5e', + 'ecb-tbl-128: I=111'), + ('18191a1ba8abaaa5303136379b848586', 'a2cd332ce3a0818769616292e87f757b', + '60616263656667686a6b6c6d6f707172', + 'ecb-tbl-128: I=112'), + ('6b6a6968a4a7a6a1d6d72829b0b7b6b5', 'd54afa6ce60fbf9341a3690e21385102', + '74757677797a7b7c7e7f808183848586', + 'ecb-tbl-128: I=113'), + ('000102038a89889755545352a6a9a8ab', '06e5c364ded628a3f5e05e613e356f46', + '88898a8b8d8e8f90929394959798999a', + 'ecb-tbl-128: I=114'), + ('2d2c2f2eb3b0b1b6b6b7b8b9f2f5f4f7', 'eae63c0e62556dac85d221099896355a', + '9c9d9e9fa1a2a3a4a6a7a8a9abacadae', + 'ecb-tbl-128: I=115'), + ('979695943536373856575051e09f9e9d', '1fed060e2c6fc93ee764403a889985a2', + 'b0b1b2b3b5b6b7b8babbbcbdbfc0c1c2', + 'ecb-tbl-128: I=116'), + ('a4a5a6a7989b9a9db1b0afae7a7d7c7f', 'c25235c1a30fdec1c7cb5c5737b2a588', + 'c4c5c6c7c9cacbcccecfd0d1d3d4d5d6', + 'ecb-tbl-128: I=117'), + ('c1c0c3c2686b6a55a8a9aeafeae5e4e7', '796dbef95147d4d30873ad8b7b92efc0', + 'd8d9dadbdddedfe0e2e3e4e5e7e8e9ea', + 'ecb-tbl-128: I=118'), + ('c1c0c3c2141716118c8d828364636261', 'cbcf0fb34d98d0bd5c22ce37211a46bf', + 'ecedeeeff1f2f3f4f6f7f8f9fbfcfdfe', + 'ecb-tbl-128: I=119'), + ('93929190cccfcec196979091e0fffefd', '94b44da6466126cafa7c7fd09063fc24', + '00010203050607080a0b0c0d0f101112', + 'ecb-tbl-128: I=120'), + ('b4b5b6b7f9fafbfc25241b1a6e69686b', 'd78c5b5ebf9b4dbda6ae506c5074c8fe', + '14151617191a1b1c1e1f202123242526', + 'ecb-tbl-128: I=121'), + ('868784850704051ac7c6c1c08788898a', '6c27444c27204b043812cf8cf95f9769', + '28292a2b2d2e2f30323334353738393a', + 'ecb-tbl-128: I=122'), + ('f4f5f6f7aaa9a8affdfcf3f277707172', 'be94524ee5a2aa50bba8b75f4c0aebcf', + '3c3d3e3f41424344464748494b4c4d4e', + 'ecb-tbl-128: I=123'), + ('d3d2d1d00605040bc3c2c5c43e010003', 'a0aeaae91ba9f31f51aeb3588cf3a39e', + '50515253555657585a5b5c5d5f606162', + 'ecb-tbl-128: I=124'), + ('73727170424140476a6b74750d0a0b08', '275297779c28266ef9fe4c6a13c08488', + '64656667696a6b6c6e6f707173747576', + 'ecb-tbl-128: I=125'), + ('c2c3c0c10a0908f754555253a1aeafac', '86523d92bb8672cb01cf4a77fd725882', + '78797a7b7d7e7f80828384858788898a', + 'ecb-tbl-128: I=126'), + ('6d6c6f6ef8fbfafd82838c8df8fffefd', '4b8327640e9f33322a04dd96fcbf9a36', + '8c8d8e8f91929394969798999b9c9d9e', + 'ecb-tbl-128: I=127'), + ('f5f4f7f684878689a6a7a0a1d2cdcccf', 'ce52af650d088ca559425223f4d32694', + 'a0a1a2a3a5a6a7a8aaabacadafb0b1b2', + 'ecb-tbl-128: I=128'), + + # ecb_tbl.txt, KEYSIZE=192 + ('2d33eef2c0430a8a9ebf45e809c40bb6', 'dff4945e0336df4c1c56bc700eff837f', + '00010203050607080a0b0c0d0f10111214151617191a1b1c', + 'ecb-tbl-192: I=1'), + ('6aa375d1fa155a61fb72353e0a5a8756', 'b6fddef4752765e347d5d2dc196d1252', + '1e1f20212324252628292a2b2d2e2f30323334353738393a', + 'ecb-tbl-192: I=2'), + ('bc3736518b9490dcb8ed60eb26758ed4', 'd23684e3d963b3afcf1a114aca90cbd6', + '3c3d3e3f41424344464748494b4c4d4e5051525355565758', + 'ecb-tbl-192: I=3'), + ('aa214402b46cffb9f761ec11263a311e', '3a7ac027753e2a18c2ceab9e17c11fd0', + '5a5b5c5d5f60616264656667696a6b6c6e6f707173747576', + 'ecb-tbl-192: I=4'), + ('02aea86e572eeab66b2c3af5e9a46fd6', '8f6786bd007528ba26603c1601cdd0d8', + '78797a7b7d7e7f80828384858788898a8c8d8e8f91929394', + 'ecb-tbl-192: I=5'), + ('e2aef6acc33b965c4fa1f91c75ff6f36', 'd17d073b01e71502e28b47ab551168b3', + '969798999b9c9d9ea0a1a2a3a5a6a7a8aaabacadafb0b1b2', + 'ecb-tbl-192: I=6'), + ('0659df46427162b9434865dd9499f91d', 'a469da517119fab95876f41d06d40ffa', + 'b4b5b6b7b9babbbcbebfc0c1c3c4c5c6c8c9cacbcdcecfd0', + 'ecb-tbl-192: I=7'), + ('49a44239c748feb456f59c276a5658df', '6091aa3b695c11f5c0b6ad26d3d862ff', + 'd2d3d4d5d7d8d9dadcdddedfe1e2e3e4e6e7e8e9ebecedee', + 'ecb-tbl-192: I=8'), + ('66208f6e9d04525bdedb2733b6a6be37', '70f9e67f9f8df1294131662dc6e69364', + 'f0f1f2f3f5f6f7f8fafbfcfdfe01000204050607090a0b0c', + 'ecb-tbl-192: I=9'), + ('3393f8dfc729c97f5480b950bc9666b0', 'd154dcafad8b207fa5cbc95e9996b559', + '0e0f10111314151618191a1b1d1e1f20222324252728292a', + 'ecb-tbl-192: I=10'), + ('606834c8ce063f3234cf1145325dbd71', '4934d541e8b46fa339c805a7aeb9e5da', + '2c2d2e2f31323334363738393b3c3d3e4041424345464748', + 'ecb-tbl-192: I=11'), + ('fec1c04f529bbd17d8cecfcc4718b17f', '62564c738f3efe186e1a127a0c4d3c61', + '4a4b4c4d4f50515254555657595a5b5c5e5f606163646566', + 'ecb-tbl-192: I=12'), + ('32df99b431ed5dc5acf8caf6dc6ce475', '07805aa043986eb23693e23bef8f3438', + '68696a6b6d6e6f70727374757778797a7c7d7e7f81828384', + 'ecb-tbl-192: I=13'), + ('7fdc2b746f3f665296943b83710d1f82', 'df0b4931038bade848dee3b4b85aa44b', + '868788898b8c8d8e90919293959697989a9b9c9d9fa0a1a2', + 'ecb-tbl-192: I=14'), + ('8fba1510a3c5b87e2eaa3f7a91455ca2', '592d5fded76582e4143c65099309477c', + 'a4a5a6a7a9aaabacaeafb0b1b3b4b5b6b8b9babbbdbebfc0', + 'ecb-tbl-192: I=15'), + ('2c9b468b1c2eed92578d41b0716b223b', 'c9b8d6545580d3dfbcdd09b954ed4e92', + 'c2c3c4c5c7c8c9cacccdcecfd1d2d3d4d6d7d8d9dbdcddde', + 'ecb-tbl-192: I=16'), + ('0a2bbf0efc6bc0034f8a03433fca1b1a', '5dccd5d6eb7c1b42acb008201df707a0', + 'e0e1e2e3e5e6e7e8eaebecedeff0f1f2f4f5f6f7f9fafbfc', + 'ecb-tbl-192: I=17'), + ('25260e1f31f4104d387222e70632504b', 'a2a91682ffeb6ed1d34340946829e6f9', + 'fefe01010304050608090a0b0d0e0f10121314151718191a', + 'ecb-tbl-192: I=18'), + ('c527d25a49f08a5228d338642ae65137', 'e45d185b797000348d9267960a68435d', + '1c1d1e1f21222324262728292b2c2d2e3031323335363738', + 'ecb-tbl-192: I=19'), + ('3b49fc081432f5890d0e3d87e884a69e', '45e060dae5901cda8089e10d4f4c246b', + '3a3b3c3d3f40414244454647494a4b4c4e4f505153545556', + 'ecb-tbl-192: I=20'), + ('d173f9ed1e57597e166931df2754a083', 'f6951afacc0079a369c71fdcff45df50', + '58595a5b5d5e5f60626364656768696a6c6d6e6f71727374', + 'ecb-tbl-192: I=21'), + ('8c2b7cafa5afe7f13562daeae1adede0', '9e95e00f351d5b3ac3d0e22e626ddad6', + '767778797b7c7d7e80818283858687888a8b8c8d8f909192', + 'ecb-tbl-192: I=22'), + ('aaf4ec8c1a815aeb826cab741339532c', '9cb566ff26d92dad083b51fdc18c173c', + '94959697999a9b9c9e9fa0a1a3a4a5a6a8a9aaabadaeafb0', + 'ecb-tbl-192: I=23'), + ('40be8c5d9108e663f38f1a2395279ecf', 'c9c82766176a9b228eb9a974a010b4fb', + 'd0d1d2d3d5d6d7d8dadbdcdddfe0e1e2e4e5e6e7e9eaebec', + 'ecb-tbl-192: I=24'), + ('0c8ad9bc32d43e04716753aa4cfbe351', 'd8e26aa02945881d5137f1c1e1386e88', + '2a2b2c2d2f30313234353637393a3b3c3e3f404143444546', + 'ecb-tbl-192: I=25'), + ('1407b1d5f87d63357c8dc7ebbaebbfee', 'c0e024ccd68ff5ffa4d139c355a77c55', + '48494a4b4d4e4f50525354555758595a5c5d5e5f61626364', + 'ecb-tbl-192: I=26'), + ('e62734d1ae3378c4549e939e6f123416', '0b18b3d16f491619da338640df391d43', + '84858687898a8b8c8e8f90919394959698999a9b9d9e9fa0', + 'ecb-tbl-192: I=27'), + ('5a752cff2a176db1a1de77f2d2cdee41', 'dbe09ac8f66027bf20cb6e434f252efc', + 'a2a3a4a5a7a8a9aaacadaeafb1b2b3b4b6b7b8b9bbbcbdbe', + 'ecb-tbl-192: I=28'), + ('a9c8c3a4eabedc80c64730ddd018cd88', '6d04e5e43c5b9cbe05feb9606b6480fe', + 'c0c1c2c3c5c6c7c8cacbcccdcfd0d1d2d4d5d6d7d9dadbdc', + 'ecb-tbl-192: I=29'), + ('ee9b3dbbdb86180072130834d305999a', 'dd1d6553b96be526d9fee0fbd7176866', + '1a1b1c1d1f20212224252627292a2b2c2e2f303133343536', + 'ecb-tbl-192: I=30'), + ('a7fa8c3586b8ebde7568ead6f634a879', '0260ca7e3f979fd015b0dd4690e16d2a', + '38393a3b3d3e3f40424344454748494a4c4d4e4f51525354', + 'ecb-tbl-192: I=31'), + ('37e0f4a87f127d45ac936fe7ad88c10a', '9893734de10edcc8a67c3b110b8b8cc6', + '929394959798999a9c9d9e9fa1a2a3a4a6a7a8a9abacadae', + 'ecb-tbl-192: I=32'), + ('3f77d8b5d92bac148e4e46f697a535c5', '93b30b750516b2d18808d710c2ee84ef', + '464748494b4c4d4e50515253555657585a5b5c5d5f606162', + 'ecb-tbl-192: I=33'), + ('d25ebb686c40f7e2c4da1014936571ca', '16f65fa47be3cb5e6dfe7c6c37016c0e', + '828384858788898a8c8d8e8f91929394969798999b9c9d9e', + 'ecb-tbl-192: I=34'), + ('4f1c769d1e5b0552c7eca84dea26a549', 'f3847210d5391e2360608e5acb560581', + 'a0a1a2a3a5a6a7a8aaabacadafb0b1b2b4b5b6b7b9babbbc', + 'ecb-tbl-192: I=35'), + ('8548e2f882d7584d0fafc54372b6633a', '8754462cd223366d0753913e6af2643d', + 'bebfc0c1c3c4c5c6c8c9cacbcdcecfd0d2d3d4d5d7d8d9da', + 'ecb-tbl-192: I=36'), + ('87d7a336cb476f177cd2a51af2a62cdf', '1ea20617468d1b806a1fd58145462017', + 'dcdddedfe1e2e3e4e6e7e8e9ebecedeef0f1f2f3f5f6f7f8', + 'ecb-tbl-192: I=37'), + ('03b1feac668c4e485c1065dfc22b44ee', '3b155d927355d737c6be9dda60136e2e', + 'fafbfcfdfe01000204050607090a0b0c0e0f101113141516', + 'ecb-tbl-192: I=38'), + ('bda15e66819fa72d653a6866aa287962', '26144f7b66daa91b6333dbd3850502b3', + '18191a1b1d1e1f20222324252728292a2c2d2e2f31323334', + 'ecb-tbl-192: I=39'), + ('4d0c7a0d2505b80bf8b62ceb12467f0a', 'e4f9a4ab52ced8134c649bf319ebcc90', + '363738393b3c3d3e40414243454647484a4b4c4d4f505152', + 'ecb-tbl-192: I=40'), + ('626d34c9429b37211330986466b94e5f', 'b9ddd29ac6128a6cab121e34a4c62b36', + '54555657595a5b5c5e5f60616364656668696a6b6d6e6f70', + 'ecb-tbl-192: I=41'), + ('333c3e6bf00656b088a17e5ff0e7f60a', '6fcddad898f2ce4eff51294f5eaaf5c9', + '727374757778797a7c7d7e7f81828384868788898b8c8d8e', + 'ecb-tbl-192: I=42'), + ('687ed0cdc0d2a2bc8c466d05ef9d2891', 'c9a6fe2bf4028080bea6f7fc417bd7e3', + '90919293959697989a9b9c9d9fa0a1a2a4a5a6a7a9aaabac', + 'ecb-tbl-192: I=43'), + ('487830e78cc56c1693e64b2a6660c7b6', '6a2026846d8609d60f298a9c0673127f', + 'aeafb0b1b3b4b5b6b8b9babbbdbebfc0c2c3c4c5c7c8c9ca', + 'ecb-tbl-192: I=44'), + ('7a48d6b7b52b29392aa2072a32b66160', '2cb25c005e26efea44336c4c97a4240b', + 'cccdcecfd1d2d3d4d6d7d8d9dbdcdddee0e1e2e3e5e6e7e8', + 'ecb-tbl-192: I=45'), + ('907320e64c8c5314d10f8d7a11c8618d', '496967ab8680ddd73d09a0e4c7dcc8aa', + 'eaebecedeff0f1f2f4f5f6f7f9fafbfcfefe010103040506', + 'ecb-tbl-192: I=46'), + ('b561f2ca2d6e65a4a98341f3ed9ff533', 'd5af94de93487d1f3a8c577cb84a66a4', + '08090a0b0d0e0f10121314151718191a1c1d1e1f21222324', + 'ecb-tbl-192: I=47'), + ('df769380d212792d026f049e2e3e48ef', '84bdac569cae2828705f267cc8376e90', + '262728292b2c2d2e30313233353637383a3b3c3d3f404142', + 'ecb-tbl-192: I=48'), + ('79f374bc445bdabf8fccb8843d6054c6', 'f7401dda5ad5ab712b7eb5d10c6f99b6', + '44454647494a4b4c4e4f50515354555658595a5b5d5e5f60', + 'ecb-tbl-192: I=49'), + ('4e02f1242fa56b05c68dbae8fe44c9d6', '1c9d54318539ebd4c3b5b7e37bf119f0', + '626364656768696a6c6d6e6f71727374767778797b7c7d7e', + 'ecb-tbl-192: I=50'), + ('cf73c93cbff57ac635a6f4ad2a4a1545', 'aca572d65fb2764cffd4a6eca090ea0d', + '80818283858687888a8b8c8d8f90919294959697999a9b9c', + 'ecb-tbl-192: I=51'), + ('9923548e2875750725b886566784c625', '36d9c627b8c2a886a10ccb36eae3dfbb', + '9e9fa0a1a3a4a5a6a8a9aaabadaeafb0b2b3b4b5b7b8b9ba', + 'ecb-tbl-192: I=52'), + ('4888336b723a022c9545320f836a4207', '010edbf5981e143a81d646e597a4a568', + 'bcbdbebfc1c2c3c4c6c7c8c9cbcccdced0d1d2d3d5d6d7d8', + 'ecb-tbl-192: I=53'), + ('f84d9a5561b0608b1160dee000c41ba8', '8db44d538dc20cc2f40f3067fd298e60', + 'dadbdcdddfe0e1e2e4e5e6e7e9eaebeceeeff0f1f3f4f5f6', + 'ecb-tbl-192: I=54'), + ('c23192a0418e30a19b45ae3e3625bf22', '930eb53bc71e6ac4b82972bdcd5aafb3', + 'f8f9fafbfdfefe00020304050708090a0c0d0e0f11121314', + 'ecb-tbl-192: I=55'), + ('b84e0690b28b0025381ad82a15e501a7', '6c42a81edcbc9517ccd89c30c95597b4', + '161718191b1c1d1e20212223252627282a2b2c2d2f303132', + 'ecb-tbl-192: I=56'), + ('acef5e5c108876c4f06269f865b8f0b0', 'da389847ad06df19d76ee119c71e1dd3', + '34353637393a3b3c3e3f40414344454648494a4b4d4e4f50', + 'ecb-tbl-192: I=57'), + ('0f1b3603e0f5ddea4548246153a5e064', 'e018fdae13d3118f9a5d1a647a3f0462', + '525354555758595a5c5d5e5f61626364666768696b6c6d6e', + 'ecb-tbl-192: I=58'), + ('fbb63893450d42b58c6d88cd3c1809e3', '2aa65db36264239d3846180fabdfad20', + '70717273757677787a7b7c7d7f80818284858687898a8b8c', + 'ecb-tbl-192: I=59'), + ('4bef736df150259dae0c91354e8a5f92', '1472163e9a4f780f1ceb44b07ecf4fdb', + '8e8f90919394959698999a9b9d9e9fa0a2a3a4a5a7a8a9aa', + 'ecb-tbl-192: I=60'), + ('7d2d46242056ef13d3c3fc93c128f4c7', 'c8273fdc8f3a9f72e91097614b62397c', + 'acadaeafb1b2b3b4b6b7b8b9bbbcbdbec0c1c2c3c5c6c7c8', + 'ecb-tbl-192: I=61'), + ('e9c1ba2df415657a256edb33934680fd', '66c8427dcd733aaf7b3470cb7d976e3f', + 'cacbcccdcfd0d1d2d4d5d6d7d9dadbdcdedfe0e1e3e4e5e6', + 'ecb-tbl-192: I=62'), + ('e23ee277b0aa0a1dfb81f7527c3514f1', '146131cb17f1424d4f8da91e6f80c1d0', + 'e8e9eaebedeeeff0f2f3f4f5f7f8f9fafcfdfeff01020304', + 'ecb-tbl-192: I=63'), + ('3e7445b0b63caaf75e4a911e12106b4c', '2610d0ad83659081ae085266a88770dc', + '060708090b0c0d0e10111213151617181a1b1c1d1f202122', + 'ecb-tbl-192: I=64'), + ('767774752023222544455a5be6e1e0e3', '38a2b5a974b0575c5d733917fb0d4570', + '24252627292a2b2c2e2f30313334353638393a3b3d3e3f40', + 'ecb-tbl-192: I=65'), + ('72737475717e7f7ce9e8ebea696a6b6c', 'e21d401ebc60de20d6c486e4f39a588b', + '424344454748494a4c4d4e4f51525354565758595b5c5d5e', + 'ecb-tbl-192: I=66'), + ('dfdedddc25262728c9c8cfcef1eeefec', 'e51d5f88c670b079c0ca1f0c2c4405a2', + '60616263656667686a6b6c6d6f70717274757677797a7b7c', + 'ecb-tbl-192: I=67'), + ('fffe0100707776755f5e5d5c7675746b', '246a94788a642fb3d1b823c8762380c8', + '7e7f80818384858688898a8b8d8e8f90929394959798999a', + 'ecb-tbl-192: I=68'), + ('e0e1e2e3424140479f9e9190292e2f2c', 'b80c391c5c41a4c3b30c68e0e3d7550f', + '9c9d9e9fa1a2a3a4a6a7a8a9abacadaeb0b1b2b3b5b6b7b8', + 'ecb-tbl-192: I=69'), + ('2120272690efeeed3b3a39384e4d4c4b', 'b77c4754fc64eb9a1154a9af0bb1f21c', + 'babbbcbdbfc0c1c2c4c5c6c7c9cacbcccecfd0d1d3d4d5d6', + 'ecb-tbl-192: I=70'), + ('ecedeeef5350516ea1a0a7a6a3acadae', 'fb554de520d159a06bf219fc7f34a02f', + 'd8d9dadbdddedfe0e2e3e4e5e7e8e9eaecedeeeff1f2f3f4', + 'ecb-tbl-192: I=71'), + ('32333c3d25222320e9e8ebeacecdccc3', 'a89fba152d76b4927beed160ddb76c57', + 'f6f7f8f9fbfcfdfe00010203050607080a0b0c0d0f101112', + 'ecb-tbl-192: I=72'), + ('40414243626160678a8bb4b511161714', '5676eab4a98d2e8473b3f3d46424247c', + '14151617191a1b1c1e1f20212324252628292a2b2d2e2f30', + 'ecb-tbl-192: I=73'), + ('94959293f5fafbf81f1e1d1c7c7f7e79', '4e8f068bd7ede52a639036ec86c33568', + '323334353738393a3c3d3e3f41424344464748494b4c4d4e', + 'ecb-tbl-192: I=74'), + ('bebfbcbd191a1b14cfcec9c8546b6a69', 'f0193c4d7aff1791ee4c07eb4a1824fc', + '50515253555657585a5b5c5d5f60616264656667696a6b6c', + 'ecb-tbl-192: I=75'), + ('2c2d3233898e8f8cbbbab9b8333031ce', 'ac8686eeca9ba761afe82d67b928c33f', + '6e6f70717374757678797a7b7d7e7f80828384858788898a', + 'ecb-tbl-192: I=76'), + ('84858687bfbcbdba37363938fdfafbf8', '5faf8573e33b145b6a369cd3606ab2c9', + '8c8d8e8f91929394969798999b9c9d9ea0a1a2a3a5a6a7a8', + 'ecb-tbl-192: I=77'), + ('828384857669686b909192930b08090e', '31587e9944ab1c16b844ecad0df2e7da', + 'aaabacadafb0b1b2b4b5b6b7b9babbbcbebfc0c1c3c4c5c6', + 'ecb-tbl-192: I=78'), + ('bebfbcbd9695948b707176779e919093', 'd017fecd91148aba37f6f3068aa67d8a', + 'c8c9cacbcdcecfd0d2d3d4d5d7d8d9dadcdddedfe1e2e3e4', + 'ecb-tbl-192: I=79'), + ('8b8a85846067666521202322d0d3d2dd', '788ef2f021a73cba2794b616078a8500', + 'e6e7e8e9ebecedeef0f1f2f3f5f6f7f8fafbfcfdfe010002', + 'ecb-tbl-192: I=80'), + ('76777475f1f2f3f4f8f9e6e777707172', '5d1ef20dced6bcbc12131ac7c54788aa', + '04050607090a0b0c0e0f10111314151618191a1b1d1e1f20', + 'ecb-tbl-192: I=81'), + ('a4a5a2a34f404142b4b5b6b727242522', 'b3c8cf961faf9ea05fdde6d1e4d8f663', + '222324252728292a2c2d2e2f31323334363738393b3c3d3e', + 'ecb-tbl-192: I=82'), + ('94959697e1e2e3ec16171011839c9d9e', '143075c70605861c7fac6526199e459f', + '40414243454647484a4b4c4d4f50515254555657595a5b5c', + 'ecb-tbl-192: I=83'), + ('03023d3c06010003dedfdcddfffcfde2', 'a5ae12eade9a87268d898bfc8fc0252a', + '5e5f60616364656668696a6b6d6e6f70727374757778797a', + 'ecb-tbl-192: I=84'), + ('10111213f1f2f3f4cecfc0c1dbdcddde', '0924f7cf2e877a4819f5244a360dcea9', + '7c7d7e7f81828384868788898b8c8d8e9091929395969798', + 'ecb-tbl-192: I=85'), + ('67666160724d4c4f1d1c1f1e73707176', '3d9e9635afcc3e291cc7ab3f27d1c99a', + '9a9b9c9d9fa0a1a2a4a5a6a7a9aaabacaeafb0b1b3b4b5b6', + 'ecb-tbl-192: I=86'), + ('e6e7e4e5a8abaad584858283909f9e9d', '9d80feebf87510e2b8fb98bb54fd788c', + 'b8b9babbbdbebfc0c2c3c4c5c7c8c9cacccdcecfd1d2d3d4', + 'ecb-tbl-192: I=87'), + ('71707f7e565150537d7c7f7e6162636c', '5f9d1a082a1a37985f174002eca01309', + 'd6d7d8d9dbdcdddee0e1e2e3e5e6e7e8eaebecedeff0f1f2', + 'ecb-tbl-192: I=88'), + ('64656667212223245555aaaa03040506', 'a390ebb1d1403930184a44b4876646e4', + 'f4f5f6f7f9fafbfcfefe01010304050608090a0b0d0e0f10', + 'ecb-tbl-192: I=89'), + ('9e9f9899aba4a5a6cfcecdcc2b28292e', '700fe918981c3195bb6c4bcb46b74e29', + '121314151718191a1c1d1e1f21222324262728292b2c2d2e', + 'ecb-tbl-192: I=90'), + ('c7c6c5c4d1d2d3dc626364653a454447', '907984406f7bf2d17fb1eb15b673d747', + '30313233353637383a3b3c3d3f40414244454647494a4b4c', + 'ecb-tbl-192: I=91'), + ('f6f7e8e9e0e7e6e51d1c1f1e5b585966', 'c32a956dcfc875c2ac7c7cc8b8cc26e1', + '4e4f50515354555658595a5b5d5e5f60626364656768696a', + 'ecb-tbl-192: I=92'), + ('bcbdbebf5d5e5f5868696667f4f3f2f1', '02646e2ebfa9b820cf8424e9b9b6eb51', + '6c6d6e6f71727374767778797b7c7d7e8081828385868788', + 'ecb-tbl-192: I=93'), + ('40414647b0afaead9b9a99989b98999e', '621fda3a5bbd54c6d3c685816bd4ead8', + '8a8b8c8d8f90919294959697999a9b9c9e9fa0a1a3a4a5a6', + 'ecb-tbl-192: I=94'), + ('69686b6a0201001f0f0e0908b4bbbab9', 'd4e216040426dfaf18b152469bc5ac2f', + 'a8a9aaabadaeafb0b2b3b4b5b7b8b9babcbdbebfc1c2c3c4', + 'ecb-tbl-192: I=95'), + ('c7c6c9c8d8dfdedd5a5b5859bebdbcb3', '9d0635b9d33b6cdbd71f5d246ea17cc8', + 'c6c7c8c9cbcccdced0d1d2d3d5d6d7d8dadbdcdddfe0e1e2', + 'ecb-tbl-192: I=96'), + ('dedfdcdd787b7a7dfffee1e0b2b5b4b7', '10abad1bd9bae5448808765583a2cc1a', + 'e4e5e6e7e9eaebeceeeff0f1f3f4f5f6f8f9fafbfdfefe00', + 'ecb-tbl-192: I=97'), + ('4d4c4b4a606f6e6dd0d1d2d3fbf8f9fe', '6891889e16544e355ff65a793c39c9a8', + '020304050708090a0c0d0e0f11121314161718191b1c1d1e', + 'ecb-tbl-192: I=98'), + ('b7b6b5b4d7d4d5dae5e4e3e2e1fefffc', 'cc735582e68072c163cd9ddf46b91279', + '20212223252627282a2b2c2d2f30313234353637393a3b3c', + 'ecb-tbl-192: I=99'), + ('cecfb0b1f7f0f1f2aeafacad3e3d3c23', 'c5c68b9aeeb7f878df578efa562f9574', + '3e3f40414344454648494a4b4d4e4f50525354555758595a', + 'ecb-tbl-192: I=100'), + ('cacbc8c9cdcecfc812131c1d494e4f4c', '5f4764395a667a47d73452955d0d2ce8', + '5c5d5e5f61626364666768696b6c6d6e7071727375767778', + 'ecb-tbl-192: I=101'), + ('9d9c9b9ad22d2c2fb1b0b3b20c0f0e09', '701448331f66106cefddf1eb8267c357', + '7a7b7c7d7f80818284858687898a8b8c8e8f909193949596', + 'ecb-tbl-192: I=102'), + ('7a7b787964676659959493924f404142', 'cb3ee56d2e14b4e1941666f13379d657', + '98999a9b9d9e9fa0a2a3a4a5a7a8a9aaacadaeafb1b2b3b4', + 'ecb-tbl-192: I=103'), + ('aaaba4a5cec9c8cb1f1e1d1caba8a9a6', '9fe16efd18ab6e1981191851fedb0764', + 'b6b7b8b9bbbcbdbec0c1c2c3c5c6c7c8cacbcccdcfd0d1d2', + 'ecb-tbl-192: I=104'), + ('93929190282b2a2dc4c5fafb92959497', '3dc9ba24e1b223589b147adceb4c8e48', + 'd4d5d6d7d9dadbdcdedfe0e1e3e4e5e6e8e9eaebedeeeff0', + 'ecb-tbl-192: I=105'), + ('efeee9e8ded1d0d339383b3a888b8a8d', '1c333032682e7d4de5e5afc05c3e483c', + 'f2f3f4f5f7f8f9fafcfdfeff01020304060708090b0c0d0e', + 'ecb-tbl-192: I=106'), + ('7f7e7d7ca2a1a0af78797e7f112e2f2c', 'd593cc99a95afef7e92038e05a59d00a', + '10111213151617181a1b1c1d1f20212224252627292a2b2c', + 'ecb-tbl-192: I=107'), + ('84859a9b2b2c2d2e868784852625245b', '51e7f96f53b4353923452c222134e1ec', + '2e2f30313334353638393a3b3d3e3f40424344454748494a', + 'ecb-tbl-192: I=108'), + ('b0b1b2b3070405026869666710171615', '4075b357a1a2b473400c3b25f32f81a4', + '4c4d4e4f51525354565758595b5c5d5e6061626365666768', + 'ecb-tbl-192: I=109'), + ('acadaaabbda2a3a00d0c0f0e595a5b5c', '302e341a3ebcd74f0d55f61714570284', + '6a6b6c6d6f70717274757677797a7b7c7e7f808183848586', + 'ecb-tbl-192: I=110'), + ('121310115655544b5253545569666764', '57abdd8231280da01c5042b78cf76522', + '88898a8b8d8e8f90929394959798999a9c9d9e9fa1a2a3a4', + 'ecb-tbl-192: I=111'), + ('dedfd0d166616063eaebe8e94142434c', '17f9ea7eea17ac1adf0e190fef799e92', + 'a6a7a8a9abacadaeb0b1b2b3b5b6b7b8babbbcbdbfc0c1c2', + 'ecb-tbl-192: I=112'), + ('dbdad9d81417161166677879e0e7e6e5', '2e1bdd563dd87ee5c338dd6d098d0a7a', + 'c4c5c6c7c9cacbcccecfd0d1d3d4d5d6d8d9dadbdddedfe0', + 'ecb-tbl-192: I=113'), + ('6a6b6c6de0efeeed2b2a2928c0c3c2c5', 'eb869996e6f8bfb2bfdd9e0c4504dbb2', + 'e2e3e4e5e7e8e9eaecedeeeff1f2f3f4f6f7f8f9fbfcfdfe', + 'ecb-tbl-192: I=114'), + ('b1b0b3b21714151a1a1b1c1d5649484b', 'c2e01549e9decf317468b3e018c61ba8', + '00010203050607080a0b0c0d0f10111214151617191a1b1c', + 'ecb-tbl-192: I=115'), + ('39380706a3a4a5a6c4c5c6c77271706f', '8da875d033c01dd463b244a1770f4a22', + '1e1f20212324252628292a2b2d2e2f30323334353738393a', + 'ecb-tbl-192: I=116'), + ('5c5d5e5f1013121539383736e2e5e4e7', '8ba0dcf3a186844f026d022f8839d696', + '3c3d3e3f41424344464748494b4c4d4e5051525355565758', + 'ecb-tbl-192: I=117'), + ('43424544ead5d4d72e2f2c2d64676661', 'e9691ff9a6cc6970e51670a0fd5b88c1', + '5a5b5c5d5f60616264656667696a6b6c6e6f707173747576', + 'ecb-tbl-192: I=118'), + ('55545756989b9a65f8f9feff18171615', 'f2baec06faeed30f88ee63ba081a6e5b', + '78797a7b7d7e7f80828384858788898a8c8d8e8f91929394', + 'ecb-tbl-192: I=119'), + ('05040b0a525554573c3d3e3f4a494847', '9c39d4c459ae5753394d6094adc21e78', + '969798999b9c9d9ea0a1a2a3a5a6a7a8aaabacadafb0b1b2', + 'ecb-tbl-192: I=120'), + ('14151617595a5b5c8584fbfa8e89888b', '6345b532a11904502ea43ba99c6bd2b2', + 'b4b5b6b7b9babbbcbebfc0c1c3c4c5c6c8c9cacbcdcecfd0', + 'ecb-tbl-192: I=121'), + ('7c7d7a7bfdf2f3f029282b2a51525354', '5ffae3061a95172e4070cedce1e428c8', + 'd2d3d4d5d7d8d9dadcdddedfe1e2e3e4e6e7e8e9ebecedee', + 'ecb-tbl-192: I=122'), + ('38393a3b1e1d1c1341404746c23d3c3e', '0a4566be4cdf9adce5dec865b5ab34cd', + 'f0f1f2f3f5f6f7f8fafbfcfdfe01000204050607090a0b0c', + 'ecb-tbl-192: I=123'), + ('8d8c939240474645818083827c7f7e41', 'ca17fcce79b7404f2559b22928f126fb', + '0e0f10111314151618191a1b1d1e1f20222324252728292a', + 'ecb-tbl-192: I=124'), + ('3b3a39381a19181f32333c3d45424340', '97ca39b849ed73a6470a97c821d82f58', + '2c2d2e2f31323334363738393b3c3d3e4041424345464748', + 'ecb-tbl-192: I=125'), + ('f0f1f6f738272625828380817f7c7d7a', '8198cb06bc684c6d3e9b7989428dcf7a', + '4a4b4c4d4f50515254555657595a5b5c5e5f606163646566', + 'ecb-tbl-192: I=126'), + ('89888b8a0407061966676061141b1a19', 'f53c464c705ee0f28d9a4c59374928bd', + '68696a6b6d6e6f70727374757778797a7c7d7e7f81828384', + 'ecb-tbl-192: I=127'), + ('d3d2dddcaaadacaf9c9d9e9fe8ebeae5', '9adb3d4cca559bb98c3e2ed73dbf1154', + '868788898b8c8d8e90919293959697989a9b9c9d9fa0a1a2', + 'ecb-tbl-192: I=128'), + + # ecb_tbl.txt, KEYSIZE=256 + ('834eadfccac7e1b30664b1aba44815ab', '1946dabf6a03a2a2c3d0b05080aed6fc', + '00010203050607080a0b0c0d0f10111214151617191a1b1c1e1f202123242526', + 'ecb-tbl-256: I=1'), + ('d9dc4dba3021b05d67c0518f72b62bf1', '5ed301d747d3cc715445ebdec62f2fb4', + '28292a2b2d2e2f30323334353738393a3c3d3e3f41424344464748494b4c4d4e', + 'ecb-tbl-256: I=2'), + ('a291d86301a4a739f7392173aa3c604c', '6585c8f43d13a6beab6419fc5935b9d0', + '50515253555657585a5b5c5d5f60616264656667696a6b6c6e6f707173747576', + 'ecb-tbl-256: I=3'), + ('4264b2696498de4df79788a9f83e9390', '2a5b56a596680fcc0e05f5e0f151ecae', + '78797a7b7d7e7f80828384858788898a8c8d8e8f91929394969798999b9c9d9e', + 'ecb-tbl-256: I=4'), + ('ee9932b3721804d5a83ef5949245b6f6', 'f5d6ff414fd2c6181494d20c37f2b8c4', + 'a0a1a2a3a5a6a7a8aaabacadafb0b1b2b4b5b6b7b9babbbcbebfc0c1c3c4c5c6', + 'ecb-tbl-256: I=5'), + ('e6248f55c5fdcbca9cbbb01c88a2ea77', '85399c01f59fffb5204f19f8482f00b8', + 'c8c9cacbcdcecfd0d2d3d4d5d7d8d9dadcdddedfe1e2e3e4e6e7e8e9ebecedee', + 'ecb-tbl-256: I=6'), + ('b8358e41b9dff65fd461d55a99266247', '92097b4c88a041ddf98144bc8d22e8e7', + 'f0f1f2f3f5f6f7f8fafbfcfdfe01000204050607090a0b0c0e0f101113141516', + 'ecb-tbl-256: I=7'), + ('f0e2d72260af58e21e015ab3a4c0d906', '89bd5b73b356ab412aef9f76cea2d65c', + '18191a1b1d1e1f20222324252728292a2c2d2e2f31323334363738393b3c3d3e', + 'ecb-tbl-256: I=8'), + ('475b8b823ce8893db3c44a9f2a379ff7', '2536969093c55ff9454692f2fac2f530', + '40414243454647484a4b4c4d4f50515254555657595a5b5c5e5f606163646566', + 'ecb-tbl-256: I=9'), + ('688f5281945812862f5f3076cf80412f', '07fc76a872843f3f6e0081ee9396d637', + '68696a6b6d6e6f70727374757778797a7c7d7e7f81828384868788898b8c8d8e', + 'ecb-tbl-256: I=10'), + ('08d1d2bc750af553365d35e75afaceaa', 'e38ba8ec2aa741358dcc93e8f141c491', + '90919293959697989a9b9c9d9fa0a1a2a4a5a6a7a9aaabacaeafb0b1b3b4b5b6', + 'ecb-tbl-256: I=11'), + ('8707121f47cc3efceca5f9a8474950a1', 'd028ee23e4a89075d0b03e868d7d3a42', + 'b8b9babbbdbebfc0c2c3c4c5c7c8c9cacccdcecfd1d2d3d4d6d7d8d9dbdcddde', + 'ecb-tbl-256: I=12'), + ('e51aa0b135dba566939c3b6359a980c5', '8cd9423dfc459e547155c5d1d522e540', + 'e0e1e2e3e5e6e7e8eaebecedeff0f1f2f4f5f6f7f9fafbfcfefe010103040506', + 'ecb-tbl-256: I=13'), + ('069a007fc76a459f98baf917fedf9521', '080e9517eb1677719acf728086040ae3', + '08090a0b0d0e0f10121314151718191a1c1d1e1f21222324262728292b2c2d2e', + 'ecb-tbl-256: I=14'), + ('726165c1723fbcf6c026d7d00b091027', '7c1700211a3991fc0ecded0ab3e576b0', + '30313233353637383a3b3c3d3f40414244454647494a4b4c4e4f505153545556', + 'ecb-tbl-256: I=15'), + ('d7c544de91d55cfcde1f84ca382200ce', 'dabcbcc855839251db51e224fbe87435', + '58595a5b5d5e5f60626364656768696a6c6d6e6f71727374767778797b7c7d7e', + 'ecb-tbl-256: I=16'), + ('fed3c9a161b9b5b2bd611b41dc9da357', '68d56fad0406947a4dd27a7448c10f1d', + '80818283858687888a8b8c8d8f90919294959697999a9b9c9e9fa0a1a3a4a5a6', + 'ecb-tbl-256: I=17'), + ('4f634cdc6551043409f30b635832cf82', 'da9a11479844d1ffee24bbf3719a9925', + 'a8a9aaabadaeafb0b2b3b4b5b7b8b9babcbdbebfc1c2c3c4c6c7c8c9cbcccdce', + 'ecb-tbl-256: I=18'), + ('109ce98db0dfb36734d9f3394711b4e6', '5e4ba572f8d23e738da9b05ba24b8d81', + 'd0d1d2d3d5d6d7d8dadbdcdddfe0e1e2e4e5e6e7e9eaebeceeeff0f1f3f4f5f6', + 'ecb-tbl-256: I=19'), + ('4ea6dfaba2d8a02ffdffa89835987242', 'a115a2065d667e3f0b883837a6e903f8', + '70717273757677787a7b7c7d7f80818284858687898a8b8c8e8f909193949596', + 'ecb-tbl-256: I=20'), + ('5ae094f54af58e6e3cdbf976dac6d9ef', '3e9e90dc33eac2437d86ad30b137e66e', + '98999a9b9d9e9fa0a2a3a4a5a7a8a9aaacadaeafb1b2b3b4b6b7b8b9bbbcbdbe', + 'ecb-tbl-256: I=21'), + ('764d8e8e0f29926dbe5122e66354fdbe', '01ce82d8fbcdae824cb3c48e495c3692', + 'c0c1c2c3c5c6c7c8cacbcccdcfd0d1d2d4d5d6d7d9dadbdcdedfe0e1e3e4e5e6', + 'ecb-tbl-256: I=22'), + ('3f0418f888cdf29a982bf6b75410d6a9', '0c9cff163ce936faaf083cfd3dea3117', + 'e8e9eaebedeeeff0f2f3f4f5f7f8f9fafcfdfeff01020304060708090b0c0d0e', + 'ecb-tbl-256: I=23'), + ('e4a3e7cb12cdd56aa4a75197a9530220', '5131ba9bd48f2bba85560680df504b52', + '10111213151617181a1b1c1d1f20212224252627292a2b2c2e2f303133343536', + 'ecb-tbl-256: I=24'), + ('211677684aac1ec1a160f44c4ebf3f26', '9dc503bbf09823aec8a977a5ad26ccb2', + '38393a3b3d3e3f40424344454748494a4c4d4e4f51525354565758595b5c5d5e', + 'ecb-tbl-256: I=25'), + ('d21e439ff749ac8f18d6d4b105e03895', '9a6db0c0862e506a9e397225884041d7', + '60616263656667686a6b6c6d6f70717274757677797a7b7c7e7f808183848586', + 'ecb-tbl-256: I=26'), + ('d9f6ff44646c4725bd4c0103ff5552a7', '430bf9570804185e1ab6365fc6a6860c', + '88898a8b8d8e8f90929394959798999a9c9d9e9fa1a2a3a4a6a7a8a9abacadae', + 'ecb-tbl-256: I=27'), + ('0b1256c2a00b976250cfc5b0c37ed382', '3525ebc02f4886e6a5a3762813e8ce8a', + 'b0b1b2b3b5b6b7b8babbbcbdbfc0c1c2c4c5c6c7c9cacbcccecfd0d1d3d4d5d6', + 'ecb-tbl-256: I=28'), + ('b056447ffc6dc4523a36cc2e972a3a79', '07fa265c763779cce224c7bad671027b', + 'd8d9dadbdddedfe0e2e3e4e5e7e8e9eaecedeeeff1f2f3f4f6f7f8f9fbfcfdfe', + 'ecb-tbl-256: I=29'), + ('5e25ca78f0de55802524d38da3fe4456', 'e8b72b4e8be243438c9fff1f0e205872', + '00010203050607080a0b0c0d0f10111214151617191a1b1c1e1f202123242526', + 'ecb-tbl-256: I=30'), + ('a5bcf4728fa5eaad8567c0dc24675f83', '109d4f999a0e11ace1f05e6b22cbcb50', + '28292a2b2d2e2f30323334353738393a3c3d3e3f41424344464748494b4c4d4e', + 'ecb-tbl-256: I=31'), + ('814e59f97ed84646b78b2ca022e9ca43', '45a5e8d4c3ed58403ff08d68a0cc4029', + '50515253555657585a5b5c5d5f60616264656667696a6b6c6e6f707173747576', + 'ecb-tbl-256: I=32'), + ('15478beec58f4775c7a7f5d4395514d7', '196865964db3d417b6bd4d586bcb7634', + '78797a7b7d7e7f80828384858788898a8c8d8e8f91929394969798999b9c9d9e', + 'ecb-tbl-256: I=33'), + ('253548ffca461c67c8cbc78cd59f4756', '60436ad45ac7d30d99195f815d98d2ae', + 'a0a1a2a3a5a6a7a8aaabacadafb0b1b2b4b5b6b7b9babbbcbebfc0c1c3c4c5c6', + 'ecb-tbl-256: I=34'), + ('fd7ad8d73b9b0f8cc41600640f503d65', 'bb07a23f0b61014b197620c185e2cd75', + 'c8c9cacbcdcecfd0d2d3d4d5d7d8d9dadcdddedfe1e2e3e4e6e7e8e9ebecedee', + 'ecb-tbl-256: I=35'), + ('06199de52c6cbf8af954cd65830bcd56', '5bc0b2850129c854423aff0751fe343b', + 'f0f1f2f3f5f6f7f8fafbfcfdfe01000204050607090a0b0c0e0f101113141516', + 'ecb-tbl-256: I=36'), + ('f17c4ffe48e44c61bd891e257e725794', '7541a78f96738e6417d2a24bd2beca40', + '18191a1b1d1e1f20222324252728292a2c2d2e2f31323334363738393b3c3d3e', + 'ecb-tbl-256: I=37'), + ('9a5b4a402a3e8a59be6bf5cd8154f029', 'b0a303054412882e464591f1546c5b9e', + '40414243454647484a4b4c4d4f50515254555657595a5b5c5e5f606163646566', + 'ecb-tbl-256: I=38'), + ('79bd40b91a7e07dc939d441782ae6b17', '778c06d8a355eeee214fcea14b4e0eef', + '68696a6b6d6e6f70727374757778797a7c7d7e7f81828384868788898b8c8d8e', + 'ecb-tbl-256: I=39'), + ('d8ceaaf8976e5fbe1012d8c84f323799', '09614206d15cbace63227d06db6beebb', + '90919293959697989a9b9c9d9fa0a1a2a4a5a6a7a9aaabacaeafb0b1b3b4b5b6', + 'ecb-tbl-256: I=40'), + ('3316e2751e2e388b083da23dd6ac3fbe', '41b97fb20e427a9fdbbb358d9262255d', + 'b8b9babbbdbebfc0c2c3c4c5c7c8c9cacccdcecfd1d2d3d4d6d7d8d9dbdcddde', + 'ecb-tbl-256: I=41'), + ('8b7cfbe37de7dca793521819242c5816', 'c1940f703d845f957652c2d64abd7adf', + 'e0e1e2e3e5e6e7e8eaebecedeff0f1f2f4f5f6f7f9fafbfcfefe010103040506', + 'ecb-tbl-256: I=42'), + ('f23f033c0eebf8ec55752662fd58ce68', 'd2d44fcdae5332343366db297efcf21b', + '08090a0b0d0e0f10121314151718191a1c1d1e1f21222324262728292b2c2d2e', + 'ecb-tbl-256: I=43'), + ('59eb34f6c8bdbacc5fc6ad73a59a1301', 'ea8196b79dbe167b6aa9896e287eed2b', + '30313233353637383a3b3c3d3f40414244454647494a4b4c4e4f505153545556', + 'ecb-tbl-256: I=44'), + ('dcde8b6bd5cf7cc22d9505e3ce81261a', 'd6b0b0c4ba6c7dbe5ed467a1e3f06c2d', + '58595a5b5d5e5f60626364656768696a6c6d6e6f71727374767778797b7c7d7e', + 'ecb-tbl-256: I=45'), + ('e33cf7e524fed781e7042ff9f4b35dc7', 'ec51eb295250c22c2fb01816fb72bcae', + '80818283858687888a8b8c8d8f90919294959697999a9b9c9e9fa0a1a3a4a5a6', + 'ecb-tbl-256: I=46'), + ('27963c8facdf73062867d164df6d064c', 'aded6630a07ce9c7408a155d3bd0d36f', + 'a8a9aaabadaeafb0b2b3b4b5b7b8b9babcbdbebfc1c2c3c4c6c7c8c9cbcccdce', + 'ecb-tbl-256: I=47'), + ('77b1ce386b551b995f2f2a1da994eef8', '697c9245b9937f32f5d1c82319f0363a', + 'd0d1d2d3d5d6d7d8dadbdcdddfe0e1e2e4e5e6e7e9eaebeceeeff0f1f3f4f5f6', + 'ecb-tbl-256: I=48'), + ('f083388b013679efcf0bb9b15d52ae5c', 'aad5ad50c6262aaec30541a1b7b5b19c', + 'f8f9fafbfdfefe00020304050708090a0c0d0e0f11121314161718191b1c1d1e', + 'ecb-tbl-256: I=49'), + ('c5009e0dab55db0abdb636f2600290c8', '7d34b893855341ec625bd6875ac18c0d', + '20212223252627282a2b2c2d2f30313234353637393a3b3c3e3f404143444546', + 'ecb-tbl-256: I=50'), + ('7804881e26cd532d8514d3683f00f1b9', '7ef05105440f83862f5d780e88f02b41', + '48494a4b4d4e4f50525354555758595a5c5d5e5f61626364666768696b6c6d6e', + 'ecb-tbl-256: I=51'), + ('46cddcd73d1eb53e675ca012870a92a3', 'c377c06403382061af2c9c93a8e70df6', + '70717273757677787a7b7c7d7f80818284858687898a8b8c8e8f909193949596', + 'ecb-tbl-256: I=52'), + ('a9fb44062bb07fe130a8e8299eacb1ab', '1dbdb3ffdc052dacc83318853abc6de5', + '98999a9b9d9e9fa0a2a3a4a5a7a8a9aaacadaeafb1b2b3b4b6b7b8b9bbbcbdbe', + 'ecb-tbl-256: I=53'), + ('2b6ff8d7a5cc3a28a22d5a6f221af26b', '69a6eab00432517d0bf483c91c0963c7', + 'c0c1c2c3c5c6c7c8cacbcccdcfd0d1d2d4d5d6d7d9dadbdcdedfe0e1e3e4e5e6', + 'ecb-tbl-256: I=54'), + ('1a9527c29b8add4b0e3e656dbb2af8b4', '0797f41dc217c80446e1d514bd6ab197', + 'e8e9eaebedeeeff0f2f3f4f5f7f8f9fafcfdfeff01020304060708090b0c0d0e', + 'ecb-tbl-256: I=55'), + ('7f99cf2c75244df015eb4b0c1050aeae', '9dfd76575902a637c01343c58e011a03', + '10111213151617181a1b1c1d1f20212224252627292a2b2c2e2f303133343536', + 'ecb-tbl-256: I=56'), + ('e84ff85b0d9454071909c1381646c4ed', 'acf4328ae78f34b9fa9b459747cc2658', + '38393a3b3d3e3f40424344454748494a4c4d4e4f51525354565758595b5c5d5e', + 'ecb-tbl-256: I=57'), + ('89afd40f99521280d5399b12404f6db4', 'b0479aea12bac4fe2384cf98995150c6', + '60616263656667686a6b6c6d6f70717274757677797a7b7c7e7f808183848586', + 'ecb-tbl-256: I=58'), + ('a09ef32dbc5119a35ab7fa38656f0329', '9dd52789efe3ffb99f33b3da5030109a', + '88898a8b8d8e8f90929394959798999a9c9d9e9fa1a2a3a4a6a7a8a9abacadae', + 'ecb-tbl-256: I=59'), + ('61773457f068c376c7829b93e696e716', 'abbb755e4621ef8f1214c19f649fb9fd', + 'b0b1b2b3b5b6b7b8babbbcbdbfc0c1c2c4c5c6c7c9cacbcccecfd0d1d3d4d5d6', + 'ecb-tbl-256: I=60'), + ('a34f0cae726cce41dd498747d891b967', 'da27fb8174357bce2bed0e7354f380f9', + 'd8d9dadbdddedfe0e2e3e4e5e7e8e9eaecedeeeff1f2f3f4f6f7f8f9fbfcfdfe', + 'ecb-tbl-256: I=61'), + ('856f59496c7388ee2d2b1a27b7697847', 'c59a0663f0993838f6e5856593bdc5ef', + '00010203050607080a0b0c0d0f10111214151617191a1b1c1e1f202123242526', + 'ecb-tbl-256: I=62'), + ('cb090c593ef7720bd95908fb93b49df4', 'ed60b264b5213e831607a99c0ce5e57e', + '28292a2b2d2e2f30323334353738393a3c3d3e3f41424344464748494b4c4d4e', + 'ecb-tbl-256: I=63'), + ('a0ac75cd2f1923d460fc4d457ad95baf', 'e50548746846f3eb77b8c520640884ed', + '50515253555657585a5b5c5d5f60616264656667696a6b6c6e6f707173747576', + 'ecb-tbl-256: I=64'), + ('2a2b282974777689e8e9eeef525d5c5f', '28282cc7d21d6a2923641e52d188ef0c', + '78797a7b7d7e7f80828384858788898a8c8d8e8f91929394969798999b9c9d9e', + 'ecb-tbl-256: I=65'), + ('909192939390919e0f0e09089788898a', '0dfa5b02abb18e5a815305216d6d4f8e', + 'a0a1a2a3a5a6a7a8aaabacadafb0b1b2b4b5b6b7b9babbbcbebfc0c1c3c4c5c6', + 'ecb-tbl-256: I=66'), + ('777675748d8e8f907170777649464744', '7359635c0eecefe31d673395fb46fb99', + 'c8c9cacbcdcecfd0d2d3d4d5d7d8d9dadcdddedfe1e2e3e4e6e7e8e9ebecedee', + 'ecb-tbl-256: I=67'), + ('717073720605040b2d2c2b2a05fafbf9', '73c679f7d5aef2745c9737bb4c47fb36', + 'f0f1f2f3f5f6f7f8fafbfcfdfe01000204050607090a0b0c0e0f101113141516', + 'ecb-tbl-256: I=68'), + ('64656667fefdfcc31b1a1d1ca5aaaba8', 'b192bd472a4d2eafb786e97458967626', + '18191a1b1d1e1f20222324252728292a2c2d2e2f31323334363738393b3c3d3e', + 'ecb-tbl-256: I=69'), + ('dbdad9d86a696867b5b4b3b2c8d7d6d5', '0ec327f6c8a2b147598ca3fde61dc6a4', + '40414243454647484a4b4c4d4f50515254555657595a5b5c5e5f606163646566', + 'ecb-tbl-256: I=70'), + ('5c5d5e5fe3e0e1fe31303736333c3d3e', 'fc418eb3c41b859b38d4b6f646629729', + '68696a6b6d6e6f70727374757778797a7c7d7e7f81828384868788898b8c8d8e', + 'ecb-tbl-256: I=71'), + ('545556574b48494673727574546b6a69', '30249e5ac282b1c981ea64b609f3a154', + '90919293959697989a9b9c9d9fa0a1a2a4a5a6a7a9aaabacaeafb0b1b3b4b5b6', + 'ecb-tbl-256: I=72'), + ('ecedeeefc6c5c4bb56575051f5fafbf8', '5e6e08646d12150776bb43c2d78a9703', + 'b8b9babbbdbebfc0c2c3c4c5c7c8c9cacccdcecfd1d2d3d4d6d7d8d9dbdcddde', + 'ecb-tbl-256: I=73'), + ('464744452724252ac9c8cfced2cdcccf', 'faeb3d5de652cd3447dceb343f30394a', + 'e0e1e2e3e5e6e7e8eaebecedeff0f1f2f4f5f6f7f9fafbfcfefe010103040506', + 'ecb-tbl-256: I=74'), + ('e6e7e4e54142435c878681801c131211', 'a8e88706823f6993ef80d05c1c7b2cf0', + '08090a0b0d0e0f10121314151718191a1c1d1e1f21222324262728292b2c2d2e', + 'ecb-tbl-256: I=75'), + ('72737071cfcccdc2f9f8fffe710e0f0c', '8ced86677e6e00a1a1b15968f2d3cce6', + '30313233353637383a3b3c3d3f40414244454647494a4b4c4e4f505153545556', + 'ecb-tbl-256: I=76'), + ('505152537370714ec3c2c5c4010e0f0c', '9fc7c23858be03bdebb84e90db6786a9', + '58595a5b5d5e5f60626364656768696a6c6d6e6f71727374767778797b7c7d7e', + 'ecb-tbl-256: I=77'), + ('a8a9aaab5c5f5e51aeafa8a93d222320', 'b4fbd65b33f70d8cf7f1111ac4649c36', + '80818283858687888a8b8c8d8f90919294959697999a9b9c9e9fa0a1a3a4a5a6', + 'ecb-tbl-256: I=78'), + ('dedfdcddf6f5f4eb10111617fef1f0f3', 'c5c32d5ed03c4b53cc8c1bd0ef0dbbf6', + 'a8a9aaabadaeafb0b2b3b4b5b7b8b9babcbdbebfc1c2c3c4c6c7c8c9cbcccdce', + 'ecb-tbl-256: I=79'), + ('bdbcbfbe5e5d5c530b0a0d0cfac5c4c7', 'd1a7f03b773e5c212464b63709c6a891', + 'd0d1d2d3d5d6d7d8dadbdcdddfe0e1e2e4e5e6e7e9eaebeceeeff0f1f3f4f5f6', + 'ecb-tbl-256: I=80'), + ('8a8b8889050606f8f4f5f2f3636c6d6e', '6b7161d8745947ac6950438ea138d028', + 'f8f9fafbfdfefe00020304050708090a0c0d0e0f11121314161718191b1c1d1e', + 'ecb-tbl-256: I=81'), + ('a6a7a4a54d4e4f40b2b3b4b539262724', 'fd47a9f7e366ee7a09bc508b00460661', + '20212223252627282a2b2c2d2f30313234353637393a3b3c3e3f404143444546', + 'ecb-tbl-256: I=82'), + ('9c9d9e9fe9eaebf40e0f08099b949596', '00d40b003dc3a0d9310b659b98c7e416', + '48494a4b4d4e4f50525354555758595a5c5d5e5f61626364666768696b6c6d6e', + 'ecb-tbl-256: I=83'), + ('2d2c2f2e1013121dcccdcacbed121310', 'eea4c79dcc8e2bda691f20ac48be0717', + '70717273757677787a7b7c7d7f80818284858687898a8b8c8e8f909193949596', + 'ecb-tbl-256: I=84'), + ('f4f5f6f7edeeefd0eaebecedf7f8f9fa', 'e78f43b11c204403e5751f89d05a2509', + '98999a9b9d9e9fa0a2a3a4a5a7a8a9aaacadaeafb1b2b3b4b6b7b8b9bbbcbdbe', + 'ecb-tbl-256: I=85'), + ('3d3c3f3e282b2a2573727574150a0b08', 'd0f0e3d1f1244bb979931e38dd1786ef', + 'c0c1c2c3c5c6c7c8cacbcccdcfd0d1d2d4d5d6d7d9dadbdcdedfe0e1e3e4e5e6', + 'ecb-tbl-256: I=86'), + ('b6b7b4b5f8fbfae5b4b5b2b3a0afaead', '042e639dc4e1e4dde7b75b749ea6f765', + 'e8e9eaebedeeeff0f2f3f4f5f7f8f9fafcfdfeff01020304060708090b0c0d0e', + 'ecb-tbl-256: I=87'), + ('b7b6b5b4989b9a95878681809ba4a5a6', 'bc032fdd0efe29503a980a7d07ab46a8', + '10111213151617181a1b1c1d1f20212224252627292a2b2c2e2f303133343536', + 'ecb-tbl-256: I=88'), + ('a8a9aaabe5e6e798e9e8efee4748494a', '0c93ac949c0da6446effb86183b6c910', + '38393a3b3d3e3f40424344454748494a4c4d4e4f51525354565758595b5c5d5e', + 'ecb-tbl-256: I=89'), + ('ecedeeefd9dadbd4b9b8bfbe657a7b78', 'e0d343e14da75c917b4a5cec4810d7c2', + '60616263656667686a6b6c6d6f70717274757677797a7b7c7e7f808183848586', + 'ecb-tbl-256: I=90'), + ('7f7e7d7c696a6b74cacbcccd929d9c9f', '0eafb821748408279b937b626792e619', + '88898a8b8d8e8f90929394959798999a9c9d9e9fa1a2a3a4a6a7a8a9abacadae', + 'ecb-tbl-256: I=91'), + ('08090a0b0605040bfffef9f8b9c6c7c4', 'fa1ac6e02d23b106a1fef18b274a553f', + 'b0b1b2b3b5b6b7b8babbbcbdbfc0c1c2c4c5c6c7c9cacbcccecfd0d1d3d4d5d6', + 'ecb-tbl-256: I=92'), + ('08090a0bf1f2f3ccfcfdfafb68676665', '0dadfe019cd12368075507df33c1a1e9', + 'd8d9dadbdddedfe0e2e3e4e5e7e8e9eaecedeeeff1f2f3f4f6f7f8f9fbfcfdfe', + 'ecb-tbl-256: I=93'), + ('cacbc8c93a393837050403020d121310', '3a0879b414465d9ffbaf86b33a63a1b9', + '00010203050607080a0b0c0d0f10111214151617191a1b1c1e1f202123242526', + 'ecb-tbl-256: I=94'), + ('e9e8ebea8281809f8f8e8988343b3a39', '62199fadc76d0be1805d3ba0b7d914bf', + '28292a2b2d2e2f30323334353738393a3c3d3e3f41424344464748494b4c4d4e', + 'ecb-tbl-256: I=95'), + ('515053524645444bd0d1d6d7340b0a09', '1b06d6c5d333e742730130cf78e719b4', + '50515253555657585a5b5c5d5f60616264656667696a6b6c6e6f707173747576', + 'ecb-tbl-256: I=96'), + ('42434041ecefee1193929594c6c9c8cb', 'f1f848824c32e9dcdcbf21580f069329', + '78797a7b7d7e7f80828384858788898a8c8d8e8f91929394969798999b9c9d9e', + 'ecb-tbl-256: I=97'), + ('efeeedecc2c1c0cf76777071455a5b58', '1a09050cbd684f784d8e965e0782f28a', + 'a0a1a2a3a5a6a7a8aaabacadafb0b1b2b4b5b6b7b9babbbcbebfc0c1c3c4c5c6', + 'ecb-tbl-256: I=98'), + ('5f5e5d5c3f3c3d221d1c1b1a19161714', '79c2969e7ded2ba7d088f3f320692360', + 'c8c9cacbcdcecfd0d2d3d4d5d7d8d9dadcdddedfe1e2e3e4e6e7e8e9ebecedee', + 'ecb-tbl-256: I=99'), + ('000102034142434c1c1d1a1b8d727371', '091a658a2f7444c16accb669450c7b63', + 'f0f1f2f3f5f6f7f8fafbfcfdfe01000204050607090a0b0c0e0f101113141516', + 'ecb-tbl-256: I=100'), + ('8e8f8c8db1b2b38c56575051050a0b08', '97c1e3a72cca65fa977d5ed0e8a7bbfc', + '18191a1b1d1e1f20222324252728292a2c2d2e2f31323334363738393b3c3d3e', + 'ecb-tbl-256: I=101'), + ('a7a6a5a4e8ebeae57f7e7978cad5d4d7', '70c430c6db9a17828937305a2df91a2a', + '40414243454647484a4b4c4d4f50515254555657595a5b5c5e5f606163646566', + 'ecb-tbl-256: I=102'), + ('8a8b888994979689454443429f909192', '629553457fbe2479098571c7c903fde8', + '68696a6b6d6e6f70727374757778797a7c7d7e7f81828384868788898b8c8d8e', + 'ecb-tbl-256: I=103'), + ('8c8d8e8fe0e3e2ed45444342f1cecfcc', 'a25b25a61f612669e7d91265c7d476ba', + '90919293959697989a9b9c9d9fa0a1a2a4a5a6a7a9aaabacaeafb0b1b3b4b5b6', + 'ecb-tbl-256: I=104'), + ('fffefdfc4c4f4e31d8d9dedfb6b9b8bb', 'eb7e4e49b8ae0f024570dda293254fed', + 'b8b9babbbdbebfc0c2c3c4c5c7c8c9cacccdcecfd1d2d3d4d6d7d8d9dbdcddde', + 'ecb-tbl-256: I=105'), + ('fdfcfffecccfcec12f2e29286679787b', '38fe15d61cca84516e924adce5014f67', + 'e0e1e2e3e5e6e7e8eaebecedeff0f1f2f4f5f6f7f9fafbfcfefe010103040506', + 'ecb-tbl-256: I=106'), + ('67666564bab9b8a77071767719161714', '3ad208492249108c9f3ebeb167ad0583', + '08090a0b0d0e0f10121314151718191a1c1d1e1f21222324262728292b2c2d2e', + 'ecb-tbl-256: I=107'), + ('9a9b98992d2e2f2084858283245b5a59', '299ba9f9bf5ab05c3580fc26edd1ed12', + '30313233353637383a3b3c3d3f40414244454647494a4b4c4e4f505153545556', + 'ecb-tbl-256: I=108'), + ('a4a5a6a70b0809365c5d5a5b2c232221', '19dc705b857a60fb07717b2ea5717781', + '58595a5b5d5e5f60626364656768696a6c6d6e6f71727374767778797b7c7d7e', + 'ecb-tbl-256: I=109'), + ('464744455754555af3f2f5f4afb0b1b2', 'ffc8aeb885b5efcad06b6dbebf92e76b', + '80818283858687888a8b8c8d8f90919294959697999a9b9c9e9fa0a1a3a4a5a6', + 'ecb-tbl-256: I=110'), + ('323330317675746b7273747549464744', 'f58900c5e0b385253ff2546250a0142b', + 'a8a9aaabadaeafb0b2b3b4b5b7b8b9babcbdbebfc1c2c3c4c6c7c8c9cbcccdce', + 'ecb-tbl-256: I=111'), + ('a8a9aaab181b1a15808186872b141516', '2ee67b56280bc462429cee6e3370cbc1', + 'd0d1d2d3d5d6d7d8dadbdcdddfe0e1e2e4e5e6e7e9eaebeceeeff0f1f3f4f5f6', + 'ecb-tbl-256: I=112'), + ('e7e6e5e4202323ddaaabacad343b3a39', '20db650a9c8e9a84ab4d25f7edc8f03f', + 'f8f9fafbfdfefe00020304050708090a0c0d0e0f11121314161718191b1c1d1e', + 'ecb-tbl-256: I=113'), + ('a8a9aaab2221202fedecebea1e010003', '3c36da169525cf818843805f25b78ae5', + '20212223252627282a2b2c2d2f30313234353637393a3b3c3e3f404143444546', + 'ecb-tbl-256: I=114'), + ('f9f8fbfa5f5c5d42424344450e010003', '9a781d960db9e45e37779042fea51922', + '48494a4b4d4e4f50525354555758595a5c5d5e5f61626364666768696b6c6d6e', + 'ecb-tbl-256: I=115'), + ('57565554f5f6f7f89697909120dfdedd', '6560395ec269c672a3c288226efdba77', + '70717273757677787a7b7c7d7f80818284858687898a8b8c8e8f909193949596', + 'ecb-tbl-256: I=116'), + ('f8f9fafbcccfcef1dddcdbda0e010003', '8c772b7a189ac544453d5916ebb27b9a', + '98999a9b9d9e9fa0a2a3a4a5a7a8a9aaacadaeafb1b2b3b4b6b7b8b9bbbcbdbe', + 'ecb-tbl-256: I=117'), + ('d9d8dbda7073727d80818687c2dddcdf', '77ca5468cc48e843d05f78eed9d6578f', + 'c0c1c2c3c5c6c7c8cacbcccdcfd0d1d2d4d5d6d7d9dadbdcdedfe0e1e3e4e5e6', + 'ecb-tbl-256: I=118'), + ('c5c4c7c6080b0a1588898e8f68676665', '72cdcc71dc82c60d4429c9e2d8195baa', + 'e8e9eaebedeeeff0f2f3f4f5f7f8f9fafcfdfeff01020304060708090b0c0d0e', + 'ecb-tbl-256: I=119'), + ('83828180dcdfded186878081f0cfcecd', '8080d68ce60e94b40b5b8b69eeb35afa', + '10111213151617181a1b1c1d1f20212224252627292a2b2c2e2f303133343536', + 'ecb-tbl-256: I=120'), + ('98999a9bdddedfa079787f7e0a050407', '44222d3cde299c04369d58ac0eba1e8e', + '38393a3b3d3e3f40424344454748494a4c4d4e4f51525354565758595b5c5d5e', + 'ecb-tbl-256: I=121'), + ('cecfcccd4f4c4d429f9e9998dfc0c1c2', '9b8721b0a8dfc691c5bc5885dbfcb27a', + '60616263656667686a6b6c6d6f70717274757677797a7b7c7e7f808183848586', + 'ecb-tbl-256: I=122'), + ('404142436665647b29282f2eaba4a5a6', '0dc015ce9a3a3414b5e62ec643384183', + '88898a8b8d8e8f90929394959798999a9c9d9e9fa1a2a3a4a6a7a8a9abacadae', + 'ecb-tbl-256: I=123'), + ('33323130e6e5e4eb23222524dea1a0a3', '705715448a8da412025ce38345c2a148', + 'b0b1b2b3b5b6b7b8babbbcbdbfc0c1c2c4c5c6c7c9cacbcccecfd0d1d3d4d5d6', + 'ecb-tbl-256: I=124'), + ('cfcecdccf6f5f4cbe6e7e0e199969794', 'c32b5b0b6fbae165266c569f4b6ecf0b', + 'd8d9dadbdddedfe0e2e3e4e5e7e8e9eaecedeeeff1f2f3f4f6f7f8f9fbfcfdfe', + 'ecb-tbl-256: I=125'), + ('babbb8b97271707fdcdddadb29363734', '4dca6c75192a01ddca9476af2a521e87', + '00010203050607080a0b0c0d0f10111214151617191a1b1c1e1f202123242526', + 'ecb-tbl-256: I=126'), + ('c9c8cbca4447465926272021545b5a59', '058691e627ecbc36ac07b6db423bd698', + '28292a2b2d2e2f30323334353738393a3c3d3e3f41424344464748494b4c4d4e', + 'ecb-tbl-256: I=127'), + ('050407067477767956575051221d1c1f', '7444527095838fe080fc2bcdd30847eb', + '50515253555657585a5b5c5d5f60616264656667696a6b6c6e6f707173747576', + 'ecb-tbl-256: I=128'), + + # FIPS PUB 800-38A test vectors, 2001 edition. Annex F. + + ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+ + '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710', + '3ad77bb40d7a3660a89ecaf32466ef97'+'f5d3d58503b9699de785895a96fdbaaf'+ + '43b1cd7f598ece23881b00e3ed030688'+'7b0c785e27e8ad3f8223207104725dd4', + '2b7e151628aed2a6abf7158809cf4f3c', + 'NIST 800-38A, F.1.1, ECB and AES-128'), + + ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+ + '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710', + 'bd334f1d6e45f25ff712a214571fa5cc'+'974104846d0ad3ad7734ecb3ecee4eef'+ + 'ef7afd2270e2e60adce0ba2face6444e'+'9a4b41ba738d6c72fb16691603c18e0e', + '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b', + 'NIST 800-38A, F.1.3, ECB and AES-192'), + + ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+ + '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710', + 'f3eed1bdb5d2a03c064b5a7e3db181f8'+'591ccb10d410ed26dc5ba74a31362870'+ + 'b6ed21b99ca6f4f9f153e7b1beafed1d'+'23304b7a39f9f3ff067d8d8f9e24ecc7', + '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4', + 'NIST 800-38A, F.1.3, ECB and AES-256'), + + ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+ + '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710', + '7649abac8119b246cee98e9b12e9197d'+'5086cb9b507219ee95db113a917678b2'+ + '73bed6b8e3c1743b7116e69e22229516'+'3ff1caa1681fac09120eca307586e1a7', + '2b7e151628aed2a6abf7158809cf4f3c', + 'NIST 800-38A, F.2.1, CBC and AES-128', + dict(mode='CBC', iv='000102030405060708090a0b0c0d0e0f')), + + ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+ + '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710', + '4f021db243bc633d7178183a9fa071e8'+'b4d9ada9ad7dedf4e5e738763f69145a'+ + '571b242012fb7ae07fa9baac3df102e0'+'08b0e27988598881d920a9e64f5615cd', + '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b', + 'NIST 800-38A, F.2.1, CBC and AES-192', + dict(mode='CBC', iv='000102030405060708090a0b0c0d0e0f')), + + ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+ + '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710', + 'f58c4c04d6e5f1ba779eabfb5f7bfbd6'+'9cfc4e967edb808d679f777bc6702c7d'+ + '39f23369a9d9bacfa530e26304231461'+'b2eb05e2c39be9fcda6c19078c6a9d1b', + '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4', + 'NIST 800-38A, F.2.1, CBC and AES-256', + dict(mode='CBC', iv='000102030405060708090a0b0c0d0e0f')), + + # Skip CFB-1 since it is not supported by PyCrypto + + ('6bc1bee22e409f96e93d7e117393172aae2d','3b79424c9c0dd436bace9e0ed4586a4f32b9', + '2b7e151628aed2a6abf7158809cf4f3c', + 'NIST 800-38A, F.3.7, CFB-8 and AES-128', + dict(mode='CFB', iv='000102030405060708090a0b0c0d0e0f', segment_size=8)), + + ('6bc1bee22e409f96e93d7e117393172aae2d','cda2521ef0a905ca44cd057cbf0d47a0678a', + '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b', + 'NIST 800-38A, F.3.9, CFB-8 and AES-192', + dict(mode='CFB', iv='000102030405060708090a0b0c0d0e0f', segment_size=8)), + + ('6bc1bee22e409f96e93d7e117393172aae2d','dc1f1a8520a64db55fcc8ac554844e889700', + '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4', + 'NIST 800-38A, F.3.11, CFB-8 and AES-256', + dict(mode='CFB', iv='000102030405060708090a0b0c0d0e0f', segment_size=8)), + + ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+ + '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710', + '3b3fd92eb72dad20333449f8e83cfb4a'+'c8a64537a0b3a93fcde3cdad9f1ce58b'+ + '26751f67a3cbb140b1808cf187a4f4df'+'c04b05357c5d1c0eeac4c66f9ff7f2e6', + '2b7e151628aed2a6abf7158809cf4f3c', + 'NIST 800-38A, F.3.13, CFB-128 and AES-128', + dict(mode='CFB', iv='000102030405060708090a0b0c0d0e0f', segment_size=128)), + + ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+ + '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710', + 'cdc80d6fddf18cab34c25909c99a4174'+'67ce7f7f81173621961a2b70171d3d7a'+ + '2e1e8a1dd59b88b1c8e60fed1efac4c9'+'c05f9f9ca9834fa042ae8fba584b09ff', + '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b', + 'NIST 800-38A, F.3.15, CFB-128 and AES-192', + dict(mode='CFB', iv='000102030405060708090a0b0c0d0e0f', segment_size=128)), + + ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+ + '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710', + 'dc7e84bfda79164b7ecd8486985d3860'+'39ffed143b28b1c832113c6331e5407b'+ + 'df10132415e54b92a13ed0a8267ae2f9'+'75a385741ab9cef82031623d55b1e471', + '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4', + 'NIST 800-38A, F.3.17, CFB-128 and AES-256', + dict(mode='CFB', iv='000102030405060708090a0b0c0d0e0f', segment_size=128)), + + ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+ + '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710', + '3b3fd92eb72dad20333449f8e83cfb4a'+'7789508d16918f03f53c52dac54ed825'+ + '9740051e9c5fecf64344f7a82260edcc'+'304c6528f659c77866a510d9c1d6ae5e', + '2b7e151628aed2a6abf7158809cf4f3c', + 'NIST 800-38A, F.4.1, OFB and AES-128', + dict(mode='OFB', iv='000102030405060708090a0b0c0d0e0f')), + + ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+ + '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710', + 'cdc80d6fddf18cab34c25909c99a4174'+'fcc28b8d4c63837c09e81700c1100401'+ + '8d9a9aeac0f6596f559c6d4daf59a5f2'+'6d9f200857ca6c3e9cac524bd9acc92a', + '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b', + 'NIST 800-38A, F.4.3, OFB and AES-192', + dict(mode='OFB', iv='000102030405060708090a0b0c0d0e0f')), + + ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+ + '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710', + 'dc7e84bfda79164b7ecd8486985d3860'+'4febdc6740d20b3ac88f6ad82a4fb08d'+ + '71ab47a086e86eedf39d1c5bba97c408'+'0126141d67f37be8538f5a8be740e484', + '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4', + 'NIST 800-38A, F.4.5, OFB and AES-256', + dict(mode='OFB', iv='000102030405060708090a0b0c0d0e0f')), + + ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+ + '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710', + '874d6191b620e3261bef6864990db6ce'+'9806f66b7970fdff8617187bb9fffdff'+ + '5ae4df3edbd5d35e5b4f09020db03eab'+'1e031dda2fbe03d1792170a0f3009cee', + '2b7e151628aed2a6abf7158809cf4f3c', + 'NIST 800-38A, F.5.1, CTR and AES-128', + dict(mode='CTR', ctr_params=dict(nbits=16, prefix='f0f1f2f3f4f5f6f7f8f9fafbfcfd', initial_value=0xfeff))), + + ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+ + '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710', + '1abc932417521ca24f2b0459fe7e6e0b'+'090339ec0aa6faefd5ccc2c6f4ce8e94'+ + '1e36b26bd1ebc670d1bd1d665620abf7'+'4f78a7f6d29809585a97daec58c6b050', + '8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b', + 'NIST 800-38A, F.5.3, CTR and AES-192', + dict(mode='CTR', ctr_params=dict(nbits=16, prefix='f0f1f2f3f4f5f6f7f8f9fafbfcfd', initial_value=0xfeff))), + + ('6bc1bee22e409f96e93d7e117393172a'+'ae2d8a571e03ac9c9eb76fac45af8e51'+ + '30c81c46a35ce411e5fbc1191a0a52ef'+'f69f2445df4f9b17ad2b417be66c3710', + '601ec313775789a5b7a7f504bbf3d228'+'f443e3ca4d62b59aca84e990cacaf5c5'+ + '2b0930daa23de94ce87017ba2d84988d'+'dfc9c58db67aada613c2dd08457941a6', + '603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4', + 'NIST 800-38A, F.5.5, CTR and AES-256', + dict(mode='CTR', ctr_params=dict(nbits=16, prefix='f0f1f2f3f4f5f6f7f8f9fafbfcfd', initial_value=0xfeff))), + + # RFC 3686 test vectors + # This is a list of (plaintext, ciphertext, key[, description[, params]]) tuples. + ('53696e676c6520626c6f636b206d7367', 'e4095d4fb7a7b3792d6175a3261311b8', + 'ae6852f8121067cc4bf7a5765577f39e', + 'RFC 3686 Test Vector #1: Encrypting 16 octets using AES-CTR with 128-bit key', + dict(mode='CTR', ctr_params=dict(nbits=32, prefix='00000030'+'0000000000000000'))), + ('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', + '5104a106168a72d9790d41ee8edad388eb2e1efc46da57c8fce630df9141be28', + '7e24067817fae0d743d6ce1f32539163', + 'RFC 3686 Test Vector #2: Encrypting 32 octets using AES-CTR with 128-bit key', + dict(mode='CTR', ctr_params=dict(nbits=32, prefix='006cb6db'+'c0543b59da48d90b'))), + ('000102030405060708090a0b0c0d0e0f'+'101112131415161718191a1b1c1d1e1f'+'20212223', + 'c1cf48a89f2ffdd9cf4652e9efdb72d7'+'4540a42bde6d7836d59a5ceaaef31053'+'25b2072f', + '7691be035e5020a8ac6e618529f9a0dc', + 'RFC 3686 Test Vector #3: Encrypting 36 octets using AES-CTR with 128-bit key', + dict(mode='CTR', ctr_params=dict(nbits=32, prefix='00e0017b'+'27777f3f4a1786f0'))), + ('53696e676c6520626c6f636b206d7367', + '4b55384fe259c9c84e7935a003cbe928', + '16af5b145fc9f579c175f93e3bfb0eed'+'863d06ccfdb78515', + 'RFC 3686 Test Vector #4: Encrypting 16 octets using AES-CTR with 192-bit key', + dict(mode='CTR', ctr_params=dict(nbits=32, prefix='00000048'+'36733c147d6d93cb'))), + ('000102030405060708090a0b0c0d0e0f'+'101112131415161718191a1b1c1d1e1f', + '453243fc609b23327edfaafa7131cd9f'+'8490701c5ad4a79cfc1fe0ff42f4fb00', + '7c5cb2401b3dc33c19e7340819e0f69c'+'678c3db8e6f6a91a', + 'RFC 3686 Test Vector #5: Encrypting 32 octets using AES-CTR with 192-bit key', + dict(mode='CTR', ctr_params=dict(nbits=32, prefix='0096b03b'+'020c6eadc2cb500d'))), + ('000102030405060708090a0b0c0d0e0f'+'101112131415161718191a1b1c1d1e1f'+'20212223', + '96893fc55e5c722f540b7dd1ddf7e758'+'d288bc95c69165884536c811662f2188'+'abee0935', + '02bf391ee8ecb159b959617b0965279b'+'f59b60a786d3e0fe', + 'RFC 3686 Test Vector #6: Encrypting 36 octets using AES-CTR with 192-bit key', + dict(mode='CTR', ctr_params=dict(nbits=32, prefix='0007bdfd'+'5cbd60278dcc0912'))), + ('53696e676c6520626c6f636b206d7367', + '145ad01dbf824ec7560863dc71e3e0c0', + '776beff2851db06f4c8a0542c8696f6c'+'6a81af1eec96b4d37fc1d689e6c1c104', + 'RFC 3686 Test Vector #7: Encrypting 16 octets using AES-CTR with 256-bit key', + dict(mode='CTR', ctr_params=dict(nbits=32, prefix='00000060'+'db5672c97aa8f0b2'))), + ('000102030405060708090a0b0c0d0e0f'+'101112131415161718191a1b1c1d1e1f', + 'f05e231b3894612c49ee000b804eb2a9'+'b8306b508f839d6a5530831d9344af1c', + 'f6d66d6bd52d59bb0796365879eff886'+'c66dd51a5b6a99744b50590c87a23884', + 'RFC 3686 Test Vector #8: Encrypting 32 octets using AES-CTR with 256-bit key', + dict(mode='CTR', ctr_params=dict(nbits=32, prefix='00faac24'+'c1585ef15a43d875'))), + ('000102030405060708090a0b0c0d0e0f'+'101112131415161718191a1b1c1d1e1f'+'20212223', + 'eb6c52821d0bbbf7ce7594462aca4faa'+'b407df866569fd07f48cc0b583d6071f'+'1ec0e6b8', + 'ff7a617ce69148e4f1726e2f43581de2'+'aa62d9f805532edff1eed687fb54153d', + 'RFC 3686 Test Vector #9: Encrypting 36 octets using AES-CTR with 256-bit key', + dict(mode='CTR', ctr_params=dict(nbits=32, prefix='001cc5b7'+'51a51d70a1c11148'))), + + # The following test vectors have been generated with gpg v1.4.0. + # The command line used was: + # + # gpg -c -z 0 --cipher-algo AES --passphrase secret_passphrase \ + # --disable-mdc --s2k-mode 0 --output ct pt + # + # As result, the content of the file 'pt' is encrypted with a key derived + # from 'secret_passphrase' and written to file 'ct'. + # Test vectors must be extracted from 'ct', which is a collection of + # TLVs (see RFC4880 for all details): + # - the encrypted data (with the encrypted IV as prefix) is the payload + # of the TLV with tag 9 (Symmetrical Encrypted Data Packet). + # This is the ciphertext in the test vector. + # - inside the encrypted part, there is a further layer of TLVs. One must + # look for tag 11 (Literal Data Packet); in its payload, after a short + # but time dependent header, there is the content of file 'pt'. + # In the test vector, the plaintext is the complete set of TLVs that gets + # encrypted. It is not just the content of 'pt'. + # - the key is the leftmost 16 bytes of the SHA1 digest of the password. + # The test vector contains such shortened digest. + # + # Note that encryption uses a clear IV, and decryption an encrypted IV + ( 'ac18620270744fb4f647426c61636b4361745768697465436174', # Plaintext, 'BlackCatWhiteCat' + 'dc6b9e1f095de609765c59983db5956ae4f63aea7405389d2ebb', # Ciphertext + '5baa61e4c9b93f3f0682250b6cf8331b', # Key (hash of 'password') + 'GPG Test Vector #1', + dict(mode='OPENPGP', iv='3d7d3e62282add7eb203eeba5c800733', encrypted_iv='fd934601ef49cb58b6d9aebca6056bdb96ef' ) ), +] + +def get_tests(config={}): + from Crypto.Cipher import AES + from common import make_block_tests + return make_block_tests(AES, "AES", test_data) + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Cipher/test_ARC2.py b/lib/Crypto/SelfTest/Cipher/test_ARC2.py new file mode 100644 index 0000000..b6bc519 --- /dev/null +++ b/lib/Crypto/SelfTest/Cipher/test_ARC2.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Cipher/ARC2.py: Self-test for the Alleged-RC2 cipher +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Cipher.ARC2""" + +__revision__ = "$Id$" + +from common import dict # For compatibility with Python 2.1 and 2.2 + +import unittest +from Crypto.Util.py3compat import * + +# This is a list of (plaintext, ciphertext, key[, description[, extra_params]]) tuples. +test_data = [ + # Test vectors from RFC 2268 + + # 63-bit effective key length + ('0000000000000000', 'ebb773f993278eff', '0000000000000000', + 'RFC2268-1', dict(effective_keylen=63)), + + # 64-bit effective key length + ('ffffffffffffffff', '278b27e42e2f0d49', 'ffffffffffffffff', + 'RFC2268-2', dict(effective_keylen=64)), + ('1000000000000001', '30649edf9be7d2c2', '3000000000000000', + 'RFC2268-3', dict(effective_keylen=64)), + ('0000000000000000', '61a8a244adacccf0', '88', + 'RFC2268-4', dict(effective_keylen=64)), + ('0000000000000000', '6ccf4308974c267f', '88bca90e90875a', + 'RFC2268-5', dict(effective_keylen=64)), + ('0000000000000000', '1a807d272bbe5db1', '88bca90e90875a7f0f79c384627bafb2', + 'RFC2268-6', dict(effective_keylen=64)), + + # 128-bit effective key length + ('0000000000000000', '2269552ab0f85ca6', '88bca90e90875a7f0f79c384627bafb2', + "RFC2268-7", dict(effective_keylen=128)), + ('0000000000000000', '5b78d3a43dfff1f1', + '88bca90e90875a7f0f79c384627bafb216f80a6f85920584c42fceb0be255daf1e', + "RFC2268-8", dict(effective_keylen=129)), + + # Test vectors from PyCrypto 2.0.1's testdata.py + # 1024-bit effective key length + ('0000000000000000', '624fb3e887419e48', '5068696c6970476c617373', + 'PCTv201-0'), + ('ffffffffffffffff', '79cadef44c4a5a85', '5068696c6970476c617373', + 'PCTv201-1'), + ('0001020304050607', '90411525b34e4c2c', '5068696c6970476c617373', + 'PCTv201-2'), + ('0011223344556677', '078656aaba61cbfb', '5068696c6970476c617373', + 'PCTv201-3'), + ('0000000000000000', 'd7bcc5dbb4d6e56a', 'ffffffffffffffff', + 'PCTv201-4'), + ('ffffffffffffffff', '7259018ec557b357', 'ffffffffffffffff', + 'PCTv201-5'), + ('0001020304050607', '93d20a497f2ccb62', 'ffffffffffffffff', + 'PCTv201-6'), + ('0011223344556677', 'cb15a7f819c0014d', 'ffffffffffffffff', + 'PCTv201-7'), + ('0000000000000000', '63ac98cdf3843a7a', 'ffffffffffffffff5065746572477265656e6177617953e5ffe553', + 'PCTv201-8'), + ('ffffffffffffffff', '3fb49e2fa12371dd', 'ffffffffffffffff5065746572477265656e6177617953e5ffe553', + 'PCTv201-9'), + ('0001020304050607', '46414781ab387d5f', 'ffffffffffffffff5065746572477265656e6177617953e5ffe553', + 'PCTv201-10'), + ('0011223344556677', 'be09dc81feaca271', 'ffffffffffffffff5065746572477265656e6177617953e5ffe553', + 'PCTv201-11'), + ('0000000000000000', 'e64221e608be30ab', '53e5ffe553', + 'PCTv201-12'), + ('ffffffffffffffff', '862bc60fdcd4d9a9', '53e5ffe553', + 'PCTv201-13'), + ('0001020304050607', '6a34da50fa5e47de', '53e5ffe553', + 'PCTv201-14'), + ('0011223344556677', '584644c34503122c', '53e5ffe553', + 'PCTv201-15'), +] + +class BufferOverflowTest(unittest.TestCase): + # Test a buffer overflow found in older versions of PyCrypto + + def setUp(self): + global ARC2 + from Crypto.Cipher import ARC2 + + def runTest(self): + """ARC2 with keylength > 128""" + key = "x" * 16384 + mode = ARC2.MODE_ECB + self.assertRaises(ValueError, ARC2.new, key, mode) + +def get_tests(config={}): + from Crypto.Cipher import ARC2 + from common import make_block_tests + + tests = make_block_tests(ARC2, "ARC2", test_data) + tests.append(BufferOverflowTest()) + + return tests + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Cipher/test_ARC4.py b/lib/Crypto/SelfTest/Cipher/test_ARC4.py new file mode 100644 index 0000000..801d2cb --- /dev/null +++ b/lib/Crypto/SelfTest/Cipher/test_ARC4.py @@ -0,0 +1,457 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Cipher/ARC4.py: Self-test for the Alleged-RC4 cipher +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Cipher.ARC4""" + +__revision__ = "$Id$" + +from Crypto.Util.py3compat import * +from Crypto.SelfTest.st_common import * +from binascii import unhexlify + +from Crypto.Cipher import ARC4 + +# This is a list of (plaintext, ciphertext, key[, description]) tuples. +test_data = [ + # Test vectors from Eric Rescorla's message with the subject + # "RC4 compatibility testing", sent to the cipherpunks mailing list on + # September 13, 1994. + # http://cypherpunks.venona.com/date/1994/09/msg00420.html + + ('0123456789abcdef', '75b7878099e0c596', '0123456789abcdef', + 'Test vector 0'), + + ('0000000000000000', '7494c2e7104b0879', '0123456789abcdef', + 'Test vector 1'), + + ('0000000000000000', 'de188941a3375d3a', '0000000000000000', + 'Test vector 2'), + + ('00000000000000000000', 'd6a141a7ec3c38dfbd61', 'ef012345', + 'Test vector 3'), + + ('01' * 512, + '7595c3e6114a09780c4ad452338e1ffd9a1be9498f813d76533449b6778dcad8' + + 'c78a8d2ba9ac66085d0e53d59c26c2d1c490c1ebbe0ce66d1b6b1b13b6b919b8' + + '47c25a91447a95e75e4ef16779cde8bf0a95850e32af9689444fd377108f98fd' + + 'cbd4e726567500990bcc7e0ca3c4aaa304a387d20f3b8fbbcd42a1bd311d7a43' + + '03dda5ab078896ae80c18b0af66dff319616eb784e495ad2ce90d7f772a81747' + + 'b65f62093b1e0db9e5ba532fafec47508323e671327df9444432cb7367cec82f' + + '5d44c0d00b67d650a075cd4b70dedd77eb9b10231b6b5b741347396d62897421' + + 'd43df9b42e446e358e9c11a9b2184ecbef0cd8e7a877ef968f1390ec9b3d35a5' + + '585cb009290e2fcde7b5ec66d9084be44055a619d9dd7fc3166f9487f7cb2729' + + '12426445998514c15d53a18c864ce3a2b7555793988126520eacf2e3066e230c' + + '91bee4dd5304f5fd0405b35bd99c73135d3d9bc335ee049ef69b3867bf2d7bd1' + + 'eaa595d8bfc0066ff8d31509eb0c6caa006c807a623ef84c3d33c195d23ee320' + + 'c40de0558157c822d4b8c569d849aed59d4e0fd7f379586b4b7ff684ed6a189f' + + '7486d49b9c4bad9ba24b96abf924372c8a8fffb10d55354900a77a3db5f205e1' + + 'b99fcd8660863a159ad4abe40fa48934163ddde542a6585540fd683cbfd8c00f' + + '12129a284deacc4cdefe58be7137541c047126c8d49e2755ab181ab7e940b0c0', + '0123456789abcdef', + "Test vector 4"), +] + +class RFC6229_Tests(unittest.TestCase): + # Test vectors from RFC 6229. Each test vector is a tuple with two items: + # the ARC4 key and a dictionary. The dictionary has keystream offsets as keys + # and the 16-byte keystream starting at the relevant offset as value. + rfc6229_data = [ + # Page 3 + ( + '0102030405', + { + 0: 'b2 39 63 05 f0 3d c0 27 cc c3 52 4a 0a 11 18 a8', + 16: '69 82 94 4f 18 fc 82 d5 89 c4 03 a4 7a 0d 09 19', + 240: '28 cb 11 32 c9 6c e2 86 42 1d ca ad b8 b6 9e ae', + 256: '1c fc f6 2b 03 ed db 64 1d 77 df cf 7f 8d 8c 93', + 496: '42 b7 d0 cd d9 18 a8 a3 3d d5 17 81 c8 1f 40 41', + 512: '64 59 84 44 32 a7 da 92 3c fb 3e b4 98 06 61 f6', + 752: 'ec 10 32 7b de 2b ee fd 18 f9 27 76 80 45 7e 22', + 768: 'eb 62 63 8d 4f 0b a1 fe 9f ca 20 e0 5b f8 ff 2b', + 1008:'45 12 90 48 e6 a0 ed 0b 56 b4 90 33 8f 07 8d a5', + 1024:'30 ab bc c7 c2 0b 01 60 9f 23 ee 2d 5f 6b b7 df', + 1520:'32 94 f7 44 d8 f9 79 05 07 e7 0f 62 e5 bb ce ea', + 1536:'d8 72 9d b4 18 82 25 9b ee 4f 82 53 25 f5 a1 30', + 2032:'1e b1 4a 0c 13 b3 bf 47 fa 2a 0b a9 3a d4 5b 8b', + 2048:'cc 58 2f 8b a9 f2 65 e2 b1 be 91 12 e9 75 d2 d7', + 3056:'f2 e3 0f 9b d1 02 ec bf 75 aa ad e9 bc 35 c4 3c', + 3072:'ec 0e 11 c4 79 dc 32 9d c8 da 79 68 fe 96 56 81', + 4080:'06 83 26 a2 11 84 16 d2 1f 9d 04 b2 cd 1c a0 50', + 4096:'ff 25 b5 89 95 99 67 07 e5 1f bd f0 8b 34 d8 75' + } + ), + # Page 4 + ( + '01020304050607', + { + 0: '29 3f 02 d4 7f 37 c9 b6 33 f2 af 52 85 fe b4 6b', + 16: 'e6 20 f1 39 0d 19 bd 84 e2 e0 fd 75 20 31 af c1', + 240: '91 4f 02 53 1c 92 18 81 0d f6 0f 67 e3 38 15 4c', + 256: 'd0 fd b5 83 07 3c e8 5a b8 39 17 74 0e c0 11 d5', + 496: '75 f8 14 11 e8 71 cf fa 70 b9 0c 74 c5 92 e4 54', + 512: '0b b8 72 02 93 8d ad 60 9e 87 a5 a1 b0 79 e5 e4', + 752: 'c2 91 12 46 b6 12 e7 e7 b9 03 df ed a1 da d8 66', + 768: '32 82 8f 91 50 2b 62 91 36 8d e8 08 1d e3 6f c2', + 1008:'f3 b9 a7 e3 b2 97 bf 9a d8 04 51 2f 90 63 ef f1', + 1024:'8e cb 67 a9 ba 1f 55 a5 a0 67 e2 b0 26 a3 67 6f', + 1520:'d2 aa 90 2b d4 2d 0d 7c fd 34 0c d4 58 10 52 9f', + 1536:'78 b2 72 c9 6e 42 ea b4 c6 0b d9 14 e3 9d 06 e3', + 2032:'f4 33 2f d3 1a 07 93 96 ee 3c ee 3f 2a 4f f0 49', + 2048:'05 45 97 81 d4 1f da 7f 30 c1 be 7e 12 46 c6 23', + 3056:'ad fd 38 68 b8 e5 14 85 d5 e6 10 01 7e 3d d6 09', + 3072:'ad 26 58 1c 0c 5b e4 5f 4c ea 01 db 2f 38 05 d5', + 4080:'f3 17 2c ef fc 3b 3d 99 7c 85 cc d5 af 1a 95 0c', + 4096:'e7 4b 0b 97 31 22 7f d3 7c 0e c0 8a 47 dd d8 b8' + } + ), + ( + '0102030405060708', + { + 0: '97 ab 8a 1b f0 af b9 61 32 f2 f6 72 58 da 15 a8', + 16: '82 63 ef db 45 c4 a1 86 84 ef 87 e6 b1 9e 5b 09', + 240: '96 36 eb c9 84 19 26 f4 f7 d1 f3 62 bd df 6e 18', + 256: 'd0 a9 90 ff 2c 05 fe f5 b9 03 73 c9 ff 4b 87 0a', + 496: '73 23 9f 1d b7 f4 1d 80 b6 43 c0 c5 25 18 ec 63', + 512: '16 3b 31 99 23 a6 bd b4 52 7c 62 61 26 70 3c 0f', + 752: '49 d6 c8 af 0f 97 14 4a 87 df 21 d9 14 72 f9 66', + 768: '44 17 3a 10 3b 66 16 c5 d5 ad 1c ee 40 c8 63 d0', + 1008:'27 3c 9c 4b 27 f3 22 e4 e7 16 ef 53 a4 7d e7 a4', + 1024:'c6 d0 e7 b2 26 25 9f a9 02 34 90 b2 61 67 ad 1d', + 1520:'1f e8 98 67 13 f0 7c 3d 9a e1 c1 63 ff 8c f9 d3', + 1536:'83 69 e1 a9 65 61 0b e8 87 fb d0 c7 91 62 aa fb', + 2032:'0a 01 27 ab b4 44 84 b9 fb ef 5a bc ae 1b 57 9f', + 2048:'c2 cd ad c6 40 2e 8e e8 66 e1 f3 7b db 47 e4 2c', + 3056:'26 b5 1e a3 7d f8 e1 d6 f7 6f c3 b6 6a 74 29 b3', + 3072:'bc 76 83 20 5d 4f 44 3d c1 f2 9d da 33 15 c8 7b', + 4080:'d5 fa 5a 34 69 d2 9a aa f8 3d 23 58 9d b8 c8 5b', + 4096:'3f b4 6e 2c 8f 0f 06 8e dc e8 cd cd 7d fc 58 62' + } + ), + # Page 5 + ( + '0102030405060708090a', + { + 0: 'ed e3 b0 46 43 e5 86 cc 90 7d c2 18 51 70 99 02', + 16: '03 51 6b a7 8f 41 3b eb 22 3a a5 d4 d2 df 67 11', + 240: '3c fd 6c b5 8e e0 fd de 64 01 76 ad 00 00 04 4d', + 256: '48 53 2b 21 fb 60 79 c9 11 4c 0f fd 9c 04 a1 ad', + 496: '3e 8c ea 98 01 71 09 97 90 84 b1 ef 92 f9 9d 86', + 512: 'e2 0f b4 9b db 33 7e e4 8b 8d 8d c0 f4 af ef fe', + 752: '5c 25 21 ea cd 79 66 f1 5e 05 65 44 be a0 d3 15', + 768: 'e0 67 a7 03 19 31 a2 46 a6 c3 87 5d 2f 67 8a cb', + 1008:'a6 4f 70 af 88 ae 56 b6 f8 75 81 c0 e2 3e 6b 08', + 1024:'f4 49 03 1d e3 12 81 4e c6 f3 19 29 1f 4a 05 16', + 1520:'bd ae 85 92 4b 3c b1 d0 a2 e3 3a 30 c6 d7 95 99', + 1536:'8a 0f ed db ac 86 5a 09 bc d1 27 fb 56 2e d6 0a', + 2032:'b5 5a 0a 5b 51 a1 2a 8b e3 48 99 c3 e0 47 51 1a', + 2048:'d9 a0 9c ea 3c e7 5f e3 96 98 07 03 17 a7 13 39', + 3056:'55 22 25 ed 11 77 f4 45 84 ac 8c fa 6c 4e b5 fc', + 3072:'7e 82 cb ab fc 95 38 1b 08 09 98 44 21 29 c2 f8', + 4080:'1f 13 5e d1 4c e6 0a 91 36 9d 23 22 be f2 5e 3c', + 4096:'08 b6 be 45 12 4a 43 e2 eb 77 95 3f 84 dc 85 53' + } + ), + ( + '0102030405060708090a0b0c0d0e0f10', + { + 0: '9a c7 cc 9a 60 9d 1e f7 b2 93 28 99 cd e4 1b 97', + 16: '52 48 c4 95 90 14 12 6a 6e 8a 84 f1 1d 1a 9e 1c', + 240: '06 59 02 e4 b6 20 f6 cc 36 c8 58 9f 66 43 2f 2b', + 256: 'd3 9d 56 6b c6 bc e3 01 07 68 15 15 49 f3 87 3f', + 496: 'b6 d1 e6 c4 a5 e4 77 1c ad 79 53 8d f2 95 fb 11', + 512: 'c6 8c 1d 5c 55 9a 97 41 23 df 1d bc 52 a4 3b 89', + 752: 'c5 ec f8 8d e8 97 fd 57 fe d3 01 70 1b 82 a2 59', + 768: 'ec cb e1 3d e1 fc c9 1c 11 a0 b2 6c 0b c8 fa 4d', + 1008:'e7 a7 25 74 f8 78 2a e2 6a ab cf 9e bc d6 60 65', + 1024:'bd f0 32 4e 60 83 dc c6 d3 ce dd 3c a8 c5 3c 16', + 1520:'b4 01 10 c4 19 0b 56 22 a9 61 16 b0 01 7e d2 97', + 1536:'ff a0 b5 14 64 7e c0 4f 63 06 b8 92 ae 66 11 81', + 2032:'d0 3d 1b c0 3c d3 3d 70 df f9 fa 5d 71 96 3e bd', + 2048:'8a 44 12 64 11 ea a7 8b d5 1e 8d 87 a8 87 9b f5', + 3056:'fa be b7 60 28 ad e2 d0 e4 87 22 e4 6c 46 15 a3', + 3072:'c0 5d 88 ab d5 03 57 f9 35 a6 3c 59 ee 53 76 23', + 4080:'ff 38 26 5c 16 42 c1 ab e8 d3 c2 fe 5e 57 2b f8', + 4096:'a3 6a 4c 30 1a e8 ac 13 61 0c cb c1 22 56 ca cc' + } + ), + # Page 6 + ( + '0102030405060708090a0b0c0d0e0f101112131415161718', + { + 0: '05 95 e5 7f e5 f0 bb 3c 70 6e da c8 a4 b2 db 11', + 16: 'df de 31 34 4a 1a f7 69 c7 4f 07 0a ee 9e 23 26', + 240: 'b0 6b 9b 1e 19 5d 13 d8 f4 a7 99 5c 45 53 ac 05', + 256: '6b d2 37 8e c3 41 c9 a4 2f 37 ba 79 f8 8a 32 ff', + 496: 'e7 0b ce 1d f7 64 5a db 5d 2c 41 30 21 5c 35 22', + 512: '9a 57 30 c7 fc b4 c9 af 51 ff da 89 c7 f1 ad 22', + 752: '04 85 05 5f d4 f6 f0 d9 63 ef 5a b9 a5 47 69 82', + 768: '59 1f c6 6b cd a1 0e 45 2b 03 d4 55 1f 6b 62 ac', + 1008:'27 53 cc 83 98 8a fa 3e 16 88 a1 d3 b4 2c 9a 02', + 1024:'93 61 0d 52 3d 1d 3f 00 62 b3 c2 a3 bb c7 c7 f0', + 1520:'96 c2 48 61 0a ad ed fe af 89 78 c0 3d e8 20 5a', + 1536:'0e 31 7b 3d 1c 73 b9 e9 a4 68 8f 29 6d 13 3a 19', + 2032:'bd f0 e6 c3 cc a5 b5 b9 d5 33 b6 9c 56 ad a1 20', + 2048:'88 a2 18 b6 e2 ec e1 e6 24 6d 44 c7 59 d1 9b 10', + 3056:'68 66 39 7e 95 c1 40 53 4f 94 26 34 21 00 6e 40', + 3072:'32 cb 0a 1e 95 42 c6 b3 b8 b3 98 ab c3 b0 f1 d5', + 4080:'29 a0 b8 ae d5 4a 13 23 24 c6 2e 42 3f 54 b4 c8', + 4096:'3c b0 f3 b5 02 0a 98 b8 2a f9 fe 15 44 84 a1 68' + } + ), + ( + '0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20', + { + 0: 'ea a6 bd 25 88 0b f9 3d 3f 5d 1e 4c a2 61 1d 91', + 16: 'cf a4 5c 9f 7e 71 4b 54 bd fa 80 02 7c b1 43 80', + 240: '11 4a e3 44 de d7 1b 35 f2 e6 0f eb ad 72 7f d8', + 256: '02 e1 e7 05 6b 0f 62 39 00 49 64 22 94 3e 97 b6', + 496: '91 cb 93 c7 87 96 4e 10 d9 52 7d 99 9c 6f 93 6b', + 512: '49 b1 8b 42 f8 e8 36 7c be b5 ef 10 4b a1 c7 cd', + 752: '87 08 4b 3b a7 00 ba de 95 56 10 67 27 45 b3 74', + 768: 'e7 a7 b9 e9 ec 54 0d 5f f4 3b db 12 79 2d 1b 35', + 1008:'c7 99 b5 96 73 8f 6b 01 8c 76 c7 4b 17 59 bd 90', + 1024:'7f ec 5b fd 9f 9b 89 ce 65 48 30 90 92 d7 e9 58', + 1520:'40 f2 50 b2 6d 1f 09 6a 4a fd 4c 34 0a 58 88 15', + 1536:'3e 34 13 5c 79 db 01 02 00 76 76 51 cf 26 30 73', + 2032:'f6 56 ab cc f8 8d d8 27 02 7b 2c e9 17 d4 64 ec', + 2048:'18 b6 25 03 bf bc 07 7f ba bb 98 f2 0d 98 ab 34', + 3056:'8a ed 95 ee 5b 0d cb fb ef 4e b2 1d 3a 3f 52 f9', + 3072:'62 5a 1a b0 0e e3 9a 53 27 34 6b dd b0 1a 9c 18', + 4080:'a1 3a 7c 79 c7 e1 19 b5 ab 02 96 ab 28 c3 00 b9', + 4096:'f3 e4 c0 a2 e0 2d 1d 01 f7 f0 a7 46 18 af 2b 48' + } + ), + # Page 7 + ( + '833222772a', + { + 0: '80 ad 97 bd c9 73 df 8a 2e 87 9e 92 a4 97 ef da', + 16: '20 f0 60 c2 f2 e5 12 65 01 d3 d4 fe a1 0d 5f c0', + 240: 'fa a1 48 e9 90 46 18 1f ec 6b 20 85 f3 b2 0e d9', + 256: 'f0 da f5 ba b3 d5 96 83 98 57 84 6f 73 fb fe 5a', + 496: '1c 7e 2f c4 63 92 32 fe 29 75 84 b2 96 99 6b c8', + 512: '3d b9 b2 49 40 6c c8 ed ff ac 55 cc d3 22 ba 12', + 752: 'e4 f9 f7 e0 06 61 54 bb d1 25 b7 45 56 9b c8 97', + 768: '75 d5 ef 26 2b 44 c4 1a 9c f6 3a e1 45 68 e1 b9', + 1008:'6d a4 53 db f8 1e 82 33 4a 3d 88 66 cb 50 a1 e3', + 1024:'78 28 d0 74 11 9c ab 5c 22 b2 94 d7 a9 bf a0 bb', + 1520:'ad b8 9c ea 9a 15 fb e6 17 29 5b d0 4b 8c a0 5c', + 1536:'62 51 d8 7f d4 aa ae 9a 7e 4a d5 c2 17 d3 f3 00', + 2032:'e7 11 9b d6 dd 9b 22 af e8 f8 95 85 43 28 81 e2', + 2048:'78 5b 60 fd 7e c4 e9 fc b6 54 5f 35 0d 66 0f ab', + 3056:'af ec c0 37 fd b7 b0 83 8e b3 d7 0b cd 26 83 82', + 3072:'db c1 a7 b4 9d 57 35 8c c9 fa 6d 61 d7 3b 7c f0', + 4080:'63 49 d1 26 a3 7a fc ba 89 79 4f 98 04 91 4f dc', + 4096:'bf 42 c3 01 8c 2f 7c 66 bf de 52 49 75 76 81 15' + } + ), + ( + '1910833222772a', + { + 0: 'bc 92 22 db d3 27 4d 8f c6 6d 14 cc bd a6 69 0b', + 16: '7a e6 27 41 0c 9a 2b e6 93 df 5b b7 48 5a 63 e3', + 240: '3f 09 31 aa 03 de fb 30 0f 06 01 03 82 6f 2a 64', + 256: 'be aa 9e c8 d5 9b b6 81 29 f3 02 7c 96 36 11 81', + 496: '74 e0 4d b4 6d 28 64 8d 7d ee 8a 00 64 b0 6c fe', + 512: '9b 5e 81 c6 2f e0 23 c5 5b e4 2f 87 bb f9 32 b8', + 752: 'ce 17 8f c1 82 6e fe cb c1 82 f5 79 99 a4 61 40', + 768: '8b df 55 cd 55 06 1c 06 db a6 be 11 de 4a 57 8a', + 1008:'62 6f 5f 4d ce 65 25 01 f3 08 7d 39 c9 2c c3 49', + 1024:'42 da ac 6a 8f 9a b9 a7 fd 13 7c 60 37 82 56 82', + 1520:'cc 03 fd b7 91 92 a2 07 31 2f 53 f5 d4 dc 33 d9', + 1536:'f7 0f 14 12 2a 1c 98 a3 15 5d 28 b8 a0 a8 a4 1d', + 2032:'2a 3a 30 7a b2 70 8a 9c 00 fe 0b 42 f9 c2 d6 a1', + 2048:'86 26 17 62 7d 22 61 ea b0 b1 24 65 97 ca 0a e9', + 3056:'55 f8 77 ce 4f 2e 1d db bf 8e 13 e2 cd e0 fd c8', + 3072:'1b 15 56 cb 93 5f 17 33 37 70 5f bb 5d 50 1f c1', + 4080:'ec d0 e9 66 02 be 7f 8d 50 92 81 6c cc f2 c2 e9', + 4096:'02 78 81 fa b4 99 3a 1c 26 20 24 a9 4f ff 3f 61' + } + ), + # Page 8 + ( + '641910833222772a', + { + 0: 'bb f6 09 de 94 13 17 2d 07 66 0c b6 80 71 69 26', + 16: '46 10 1a 6d ab 43 11 5d 6c 52 2b 4f e9 36 04 a9', + 240: 'cb e1 ff f2 1c 96 f3 ee f6 1e 8f e0 54 2c bd f0', + 256: '34 79 38 bf fa 40 09 c5 12 cf b4 03 4b 0d d1 a7', + 496: '78 67 a7 86 d0 0a 71 47 90 4d 76 dd f1 e5 20 e3', + 512: '8d 3e 9e 1c ae fc cc b3 fb f8 d1 8f 64 12 0b 32', + 752: '94 23 37 f8 fd 76 f0 fa e8 c5 2d 79 54 81 06 72', + 768: 'b8 54 8c 10 f5 16 67 f6 e6 0e 18 2f a1 9b 30 f7', + 1008:'02 11 c7 c6 19 0c 9e fd 12 37 c3 4c 8f 2e 06 c4', + 1024:'bd a6 4f 65 27 6d 2a ac b8 f9 02 12 20 3a 80 8e', + 1520:'bd 38 20 f7 32 ff b5 3e c1 93 e7 9d 33 e2 7c 73', + 1536:'d0 16 86 16 86 19 07 d4 82 e3 6c da c8 cf 57 49', + 2032:'97 b0 f0 f2 24 b2 d2 31 71 14 80 8f b0 3a f7 a0', + 2048:'e5 96 16 e4 69 78 79 39 a0 63 ce ea 9a f9 56 d1', + 3056:'c4 7e 0d c1 66 09 19 c1 11 01 20 8f 9e 69 aa 1f', + 3072:'5a e4 f1 28 96 b8 37 9a 2a ad 89 b5 b5 53 d6 b0', + 4080:'6b 6b 09 8d 0c 29 3b c2 99 3d 80 bf 05 18 b6 d9', + 4096:'81 70 cc 3c cd 92 a6 98 62 1b 93 9d d3 8f e7 b9' + } + ), + ( + '8b37641910833222772a', + { + 0: 'ab 65 c2 6e dd b2 87 60 0d b2 fd a1 0d 1e 60 5c', + 16: 'bb 75 90 10 c2 96 58 f2 c7 2d 93 a2 d1 6d 29 30', + 240: 'b9 01 e8 03 6e d1 c3 83 cd 3c 4c 4d d0 a6 ab 05', + 256: '3d 25 ce 49 22 92 4c 55 f0 64 94 33 53 d7 8a 6c', + 496: '12 c1 aa 44 bb f8 7e 75 e6 11 f6 9b 2c 38 f4 9b', + 512: '28 f2 b3 43 4b 65 c0 98 77 47 00 44 c6 ea 17 0d', + 752: 'bd 9e f8 22 de 52 88 19 61 34 cf 8a f7 83 93 04', + 768: '67 55 9c 23 f0 52 15 84 70 a2 96 f7 25 73 5a 32', + 1008:'8b ab 26 fb c2 c1 2b 0f 13 e2 ab 18 5e ab f2 41', + 1024:'31 18 5a 6d 69 6f 0c fa 9b 42 80 8b 38 e1 32 a2', + 1520:'56 4d 3d ae 18 3c 52 34 c8 af 1e 51 06 1c 44 b5', + 1536:'3c 07 78 a7 b5 f7 2d 3c 23 a3 13 5c 7d 67 b9 f4', + 2032:'f3 43 69 89 0f cf 16 fb 51 7d ca ae 44 63 b2 dd', + 2048:'02 f3 1c 81 e8 20 07 31 b8 99 b0 28 e7 91 bf a7', + 3056:'72 da 64 62 83 22 8c 14 30 08 53 70 17 95 61 6f', + 3072:'4e 0a 8c 6f 79 34 a7 88 e2 26 5e 81 d6 d0 c8 f4', + 4080:'43 8d d5 ea fe a0 11 1b 6f 36 b4 b9 38 da 2a 68', + 4096:'5f 6b fc 73 81 58 74 d9 71 00 f0 86 97 93 57 d8' + } + ), + # Page 9 + ( + 'ebb46227c6cc8b37641910833222772a', + { + 0: '72 0c 94 b6 3e df 44 e1 31 d9 50 ca 21 1a 5a 30', + 16: 'c3 66 fd ea cf 9c a8 04 36 be 7c 35 84 24 d2 0b', + 240: 'b3 39 4a 40 aa bf 75 cb a4 22 82 ef 25 a0 05 9f', + 256: '48 47 d8 1d a4 94 2d bc 24 9d ef c4 8c 92 2b 9f', + 496: '08 12 8c 46 9f 27 53 42 ad da 20 2b 2b 58 da 95', + 512: '97 0d ac ef 40 ad 98 72 3b ac 5d 69 55 b8 17 61', + 752: '3c b8 99 93 b0 7b 0c ed 93 de 13 d2 a1 10 13 ac', + 768: 'ef 2d 67 6f 15 45 c2 c1 3d c6 80 a0 2f 4a db fe', + 1008:'b6 05 95 51 4f 24 bc 9f e5 22 a6 ca d7 39 36 44', + 1024:'b5 15 a8 c5 01 17 54 f5 90 03 05 8b db 81 51 4e', + 1520:'3c 70 04 7e 8c bc 03 8e 3b 98 20 db 60 1d a4 95', + 1536:'11 75 da 6e e7 56 de 46 a5 3e 2b 07 56 60 b7 70', + 2032:'00 a5 42 bb a0 21 11 cc 2c 65 b3 8e bd ba 58 7e', + 2048:'58 65 fd bb 5b 48 06 41 04 e8 30 b3 80 f2 ae de', + 3056:'34 b2 1a d2 ad 44 e9 99 db 2d 7f 08 63 f0 d9 b6', + 3072:'84 a9 21 8f c3 6e 8a 5f 2c cf be ae 53 a2 7d 25', + 4080:'a2 22 1a 11 b8 33 cc b4 98 a5 95 40 f0 54 5f 4a', + 4096:'5b be b4 78 7d 59 e5 37 3f db ea 6c 6f 75 c2 9b' + } + ), + ( + 'c109163908ebe51debb46227c6cc8b37641910833222772a', + { + 0: '54 b6 4e 6b 5a 20 b5 e2 ec 84 59 3d c7 98 9d a7', + 16: 'c1 35 ee e2 37 a8 54 65 ff 97 dc 03 92 4f 45 ce', + 240: 'cf cc 92 2f b4 a1 4a b4 5d 61 75 aa bb f2 d2 01', + 256: '83 7b 87 e2 a4 46 ad 0e f7 98 ac d0 2b 94 12 4f', + 496: '17 a6 db d6 64 92 6a 06 36 b3 f4 c3 7a 4f 46 94', + 512: '4a 5f 9f 26 ae ee d4 d4 a2 5f 63 2d 30 52 33 d9', + 752: '80 a3 d0 1e f0 0c 8e 9a 42 09 c1 7f 4e eb 35 8c', + 768: 'd1 5e 7d 5f fa aa bc 02 07 bf 20 0a 11 77 93 a2', + 1008:'34 96 82 bf 58 8e aa 52 d0 aa 15 60 34 6a ea fa', + 1024:'f5 85 4c db 76 c8 89 e3 ad 63 35 4e 5f 72 75 e3', + 1520:'53 2c 7c ec cb 39 df 32 36 31 84 05 a4 b1 27 9c', + 1536:'ba ef e6 d9 ce b6 51 84 22 60 e0 d1 e0 5e 3b 90', + 2032:'e8 2d 8c 6d b5 4e 3c 63 3f 58 1c 95 2b a0 42 07', + 2048:'4b 16 e5 0a bd 38 1b d7 09 00 a9 cd 9a 62 cb 23', + 3056:'36 82 ee 33 bd 14 8b d9 f5 86 56 cd 8f 30 d9 fb', + 3072:'1e 5a 0b 84 75 04 5d 9b 20 b2 62 86 24 ed fd 9e', + 4080:'63 ed d6 84 fb 82 62 82 fe 52 8f 9c 0e 92 37 bc', + 4096:'e4 dd 2e 98 d6 96 0f ae 0b 43 54 54 56 74 33 91' + } + ), + # Page 10 + ( + '1ada31d5cf688221c109163908ebe51debb46227c6cc8b37641910833222772a', + { + 0: 'dd 5b cb 00 18 e9 22 d4 94 75 9d 7c 39 5d 02 d3', + 16: 'c8 44 6f 8f 77 ab f7 37 68 53 53 eb 89 a1 c9 eb', + 240: 'af 3e 30 f9 c0 95 04 59 38 15 15 75 c3 fb 90 98', + 256: 'f8 cb 62 74 db 99 b8 0b 1d 20 12 a9 8e d4 8f 0e', + 496: '25 c3 00 5a 1c b8 5d e0 76 25 98 39 ab 71 98 ab', + 512: '9d cb c1 83 e8 cb 99 4b 72 7b 75 be 31 80 76 9c', + 752: 'a1 d3 07 8d fa 91 69 50 3e d9 d4 49 1d ee 4e b2', + 768: '85 14 a5 49 58 58 09 6f 59 6e 4b cd 66 b1 06 65', + 1008:'5f 40 d5 9e c1 b0 3b 33 73 8e fa 60 b2 25 5d 31', + 1024:'34 77 c7 f7 64 a4 1b ac ef f9 0b f1 4f 92 b7 cc', + 1520:'ac 4e 95 36 8d 99 b9 eb 78 b8 da 8f 81 ff a7 95', + 1536:'8c 3c 13 f8 c2 38 8b b7 3f 38 57 6e 65 b7 c4 46', + 2032:'13 c4 b9 c1 df b6 65 79 ed dd 8a 28 0b 9f 73 16', + 2048:'dd d2 78 20 55 01 26 69 8e fa ad c6 4b 64 f6 6e', + 3056:'f0 8f 2e 66 d2 8e d1 43 f3 a2 37 cf 9d e7 35 59', + 3072:'9e a3 6c 52 55 31 b8 80 ba 12 43 34 f5 7b 0b 70', + 4080:'d5 a3 9e 3d fc c5 02 80 ba c4 a6 b5 aa 0d ca 7d', + 4096:'37 0b 1c 1f e6 55 91 6d 97 fd 0d 47 ca 1d 72 b8' + } + ) + ] + + def test_keystream(self): + for tv in self.rfc6229_data: + key = unhexlify(b((tv[0]))) + cipher = ARC4.new(key) + count = 0 + for offset in range(0,4096+1,16): + ct = cipher.encrypt(b('\x00')*16) + expected = tv[1].get(offset) + if expected: + expected = unhexlify(b(expected.replace(" ",''))) + self.assertEquals(ct, expected) + count += 1 + self.assertEqual(count, len(tv[1])) + +class Drop_Tests(unittest.TestCase): + key = b('\xAA')*16 + data = b('\x00')*5000 + + def setUp(self): + self.cipher = ARC4.new(self.key) + + def test_drop256_encrypt(self): + cipher_drop = ARC4.new(self.key, 256) + ct_drop = cipher_drop.encrypt(self.data[:16]) + ct = self.cipher.encrypt(self.data)[256:256+16] + self.assertEquals(ct_drop, ct) + + def test_drop256_decrypt(self): + cipher_drop = ARC4.new(self.key, 256) + pt_drop = cipher_drop.decrypt(self.data[:16]) + pt = self.cipher.decrypt(self.data)[256:256+16] + self.assertEquals(pt_drop, pt) + +def get_tests(config={}): + from common import make_stream_tests + tests = make_stream_tests(ARC4, "ARC4", test_data) + tests += list_test_cases(RFC6229_Tests) + tests += list_test_cases(Drop_Tests) + return tests + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Cipher/test_Blowfish.py b/lib/Crypto/SelfTest/Cipher/test_Blowfish.py new file mode 100644 index 0000000..e8f73a6 --- /dev/null +++ b/lib/Crypto/SelfTest/Cipher/test_Blowfish.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Cipher/test_Blowfish.py: Self-test for the Blowfish cipher +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Cipher.Blowfish""" + +__revision__ = "$Id$" + +from Crypto.Util.py3compat import * + +# This is a list of (plaintext, ciphertext, key) tuples. +test_data = [ + # Test vectors from http://www.schneier.com/code/vectors.txt + ('0000000000000000', '4ef997456198dd78', '0000000000000000'), + ('ffffffffffffffff', '51866fd5b85ecb8a', 'ffffffffffffffff'), + ('1000000000000001', '7d856f9a613063f2', '3000000000000000'), + ('1111111111111111', '2466dd878b963c9d', '1111111111111111'), + ('1111111111111111', '61f9c3802281b096', '0123456789abcdef'), + ('0123456789abcdef', '7d0cc630afda1ec7', '1111111111111111'), + ('0000000000000000', '4ef997456198dd78', '0000000000000000'), + ('0123456789abcdef', '0aceab0fc6a0a28d', 'fedcba9876543210'), + ('01a1d6d039776742', '59c68245eb05282b', '7ca110454a1a6e57'), + ('5cd54ca83def57da', 'b1b8cc0b250f09a0', '0131d9619dc1376e'), + ('0248d43806f67172', '1730e5778bea1da4', '07a1133e4a0b2686'), + ('51454b582ddf440a', 'a25e7856cf2651eb', '3849674c2602319e'), + ('42fd443059577fa2', '353882b109ce8f1a', '04b915ba43feb5b6'), + ('059b5e0851cf143a', '48f4d0884c379918', '0113b970fd34f2ce'), + ('0756d8e0774761d2', '432193b78951fc98', '0170f175468fb5e6'), + ('762514b829bf486a', '13f04154d69d1ae5', '43297fad38e373fe'), + ('3bdd119049372802', '2eedda93ffd39c79', '07a7137045da2a16'), + ('26955f6835af609a', 'd887e0393c2da6e3', '04689104c2fd3b2f'), + ('164d5e404f275232', '5f99d04f5b163969', '37d06bb516cb7546'), + ('6b056e18759f5cca', '4a057a3b24d3977b', '1f08260d1ac2465e'), + ('004bd6ef09176062', '452031c1e4fada8e', '584023641aba6176'), + ('480d39006ee762f2', '7555ae39f59b87bd', '025816164629b007'), + ('437540c8698f3cfa', '53c55f9cb49fc019', '49793ebc79b3258f'), + ('072d43a077075292', '7a8e7bfa937e89a3', '4fb05e1515ab73a7'), + ('02fe55778117f12a', 'cf9c5d7a4986adb5', '49e95d6d4ca229bf'), + ('1d9d5c5018f728c2', 'd1abb290658bc778', '018310dc409b26d6'), + ('305532286d6f295a', '55cb3774d13ef201', '1c587f1c13924fef'), + ('0123456789abcdef', 'fa34ec4847b268b2', '0101010101010101'), + ('0123456789abcdef', 'a790795108ea3cae', '1f1f1f1f0e0e0e0e'), + ('0123456789abcdef', 'c39e072d9fac631d', 'e0fee0fef1fef1fe'), + ('ffffffffffffffff', '014933e0cdaff6e4', '0000000000000000'), + ('0000000000000000', 'f21e9a77b71c49bc', 'ffffffffffffffff'), + ('0000000000000000', '245946885754369a', '0123456789abcdef'), + ('ffffffffffffffff', '6b5c5a9c5d9e0a5a', 'fedcba9876543210'), + ('fedcba9876543210', 'f9ad597c49db005e', 'f0'), + ('fedcba9876543210', 'e91d21c1d961a6d6', 'f0e1'), + ('fedcba9876543210', 'e9c2b70a1bc65cf3', 'f0e1d2'), + ('fedcba9876543210', 'be1e639408640f05', 'f0e1d2c3'), + ('fedcba9876543210', 'b39e44481bdb1e6e', 'f0e1d2c3b4'), + ('fedcba9876543210', '9457aa83b1928c0d', 'f0e1d2c3b4a5'), + ('fedcba9876543210', '8bb77032f960629d', 'f0e1d2c3b4a596'), + ('fedcba9876543210', 'e87a244e2cc85e82', 'f0e1d2c3b4a59687'), + ('fedcba9876543210', '15750e7a4f4ec577', 'f0e1d2c3b4a5968778'), + ('fedcba9876543210', '122ba70b3ab64ae0', 'f0e1d2c3b4a596877869'), + ('fedcba9876543210', '3a833c9affc537f6', 'f0e1d2c3b4a5968778695a'), + ('fedcba9876543210', '9409da87a90f6bf2', 'f0e1d2c3b4a5968778695a4b'), + ('fedcba9876543210', '884f80625060b8b4', 'f0e1d2c3b4a5968778695a4b3c'), + ('fedcba9876543210', '1f85031c19e11968', 'f0e1d2c3b4a5968778695a4b3c2d'), + ('fedcba9876543210', '79d9373a714ca34f', 'f0e1d2c3b4a5968778695a4b3c2d1e'), + ('fedcba9876543210', '93142887ee3be15c', + 'f0e1d2c3b4a5968778695a4b3c2d1e0f'), + ('fedcba9876543210', '03429e838ce2d14b', + 'f0e1d2c3b4a5968778695a4b3c2d1e0f00'), + ('fedcba9876543210', 'a4299e27469ff67b', + 'f0e1d2c3b4a5968778695a4b3c2d1e0f0011'), + ('fedcba9876543210', 'afd5aed1c1bc96a8', + 'f0e1d2c3b4a5968778695a4b3c2d1e0f001122'), + ('fedcba9876543210', '10851c0e3858da9f', + 'f0e1d2c3b4a5968778695a4b3c2d1e0f00112233'), + ('fedcba9876543210', 'e6f51ed79b9db21f', + 'f0e1d2c3b4a5968778695a4b3c2d1e0f0011223344'), + ('fedcba9876543210', '64a6e14afd36b46f', + 'f0e1d2c3b4a5968778695a4b3c2d1e0f001122334455'), + ('fedcba9876543210', '80c7d7d45a5479ad', + 'f0e1d2c3b4a5968778695a4b3c2d1e0f00112233445566'), + ('fedcba9876543210', '05044b62fa52d080', + 'f0e1d2c3b4a5968778695a4b3c2d1e0f0011223344556677'), +] + +def get_tests(config={}): + from Crypto.Cipher import Blowfish + from common import make_block_tests + return make_block_tests(Blowfish, "Blowfish", test_data) + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Cipher/test_CAST.py b/lib/Crypto/SelfTest/Cipher/test_CAST.py new file mode 100644 index 0000000..1cfcec0 --- /dev/null +++ b/lib/Crypto/SelfTest/Cipher/test_CAST.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Cipher/CAST.py: Self-test for the CAST-128 (CAST5) cipher +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Cipher.CAST""" + +__revision__ = "$Id$" + +from Crypto.Util.py3compat import * + +# This is a list of (plaintext, ciphertext, key) tuples. +test_data = [ + # Test vectors from RFC 2144, B.1 + ('0123456789abcdef', '238b4fe5847e44b2', + '0123456712345678234567893456789a', + '128-bit key'), + + ('0123456789abcdef', 'eb6a711a2c02271b', + '01234567123456782345', + '80-bit key'), + + ('0123456789abcdef', '7ac816d16e9b302e', + '0123456712', + '40-bit key'), +] + +def get_tests(config={}): + from Crypto.Cipher import CAST + from common import make_block_tests + return make_block_tests(CAST, "CAST", test_data) + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Cipher/test_DES.py b/lib/Crypto/SelfTest/Cipher/test_DES.py new file mode 100644 index 0000000..c5d114b --- /dev/null +++ b/lib/Crypto/SelfTest/Cipher/test_DES.py @@ -0,0 +1,339 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Cipher/DES.py: Self-test for the (Single) DES cipher +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Cipher.DES""" + +__revision__ = "$Id$" + +from common import dict # For compatibility with Python 2.1 and 2.2 +from Crypto.Util.py3compat import * +import unittest + +# This is a list of (plaintext, ciphertext, key, description) tuples. +SP800_17_B1_KEY = '01' * 8 +SP800_17_B2_PT = '00' * 8 +test_data = [ + # Test vectors from Appendix A of NIST SP 800-17 + # "Modes of Operation Validation System (MOVS): Requirements and Procedures" + # http://csrc.nist.gov/publications/nistpubs/800-17/800-17.pdf + + # Appendix A - "Sample Round Outputs for the DES" + ('0000000000000000', '82dcbafbdeab6602', '10316e028c8f3b4a', + "NIST SP800-17 A"), + + # Table B.1 - Variable Plaintext Known Answer Test + ('8000000000000000', '95f8a5e5dd31d900', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #0'), + ('4000000000000000', 'dd7f121ca5015619', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #1'), + ('2000000000000000', '2e8653104f3834ea', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #2'), + ('1000000000000000', '4bd388ff6cd81d4f', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #3'), + ('0800000000000000', '20b9e767b2fb1456', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #4'), + ('0400000000000000', '55579380d77138ef', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #5'), + ('0200000000000000', '6cc5defaaf04512f', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #6'), + ('0100000000000000', '0d9f279ba5d87260', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #7'), + ('0080000000000000', 'd9031b0271bd5a0a', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #8'), + ('0040000000000000', '424250b37c3dd951', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #9'), + ('0020000000000000', 'b8061b7ecd9a21e5', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #10'), + ('0010000000000000', 'f15d0f286b65bd28', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #11'), + ('0008000000000000', 'add0cc8d6e5deba1', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #12'), + ('0004000000000000', 'e6d5f82752ad63d1', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #13'), + ('0002000000000000', 'ecbfe3bd3f591a5e', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #14'), + ('0001000000000000', 'f356834379d165cd', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #15'), + ('0000800000000000', '2b9f982f20037fa9', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #16'), + ('0000400000000000', '889de068a16f0be6', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #17'), + ('0000200000000000', 'e19e275d846a1298', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #18'), + ('0000100000000000', '329a8ed523d71aec', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #19'), + ('0000080000000000', 'e7fce22557d23c97', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #20'), + ('0000040000000000', '12a9f5817ff2d65d', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #21'), + ('0000020000000000', 'a484c3ad38dc9c19', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #22'), + ('0000010000000000', 'fbe00a8a1ef8ad72', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #23'), + ('0000008000000000', '750d079407521363', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #24'), + ('0000004000000000', '64feed9c724c2faf', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #25'), + ('0000002000000000', 'f02b263b328e2b60', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #26'), + ('0000001000000000', '9d64555a9a10b852', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #27'), + ('0000000800000000', 'd106ff0bed5255d7', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #28'), + ('0000000400000000', 'e1652c6b138c64a5', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #29'), + ('0000000200000000', 'e428581186ec8f46', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #30'), + ('0000000100000000', 'aeb5f5ede22d1a36', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #31'), + ('0000000080000000', 'e943d7568aec0c5c', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #32'), + ('0000000040000000', 'df98c8276f54b04b', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #33'), + ('0000000020000000', 'b160e4680f6c696f', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #34'), + ('0000000010000000', 'fa0752b07d9c4ab8', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #35'), + ('0000000008000000', 'ca3a2b036dbc8502', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #36'), + ('0000000004000000', '5e0905517bb59bcf', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #37'), + ('0000000002000000', '814eeb3b91d90726', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #38'), + ('0000000001000000', '4d49db1532919c9f', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #39'), + ('0000000000800000', '25eb5fc3f8cf0621', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #40'), + ('0000000000400000', 'ab6a20c0620d1c6f', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #41'), + ('0000000000200000', '79e90dbc98f92cca', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #42'), + ('0000000000100000', '866ecedd8072bb0e', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #43'), + ('0000000000080000', '8b54536f2f3e64a8', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #44'), + ('0000000000040000', 'ea51d3975595b86b', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #45'), + ('0000000000020000', 'caffc6ac4542de31', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #46'), + ('0000000000010000', '8dd45a2ddf90796c', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #47'), + ('0000000000008000', '1029d55e880ec2d0', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #48'), + ('0000000000004000', '5d86cb23639dbea9', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #49'), + ('0000000000002000', '1d1ca853ae7c0c5f', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #50'), + ('0000000000001000', 'ce332329248f3228', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #51'), + ('0000000000000800', '8405d1abe24fb942', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #52'), + ('0000000000000400', 'e643d78090ca4207', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #53'), + ('0000000000000200', '48221b9937748a23', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #54'), + ('0000000000000100', 'dd7c0bbd61fafd54', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #55'), + ('0000000000000080', '2fbc291a570db5c4', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #56'), + ('0000000000000040', 'e07c30d7e4e26e12', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #57'), + ('0000000000000020', '0953e2258e8e90a1', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #58'), + ('0000000000000010', '5b711bc4ceebf2ee', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #59'), + ('0000000000000008', 'cc083f1e6d9e85f6', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #60'), + ('0000000000000004', 'd2fd8867d50d2dfe', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #61'), + ('0000000000000002', '06e7ea22ce92708f', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #62'), + ('0000000000000001', '166b40b44aba4bd6', SP800_17_B1_KEY, + 'NIST SP800-17 B.1 #63'), + + # Table B.2 - Variable Key Known Answer Test + (SP800_17_B2_PT, '95a8d72813daa94d', '8001010101010101', + 'NIST SP800-17 B.2 #0'), + (SP800_17_B2_PT, '0eec1487dd8c26d5', '4001010101010101', + 'NIST SP800-17 B.2 #1'), + (SP800_17_B2_PT, '7ad16ffb79c45926', '2001010101010101', + 'NIST SP800-17 B.2 #2'), + (SP800_17_B2_PT, 'd3746294ca6a6cf3', '1001010101010101', + 'NIST SP800-17 B.2 #3'), + (SP800_17_B2_PT, '809f5f873c1fd761', '0801010101010101', + 'NIST SP800-17 B.2 #4'), + (SP800_17_B2_PT, 'c02faffec989d1fc', '0401010101010101', + 'NIST SP800-17 B.2 #5'), + (SP800_17_B2_PT, '4615aa1d33e72f10', '0201010101010101', + 'NIST SP800-17 B.2 #6'), + (SP800_17_B2_PT, '2055123350c00858', '0180010101010101', + 'NIST SP800-17 B.2 #7'), + (SP800_17_B2_PT, 'df3b99d6577397c8', '0140010101010101', + 'NIST SP800-17 B.2 #8'), + (SP800_17_B2_PT, '31fe17369b5288c9', '0120010101010101', + 'NIST SP800-17 B.2 #9'), + (SP800_17_B2_PT, 'dfdd3cc64dae1642', '0110010101010101', + 'NIST SP800-17 B.2 #10'), + (SP800_17_B2_PT, '178c83ce2b399d94', '0108010101010101', + 'NIST SP800-17 B.2 #11'), + (SP800_17_B2_PT, '50f636324a9b7f80', '0104010101010101', + 'NIST SP800-17 B.2 #12'), + (SP800_17_B2_PT, 'a8468ee3bc18f06d', '0102010101010101', + 'NIST SP800-17 B.2 #13'), + (SP800_17_B2_PT, 'a2dc9e92fd3cde92', '0101800101010101', + 'NIST SP800-17 B.2 #14'), + (SP800_17_B2_PT, 'cac09f797d031287', '0101400101010101', + 'NIST SP800-17 B.2 #15'), + (SP800_17_B2_PT, '90ba680b22aeb525', '0101200101010101', + 'NIST SP800-17 B.2 #16'), + (SP800_17_B2_PT, 'ce7a24f350e280b6', '0101100101010101', + 'NIST SP800-17 B.2 #17'), + (SP800_17_B2_PT, '882bff0aa01a0b87', '0101080101010101', + 'NIST SP800-17 B.2 #18'), + (SP800_17_B2_PT, '25610288924511c2', '0101040101010101', + 'NIST SP800-17 B.2 #19'), + (SP800_17_B2_PT, 'c71516c29c75d170', '0101020101010101', + 'NIST SP800-17 B.2 #20'), + (SP800_17_B2_PT, '5199c29a52c9f059', '0101018001010101', + 'NIST SP800-17 B.2 #21'), + (SP800_17_B2_PT, 'c22f0a294a71f29f', '0101014001010101', + 'NIST SP800-17 B.2 #22'), + (SP800_17_B2_PT, 'ee371483714c02ea', '0101012001010101', + 'NIST SP800-17 B.2 #23'), + (SP800_17_B2_PT, 'a81fbd448f9e522f', '0101011001010101', + 'NIST SP800-17 B.2 #24'), + (SP800_17_B2_PT, '4f644c92e192dfed', '0101010801010101', + 'NIST SP800-17 B.2 #25'), + (SP800_17_B2_PT, '1afa9a66a6df92ae', '0101010401010101', + 'NIST SP800-17 B.2 #26'), + (SP800_17_B2_PT, 'b3c1cc715cb879d8', '0101010201010101', + 'NIST SP800-17 B.2 #27'), + (SP800_17_B2_PT, '19d032e64ab0bd8b', '0101010180010101', + 'NIST SP800-17 B.2 #28'), + (SP800_17_B2_PT, '3cfaa7a7dc8720dc', '0101010140010101', + 'NIST SP800-17 B.2 #29'), + (SP800_17_B2_PT, 'b7265f7f447ac6f3', '0101010120010101', + 'NIST SP800-17 B.2 #30'), + (SP800_17_B2_PT, '9db73b3c0d163f54', '0101010110010101', + 'NIST SP800-17 B.2 #31'), + (SP800_17_B2_PT, '8181b65babf4a975', '0101010108010101', + 'NIST SP800-17 B.2 #32'), + (SP800_17_B2_PT, '93c9b64042eaa240', '0101010104010101', + 'NIST SP800-17 B.2 #33'), + (SP800_17_B2_PT, '5570530829705592', '0101010102010101', + 'NIST SP800-17 B.2 #34'), + (SP800_17_B2_PT, '8638809e878787a0', '0101010101800101', + 'NIST SP800-17 B.2 #35'), + (SP800_17_B2_PT, '41b9a79af79ac208', '0101010101400101', + 'NIST SP800-17 B.2 #36'), + (SP800_17_B2_PT, '7a9be42f2009a892', '0101010101200101', + 'NIST SP800-17 B.2 #37'), + (SP800_17_B2_PT, '29038d56ba6d2745', '0101010101100101', + 'NIST SP800-17 B.2 #38'), + (SP800_17_B2_PT, '5495c6abf1e5df51', '0101010101080101', + 'NIST SP800-17 B.2 #39'), + (SP800_17_B2_PT, 'ae13dbd561488933', '0101010101040101', + 'NIST SP800-17 B.2 #40'), + (SP800_17_B2_PT, '024d1ffa8904e389', '0101010101020101', + 'NIST SP800-17 B.2 #41'), + (SP800_17_B2_PT, 'd1399712f99bf02e', '0101010101018001', + 'NIST SP800-17 B.2 #42'), + (SP800_17_B2_PT, '14c1d7c1cffec79e', '0101010101014001', + 'NIST SP800-17 B.2 #43'), + (SP800_17_B2_PT, '1de5279dae3bed6f', '0101010101012001', + 'NIST SP800-17 B.2 #44'), + (SP800_17_B2_PT, 'e941a33f85501303', '0101010101011001', + 'NIST SP800-17 B.2 #45'), + (SP800_17_B2_PT, 'da99dbbc9a03f379', '0101010101010801', + 'NIST SP800-17 B.2 #46'), + (SP800_17_B2_PT, 'b7fc92f91d8e92e9', '0101010101010401', + 'NIST SP800-17 B.2 #47'), + (SP800_17_B2_PT, 'ae8e5caa3ca04e85', '0101010101010201', + 'NIST SP800-17 B.2 #48'), + (SP800_17_B2_PT, '9cc62df43b6eed74', '0101010101010180', + 'NIST SP800-17 B.2 #49'), + (SP800_17_B2_PT, 'd863dbb5c59a91a0', '0101010101010140', + 'NIST SP800-17 B.2 #50'), + (SP800_17_B2_PT, 'a1ab2190545b91d7', '0101010101010120', + 'NIST SP800-17 B.2 #51'), + (SP800_17_B2_PT, '0875041e64c570f7', '0101010101010110', + 'NIST SP800-17 B.2 #52'), + (SP800_17_B2_PT, '5a594528bebef1cc', '0101010101010108', + 'NIST SP800-17 B.2 #53'), + (SP800_17_B2_PT, 'fcdb3291de21f0c0', '0101010101010104', + 'NIST SP800-17 B.2 #54'), + (SP800_17_B2_PT, '869efd7f9f265a09', '0101010101010102', + 'NIST SP800-17 B.2 #55'), +] + +class RonRivestTest(unittest.TestCase): + """ Ronald L. Rivest's DES test, see + http://people.csail.mit.edu/rivest/Destest.txt + ABSTRACT + -------- + + We present a simple way to test the correctness of a DES implementation: + Use the recurrence relation: + + X0 = 9474B8E8C73BCA7D (hexadecimal) + + X(i+1) = IF (i is even) THEN E(Xi,Xi) ELSE D(Xi,Xi) + + to compute a sequence of 64-bit values: X0, X1, X2, ..., X16. Here + E(X,K) denotes the DES encryption of X using key K, and D(X,K) denotes + the DES decryption of X using key K. If you obtain + + X16 = 1B1A2DDB4C642438 + + your implementation does not have any of the 36,568 possible single-fault + errors described herein. + """ + def runTest(self): + from Crypto.Cipher import DES + from binascii import b2a_hex + + X = [] + X[0:] = [b('\x94\x74\xB8\xE8\xC7\x3B\xCA\x7D')] + + for i in range(16): + c = DES.new(X[i],DES.MODE_ECB) + if not (i&1): # (num&1) returns 1 for odd numbers + X[i+1:] = [c.encrypt(X[i])] # even + else: + X[i+1:] = [c.decrypt(X[i])] # odd + + self.assertEqual(b2a_hex(X[16]), + b2a_hex(b('\x1B\x1A\x2D\xDB\x4C\x64\x24\x38'))) + +def get_tests(config={}): + from Crypto.Cipher import DES + from common import make_block_tests + return make_block_tests(DES, "DES", test_data) + [RonRivestTest()] + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Cipher/test_DES3.py b/lib/Crypto/SelfTest/Cipher/test_DES3.py new file mode 100644 index 0000000..50e969b --- /dev/null +++ b/lib/Crypto/SelfTest/Cipher/test_DES3.py @@ -0,0 +1,333 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Cipher/DES3.py: Self-test for the Triple-DES cipher +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Cipher.DES3""" + +__revision__ = "$Id$" + +from common import dict # For compatibility with Python 2.1 and 2.2 +from Crypto.Util.py3compat import * +from binascii import hexlify + +# This is a list of (plaintext, ciphertext, key, description) tuples. +SP800_20_A1_KEY = '01' * 24 +SP800_20_A2_PT = '00' * 8 +test_data = [ + # Test vector from Appendix B of NIST SP 800-67 + # "Recommendation for the Triple Data Encryption Algorithm (TDEA) Block + # Cipher" + # http://csrc.nist.gov/publications/nistpubs/800-67/SP800-67.pdf + ('54686520717566636b2062726f776e20666f78206a756d70', + 'a826fd8ce53b855fcce21c8112256fe668d5c05dd9b6b900', + '0123456789abcdef23456789abcdef01456789abcdef0123', + 'NIST SP800-67 B.1'), + + # Test vectors "The Multi-block Message Test (MMT) for DES and TDES" + # http://csrc.nist.gov/groups/STM/cavp/documents/des/DESMMT.pdf + ('326a494cd33fe756', 'b22b8d66de970692', + '627f460e08104a1043cd265d5840eaf1313edf97df2a8a8c', + 'DESMMT #1', dict(mode='CBC', iv='8e29f75ea77e5475')), + + ('84401f78fe6c10876d8ea23094ea5309', '7b1f7c7e3b1c948ebd04a75ffba7d2f5', + '37ae5ebf46dff2dc0754b94f31cbb3855e7fd36dc870bfae', + 'DESMMT #2', dict(mode='CBC', iv='3d1de3cc132e3b65')), + + # Test vectors from Appendix A of NIST SP 800-20 + # "Modes of Operation Validation System for the Triple Data Encryption + # Algorithm (TMOVS): Requirements and Procedures" + # http://csrc.nist.gov/publications/nistpubs/800-20/800-20.pdf + + # Table A.1 - Variable Plaintext Known Answer Test + ('8000000000000000', '95f8a5e5dd31d900', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #0'), + ('4000000000000000', 'dd7f121ca5015619', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #1'), + ('2000000000000000', '2e8653104f3834ea', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #2'), + ('1000000000000000', '4bd388ff6cd81d4f', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #3'), + ('0800000000000000', '20b9e767b2fb1456', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #4'), + ('0400000000000000', '55579380d77138ef', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #5'), + ('0200000000000000', '6cc5defaaf04512f', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #6'), + ('0100000000000000', '0d9f279ba5d87260', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #7'), + ('0080000000000000', 'd9031b0271bd5a0a', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #8'), + ('0040000000000000', '424250b37c3dd951', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #9'), + ('0020000000000000', 'b8061b7ecd9a21e5', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #10'), + ('0010000000000000', 'f15d0f286b65bd28', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #11'), + ('0008000000000000', 'add0cc8d6e5deba1', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #12'), + ('0004000000000000', 'e6d5f82752ad63d1', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #13'), + ('0002000000000000', 'ecbfe3bd3f591a5e', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #14'), + ('0001000000000000', 'f356834379d165cd', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #15'), + ('0000800000000000', '2b9f982f20037fa9', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #16'), + ('0000400000000000', '889de068a16f0be6', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #17'), + ('0000200000000000', 'e19e275d846a1298', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #18'), + ('0000100000000000', '329a8ed523d71aec', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #19'), + ('0000080000000000', 'e7fce22557d23c97', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #20'), + ('0000040000000000', '12a9f5817ff2d65d', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #21'), + ('0000020000000000', 'a484c3ad38dc9c19', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #22'), + ('0000010000000000', 'fbe00a8a1ef8ad72', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #23'), + ('0000008000000000', '750d079407521363', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #24'), + ('0000004000000000', '64feed9c724c2faf', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #25'), + ('0000002000000000', 'f02b263b328e2b60', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #26'), + ('0000001000000000', '9d64555a9a10b852', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #27'), + ('0000000800000000', 'd106ff0bed5255d7', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #28'), + ('0000000400000000', 'e1652c6b138c64a5', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #29'), + ('0000000200000000', 'e428581186ec8f46', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #30'), + ('0000000100000000', 'aeb5f5ede22d1a36', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #31'), + ('0000000080000000', 'e943d7568aec0c5c', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #32'), + ('0000000040000000', 'df98c8276f54b04b', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #33'), + ('0000000020000000', 'b160e4680f6c696f', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #34'), + ('0000000010000000', 'fa0752b07d9c4ab8', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #35'), + ('0000000008000000', 'ca3a2b036dbc8502', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #36'), + ('0000000004000000', '5e0905517bb59bcf', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #37'), + ('0000000002000000', '814eeb3b91d90726', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #38'), + ('0000000001000000', '4d49db1532919c9f', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #39'), + ('0000000000800000', '25eb5fc3f8cf0621', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #40'), + ('0000000000400000', 'ab6a20c0620d1c6f', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #41'), + ('0000000000200000', '79e90dbc98f92cca', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #42'), + ('0000000000100000', '866ecedd8072bb0e', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #43'), + ('0000000000080000', '8b54536f2f3e64a8', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #44'), + ('0000000000040000', 'ea51d3975595b86b', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #45'), + ('0000000000020000', 'caffc6ac4542de31', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #46'), + ('0000000000010000', '8dd45a2ddf90796c', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #47'), + ('0000000000008000', '1029d55e880ec2d0', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #48'), + ('0000000000004000', '5d86cb23639dbea9', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #49'), + ('0000000000002000', '1d1ca853ae7c0c5f', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #50'), + ('0000000000001000', 'ce332329248f3228', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #51'), + ('0000000000000800', '8405d1abe24fb942', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #52'), + ('0000000000000400', 'e643d78090ca4207', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #53'), + ('0000000000000200', '48221b9937748a23', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #54'), + ('0000000000000100', 'dd7c0bbd61fafd54', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #55'), + ('0000000000000080', '2fbc291a570db5c4', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #56'), + ('0000000000000040', 'e07c30d7e4e26e12', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #57'), + ('0000000000000020', '0953e2258e8e90a1', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #58'), + ('0000000000000010', '5b711bc4ceebf2ee', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #59'), + ('0000000000000008', 'cc083f1e6d9e85f6', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #60'), + ('0000000000000004', 'd2fd8867d50d2dfe', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #61'), + ('0000000000000002', '06e7ea22ce92708f', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #62'), + ('0000000000000001', '166b40b44aba4bd6', SP800_20_A1_KEY, + 'NIST SP800-20 A.1 #63'), + + # Table A.2 - Variable Key Known Answer Test + (SP800_20_A2_PT, '95a8d72813daa94d', '8001010101010101'*3, + 'NIST SP800-20 A.2 #0'), + (SP800_20_A2_PT, '0eec1487dd8c26d5', '4001010101010101'*3, + 'NIST SP800-20 A.2 #1'), + (SP800_20_A2_PT, '7ad16ffb79c45926', '2001010101010101'*3, + 'NIST SP800-20 A.2 #2'), + (SP800_20_A2_PT, 'd3746294ca6a6cf3', '1001010101010101'*3, + 'NIST SP800-20 A.2 #3'), + (SP800_20_A2_PT, '809f5f873c1fd761', '0801010101010101'*3, + 'NIST SP800-20 A.2 #4'), + (SP800_20_A2_PT, 'c02faffec989d1fc', '0401010101010101'*3, + 'NIST SP800-20 A.2 #5'), + (SP800_20_A2_PT, '4615aa1d33e72f10', '0201010101010101'*3, + 'NIST SP800-20 A.2 #6'), + (SP800_20_A2_PT, '2055123350c00858', '0180010101010101'*3, + 'NIST SP800-20 A.2 #7'), + (SP800_20_A2_PT, 'df3b99d6577397c8', '0140010101010101'*3, + 'NIST SP800-20 A.2 #8'), + (SP800_20_A2_PT, '31fe17369b5288c9', '0120010101010101'*3, + 'NIST SP800-20 A.2 #9'), + (SP800_20_A2_PT, 'dfdd3cc64dae1642', '0110010101010101'*3, + 'NIST SP800-20 A.2 #10'), + (SP800_20_A2_PT, '178c83ce2b399d94', '0108010101010101'*3, + 'NIST SP800-20 A.2 #11'), + (SP800_20_A2_PT, '50f636324a9b7f80', '0104010101010101'*3, + 'NIST SP800-20 A.2 #12'), + (SP800_20_A2_PT, 'a8468ee3bc18f06d', '0102010101010101'*3, + 'NIST SP800-20 A.2 #13'), + (SP800_20_A2_PT, 'a2dc9e92fd3cde92', '0101800101010101'*3, + 'NIST SP800-20 A.2 #14'), + (SP800_20_A2_PT, 'cac09f797d031287', '0101400101010101'*3, + 'NIST SP800-20 A.2 #15'), + (SP800_20_A2_PT, '90ba680b22aeb525', '0101200101010101'*3, + 'NIST SP800-20 A.2 #16'), + (SP800_20_A2_PT, 'ce7a24f350e280b6', '0101100101010101'*3, + 'NIST SP800-20 A.2 #17'), + (SP800_20_A2_PT, '882bff0aa01a0b87', '0101080101010101'*3, + 'NIST SP800-20 A.2 #18'), + (SP800_20_A2_PT, '25610288924511c2', '0101040101010101'*3, + 'NIST SP800-20 A.2 #19'), + (SP800_20_A2_PT, 'c71516c29c75d170', '0101020101010101'*3, + 'NIST SP800-20 A.2 #20'), + (SP800_20_A2_PT, '5199c29a52c9f059', '0101018001010101'*3, + 'NIST SP800-20 A.2 #21'), + (SP800_20_A2_PT, 'c22f0a294a71f29f', '0101014001010101'*3, + 'NIST SP800-20 A.2 #22'), + (SP800_20_A2_PT, 'ee371483714c02ea', '0101012001010101'*3, + 'NIST SP800-20 A.2 #23'), + (SP800_20_A2_PT, 'a81fbd448f9e522f', '0101011001010101'*3, + 'NIST SP800-20 A.2 #24'), + (SP800_20_A2_PT, '4f644c92e192dfed', '0101010801010101'*3, + 'NIST SP800-20 A.2 #25'), + (SP800_20_A2_PT, '1afa9a66a6df92ae', '0101010401010101'*3, + 'NIST SP800-20 A.2 #26'), + (SP800_20_A2_PT, 'b3c1cc715cb879d8', '0101010201010101'*3, + 'NIST SP800-20 A.2 #27'), + (SP800_20_A2_PT, '19d032e64ab0bd8b', '0101010180010101'*3, + 'NIST SP800-20 A.2 #28'), + (SP800_20_A2_PT, '3cfaa7a7dc8720dc', '0101010140010101'*3, + 'NIST SP800-20 A.2 #29'), + (SP800_20_A2_PT, 'b7265f7f447ac6f3', '0101010120010101'*3, + 'NIST SP800-20 A.2 #30'), + (SP800_20_A2_PT, '9db73b3c0d163f54', '0101010110010101'*3, + 'NIST SP800-20 A.2 #31'), + (SP800_20_A2_PT, '8181b65babf4a975', '0101010108010101'*3, + 'NIST SP800-20 A.2 #32'), + (SP800_20_A2_PT, '93c9b64042eaa240', '0101010104010101'*3, + 'NIST SP800-20 A.2 #33'), + (SP800_20_A2_PT, '5570530829705592', '0101010102010101'*3, + 'NIST SP800-20 A.2 #34'), + (SP800_20_A2_PT, '8638809e878787a0', '0101010101800101'*3, + 'NIST SP800-20 A.2 #35'), + (SP800_20_A2_PT, '41b9a79af79ac208', '0101010101400101'*3, + 'NIST SP800-20 A.2 #36'), + (SP800_20_A2_PT, '7a9be42f2009a892', '0101010101200101'*3, + 'NIST SP800-20 A.2 #37'), + (SP800_20_A2_PT, '29038d56ba6d2745', '0101010101100101'*3, + 'NIST SP800-20 A.2 #38'), + (SP800_20_A2_PT, '5495c6abf1e5df51', '0101010101080101'*3, + 'NIST SP800-20 A.2 #39'), + (SP800_20_A2_PT, 'ae13dbd561488933', '0101010101040101'*3, + 'NIST SP800-20 A.2 #40'), + (SP800_20_A2_PT, '024d1ffa8904e389', '0101010101020101'*3, + 'NIST SP800-20 A.2 #41'), + (SP800_20_A2_PT, 'd1399712f99bf02e', '0101010101018001'*3, + 'NIST SP800-20 A.2 #42'), + (SP800_20_A2_PT, '14c1d7c1cffec79e', '0101010101014001'*3, + 'NIST SP800-20 A.2 #43'), + (SP800_20_A2_PT, '1de5279dae3bed6f', '0101010101012001'*3, + 'NIST SP800-20 A.2 #44'), + (SP800_20_A2_PT, 'e941a33f85501303', '0101010101011001'*3, + 'NIST SP800-20 A.2 #45'), + (SP800_20_A2_PT, 'da99dbbc9a03f379', '0101010101010801'*3, + 'NIST SP800-20 A.2 #46'), + (SP800_20_A2_PT, 'b7fc92f91d8e92e9', '0101010101010401'*3, + 'NIST SP800-20 A.2 #47'), + (SP800_20_A2_PT, 'ae8e5caa3ca04e85', '0101010101010201'*3, + 'NIST SP800-20 A.2 #48'), + (SP800_20_A2_PT, '9cc62df43b6eed74', '0101010101010180'*3, + 'NIST SP800-20 A.2 #49'), + (SP800_20_A2_PT, 'd863dbb5c59a91a0', '0101010101010140'*3, + 'NIST SP800-20 A.2 #50'), + (SP800_20_A2_PT, 'a1ab2190545b91d7', '0101010101010120'*3, + 'NIST SP800-20 A.2 #51'), + (SP800_20_A2_PT, '0875041e64c570f7', '0101010101010110'*3, + 'NIST SP800-20 A.2 #52'), + (SP800_20_A2_PT, '5a594528bebef1cc', '0101010101010108'*3, + 'NIST SP800-20 A.2 #53'), + (SP800_20_A2_PT, 'fcdb3291de21f0c0', '0101010101010104'*3, + 'NIST SP800-20 A.2 #54'), + (SP800_20_A2_PT, '869efd7f9f265a09', '0101010101010102'*3, + 'NIST SP800-20 A.2 #55'), + + # "Two-key 3DES". Test vector generated using PyCrypto 2.0.1. + # This test is designed to test the DES3 API, not the correctness of the + # output. + ('21e81b7ade88a259', '5c577d4d9b20c0f8', + '9b397ebf81b1181e282f4bb8adbadc6b', 'Two-key 3DES'), + + # The following test vectors have been generated with gpg v1.4.0. + # The command line used was: + # gpg -c -z 0 --cipher-algo 3DES --passphrase secret_passphrase \ + # --disable-mdc --s2k-mode 0 --output ct pt + # For an explanation, see test_AES.py . + ( 'ac1762037074324fb53ba3596f73656d69746556616c6c6579', # Plaintext, 'YosemiteValley' + '9979238528357b90e2e0be549cb0b2d5999b9a4a447e5c5c7d', # Ciphertext + '7ade65b460f5ea9be35f9e14aa883a2048e3824aa616c0b2', # Key (hash of 'BearsAhead') + 'GPG Test Vector #1', + dict(mode='OPENPGP', iv='cd47e2afb8b7e4b0', encrypted_iv='6a7eef0b58050e8b904a' ) ), +] + +def get_tests(config={}): + from Crypto.Cipher import DES3 + from common import make_block_tests + return make_block_tests(DES3, "DES3", test_data) + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Cipher/test_XOR.py b/lib/Crypto/SelfTest/Cipher/test_XOR.py new file mode 100644 index 0000000..a4d542a --- /dev/null +++ b/lib/Crypto/SelfTest/Cipher/test_XOR.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Cipher/XOR.py: Self-test for the XOR "cipher" +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Cipher.XOR""" + +import unittest + +__revision__ = "$Id$" + +from Crypto.Util.py3compat import * + +# This is a list of (plaintext, ciphertext, key) tuples. +test_data = [ + # Test vectors written from scratch. (Nobody posts XOR test vectors on the web? How disappointing.) + ('01', '01', + '00', + 'zero key'), + + ('0102040810204080', '0003050911214181', + '01', + '1-byte key'), + + ('0102040810204080', 'cda8c8a2dc8a8c2a', + 'ccaa', + '2-byte key'), + + ('ff'*64, 'fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0'*2, + '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', + '32-byte key'), +] + +class TruncationSelfTest(unittest.TestCase): + + def runTest(self): + """33-byte key (should raise ValueError under current implementation)""" + # Crypto.Cipher.XOR previously truncated its inputs at 32 bytes. Now + # it should raise a ValueError if the length is too long. + self.assertRaises(ValueError, XOR.new, "x"*33) + +def get_tests(config={}): + global XOR + from Crypto.Cipher import XOR + from common import make_stream_tests + return make_stream_tests(XOR, "XOR", test_data) + [TruncationSelfTest()] + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Cipher/test_pkcs1_15.py b/lib/Crypto/SelfTest/Cipher/test_pkcs1_15.py new file mode 100644 index 0000000..7aa1703 --- /dev/null +++ b/lib/Crypto/SelfTest/Cipher/test_pkcs1_15.py @@ -0,0 +1,174 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Cipher/test_pkcs1_15.py: Self-test for PKCS#1 v1.5 encryption +# +# =================================================================== +# 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. +# =================================================================== + +__revision__ = "$Id$" + +import unittest +import sys + +from Crypto.PublicKey import RSA +from Crypto.SelfTest.st_common import list_test_cases, a2b_hex, b2a_hex +from Crypto import Random +from Crypto.Cipher import PKCS1_v1_5 as PKCS +from Crypto.Util.py3compat import * + +def rws(t): + """Remove white spaces, tabs, and new lines from a string""" + for c in ['\n', '\t', ' ']: + t = t.replace(c,'') + return t + +def t2b(t): + """Convert a text string with bytes in hex form to a byte string""" + clean = b(rws(t)) + if len(clean)%2 == 1: + print clean + raise ValueError("Even number of characters expected") + return a2b_hex(clean) + +class PKCS1_15_Tests(unittest.TestCase): + + def setUp(self): + self.rng = Random.new().read + self.key1024 = RSA.generate(1024, self.rng) + + # List of tuples with test data for PKCS#1 v1.5. + # Each tuple is made up by: + # Item #0: dictionary with RSA key component, or key to import + # Item #1: plaintext + # Item #2: ciphertext + # Item #3: random data + + _testData = ( + + # + # Generated with openssl 0.9.8o + # + ( + # Private key + '''-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDAiAnvIAOvqVwJTaYzsKnefZftgtXGE2hPJppGsWl78yz9jeXY +W/FxX/gTPURArNhdnhP6n3p2ZaDIBrO2zizbgIXs0IsljTTcr4vnI8fMXzyNUOjA +zP3nzMqZDZK6757XQAobOssMkBFqRWwilT/3DsBhRpl3iMUhF+wvpTSHewIDAQAB +AoGAC4HV/inOrpgTvSab8Wj0riyZgQOZ3U3ZpSlsfR8ra9Ib9Uee3jCYnKscu6Gk +y6zI/cdt8EPJ4PuwAWSNJzbpbVaDvUq25OD+CX8/uRT08yBS4J8TzBitZJTD4lS7 +atdTnKT0Wmwk+u8tDbhvMKwnUHdJLcuIsycts9rwJVapUtkCQQDvDpx2JMun0YKG +uUttjmL8oJ3U0m3ZvMdVwBecA0eebZb1l2J5PvI3EJD97eKe91Nsw8T3lwpoN40k +IocSVDklAkEAzi1HLHE6EzVPOe5+Y0kGvrIYRRhncOb72vCvBZvD6wLZpQgqo6c4 +d3XHFBBQWA6xcvQb5w+VVEJZzw64y25sHwJBAMYReRl6SzL0qA0wIYrYWrOt8JeQ +8mthulcWHXmqTgC6FEXP9Es5GD7/fuKl4wqLKZgIbH4nqvvGay7xXLCXD/ECQH9a +1JYNMtRen5unSAbIOxRcKkWz92F0LKpm9ZW/S9vFHO+mBcClMGoKJHiuQxLBsLbT +NtEZfSJZAeS2sUtn3/0CQDb2M2zNBTF8LlM0nxmh0k9VGm5TVIyBEMcipmvOgqIs +HKukWBcq9f/UOmS0oEhai/6g+Uf7VHJdWaeO5LzuvwU= +-----END RSA PRIVATE KEY-----''', + # Plaintext + '''THIS IS PLAINTEXT\x0A''', + # Ciphertext + '''3f dc fd 3c cd 5c 9b 12 af 65 32 e3 f7 d0 da 36 + 8f 8f d9 e3 13 1c 7f c8 b3 f9 c1 08 e4 eb 79 9c + 91 89 1f 96 3b 94 77 61 99 a4 b1 ee 5d e6 17 c9 + 5d 0a b5 63 52 0a eb 00 45 38 2a fb b0 71 3d 11 + f7 a1 9e a7 69 b3 af 61 c0 bb 04 5b 5d 4b 27 44 + 1f 5b 97 89 ba 6a 08 95 ee 4f a2 eb 56 64 e5 0f + da 7c f9 9a 61 61 06 62 ed a0 bc 5f aa 6c 31 78 + 70 28 1a bb 98 3c e3 6a 60 3c d1 0b 0f 5a f4 75''', + # Random data + '''eb d7 7d 86 a4 35 23 a3 54 7e 02 0b 42 1d + 61 6c af 67 b8 4e 17 56 80 66 36 04 64 34 26 8a + 47 dd 44 b3 1a b2 17 60 f4 91 2e e2 b5 95 64 cc + f9 da c8 70 94 54 86 4c ef 5b 08 7d 18 c4 ab 8d + 04 06 33 8f ca 15 5f 52 60 8a a1 0c f5 08 b5 4c + bb 99 b8 94 25 04 9c e6 01 75 e6 f9 63 7a 65 61 + 13 8a a7 47 77 81 ae 0d b8 2c 4d 50 a5''' + ), + ) + + def testEncrypt1(self): + for test in self._testData: + # Build the key + key = RSA.importKey(test[0]) + # RNG that takes its random numbers from a pool given + # at initialization + class randGen: + def __init__(self, data): + self.data = data + self.idx = 0 + def __call__(self, N): + r = self.data[self.idx:N] + self.idx += N + return r + # The real test + key._randfunc = randGen(t2b(test[3])) + cipher = PKCS.new(key) + ct = cipher.encrypt(b(test[1])) + self.assertEqual(ct, t2b(test[2])) + + def testEncrypt2(self): + # Verify that encryption fail if plaintext is too long + pt = '\x00'*(128-11+1) + cipher = PKCS.new(self.key1024) + self.assertRaises(ValueError, cipher.encrypt, pt) + + def testVerify1(self): + for test in self._testData: + # Build the key + key = RSA.importKey(test[0]) + # The real test + cipher = PKCS.new(key) + pt = cipher.decrypt(t2b(test[2]), "---") + self.assertEqual(pt, b(test[1])) + + def testVerify2(self): + # Verify that decryption fails if ciphertext is not as long as + # RSA modulus + cipher = PKCS.new(self.key1024) + self.assertRaises(ValueError, cipher.decrypt, '\x00'*127, "---") + self.assertRaises(ValueError, cipher.decrypt, '\x00'*129, "---") + + # Verify that decryption fails if there are less then 8 non-zero padding + # bytes + pt = b('\x00\x02' + '\xFF'*7 + '\x00' + '\x45'*118) + ct = self.key1024.encrypt(pt, 0)[0] + ct = b('\x00'*(128-len(ct))) + ct + self.assertEqual("---", cipher.decrypt(ct, "---")) + + def testEncryptVerify1(self): + # Encrypt/Verify messages of length [0..RSAlen-11] + # and therefore padding [8..117] + for pt_len in xrange(0,128-11+1): + pt = self.rng(pt_len) + cipher = PKCS.new(self.key1024) + ct = cipher.encrypt(pt) + pt2 = cipher.decrypt(ct, "---") + self.assertEqual(pt,pt2) + + +def get_tests(config={}): + tests = [] + tests += list_test_cases(PKCS1_15_Tests) + return tests + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py b/lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py new file mode 100644 index 0000000..7ca5c15 --- /dev/null +++ b/lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py @@ -0,0 +1,373 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Cipher/test_pkcs1_oaep.py: Self-test for PKCS#1 OAEP encryption +# +# =================================================================== +# 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. +# =================================================================== + +from __future__ import nested_scopes + +__revision__ = "$Id$" + +import unittest + +from Crypto.SelfTest.st_common import list_test_cases, a2b_hex, b2a_hex + +from Crypto.Util.py3compat import * +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_OAEP as PKCS +from Crypto.Hash import MD2,MD5,SHA as SHA1,SHA256,RIPEMD +from Crypto import Random + +def rws(t): + """Remove white spaces, tabs, and new lines from a string""" + for c in ['\n', '\t', ' ']: + t = t.replace(c,'') + return t + +def t2b(t): + """Convert a text string with bytes in hex form to a byte string""" + clean = rws(t) + if len(clean)%2 == 1: + raise ValueError("Even number of characters expected") + return a2b_hex(clean) + +class PKCS1_OAEP_Tests(unittest.TestCase): + + def setUp(self): + self.rng = Random.new().read + self.key1024 = RSA.generate(1024, self.rng) + + # List of tuples with test data for PKCS#1 OAEP + # Each tuple is made up by: + # Item #0: dictionary with RSA key component + # Item #1: plaintext + # Item #2: ciphertext + # Item #3: random data (=seed) + # Item #4: hash object + + _testData = ( + + # + # From in oaep-int.txt to be found in + # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip + # + ( + # Private key + { + 'n':'''bb f8 2f 09 06 82 ce 9c 23 38 ac 2b 9d a8 71 f7 + 36 8d 07 ee d4 10 43 a4 40 d6 b6 f0 74 54 f5 1f + b8 df ba af 03 5c 02 ab 61 ea 48 ce eb 6f cd 48 + 76 ed 52 0d 60 e1 ec 46 19 71 9d 8a 5b 8b 80 7f + af b8 e0 a3 df c7 37 72 3e e6 b4 b7 d9 3a 25 84 + ee 6a 64 9d 06 09 53 74 88 34 b2 45 45 98 39 4e + e0 aa b1 2d 7b 61 a5 1f 52 7a 9a 41 f6 c1 68 7f + e2 53 72 98 ca 2a 8f 59 46 f8 e5 fd 09 1d bd cb''', + # Public key + 'e':'11', + # In the test vector, only p and q were given... + # d is computed offline as e^{-1} mod (p-1)(q-1) + 'd':'''a5dafc5341faf289c4b988db30c1cdf83f31251e0 + 668b42784813801579641b29410b3c7998d6bc465745e5c3 + 92669d6870da2c082a939e37fdcb82ec93edac97ff3ad595 + 0accfbc111c76f1a9529444e56aaf68c56c092cd38dc3bef + 5d20a939926ed4f74a13eddfbe1a1cecc4894af9428c2b7b + 8883fe4463a4bc85b1cb3c1''' + } + , + # Plaintext + '''d4 36 e9 95 69 fd 32 a7 c8 a0 5b bc 90 d3 2c 49''', + # Ciphertext + '''12 53 e0 4d c0 a5 39 7b b4 4a 7a b8 7e 9b f2 a0 + 39 a3 3d 1e 99 6f c8 2a 94 cc d3 00 74 c9 5d f7 + 63 72 20 17 06 9e 52 68 da 5d 1c 0b 4f 87 2c f6 + 53 c1 1d f8 23 14 a6 79 68 df ea e2 8d ef 04 bb + 6d 84 b1 c3 1d 65 4a 19 70 e5 78 3b d6 eb 96 a0 + 24 c2 ca 2f 4a 90 fe 9f 2e f5 c9 c1 40 e5 bb 48 + da 95 36 ad 87 00 c8 4f c9 13 0a de a7 4e 55 8d + 51 a7 4d df 85 d8 b5 0d e9 68 38 d6 06 3e 09 55''', + # Random + '''aa fd 12 f6 59 ca e6 34 89 b4 79 e5 07 6d de c2 + f0 6c b5 8f''', + # Hash + SHA1, + ), + + # + # From in oaep-vect.txt to be found in Example 1.1 + # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip + # + ( + # Private key + { + 'n':'''a8 b3 b2 84 af 8e b5 0b 38 70 34 a8 60 f1 46 c4 + 91 9f 31 87 63 cd 6c 55 98 c8 ae 48 11 a1 e0 ab + c4 c7 e0 b0 82 d6 93 a5 e7 fc ed 67 5c f4 66 85 + 12 77 2c 0c bc 64 a7 42 c6 c6 30 f5 33 c8 cc 72 + f6 2a e8 33 c4 0b f2 58 42 e9 84 bb 78 bd bf 97 + c0 10 7d 55 bd b6 62 f5 c4 e0 fa b9 84 5c b5 14 + 8e f7 39 2d d3 aa ff 93 ae 1e 6b 66 7b b3 d4 24 + 76 16 d4 f5 ba 10 d4 cf d2 26 de 88 d3 9f 16 fb''', + 'e':'''01 00 01''', + 'd':'''53 33 9c fd b7 9f c8 46 6a 65 5c 73 16 ac a8 5c + 55 fd 8f 6d d8 98 fd af 11 95 17 ef 4f 52 e8 fd + 8e 25 8d f9 3f ee 18 0f a0 e4 ab 29 69 3c d8 3b + 15 2a 55 3d 4a c4 d1 81 2b 8b 9f a5 af 0e 7f 55 + fe 73 04 df 41 57 09 26 f3 31 1f 15 c4 d6 5a 73 + 2c 48 31 16 ee 3d 3d 2d 0a f3 54 9a d9 bf 7c bf + b7 8a d8 84 f8 4d 5b eb 04 72 4d c7 36 9b 31 de + f3 7d 0c f5 39 e9 cf cd d3 de 65 37 29 ea d5 d1 ''' + } + , + # Plaintext + '''66 28 19 4e 12 07 3d b0 3b a9 4c da 9e f9 53 23 + 97 d5 0d ba 79 b9 87 00 4a fe fe 34''', + # Ciphertext + '''35 4f e6 7b 4a 12 6d 5d 35 fe 36 c7 77 79 1a 3f + 7b a1 3d ef 48 4e 2d 39 08 af f7 22 fa d4 68 fb + 21 69 6d e9 5d 0b e9 11 c2 d3 17 4f 8a fc c2 01 + 03 5f 7b 6d 8e 69 40 2d e5 45 16 18 c2 1a 53 5f + a9 d7 bf c5 b8 dd 9f c2 43 f8 cf 92 7d b3 13 22 + d6 e8 81 ea a9 1a 99 61 70 e6 57 a0 5a 26 64 26 + d9 8c 88 00 3f 84 77 c1 22 70 94 a0 d9 fa 1e 8c + 40 24 30 9c e1 ec cc b5 21 00 35 d4 7a c7 2e 8a''', + # Random + '''18 b7 76 ea 21 06 9d 69 77 6a 33 e9 6b ad 48 e1 + dd a0 a5 ef''', + SHA1 + ), + + # + # From in oaep-vect.txt to be found in Example 2.1 + # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip + # + ( + # Private key + { + 'n':'''01 94 7c 7f ce 90 42 5f 47 27 9e 70 85 1f 25 d5 + e6 23 16 fe 8a 1d f1 93 71 e3 e6 28 e2 60 54 3e + 49 01 ef 60 81 f6 8c 0b 81 41 19 0d 2a e8 da ba + 7d 12 50 ec 6d b6 36 e9 44 ec 37 22 87 7c 7c 1d + 0a 67 f1 4b 16 94 c5 f0 37 94 51 a4 3e 49 a3 2d + de 83 67 0b 73 da 91 a1 c9 9b c2 3b 43 6a 60 05 + 5c 61 0f 0b af 99 c1 a0 79 56 5b 95 a3 f1 52 66 + 32 d1 d4 da 60 f2 0e da 25 e6 53 c4 f0 02 76 6f + 45''', + 'e':'''01 00 01''', + 'd':'''08 23 f2 0f ad b5 da 89 08 8a 9d 00 89 3e 21 fa + 4a 1b 11 fb c9 3c 64 a3 be 0b aa ea 97 fb 3b 93 + c3 ff 71 37 04 c1 9c 96 3c 1d 10 7a ae 99 05 47 + 39 f7 9e 02 e1 86 de 86 f8 7a 6d de fe a6 d8 cc + d1 d3 c8 1a 47 bf a7 25 5b e2 06 01 a4 a4 b2 f0 + 8a 16 7b 5e 27 9d 71 5b 1b 45 5b dd 7e ab 24 59 + 41 d9 76 8b 9a ce fb 3c cd a5 95 2d a3 ce e7 25 + 25 b4 50 16 63 a8 ee 15 c9 e9 92 d9 24 62 fe 39''' + }, + # Plaintext + '''8f f0 0c aa 60 5c 70 28 30 63 4d 9a 6c 3d 42 c6 + 52 b5 8c f1 d9 2f ec 57 0b ee e7''', + # Ciphertext + '''01 81 af 89 22 b9 fc b4 d7 9d 92 eb e1 98 15 99 + 2f c0 c1 43 9d 8b cd 49 13 98 a0 f4 ad 3a 32 9a + 5b d9 38 55 60 db 53 26 83 c8 b7 da 04 e4 b1 2a + ed 6a ac df 47 1c 34 c9 cd a8 91 ad dc c2 df 34 + 56 65 3a a6 38 2e 9a e5 9b 54 45 52 57 eb 09 9d + 56 2b be 10 45 3f 2b 6d 13 c5 9c 02 e1 0f 1f 8a + bb 5d a0 d0 57 09 32 da cf 2d 09 01 db 72 9d 0f + ef cc 05 4e 70 96 8e a5 40 c8 1b 04 bc ae fe 72 + 0e''', + # Random + '''8c 40 7b 5e c2 89 9e 50 99 c5 3e 8c e7 93 bf 94 + e7 1b 17 82''', + SHA1 + ), + + # + # From in oaep-vect.txt to be found in Example 10.1 + # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip + # + ( + # Private key + { + 'n':'''ae 45 ed 56 01 ce c6 b8 cc 05 f8 03 93 5c 67 4d + db e0 d7 5c 4c 09 fd 79 51 fc 6b 0c ae c3 13 a8 + df 39 97 0c 51 8b ff ba 5e d6 8f 3f 0d 7f 22 a4 + 02 9d 41 3f 1a e0 7e 4e be 9e 41 77 ce 23 e7 f5 + 40 4b 56 9e 4e e1 bd cf 3c 1f b0 3e f1 13 80 2d + 4f 85 5e b9 b5 13 4b 5a 7c 80 85 ad ca e6 fa 2f + a1 41 7e c3 76 3b e1 71 b0 c6 2b 76 0e de 23 c1 + 2a d9 2b 98 08 84 c6 41 f5 a8 fa c2 6b da d4 a0 + 33 81 a2 2f e1 b7 54 88 50 94 c8 25 06 d4 01 9a + 53 5a 28 6a fe b2 71 bb 9b a5 92 de 18 dc f6 00 + c2 ae ea e5 6e 02 f7 cf 79 fc 14 cf 3b dc 7c d8 + 4f eb bb f9 50 ca 90 30 4b 22 19 a7 aa 06 3a ef + a2 c3 c1 98 0e 56 0c d6 4a fe 77 95 85 b6 10 76 + 57 b9 57 85 7e fd e6 01 09 88 ab 7d e4 17 fc 88 + d8 f3 84 c4 e6 e7 2c 3f 94 3e 0c 31 c0 c4 a5 cc + 36 f8 79 d8 a3 ac 9d 7d 59 86 0e aa da 6b 83 bb''', + 'e':'''01 00 01''', + 'd':'''05 6b 04 21 6f e5 f3 54 ac 77 25 0a 4b 6b 0c 85 + 25 a8 5c 59 b0 bd 80 c5 64 50 a2 2d 5f 43 8e 59 + 6a 33 3a a8 75 e2 91 dd 43 f4 8c b8 8b 9d 5f c0 + d4 99 f9 fc d1 c3 97 f9 af c0 70 cd 9e 39 8c 8d + 19 e6 1d b7 c7 41 0a 6b 26 75 df bf 5d 34 5b 80 + 4d 20 1a dd 50 2d 5c e2 df cb 09 1c e9 99 7b be + be 57 30 6f 38 3e 4d 58 81 03 f0 36 f7 e8 5d 19 + 34 d1 52 a3 23 e4 a8 db 45 1d 6f 4a 5b 1b 0f 10 + 2c c1 50 e0 2f ee e2 b8 8d ea 4a d4 c1 ba cc b2 + 4d 84 07 2d 14 e1 d2 4a 67 71 f7 40 8e e3 05 64 + fb 86 d4 39 3a 34 bc f0 b7 88 50 1d 19 33 03 f1 + 3a 22 84 b0 01 f0 f6 49 ea f7 93 28 d4 ac 5c 43 + 0a b4 41 49 20 a9 46 0e d1 b7 bc 40 ec 65 3e 87 + 6d 09 ab c5 09 ae 45 b5 25 19 01 16 a0 c2 61 01 + 84 82 98 50 9c 1c 3b f3 a4 83 e7 27 40 54 e1 5e + 97 07 50 36 e9 89 f6 09 32 80 7b 52 57 75 1e 79''' + }, + # Plaintext + '''8b ba 6b f8 2a 6c 0f 86 d5 f1 75 6e 97 95 68 70 + b0 89 53 b0 6b 4e b2 05 bc 16 94 ee''', + # Ciphertext + '''53 ea 5d c0 8c d2 60 fb 3b 85 85 67 28 7f a9 15 + 52 c3 0b 2f eb fb a2 13 f0 ae 87 70 2d 06 8d 19 + ba b0 7f e5 74 52 3d fb 42 13 9d 68 c3 c5 af ee + e0 bf e4 cb 79 69 cb f3 82 b8 04 d6 e6 13 96 14 + 4e 2d 0e 60 74 1f 89 93 c3 01 4b 58 b9 b1 95 7a + 8b ab cd 23 af 85 4f 4c 35 6f b1 66 2a a7 2b fc + c7 e5 86 55 9d c4 28 0d 16 0c 12 67 85 a7 23 eb + ee be ff 71 f1 15 94 44 0a ae f8 7d 10 79 3a 87 + 74 a2 39 d4 a0 4c 87 fe 14 67 b9 da f8 52 08 ec + 6c 72 55 79 4a 96 cc 29 14 2f 9a 8b d4 18 e3 c1 + fd 67 34 4b 0c d0 82 9d f3 b2 be c6 02 53 19 62 + 93 c6 b3 4d 3f 75 d3 2f 21 3d d4 5c 62 73 d5 05 + ad f4 cc ed 10 57 cb 75 8f c2 6a ee fa 44 12 55 + ed 4e 64 c1 99 ee 07 5e 7f 16 64 61 82 fd b4 64 + 73 9b 68 ab 5d af f0 e6 3e 95 52 01 68 24 f0 54 + bf 4d 3c 8c 90 a9 7b b6 b6 55 32 84 eb 42 9f cc''', + # Random + '''47 e1 ab 71 19 fe e5 6c 95 ee 5e aa d8 6f 40 d0 + aa 63 bd 33''', + SHA1 + ), + ) + + def testEncrypt1(self): + # Verify encryption using all test vectors + for test in self._testData: + # Build the key + comps = [ long(rws(test[0][x]),16) for x in ('n','e') ] + key = RSA.construct(comps) + # RNG that takes its random numbers from a pool given + # at initialization + class randGen: + def __init__(self, data): + self.data = data + self.idx = 0 + def __call__(self, N): + r = self.data[self.idx:N] + self.idx += N + return r + # The real test + key._randfunc = randGen(t2b(test[3])) + cipher = PKCS.new(key, test[4]) + ct = cipher.encrypt(t2b(test[1])) + self.assertEqual(ct, t2b(test[2])) + + def testEncrypt2(self): + # Verify that encryption fails if plaintext is too long + pt = '\x00'*(128-2*20-2+1) + cipher = PKCS.new(self.key1024) + self.assertRaises(ValueError, cipher.encrypt, pt) + + def testDecrypt1(self): + # Verify decryption using all test vectors + for test in self._testData: + # Build the key + comps = [ long(rws(test[0][x]),16) for x in ('n','e','d') ] + key = RSA.construct(comps) + # The real test + cipher = PKCS.new(key, test[4]) + pt = cipher.decrypt(t2b(test[2])) + self.assertEqual(pt, t2b(test[1])) + + def testDecrypt2(self): + # Simplest possible negative tests + for ct_size in (127,128,129): + cipher = PKCS.new(self.key1024) + self.assertRaises(ValueError, cipher.decrypt, bchr(0x00)*ct_size) + + def testEncryptDecrypt1(self): + # Encrypt/Decrypt messages of length [0..128-2*20-2] + for pt_len in xrange(0,128-2*20-2): + pt = self.rng(pt_len) + cipher = PKCS.new(self.key1024) + ct = cipher.encrypt(pt) + pt2 = cipher.decrypt(ct) + self.assertEqual(pt,pt2) + + def testEncryptDecrypt2(self): + # Helper function to monitor what's requested from RNG + global asked + def localRng(N): + global asked + asked += N + return self.rng(N) + # Verify that OAEP is friendly to all hashes + for hashmod in (MD2,MD5,SHA1,SHA256,RIPEMD): + # Verify that encrypt() asks for as many random bytes + # as the hash output size + asked = 0 + pt = self.rng(40) + self.key1024._randfunc = localRng + cipher = PKCS.new(self.key1024, hashmod) + ct = cipher.encrypt(pt) + self.assertEqual(cipher.decrypt(ct), pt) + self.failUnless(asked > hashmod.digest_size) + + def testEncryptDecrypt3(self): + # Verify that OAEP supports labels + pt = self.rng(35) + xlabel = self.rng(22) + cipher = PKCS.new(self.key1024, label=xlabel) + ct = cipher.encrypt(pt) + self.assertEqual(cipher.decrypt(ct), pt) + + def testEncryptDecrypt4(self): + # Verify that encrypt() uses the custom MGF + global mgfcalls + # Helper function to monitor what's requested from MGF + def newMGF(seed,maskLen): + global mgfcalls + mgfcalls += 1 + return bchr(0x00)*maskLen + mgfcalls = 0 + pt = self.rng(32) + cipher = PKCS.new(self.key1024, mgfunc=newMGF) + ct = cipher.encrypt(pt) + self.assertEqual(mgfcalls, 2) + self.assertEqual(cipher.decrypt(ct), pt) + +def get_tests(config={}): + tests = [] + tests += list_test_cases(PKCS1_OAEP_Tests) + return tests + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Hash/__init__.py b/lib/Crypto/SelfTest/Hash/__init__.py new file mode 100644 index 0000000..bb19f9b --- /dev/null +++ b/lib/Crypto/SelfTest/Hash/__init__.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Hash/__init__.py: Self-test for hash modules +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test for hash modules""" + +__revision__ = "$Id$" + +def get_tests(config={}): + tests = [] + from Crypto.SelfTest.Hash import test_HMAC; tests += test_HMAC.get_tests(config=config) + from Crypto.SelfTest.Hash import test_MD2; tests += test_MD2.get_tests(config=config) + from Crypto.SelfTest.Hash import test_MD4; tests += test_MD4.get_tests(config=config) + from Crypto.SelfTest.Hash import test_MD5; tests += test_MD5.get_tests(config=config) + from Crypto.SelfTest.Hash import test_RIPEMD; tests += test_RIPEMD.get_tests(config=config) + from Crypto.SelfTest.Hash import test_SHA; tests += test_SHA.get_tests(config=config) + from Crypto.SelfTest.Hash import test_SHA256; tests += test_SHA256.get_tests(config=config) + try: + from Crypto.SelfTest.Hash import test_SHA224; tests += test_SHA224.get_tests(config=config) + from Crypto.SelfTest.Hash import test_SHA384; tests += test_SHA384.get_tests(config=config) + from Crypto.SelfTest.Hash import test_SHA512; tests += test_SHA512.get_tests(config=config) + except ImportError: + import sys + sys.stderr.write("SelfTest: warning: not testing SHA224/SHA384/SHA512 modules (not available)\n") + return tests + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Hash/common.py b/lib/Crypto/SelfTest/Hash/common.py new file mode 100644 index 0000000..f77fb0f --- /dev/null +++ b/lib/Crypto/SelfTest/Hash/common.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Hash/common.py: Common code for Crypto.SelfTest.Hash +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-testing for PyCrypto hash modules""" + +__revision__ = "$Id$" + +import sys +import unittest +import binascii +from Crypto.Util.py3compat import * + +# For compatibility with Python 2.1 and Python 2.2 +if sys.hexversion < 0x02030000: + # Python 2.1 doesn't have a dict() function + # Python 2.2 dict() function raises TypeError if you do dict(MD5='blah') + def dict(**kwargs): + return kwargs.copy() +else: + dict = dict + + +class HashDigestSizeSelfTest(unittest.TestCase): + + def __init__(self, hashmod, description, expected): + unittest.TestCase.__init__(self) + self.hashmod = hashmod + self.expected = expected + self.description = description + + def shortDescription(self): + return self.description + + def runTest(self): + self.failUnless(hasattr(self.hashmod, "digest_size")) + self.assertEquals(self.hashmod.digest_size, self.expected) + h = self.hashmod.new() + self.failUnless(hasattr(h, "digest_size")) + self.assertEquals(h.digest_size, self.expected) + + +class HashSelfTest(unittest.TestCase): + + def __init__(self, hashmod, description, expected, input): + unittest.TestCase.__init__(self) + self.hashmod = hashmod + self.expected = expected + self.input = input + self.description = description + + def shortDescription(self): + return self.description + + def runTest(self): + h = self.hashmod.new() + h.update(self.input) + + out1 = binascii.b2a_hex(h.digest()) + out2 = h.hexdigest() + + h = self.hashmod.new(self.input) + + out3 = h.hexdigest() + out4 = binascii.b2a_hex(h.digest()) + + # PY3K: hexdigest() should return str(), and digest() bytes + self.assertEqual(self.expected, out1) # h = .new(); h.update(data); h.digest() + if sys.version_info[0] == 2: + self.assertEqual(self.expected, out2) # h = .new(); h.update(data); h.hexdigest() + self.assertEqual(self.expected, out3) # h = .new(data); h.hexdigest() + else: + self.assertEqual(self.expected.decode(), out2) # h = .new(); h.update(data); h.hexdigest() + self.assertEqual(self.expected.decode(), out3) # h = .new(data); h.hexdigest() + self.assertEqual(self.expected, out4) # h = .new(data); h.digest() + + # Verify that new() object method produces a fresh hash object + h2 = h.new() + h2.update(self.input) + out5 = binascii.b2a_hex(h2.digest()) + self.assertEqual(self.expected, out5) + +class HashTestOID(unittest.TestCase): + def __init__(self, hashmod, oid): + unittest.TestCase.__init__(self) + self.hashmod = hashmod + self.oid = oid + + def runTest(self): + h = self.hashmod.new() + if self.oid==None: + try: + raised = 0 + a = h.oid + except AttributeError: + raised = 1 + self.assertEqual(raised,1) + else: + self.assertEqual(h.oid, self.oid) + +class MACSelfTest(unittest.TestCase): + + def __init__(self, hashmod, description, expected_dict, input, key, hashmods): + unittest.TestCase.__init__(self) + self.hashmod = hashmod + self.expected_dict = expected_dict + self.input = input + self.key = key + self.hashmods = hashmods + self.description = description + + def shortDescription(self): + return self.description + + def runTest(self): + for hashname in self.expected_dict.keys(): + hashmod = self.hashmods[hashname] + key = binascii.a2b_hex(b(self.key)) + data = binascii.a2b_hex(b(self.input)) + + # Strip whitespace from the expected string (which should be in lowercase-hex) + expected = b("".join(self.expected_dict[hashname].split())) + + h = self.hashmod.new(key, digestmod=hashmod) + h.update(data) + out1 = binascii.b2a_hex(h.digest()) + out2 = h.hexdigest() + + h = self.hashmod.new(key, data, hashmod) + + out3 = h.hexdigest() + out4 = binascii.b2a_hex(h.digest()) + + # Test .copy() + h2 = h.copy() + h.update(b("blah blah blah")) # Corrupt the original hash object + out5 = binascii.b2a_hex(h2.digest()) # The copied hash object should return the correct result + + # PY3K: hexdigest() should return str(), and digest() bytes + self.assertEqual(expected, out1) + if sys.version_info[0] == 2: + self.assertEqual(expected, out2) + self.assertEqual(expected, out3) + else: + self.assertEqual(expected.decode(), out2) + self.assertEqual(expected.decode(), out3) + self.assertEqual(expected, out4) + self.assertEqual(expected, out5) + +def make_hash_tests(module, module_name, test_data, digest_size, oid=None): + tests = [] + for i in range(len(test_data)): + row = test_data[i] + (expected, input) = map(b,row[0:2]) + if len(row) < 3: + description = repr(input) + else: + description = row[2].encode('latin-1') + name = "%s #%d: %s" % (module_name, i+1, description) + tests.append(HashSelfTest(module, name, expected, input)) + if oid is not None: + oid = b(oid) + name = "%s #%d: digest_size" % (module_name, i+1) + tests.append(HashDigestSizeSelfTest(module, name, digest_size)) + tests.append(HashTestOID(module, oid)) + return tests + +def make_mac_tests(module, module_name, test_data, hashmods): + tests = [] + for i in range(len(test_data)): + row = test_data[i] + (key, data, results, description) = row + name = "%s #%d: %s" % (module_name, i+1, description) + tests.append(MACSelfTest(module, name, results, data, key, hashmods)) + return tests + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Hash/test_HMAC.py b/lib/Crypto/SelfTest/Hash/test_HMAC.py new file mode 100644 index 0000000..c01c97b --- /dev/null +++ b/lib/Crypto/SelfTest/Hash/test_HMAC.py @@ -0,0 +1,223 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Hash/HMAC.py: Self-test for the HMAC module +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Hash.HMAC""" + +__revision__ = "$Id$" + +from common import dict # For compatibility with Python 2.1 and 2.2 +from Crypto.Util.py3compat import * + +# This is a list of (key, data, results, description) tuples. +test_data = [ + ## Test vectors from RFC 2202 ## + # Test that the default hashmod is MD5 + ('0b' * 16, + '4869205468657265', + dict(default='9294727a3638bb1c13f48ef8158bfc9d'), + 'default-is-MD5'), + + # Test case 1 (MD5) + ('0b' * 16, + '4869205468657265', + dict(MD5='9294727a3638bb1c13f48ef8158bfc9d'), + 'RFC 2202 #1-MD5 (HMAC-MD5)'), + + # Test case 1 (SHA1) + ('0b' * 20, + '4869205468657265', + dict(SHA1='b617318655057264e28bc0b6fb378c8ef146be00'), + 'RFC 2202 #1-SHA1 (HMAC-SHA1)'), + + # Test case 2 + ('4a656665', + '7768617420646f2079612077616e7420666f72206e6f7468696e673f', + dict(MD5='750c783e6ab0b503eaa86e310a5db738', + SHA1='effcdf6ae5eb2fa2d27416d5f184df9c259a7c79'), + 'RFC 2202 #2 (HMAC-MD5/SHA1)'), + + # Test case 3 (MD5) + ('aa' * 16, + 'dd' * 50, + dict(MD5='56be34521d144c88dbb8c733f0e8b3f6'), + 'RFC 2202 #3-MD5 (HMAC-MD5)'), + + # Test case 3 (SHA1) + ('aa' * 20, + 'dd' * 50, + dict(SHA1='125d7342b9ac11cd91a39af48aa17b4f63f175d3'), + 'RFC 2202 #3-SHA1 (HMAC-SHA1)'), + + # Test case 4 + ('0102030405060708090a0b0c0d0e0f10111213141516171819', + 'cd' * 50, + dict(MD5='697eaf0aca3a3aea3a75164746ffaa79', + SHA1='4c9007f4026250c6bc8414f9bf50c86c2d7235da'), + 'RFC 2202 #4 (HMAC-MD5/SHA1)'), + + # Test case 5 (MD5) + ('0c' * 16, + '546573742057697468205472756e636174696f6e', + dict(MD5='56461ef2342edc00f9bab995690efd4c'), + 'RFC 2202 #5-MD5 (HMAC-MD5)'), + + # Test case 5 (SHA1) + # NB: We do not implement hash truncation, so we only test the full hash here. + ('0c' * 20, + '546573742057697468205472756e636174696f6e', + dict(SHA1='4c1a03424b55e07fe7f27be1d58bb9324a9a5a04'), + 'RFC 2202 #5-SHA1 (HMAC-SHA1)'), + + # Test case 6 + ('aa' * 80, + '54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a' + + '65204b6579202d2048617368204b6579204669727374', + dict(MD5='6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd', + SHA1='aa4ae5e15272d00e95705637ce8a3b55ed402112'), + 'RFC 2202 #6 (HMAC-MD5/SHA1)'), + + # Test case 7 + ('aa' * 80, + '54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a' + + '65204b657920616e64204c6172676572205468616e204f6e6520426c6f636b2d' + + '53697a652044617461', + dict(MD5='6f630fad67cda0ee1fb1f562db3aa53e', + SHA1='e8e99d0f45237d786d6bbaa7965c7808bbff1a91'), + 'RFC 2202 #7 (HMAC-MD5/SHA1)'), + + ## Test vectors from RFC 4231 ## + # 4.2. Test Case 1 + ('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', + '4869205468657265', + dict(SHA256=''' + b0344c61d8db38535ca8afceaf0bf12b + 881dc200c9833da726e9376c2e32cff7 + '''), + 'RFC 4231 #1 (HMAC-SHA256)'), + + # 4.3. Test Case 2 - Test with a key shorter than the length of the HMAC + # output. + ('4a656665', + '7768617420646f2079612077616e7420666f72206e6f7468696e673f', + dict(SHA256=''' + 5bdcc146bf60754e6a042426089575c7 + 5a003f089d2739839dec58b964ec3843 + '''), + 'RFC 4231 #2 (HMAC-SHA256)'), + + # 4.4. Test Case 3 - Test with a combined length of key and data that is + # larger than 64 bytes (= block-size of SHA-224 and SHA-256). + ('aa' * 20, + 'dd' * 50, + dict(SHA256=''' + 773ea91e36800e46854db8ebd09181a7 + 2959098b3ef8c122d9635514ced565fe + '''), + 'RFC 4231 #3 (HMAC-SHA256)'), + + # 4.5. Test Case 4 - Test with a combined length of key and data that is + # larger than 64 bytes (= block-size of SHA-224 and SHA-256). + ('0102030405060708090a0b0c0d0e0f10111213141516171819', + 'cd' * 50, + dict(SHA256=''' + 82558a389a443c0ea4cc819899f2083a + 85f0faa3e578f8077a2e3ff46729665b + '''), + 'RFC 4231 #4 (HMAC-SHA256)'), + + # 4.6. Test Case 5 - Test with a truncation of output to 128 bits. + # + # Not included because we do not implement hash truncation. + # + + # 4.7. Test Case 6 - Test with a key larger than 128 bytes (= block-size of + # SHA-384 and SHA-512). + ('aa' * 131, + '54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a' + + '65204b6579202d2048617368204b6579204669727374', + dict(SHA256=''' + 60e431591ee0b67f0d8a26aacbf5b77f + 8e0bc6213728c5140546040f0ee37f54 + '''), + 'RFC 4231 #6 (HMAC-SHA256)'), + + # 4.8. Test Case 7 - Test with a key and data that is larger than 128 bytes + # (= block-size of SHA-384 and SHA-512). + ('aa' * 131, + '5468697320697320612074657374207573696e672061206c6172676572207468' + + '616e20626c6f636b2d73697a65206b657920616e642061206c61726765722074' + + '68616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565' + + '647320746f20626520686173686564206265666f7265206265696e6720757365' + + '642062792074686520484d414320616c676f726974686d2e', + dict(SHA256=''' + 9b09ffa71b942fcb27635fbcd5b0e944 + bfdc63644f0713938a7f51535c3a35e2 + '''), + 'RFC 4231 #7 (HMAC-SHA256)'), +] + +hashlib_test_data = [ + # Test case 8 (SHA224) + ('4a656665', + '7768617420646f2079612077616e74' + + '20666f72206e6f7468696e673f', + dict(SHA224='a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44'), + 'RFC 4634 8.4 SHA224 (HMAC-SHA224)'), + + # Test case 9 (SHA384) + ('4a656665', + '7768617420646f2079612077616e74' + + '20666f72206e6f7468696e673f', + dict(SHA384='af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649'), + 'RFC 4634 8.4 SHA384 (HMAC-SHA384)'), + + # Test case 10 (SHA512) + ('4a656665', + '7768617420646f2079612077616e74' + + '20666f72206e6f7468696e673f', + dict(SHA512='164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737'), + 'RFC 4634 8.4 SHA512 (HMAC-SHA512)'), + +] + +def get_tests(config={}): + global test_data + from Crypto.Hash import HMAC, MD5, SHA as SHA1, SHA256 + from common import make_mac_tests + hashmods = dict(MD5=MD5, SHA1=SHA1, SHA256=SHA256, default=None) + try: + from Crypto.Hash import SHA224, SHA384, SHA512 + hashmods.update(dict(SHA224=SHA224, SHA384=SHA384, SHA512=SHA512)) + test_data += hashlib_test_data + except ImportError: + import sys + sys.stderr.write("SelfTest: warning: not testing HMAC-SHA224/384/512 (not available)\n") + return make_mac_tests(HMAC, "HMAC", test_data, hashmods) + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Hash/test_MD2.py b/lib/Crypto/SelfTest/Hash/test_MD2.py new file mode 100644 index 0000000..db636d4 --- /dev/null +++ b/lib/Crypto/SelfTest/Hash/test_MD2.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Hash/MD2.py: Self-test for the MD2 hash function +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Hash.MD2""" + +__revision__ = "$Id$" + +from Crypto.Util.py3compat import * + +# This is a list of (expected_result, input[, description]) tuples. +test_data = [ + # Test vectors from RFC 1319 + ('8350e5a3e24c153df2275c9f80692773', '', "'' (empty string)"), + ('32ec01ec4a6dac72c0ab96fb34c0b5d1', 'a'), + ('da853b0d3f88d99b30283a69e6ded6bb', 'abc'), + ('ab4f496bfb2a530b219ff33031fe06b0', 'message digest'), + + ('4e8ddff3650292ab5a4108c3aa47940b', 'abcdefghijklmnopqrstuvwxyz', + 'a-z'), + + ('da33def2a42df13975352846c30338cd', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', + 'A-Z, a-z, 0-9'), + + ('d5976f79d83d3a0dc9806c3c66f3efd8', + '1234567890123456789012345678901234567890123456' + + '7890123456789012345678901234567890', + "'1234567890' * 8"), +] + +def get_tests(config={}): + from Crypto.Hash import MD2 + from common import make_hash_tests + return make_hash_tests(MD2, "MD2", test_data, + digest_size=16, + oid="\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x02") + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Hash/test_MD4.py b/lib/Crypto/SelfTest/Hash/test_MD4.py new file mode 100644 index 0000000..1727bb6 --- /dev/null +++ b/lib/Crypto/SelfTest/Hash/test_MD4.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Hash/MD4.py: Self-test for the MD4 hash function +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Hash.MD4""" + +__revision__ = "$Id$" + +from Crypto.Util.py3compat import * + +# This is a list of (expected_result, input[, description]) tuples. +test_data = [ + # Test vectors from RFC 1320 + ('31d6cfe0d16ae931b73c59d7e0c089c0', '', "'' (empty string)"), + ('bde52cb31de33e46245e05fbdbd6fb24', 'a'), + ('a448017aaf21d8525fc10ae87aa6729d', 'abc'), + ('d9130a8164549fe818874806e1c7014b', 'message digest'), + + ('d79e1c308aa5bbcdeea8ed63df412da9', 'abcdefghijklmnopqrstuvwxyz', + 'a-z'), + + ('043f8582f241db351ce627e153e7f0e4', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', + 'A-Z, a-z, 0-9'), + + ('e33b4ddc9c38f2199c3e7b164fcc0536', + '1234567890123456789012345678901234567890123456' + + '7890123456789012345678901234567890', + "'1234567890' * 8"), +] + +def get_tests(config={}): + from Crypto.Hash import MD4 + from common import make_hash_tests + return make_hash_tests(MD4, "MD4", test_data, + digest_size=16, + oid="\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x04") + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Hash/test_MD5.py b/lib/Crypto/SelfTest/Hash/test_MD5.py new file mode 100644 index 0000000..2e293fc --- /dev/null +++ b/lib/Crypto/SelfTest/Hash/test_MD5.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Hash/MD5.py: Self-test for the MD5 hash function +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Hash.MD5""" + +__revision__ = "$Id$" + +from Crypto.Util.py3compat import * + +# This is a list of (expected_result, input[, description]) tuples. +test_data = [ + # Test vectors from RFC 1321 + ('d41d8cd98f00b204e9800998ecf8427e', '', "'' (empty string)"), + ('0cc175b9c0f1b6a831c399e269772661', 'a'), + ('900150983cd24fb0d6963f7d28e17f72', 'abc'), + ('f96b697d7cb7938d525a2f31aaf161d0', 'message digest'), + + ('c3fcd3d76192e4007dfb496cca67e13b', 'abcdefghijklmnopqrstuvwxyz', + 'a-z'), + + ('d174ab98d277d9f5a5611c2c9f419d9f', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', + 'A-Z, a-z, 0-9'), + + ('57edf4a22be3c955ac49da2e2107b67a', + '1234567890123456789012345678901234567890123456' + + '7890123456789012345678901234567890', + "'1234567890' * 8"), +] + +def get_tests(config={}): + from Crypto.Hash import MD5 + from common import make_hash_tests + return make_hash_tests(MD5, "MD5", test_data, + digest_size=16, + oid="\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05") + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Hash/test_RIPEMD.py b/lib/Crypto/SelfTest/Hash/test_RIPEMD.py new file mode 100644 index 0000000..6673a93 --- /dev/null +++ b/lib/Crypto/SelfTest/Hash/test_RIPEMD.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Hash/test_RIPEMD.py: Self-test for the RIPEMD-160 hash function +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +#"""Self-test suite for Crypto.Hash.RIPEMD""" + +__revision__ = "$Id$" + +from Crypto.Util.py3compat import * + +# This is a list of (expected_result, input[, description]) tuples. +test_data = [ + # Test vectors downloaded 2008-09-12 from + # http://homes.esat.kuleuven.be/~bosselae/ripemd160.html + ('9c1185a5c5e9fc54612808977ee8f548b2258d31', '', "'' (empty string)"), + ('0bdc9d2d256b3ee9daae347be6f4dc835a467ffe', 'a'), + ('8eb208f7e05d987a9b044a8e98c6b087f15a0bfc', 'abc'), + ('5d0689ef49d2fae572b881b123a85ffa21595f36', 'message digest'), + + ('f71c27109c692c1b56bbdceb5b9d2865b3708dbc', + 'abcdefghijklmnopqrstuvwxyz', + 'a-z'), + + ('12a053384a9c0c88e405a06c27dcf49ada62eb2b', + 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq', + 'abcdbcd...pnopq'), + + ('b0e20b6e3116640286ed3a87a5713079b21f5189', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', + 'A-Z, a-z, 0-9'), + + ('9b752e45573d4b39f4dbd3323cab82bf63326bfb', + '1234567890' * 8, + "'1234567890' * 8"), + + ('52783243c1697bdbe16d37f97f68f08325dc1528', + 'a' * 10**6, + '"a" * 10**6'), +] + +def get_tests(config={}): + from Crypto.Hash import RIPEMD + from common import make_hash_tests + return make_hash_tests(RIPEMD, "RIPEMD", test_data, + digest_size=20, + oid="\x06\x05\x2b\x24\x03\02\x01") + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Hash/test_SHA.py b/lib/Crypto/SelfTest/Hash/test_SHA.py new file mode 100644 index 0000000..7d72e77 --- /dev/null +++ b/lib/Crypto/SelfTest/Hash/test_SHA.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Hash/SHA.py: Self-test for the SHA-1 hash function +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Hash.SHA""" + +__revision__ = "$Id$" + +from Crypto.Util.py3compat import * + +# Test vectors from various sources +# This is a list of (expected_result, input[, description]) tuples. +test_data = [ + # FIPS PUB 180-2, A.1 - "One-Block Message" + ('a9993e364706816aba3e25717850c26c9cd0d89d', 'abc'), + + # FIPS PUB 180-2, A.2 - "Multi-Block Message" + ('84983e441c3bd26ebaae4aa1f95129e5e54670f1', + 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'), + + # FIPS PUB 180-2, A.3 - "Long Message" +# ('34aa973cd4c4daa4f61eeb2bdbad27316534016f', +# 'a' * 10**6, +# '"a" * 10**6'), + + # RFC 3174: Section 7.3, "TEST4" (multiple of 512 bits) + ('dea356a2cddd90c7a7ecedc5ebb563934f460452', + '01234567' * 80, + '"01234567" * 80'), +] + +def get_tests(config={}): + from Crypto.Hash import SHA + from common import make_hash_tests + return make_hash_tests(SHA, "SHA", test_data, + digest_size=20, + oid="\x06\x05\x2B\x0E\x03\x02\x1A") + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Hash/test_SHA224.py b/lib/Crypto/SelfTest/Hash/test_SHA224.py new file mode 100644 index 0000000..a60f35a --- /dev/null +++ b/lib/Crypto/SelfTest/Hash/test_SHA224.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Hash/test_SHA224.py: Self-test for the SHA-224 hash function +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Hash.SHA224""" + +__revision__ = "$Id$" + +# Test vectors from various sources +# This is a list of (expected_result, input[, description]) tuples. +test_data = [ + + # RFC 3874: Section 3.1, "Test Vector #1 + ('23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7', 'abc'), + + # RFC 3874: Section 3.2, "Test Vector #2 + ('75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525', 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'), + + # RFC 3874: Section 3.3, "Test Vector #3 + ('20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67', 'a' * 10**6, "'a' * 10**6"), + + # Examples from http://de.wikipedia.org/wiki/Secure_Hash_Algorithm + ('d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f', ''), + + ('49b08defa65e644cbf8a2dd9270bdededabc741997d1dadd42026d7b', + 'Franz jagt im komplett verwahrlosten Taxi quer durch Bayern'), + + ('58911e7fccf2971a7d07f93162d8bd13568e71aa8fc86fc1fe9043d1', + 'Frank jagt im komplett verwahrlosten Taxi quer durch Bayern'), + +] + +def get_tests(config={}): + from Crypto.Hash import SHA224 + from common import make_hash_tests + return make_hash_tests(SHA224, "SHA224", test_data, + digest_size=28, + oid='\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04') + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Hash/test_SHA256.py b/lib/Crypto/SelfTest/Hash/test_SHA256.py new file mode 100644 index 0000000..4b45110 --- /dev/null +++ b/lib/Crypto/SelfTest/Hash/test_SHA256.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Hash/test_SHA256.py: Self-test for the SHA-256 hash function +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Hash.SHA256""" + +__revision__ = "$Id$" + +import unittest +from Crypto.Util.py3compat import * + +class LargeSHA256Test(unittest.TestCase): + def runTest(self): + """SHA256: 512/520 MiB test""" + from Crypto.Hash import SHA256 + zeros = bchr(0x00) * (1024*1024) + + h = SHA256.new(zeros) + for i in xrange(511): + h.update(zeros) + + # This test vector is from PyCrypto's old testdata.py file. + self.assertEqual('9acca8e8c22201155389f65abbf6bc9723edc7384ead80503839f49dcc56d767', h.hexdigest()) # 512 MiB + + for i in xrange(8): + h.update(zeros) + + # This test vector is from PyCrypto's old testdata.py file. + self.assertEqual('abf51ad954b246009dfe5a50ecd582fd5b8f1b8b27f30393853c3ef721e7fa6e', h.hexdigest()) # 520 MiB + +def get_tests(config={}): + # Test vectors from FIPS PUB 180-2 + # This is a list of (expected_result, input[, description]) tuples. + test_data = [ + # FIPS PUB 180-2, B.1 - "One-Block Message" + ('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', + 'abc'), + + # FIPS PUB 180-2, B.2 - "Multi-Block Message" + ('248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1', + 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'), + + # FIPS PUB 180-2, B.3 - "Long Message" + ('cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0', + 'a' * 10**6, + '"a" * 10**6'), + + # Test for an old PyCrypto bug. + ('f7fd017a3c721ce7ff03f3552c0813adcc48b7f33f07e5e2ba71e23ea393d103', + 'This message is precisely 55 bytes long, to test a bug.', + 'Length = 55 (mod 64)'), + + # Example from http://de.wikipedia.org/wiki/Secure_Hash_Algorithm + ('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', ''), + + ('d32b568cd1b96d459e7291ebf4b25d007f275c9f13149beeb782fac0716613f8', + 'Franz jagt im komplett verwahrlosten Taxi quer durch Bayern'), + ] + + from Crypto.Hash import SHA256 + from common import make_hash_tests + tests = make_hash_tests(SHA256, "SHA256", test_data, + digest_size=32, + oid="\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01") + + if config.get('slow_tests'): + tests += [LargeSHA256Test()] + + return tests + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Hash/test_SHA384.py b/lib/Crypto/SelfTest/Hash/test_SHA384.py new file mode 100644 index 0000000..b7a72c0 --- /dev/null +++ b/lib/Crypto/SelfTest/Hash/test_SHA384.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Hash/test_SHA.py: Self-test for the SHA-384 hash function +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Hash.SHA384""" + +__revision__ = "$Id$" + +# Test vectors from various sources +# This is a list of (expected_result, input[, description]) tuples. +test_data = [ + + # RFC 4634: Section Page 8.4, "Test 1" + ('cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7', 'abc'), + + # RFC 4634: Section Page 8.4, "Test 2.2" + ('09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039', 'abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu'), + + # RFC 4634: Section Page 8.4, "Test 3" + ('9d0e1809716474cb086e834e310a4a1ced149e9c00f248527972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985', 'a' * 10**6, "'a' * 10**6"), + + # Taken from http://de.wikipedia.org/wiki/Secure_Hash_Algorithm + ('38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b', ''), + + # Example from http://de.wikipedia.org/wiki/Secure_Hash_Algorithm + ('71e8383a4cea32d6fd6877495db2ee353542f46fa44bc23100bca48f3366b84e809f0708e81041f427c6d5219a286677', + 'Franz jagt im komplett verwahrlosten Taxi quer durch Bayern'), + +] + +def get_tests(config={}): + from Crypto.Hash import SHA384 + from common import make_hash_tests + return make_hash_tests(SHA384, "SHA384", test_data, + digest_size=48, + oid='\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02') + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Hash/test_SHA512.py b/lib/Crypto/SelfTest/Hash/test_SHA512.py new file mode 100644 index 0000000..cb86177 --- /dev/null +++ b/lib/Crypto/SelfTest/Hash/test_SHA512.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Hash/test_SHA512.py: Self-test for the SHA-512 hash function +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Hash.SHA512""" + +__revision__ = "$Id$" + +# Test vectors from various sources +# This is a list of (expected_result, input[, description]) tuples. +test_data = [ + + # RFC 4634: Section Page 8.4, "Test 1" + ('ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f', 'abc'), + + # RFC 4634: Section Page 8.4, "Test 2.1" + ('8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909', 'abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu'), + + # RFC 4634: Section Page 8.4, "Test 3" + ('e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b', 'a' * 10**6, "'a' * 10**6"), + + # Taken from http://de.wikipedia.org/wiki/Secure_Hash_Algorithm + ('cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e', ''), + + ('af9ed2de700433b803240a552b41b5a472a6ef3fe1431a722b2063c75e9f07451f67a28e37d09cde769424c96aea6f8971389db9e1993d6c565c3c71b855723c', 'Franz jagt im komplett verwahrlosten Taxi quer durch Bayern'), +] + +def get_tests(config={}): + from Crypto.Hash import SHA512 + from common import make_hash_tests + return make_hash_tests(SHA512, "SHA512", test_data, + digest_size=64, + oid="\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03") + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Protocol/__init__.py b/lib/Crypto/SelfTest/Protocol/__init__.py new file mode 100644 index 0000000..a62c670 --- /dev/null +++ b/lib/Crypto/SelfTest/Protocol/__init__.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Protocol/__init__.py: Self-tests for Crypto.Protocol +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test for Crypto.Protocol""" + +__revision__ = "$Id$" + +def get_tests(config={}): + tests = [] + from Crypto.SelfTest.Protocol import test_chaffing; tests += test_chaffing.get_tests(config=config) + from Crypto.SelfTest.Protocol import test_rfc1751; tests += test_rfc1751.get_tests(config=config) + from Crypto.SelfTest.Protocol import test_AllOrNothing; tests += test_AllOrNothing.get_tests(config=config) + return tests + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Protocol/test_AllOrNothing.py b/lib/Crypto/SelfTest/Protocol/test_AllOrNothing.py new file mode 100644 index 0000000..a211eab --- /dev/null +++ b/lib/Crypto/SelfTest/Protocol/test_AllOrNothing.py @@ -0,0 +1,76 @@ +# +# Test script for Crypto.Protocol.AllOrNothing +# +# Part of the Python Cryptography Toolkit +# +# Written by Andrew Kuchling and others +# +# =================================================================== +# 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. +# =================================================================== + +__revision__ = "$Id$" + +import unittest +from Crypto.Protocol import AllOrNothing +from Crypto.Util.py3compat import * + +text = b("""\ +When in the Course of human events, it becomes necessary for one people to +dissolve the political bands which have connected them with another, and to +assume among the powers of the earth, the separate and equal station to which +the Laws of Nature and of Nature's God entitle them, a decent respect to the +opinions of mankind requires that they should declare the causes which impel +them to the separation. + +We hold these truths to be self-evident, that all men are created equal, that +they are endowed by their Creator with certain unalienable Rights, that among +these are Life, Liberty, and the pursuit of Happiness. That to secure these +rights, Governments are instituted among Men, deriving their just powers from +the consent of the governed. That whenever any Form of Government becomes +destructive of these ends, it is the Right of the People to alter or to +abolish it, and to institute new Government, laying its foundation on such +principles and organizing its powers in such form, as to them shall seem most +likely to effect their Safety and Happiness. +""") + +class AllOrNothingTest (unittest.TestCase): + + def runTest(self): + "Simple test of AllOrNothing" + + from Crypto.Cipher import AES + import base64 + + # The current AllOrNothing will fail + # every so often. Repeat the test + # several times to force this. + for i in range(50): + x = AllOrNothing.AllOrNothing(AES) + + msgblocks = x.digest(text) + + # get a new undigest-only object so there's no leakage + y = AllOrNothing.AllOrNothing(AES) + text2 = y.undigest(msgblocks) + self.assertEqual(text, text2) + +def get_tests(config={}): + return [AllOrNothingTest()] + +if __name__ == "__main__": + unittest.main() diff --git a/lib/Crypto/SelfTest/Protocol/test_KDF.py b/lib/Crypto/SelfTest/Protocol/test_KDF.py new file mode 100644 index 0000000..119836b --- /dev/null +++ b/lib/Crypto/SelfTest/Protocol/test_KDF.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Protocol/test_KDF.py: Self-test for key derivation functions +# +# =================================================================== +# 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. +# =================================================================== + +__revision__ = "$Id$" + +import unittest +from binascii import unhexlify + +from Crypto.SelfTest.st_common import list_test_cases +from Crypto.Hash import SHA as SHA1,HMAC + +from Crypto.Protocol.KDF import * + +def t2b(t): return unhexlify(b(t)) + +class PBKDF1_Tests(unittest.TestCase): + + # List of tuples with test data. + # Each tuple is made up by: + # Item #0: a pass phrase + # Item #1: salt (8 bytes encoded in hex) + # Item #2: output key length + # Item #3: iterations to use + # Item #4: expected result (encoded in hex) + _testData = ( + # From http://www.di-mgt.com.au/cryptoKDFs.html#examplespbkdf + ("password","78578E5A5D63CB06",16,1000,"DC19847E05C64D2FAF10EBFB4A3D2A20"), + ) + + def test1(self): + v = self._testData[0] + res = PBKDF1(v[0], t2b(v[1]), v[2], v[3], SHA1) + self.assertEqual(res, t2b(v[4])) + +class PBKDF2_Tests(unittest.TestCase): + + # List of tuples with test data. + # Each tuple is made up by: + # Item #0: a pass phrase + # Item #1: salt (encoded in hex) + # Item #2: output key length + # Item #3: iterations to use + # Item #4: expected result (encoded in hex) + _testData = ( + # From http://www.di-mgt.com.au/cryptoKDFs.html#examplespbkdf + ("password","78578E5A5D63CB06",24,2048,"BFDE6BE94DF7E11DD409BCE20A0255EC327CB936FFE93643"), + # From RFC 6050 + ("password","73616c74", 20, 1, "0c60c80f961f0e71f3a9b524af6012062fe037a6"), + ("password","73616c74", 20, 2, "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"), + ("password","73616c74", 20, 4096, "4b007901b765489abead49d926f721d065a429c1"), + ("passwordPASSWORDpassword","73616c7453414c5473616c7453414c5473616c7453414c5473616c7453414c5473616c74", + 25, 4096, "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"), + ( 'pass\x00word',"7361006c74",16,4096, "56fa6aa75548099dcc37d7f03425e0c3"), + ) + + def test1(self): + # Test only for HMAC-SHA1 as PRF + + def prf(p,s): + return HMAC.new(p,s,SHA1).digest() + + for i in xrange(len(self._testData)): + v = self._testData[i] + res = PBKDF2(v[0], t2b(v[1]), v[2], v[3]) + res2 = PBKDF2(v[0], t2b(v[1]), v[2], v[3], prf) + self.assertEqual(res, t2b(v[4])) + self.assertEqual(res, res2) + +def get_tests(config={}): + tests = [] + tests += list_test_cases(PBKDF1_Tests) + tests += list_test_cases(PBKDF2_Tests) + return tests + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 diff --git a/lib/Crypto/SelfTest/Protocol/test_chaffing.py b/lib/Crypto/SelfTest/Protocol/test_chaffing.py new file mode 100644 index 0000000..5fa0120 --- /dev/null +++ b/lib/Crypto/SelfTest/Protocol/test_chaffing.py @@ -0,0 +1,74 @@ +# +# Test script for Crypto.Protocol.Chaffing +# +# Part of the Python Cryptography Toolkit +# +# Written by Andrew Kuchling and others +# +# =================================================================== +# 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. +# =================================================================== + +__revision__ = "$Id$" + +import unittest +from Crypto.Protocol import Chaffing + +text = """\ +When in the Course of human events, it becomes necessary for one people to +dissolve the political bands which have connected them with another, and to +assume among the powers of the earth, the separate and equal station to which +the Laws of Nature and of Nature's God entitle them, a decent respect to the +opinions of mankind requires that they should declare the causes which impel +them to the separation. + +We hold these truths to be self-evident, that all men are created equal, that +they are endowed by their Creator with certain unalienable Rights, that among +these are Life, Liberty, and the pursuit of Happiness. That to secure these +rights, Governments are instituted among Men, deriving their just powers from +the consent of the governed. That whenever any Form of Government becomes +destructive of these ends, it is the Right of the People to alter or to +abolish it, and to institute new Government, laying its foundation on such +principles and organizing its powers in such form, as to them shall seem most +likely to effect their Safety and Happiness. +""" + +class ChaffingTest (unittest.TestCase): + + def runTest(self): + "Simple tests of chaffing and winnowing" + # Test constructors + Chaffing.Chaff() + Chaffing.Chaff(0.5, 1) + self.assertRaises(ValueError, Chaffing.Chaff, factor=-1) + self.assertRaises(ValueError, Chaffing.Chaff, blocksper=-1) + + data = [(1, 'data1', 'data1'), (2, 'data2', 'data2')] + c = Chaffing.Chaff(1.0, 1) + c.chaff(data) + chaff = c.chaff(data) + self.assertEqual(len(chaff), 4) + + c = Chaffing.Chaff(0.0, 1) + chaff = c.chaff(data) + self.assertEqual(len(chaff), 2) + +def get_tests(config={}): + return [ChaffingTest()] + +if __name__ == "__main__": + unittest.main() diff --git a/lib/Crypto/SelfTest/Protocol/test_rfc1751.py b/lib/Crypto/SelfTest/Protocol/test_rfc1751.py new file mode 100644 index 0000000..0878cc5 --- /dev/null +++ b/lib/Crypto/SelfTest/Protocol/test_rfc1751.py @@ -0,0 +1,62 @@ +# +# Test script for Crypto.Util.RFC1751. +# +# Part of the Python Cryptography Toolkit +# +# Written by Andrew Kuchling and others +# +# =================================================================== +# 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. +# =================================================================== + +__revision__ = "$Id$" + +import binascii +import unittest +from Crypto.Util import RFC1751 +from Crypto.Util.py3compat import * + +test_data = [('EB33F77EE73D4053', 'TIDE ITCH SLOW REIN RULE MOT'), + ('CCAC2AED591056BE4F90FD441C534766', + 'RASH BUSH MILK LOOK BAD BRIM AVID GAFF BAIT ROT POD LOVE'), + ('EFF81F9BFBC65350920CDD7416DE8009', + 'TROD MUTE TAIL WARM CHAR KONG HAAG CITY BORE O TEAL AWL') + ] + +class RFC1751Test_k2e (unittest.TestCase): + + def runTest (self): + "Check converting keys to English" + for key, words in test_data: + key=binascii.a2b_hex(b(key)) + self.assertEqual(RFC1751.key_to_english(key), words) + +class RFC1751Test_e2k (unittest.TestCase): + + def runTest (self): + "Check converting English strings to keys" + for key, words in test_data: + key=binascii.a2b_hex(b(key)) + self.assertEqual(RFC1751.english_to_key(words), key) + +# class RFC1751Test + +def get_tests(config={}): + return [RFC1751Test_k2e(), RFC1751Test_e2k()] + +if __name__ == "__main__": + unittest.main() diff --git a/lib/Crypto/SelfTest/PublicKey/__init__.py b/lib/Crypto/SelfTest/PublicKey/__init__.py new file mode 100644 index 0000000..61ba53f --- /dev/null +++ b/lib/Crypto/SelfTest/PublicKey/__init__.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/PublicKey/__init__.py: Self-test for public key crypto +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test for public-key crypto""" + +__revision__ = "$Id$" + +import os + +def get_tests(config={}): + tests = [] + from Crypto.SelfTest.PublicKey import test_DSA; tests += test_DSA.get_tests(config=config) + from Crypto.SelfTest.PublicKey import test_RSA; tests += test_RSA.get_tests(config=config) + from Crypto.SelfTest.PublicKey import test_importKey; tests += test_importKey.get_tests(config=config) + from Crypto.SelfTest.PublicKey import test_ElGamal; tests += test_ElGamal.get_tests(config=config) + return tests + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/PublicKey/test_DSA.py b/lib/Crypto/SelfTest/PublicKey/test_DSA.py new file mode 100644 index 0000000..b05f69a --- /dev/null +++ b/lib/Crypto/SelfTest/PublicKey/test_DSA.py @@ -0,0 +1,244 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/PublicKey/test_DSA.py: Self-test for the DSA primitive +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.PublicKey.DSA""" + +__revision__ = "$Id$" + +import sys +import os +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * +from Crypto.Util.py3compat import * + +import unittest +from Crypto.SelfTest.st_common import list_test_cases, a2b_hex, b2a_hex + +def _sws(s): + """Remove whitespace from a text or byte string""" + if isinstance(s,str): + return "".join(s.split()) + else: + return b("").join(s.split()) + +class DSATest(unittest.TestCase): + # Test vector from "Appendix 5. Example of the DSA" of + # "Digital Signature Standard (DSS)", + # U.S. Department of Commerce/National Institute of Standards and Technology + # FIPS 186-2 (+Change Notice), 2000 January 27. + # http://csrc.nist.gov/publications/fips/fips186-2/fips186-2-change1.pdf + + y = _sws("""19131871 d75b1612 a819f29d 78d1b0d7 346f7aa7 7bb62a85 + 9bfd6c56 75da9d21 2d3a36ef 1672ef66 0b8c7c25 5cc0ec74 + 858fba33 f44c0669 9630a76b 030ee333""") + + g = _sws("""626d0278 39ea0a13 413163a5 5b4cb500 299d5522 956cefcb + 3bff10f3 99ce2c2e 71cb9de5 fa24babf 58e5b795 21925c9c + c42e9f6f 464b088c c572af53 e6d78802""") + + p = _sws("""8df2a494 492276aa 3d25759b b06869cb eac0d83a fb8d0cf7 + cbb8324f 0d7882e5 d0762fc5 b7210eaf c2e9adac 32ab7aac + 49693dfb f83724c2 ec0736ee 31c80291""") + + q = _sws("""c773218c 737ec8ee 993b4f2d ed30f48e dace915f""") + + x = _sws("""2070b322 3dba372f de1c0ffc 7b2e3b49 8b260614""") + + k = _sws("""358dad57 1462710f 50e254cf 1a376b2b deaadfbf""") + k_inverse = _sws("""0d516729 8202e49b 4116ac10 4fc3f415 ae52f917""") + m = b2a_hex(b("abc")) + m_hash = _sws("""a9993e36 4706816a ba3e2571 7850c26c 9cd0d89d""") + r = _sws("""8bac1ab6 6410435c b7181f95 b16ab97c 92b341c0""") + s = _sws("""41e2345f 1f56df24 58f426d1 55b4ba2d b6dcd8c8""") + + def setUp(self): + global DSA, Random, bytes_to_long, size + from Crypto.PublicKey import DSA + from Crypto import Random + from Crypto.Util.number import bytes_to_long, inverse, size + + self.dsa = DSA + + def test_generate_1arg(self): + """DSA (default implementation) generated key (1 argument)""" + dsaObj = self.dsa.generate(1024) + self._check_private_key(dsaObj) + pub = dsaObj.publickey() + self._check_public_key(pub) + + def test_generate_2arg(self): + """DSA (default implementation) generated key (2 arguments)""" + dsaObj = self.dsa.generate(1024, Random.new().read) + self._check_private_key(dsaObj) + pub = dsaObj.publickey() + self._check_public_key(pub) + + def test_construct_4tuple(self): + """DSA (default implementation) constructed key (4-tuple)""" + (y, g, p, q) = [bytes_to_long(a2b_hex(param)) for param in (self.y, self.g, self.p, self.q)] + dsaObj = self.dsa.construct((y, g, p, q)) + self._test_verification(dsaObj) + + def test_construct_5tuple(self): + """DSA (default implementation) constructed key (5-tuple)""" + (y, g, p, q, x) = [bytes_to_long(a2b_hex(param)) for param in (self.y, self.g, self.p, self.q, self.x)] + dsaObj = self.dsa.construct((y, g, p, q, x)) + self._test_signing(dsaObj) + self._test_verification(dsaObj) + + def _check_private_key(self, dsaObj): + # Check capabilities + self.assertEqual(1, dsaObj.has_private()) + self.assertEqual(1, dsaObj.can_sign()) + self.assertEqual(0, dsaObj.can_encrypt()) + self.assertEqual(0, dsaObj.can_blind()) + + # Check dsaObj.[ygpqx] -> dsaObj.key.[ygpqx] mapping + self.assertEqual(dsaObj.y, dsaObj.key.y) + self.assertEqual(dsaObj.g, dsaObj.key.g) + self.assertEqual(dsaObj.p, dsaObj.key.p) + self.assertEqual(dsaObj.q, dsaObj.key.q) + self.assertEqual(dsaObj.x, dsaObj.key.x) + + # Sanity check key data + self.assertEqual(1, dsaObj.p > dsaObj.q) # p > q + self.assertEqual(160, size(dsaObj.q)) # size(q) == 160 bits + self.assertEqual(0, (dsaObj.p - 1) % dsaObj.q) # q is a divisor of p-1 + self.assertEqual(dsaObj.y, pow(dsaObj.g, dsaObj.x, dsaObj.p)) # y == g**x mod p + self.assertEqual(1, 0 < dsaObj.x < dsaObj.q) # 0 < x < q + + def _check_public_key(self, dsaObj): + k = a2b_hex(self.k) + m_hash = a2b_hex(self.m_hash) + + # Check capabilities + self.assertEqual(0, dsaObj.has_private()) + self.assertEqual(1, dsaObj.can_sign()) + self.assertEqual(0, dsaObj.can_encrypt()) + self.assertEqual(0, dsaObj.can_blind()) + + # Check dsaObj.[ygpq] -> dsaObj.key.[ygpq] mapping + self.assertEqual(dsaObj.y, dsaObj.key.y) + self.assertEqual(dsaObj.g, dsaObj.key.g) + self.assertEqual(dsaObj.p, dsaObj.key.p) + self.assertEqual(dsaObj.q, dsaObj.key.q) + + # Check that private parameters are all missing + self.assertEqual(0, hasattr(dsaObj, 'x')) + self.assertEqual(0, hasattr(dsaObj.key, 'x')) + + # Sanity check key data + self.assertEqual(1, dsaObj.p > dsaObj.q) # p > q + self.assertEqual(160, size(dsaObj.q)) # size(q) == 160 bits + self.assertEqual(0, (dsaObj.p - 1) % dsaObj.q) # q is a divisor of p-1 + + # Public-only key objects should raise an error when .sign() is called + self.assertRaises(TypeError, dsaObj.sign, m_hash, k) + + # Check __eq__ and __ne__ + self.assertEqual(dsaObj.publickey() == dsaObj.publickey(),True) # assert_ + self.assertEqual(dsaObj.publickey() != dsaObj.publickey(),False) # failIf + + def _test_signing(self, dsaObj): + k = a2b_hex(self.k) + m_hash = a2b_hex(self.m_hash) + r = bytes_to_long(a2b_hex(self.r)) + s = bytes_to_long(a2b_hex(self.s)) + (r_out, s_out) = dsaObj.sign(m_hash, k) + self.assertEqual((r, s), (r_out, s_out)) + + def _test_verification(self, dsaObj): + m_hash = a2b_hex(self.m_hash) + r = bytes_to_long(a2b_hex(self.r)) + s = bytes_to_long(a2b_hex(self.s)) + self.assertEqual(1, dsaObj.verify(m_hash, (r, s))) + self.assertEqual(0, dsaObj.verify(m_hash + b("\0"), (r, s))) + +class DSAFastMathTest(DSATest): + def setUp(self): + DSATest.setUp(self) + self.dsa = DSA.DSAImplementation(use_fast_math=True) + + def test_generate_1arg(self): + """DSA (_fastmath implementation) generated key (1 argument)""" + DSATest.test_generate_1arg(self) + + def test_generate_2arg(self): + """DSA (_fastmath implementation) generated key (2 arguments)""" + DSATest.test_generate_2arg(self) + + def test_construct_4tuple(self): + """DSA (_fastmath implementation) constructed key (4-tuple)""" + DSATest.test_construct_4tuple(self) + + def test_construct_5tuple(self): + """DSA (_fastmath implementation) constructed key (5-tuple)""" + DSATest.test_construct_5tuple(self) + +class DSASlowMathTest(DSATest): + def setUp(self): + DSATest.setUp(self) + self.dsa = DSA.DSAImplementation(use_fast_math=False) + + def test_generate_1arg(self): + """DSA (_slowmath implementation) generated key (1 argument)""" + DSATest.test_generate_1arg(self) + + def test_generate_2arg(self): + """DSA (_slowmath implementation) generated key (2 arguments)""" + DSATest.test_generate_2arg(self) + + def test_construct_4tuple(self): + """DSA (_slowmath implementation) constructed key (4-tuple)""" + DSATest.test_construct_4tuple(self) + + def test_construct_5tuple(self): + """DSA (_slowmath implementation) constructed key (5-tuple)""" + DSATest.test_construct_5tuple(self) + + +def get_tests(config={}): + tests = [] + tests += list_test_cases(DSATest) + try: + from Crypto.PublicKey import _fastmath + tests += list_test_cases(DSAFastMathTest) + except ImportError: + from distutils.sysconfig import get_config_var + import inspect + _fm_path = os.path.normpath(os.path.dirname(os.path.abspath( + inspect.getfile(inspect.currentframe()))) + +"/../../PublicKey/_fastmath"+get_config_var("SO")) + if os.path.exists(_fm_path): + raise ImportError("While the _fastmath module exists, importing "+ + "it failed. This may point to the gmp or mpir shared library "+ + "not being in the path. _fastmath was found at "+_fm_path) + tests += list_test_cases(DSASlowMathTest) + return tests + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/PublicKey/test_ElGamal.py b/lib/Crypto/SelfTest/PublicKey/test_ElGamal.py new file mode 100644 index 0000000..cdee8cf --- /dev/null +++ b/lib/Crypto/SelfTest/PublicKey/test_ElGamal.py @@ -0,0 +1,210 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/PublicKey/test_ElGamal.py: Self-test for the ElGamal primitive +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.PublicKey.ElGamal""" + +__revision__ = "$Id$" + +import unittest +from Crypto.SelfTest.st_common import list_test_cases, a2b_hex, b2a_hex +from Crypto import Random +from Crypto.PublicKey import ElGamal +from Crypto.Util.number import * +from Crypto.Util.py3compat import * + +class ElGamalTest(unittest.TestCase): + + # + # Test vectors + # + # There seem to be no real ElGamal test vectors available in the + # public domain. The following test vectors have been generated + # with libgcrypt 1.5.0. + # + # Encryption + tve=[ + { + # 256 bits + 'p' :'BA4CAEAAED8CBE952AFD2126C63EB3B345D65C2A0A73D2A3AD4138B6D09BD933', + 'g' :'05', + 'y' :'60D063600ECED7C7C55146020E7A31C4476E9793BEAED420FEC9E77604CAE4EF', + 'x' :'1D391BA2EE3C37FE1BA175A69B2C73A11238AD77675932', + 'k' :'F5893C5BAB4131264066F57AB3D8AD89E391A0B68A68A1', + 'pt' :'48656C6C6F207468657265', + 'ct1':'32BFD5F487966CEA9E9356715788C491EC515E4ED48B58F0F00971E93AAA5EC7', + 'ct2':'7BE8FBFF317C93E82FCEF9BD515284BA506603FEA25D01C0CB874A31F315EE68' + }, + + { + # 512 bits + 'p' :'F1B18AE9F7B4E08FDA9A04832F4E919D89462FD31BF12F92791A93519F75076D6CE3942689CDFF2F344CAFF0F82D01864F69F3AECF566C774CBACF728B81A227', + 'g' :'07', + 'y' :'688628C676E4F05D630E1BE39D0066178CA7AA83836B645DE5ADD359B4825A12B02EF4252E4E6FA9BEC1DB0BE90F6D7C8629CABB6E531F472B2664868156E20C', + 'x' :'14E60B1BDFD33436C0DA8A22FDC14A2CCDBBED0627CE68', + 'k' :'38DBF14E1F319BDA9BAB33EEEADCAF6B2EA5250577ACE7', + 'pt' :'48656C6C6F207468657265', + 'ct1':'290F8530C2CC312EC46178724F196F308AD4C523CEABB001FACB0506BFED676083FE0F27AC688B5C749AB3CB8A80CD6F7094DBA421FB19442F5A413E06A9772B', + 'ct2':'1D69AAAD1DC50493FB1B8E8721D621D683F3BF1321BE21BC4A43E11B40C9D4D9C80DE3AAC2AB60D31782B16B61112E68220889D53C4C3136EE6F6CE61F8A23A0' + } + ] + + # Signature + tvs=[ + { + # 256 bits + 'p' :'D2F3C41EA66530838A704A48FFAC9334F4701ECE3A97CEE4C69DD01AE7129DD7', + 'g' :'05', + 'y' :'C3F9417DC0DAFEA6A05C1D2333B7A95E63B3F4F28CC962254B3256984D1012E7', + 'x' :'165E4A39BE44D5A2D8B1332D416BC559616F536BC735BB', + 'k' :'C7F0C794A7EAD726E25A47FF8928013680E73C51DD3D7D99BFDA8F492585928F', + 'h' :'48656C6C6F207468657265', + 'sig1':'35CA98133779E2073EF31165AFCDEB764DD54E96ADE851715495F9C635E1E7C2', + 'sig2':'0135B88B1151279FE5D8078D4FC685EE81177EE9802AB123A73925FC1CB059A7', + }, + { + # 512 bits + 'p' :'E24CF3A4B8A6AF749DCA6D714282FE4AABEEE44A53BB6ED15FBE32B5D3C3EF9CC4124A2ECA331F3C1C1B667ACA3766825217E7B5F9856648D95F05330C6A19CF', + 'g' :'0B', + 'y' :'2AD3A1049CA5D4ED207B2431C79A8719BB4073D4A94E450EA6CEE8A760EB07ADB67C0D52C275EE85D7B52789061EE45F2F37D9B2AE522A51C28329766BFE68AC', + 'x' :'16CBB4F46D9ECCF24FF9F7E63CAA3BD8936341555062AB', + 'k' :'8A3D89A4E429FD2476D7D717251FB79BF900FFE77444E6BB8299DC3F84D0DD57ABAB50732AE158EA52F5B9E7D8813E81FD9F79470AE22F8F1CF9AEC820A78C69', + 'h' :'48656C6C6F207468657265', + 'sig1':'BE001AABAFFF976EC9016198FBFEA14CBEF96B000CCC0063D3324016F9E91FE80D8F9325812ED24DDB2B4D4CF4430B169880B3CE88313B53255BD4EC0378586F', + 'sig2':'5E266F3F837BA204E3BBB6DBECC0611429D96F8C7CE8F4EFDF9D4CB681C2A954468A357BF4242CEC7418B51DFC081BCD21299EF5B5A0DDEF3A139A1817503DDE', + } + ] + + def test_generate_128(self): + self._test_random_key(128) + + def test_generate_512(self): + self._test_random_key(512) + + def test_encryption(self): + for tv in self.tve: + for as_longs in (0,1): + d = self.convert_tv(tv, as_longs) + key = ElGamal.construct(d['key']) + ct = key.encrypt(d['pt'], d['k']) + self.assertEquals(ct[0], d['ct1']) + self.assertEquals(ct[1], d['ct2']) + + def test_decryption(self): + for tv in self.tve: + for as_longs in (0,1): + d = self.convert_tv(tv, as_longs) + key = ElGamal.construct(d['key']) + pt = key.decrypt((d['ct1'], d['ct2'])) + self.assertEquals(pt, d['pt']) + + def test_signing(self): + for tv in self.tvs: + for as_longs in (0,1): + d = self.convert_tv(tv, as_longs) + key = ElGamal.construct(d['key']) + sig1, sig2 = key.sign(d['h'], d['k']) + self.assertEquals(sig1, d['sig1']) + self.assertEquals(sig2, d['sig2']) + + def test_verification(self): + for tv in self.tvs: + for as_longs in (0,1): + d = self.convert_tv(tv, as_longs) + key = ElGamal.construct(d['key']) + # Positive test + res = key.verify( d['h'], (d['sig1'],d['sig2']) ) + self.failUnless(res) + # Negative test + res = key.verify( d['h'], (d['sig1']+1,d['sig2']) ) + self.failIf(res) + + def convert_tv(self, tv, as_longs=0): + """Convert a test vector from textual form (hexadecimal ascii + to either integers or byte strings.""" + key_comps = 'p','g','y','x' + tv2 = {} + for c in tv.keys(): + tv2[c] = a2b_hex(tv[c]) + if as_longs or c in key_comps or c in ('sig1','sig2'): + tv2[c] = bytes_to_long(tv2[c]) + tv2['key']=[] + for c in key_comps: + tv2['key'] += [tv2[c]] + del tv2[c] + return tv2 + + def _test_random_key(self, bits): + elgObj = ElGamal.generate(bits, Random.new().read) + self._check_private_key(elgObj) + self._exercise_primitive(elgObj) + pub = elgObj.publickey() + self._check_public_key(pub) + self._exercise_public_primitive(elgObj) + + def _check_private_key(self, elgObj): + + # Check capabilities + self.failUnless(elgObj.has_private()) + self.failUnless(elgObj.can_sign()) + self.failUnless(elgObj.can_encrypt()) + + # Sanity check key data + self.failUnless(1 +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.PublicKey.RSA""" + +__revision__ = "$Id$" + +import sys +import os +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * +from Crypto.Util.py3compat import * + +import unittest +from Crypto.SelfTest.st_common import list_test_cases, a2b_hex, b2a_hex + +class RSATest(unittest.TestCase): + # Test vectors from "RSA-OAEP and RSA-PSS test vectors (.zip file)" + # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip + # See RSADSI's PKCS#1 page at + # http://www.rsa.com/rsalabs/node.asp?id=2125 + + # from oaep-int.txt + + # TODO: PyCrypto treats the message as starting *after* the leading "00" + # TODO: That behaviour should probably be changed in the future. + plaintext = """ + eb 7a 19 ac e9 e3 00 63 50 e3 29 50 4b 45 e2 + ca 82 31 0b 26 dc d8 7d 5c 68 f1 ee a8 f5 52 67 + c3 1b 2e 8b b4 25 1f 84 d7 e0 b2 c0 46 26 f5 af + f9 3e dc fb 25 c9 c2 b3 ff 8a e1 0e 83 9a 2d db + 4c dc fe 4f f4 77 28 b4 a1 b7 c1 36 2b aa d2 9a + b4 8d 28 69 d5 02 41 21 43 58 11 59 1b e3 92 f9 + 82 fb 3e 87 d0 95 ae b4 04 48 db 97 2f 3a c1 4f + 7b c2 75 19 52 81 ce 32 d2 f1 b7 6d 4d 35 3e 2d + """ + + ciphertext = """ + 12 53 e0 4d c0 a5 39 7b b4 4a 7a b8 7e 9b f2 a0 + 39 a3 3d 1e 99 6f c8 2a 94 cc d3 00 74 c9 5d f7 + 63 72 20 17 06 9e 52 68 da 5d 1c 0b 4f 87 2c f6 + 53 c1 1d f8 23 14 a6 79 68 df ea e2 8d ef 04 bb + 6d 84 b1 c3 1d 65 4a 19 70 e5 78 3b d6 eb 96 a0 + 24 c2 ca 2f 4a 90 fe 9f 2e f5 c9 c1 40 e5 bb 48 + da 95 36 ad 87 00 c8 4f c9 13 0a de a7 4e 55 8d + 51 a7 4d df 85 d8 b5 0d e9 68 38 d6 06 3e 09 55 + """ + + modulus = """ + bb f8 2f 09 06 82 ce 9c 23 38 ac 2b 9d a8 71 f7 + 36 8d 07 ee d4 10 43 a4 40 d6 b6 f0 74 54 f5 1f + b8 df ba af 03 5c 02 ab 61 ea 48 ce eb 6f cd 48 + 76 ed 52 0d 60 e1 ec 46 19 71 9d 8a 5b 8b 80 7f + af b8 e0 a3 df c7 37 72 3e e6 b4 b7 d9 3a 25 84 + ee 6a 64 9d 06 09 53 74 88 34 b2 45 45 98 39 4e + e0 aa b1 2d 7b 61 a5 1f 52 7a 9a 41 f6 c1 68 7f + e2 53 72 98 ca 2a 8f 59 46 f8 e5 fd 09 1d bd cb + """ + + e = 0x11L # public exponent + + prime_factor = """ + c9 7f b1 f0 27 f4 53 f6 34 12 33 ea aa d1 d9 35 + 3f 6c 42 d0 88 66 b1 d0 5a 0f 20 35 02 8b 9d 86 + 98 40 b4 16 66 b4 2e 92 ea 0d a3 b4 32 04 b5 cf + ce 33 52 52 4d 04 16 a5 a4 41 e7 00 af 46 15 03 + """ + + def setUp(self): + global RSA, Random, bytes_to_long + from Crypto.PublicKey import RSA + from Crypto import Random + from Crypto.Util.number import bytes_to_long, inverse + self.n = bytes_to_long(a2b_hex(self.modulus)) + self.p = bytes_to_long(a2b_hex(self.prime_factor)) + + # Compute q, d, and u from n, e, and p + self.q = divmod(self.n, self.p)[0] + self.d = inverse(self.e, (self.p-1)*(self.q-1)) + self.u = inverse(self.p, self.q) # u = e**-1 (mod q) + + self.rsa = RSA + + def test_generate_1arg(self): + """RSA (default implementation) generated key (1 argument)""" + rsaObj = self.rsa.generate(1024) + self._check_private_key(rsaObj) + self._exercise_primitive(rsaObj) + pub = rsaObj.publickey() + self._check_public_key(pub) + self._exercise_public_primitive(rsaObj) + + def test_generate_2arg(self): + """RSA (default implementation) generated key (2 arguments)""" + rsaObj = self.rsa.generate(1024, Random.new().read) + self._check_private_key(rsaObj) + self._exercise_primitive(rsaObj) + pub = rsaObj.publickey() + self._check_public_key(pub) + self._exercise_public_primitive(rsaObj) + + def test_generate_3args(self): + rsaObj = self.rsa.generate(1024, Random.new().read,e=65537) + self._check_private_key(rsaObj) + self._exercise_primitive(rsaObj) + pub = rsaObj.publickey() + self._check_public_key(pub) + self._exercise_public_primitive(rsaObj) + self.assertEqual(65537,rsaObj.e) + + def test_construct_2tuple(self): + """RSA (default implementation) constructed key (2-tuple)""" + pub = self.rsa.construct((self.n, self.e)) + self._check_public_key(pub) + self._check_encryption(pub) + self._check_verification(pub) + + def test_construct_3tuple(self): + """RSA (default implementation) constructed key (3-tuple)""" + rsaObj = self.rsa.construct((self.n, self.e, self.d)) + self._check_encryption(rsaObj) + self._check_decryption(rsaObj) + self._check_signing(rsaObj) + self._check_verification(rsaObj) + + def test_construct_4tuple(self): + """RSA (default implementation) constructed key (4-tuple)""" + rsaObj = self.rsa.construct((self.n, self.e, self.d, self.p)) + self._check_encryption(rsaObj) + self._check_decryption(rsaObj) + self._check_signing(rsaObj) + self._check_verification(rsaObj) + + def test_construct_5tuple(self): + """RSA (default implementation) constructed key (5-tuple)""" + rsaObj = self.rsa.construct((self.n, self.e, self.d, self.p, self.q)) + self._check_private_key(rsaObj) + self._check_encryption(rsaObj) + self._check_decryption(rsaObj) + self._check_signing(rsaObj) + self._check_verification(rsaObj) + + def test_construct_6tuple(self): + """RSA (default implementation) constructed key (6-tuple)""" + rsaObj = self.rsa.construct((self.n, self.e, self.d, self.p, self.q, self.u)) + self._check_private_key(rsaObj) + self._check_encryption(rsaObj) + self._check_decryption(rsaObj) + self._check_signing(rsaObj) + self._check_verification(rsaObj) + + def test_factoring(self): + rsaObj = self.rsa.construct([self.n, self.e, self.d]) + self.failUnless(rsaObj.p==self.p or rsaObj.p==self.q) + self.failUnless(rsaObj.q==self.p or rsaObj.q==self.q) + self.failUnless(rsaObj.q*rsaObj.p == self.n) + + self.assertRaises(ValueError, self.rsa.construct, [self.n, self.e, self.n-1]) + + def _check_private_key(self, rsaObj): + # Check capabilities + self.assertEqual(1, rsaObj.has_private()) + self.assertEqual(1, rsaObj.can_sign()) + self.assertEqual(1, rsaObj.can_encrypt()) + self.assertEqual(1, rsaObj.can_blind()) + + # Check rsaObj.[nedpqu] -> rsaObj.key.[nedpqu] mapping + self.assertEqual(rsaObj.n, rsaObj.key.n) + self.assertEqual(rsaObj.e, rsaObj.key.e) + self.assertEqual(rsaObj.d, rsaObj.key.d) + self.assertEqual(rsaObj.p, rsaObj.key.p) + self.assertEqual(rsaObj.q, rsaObj.key.q) + self.assertEqual(rsaObj.u, rsaObj.key.u) + + # Sanity check key data + self.assertEqual(rsaObj.n, rsaObj.p * rsaObj.q) # n = pq + self.assertEqual(1, rsaObj.d * rsaObj.e % ((rsaObj.p-1) * (rsaObj.q-1))) # ed = 1 (mod (p-1)(q-1)) + self.assertEqual(1, rsaObj.p * rsaObj.u % rsaObj.q) # pu = 1 (mod q) + self.assertEqual(1, rsaObj.p > 1) # p > 1 + self.assertEqual(1, rsaObj.q > 1) # q > 1 + self.assertEqual(1, rsaObj.e > 1) # e > 1 + self.assertEqual(1, rsaObj.d > 1) # d > 1 + + def _check_public_key(self, rsaObj): + ciphertext = a2b_hex(self.ciphertext) + + # Check capabilities + self.assertEqual(0, rsaObj.has_private()) + self.assertEqual(1, rsaObj.can_sign()) + self.assertEqual(1, rsaObj.can_encrypt()) + self.assertEqual(1, rsaObj.can_blind()) + + # Check rsaObj.[ne] -> rsaObj.key.[ne] mapping + self.assertEqual(rsaObj.n, rsaObj.key.n) + self.assertEqual(rsaObj.e, rsaObj.key.e) + + # Check that private parameters are all missing + self.assertEqual(0, hasattr(rsaObj, 'd')) + self.assertEqual(0, hasattr(rsaObj, 'p')) + self.assertEqual(0, hasattr(rsaObj, 'q')) + self.assertEqual(0, hasattr(rsaObj, 'u')) + self.assertEqual(0, hasattr(rsaObj.key, 'd')) + self.assertEqual(0, hasattr(rsaObj.key, 'p')) + self.assertEqual(0, hasattr(rsaObj.key, 'q')) + self.assertEqual(0, hasattr(rsaObj.key, 'u')) + + # Sanity check key data + self.assertEqual(1, rsaObj.e > 1) # e > 1 + + # Public keys should not be able to sign or decrypt + self.assertRaises(TypeError, rsaObj.sign, ciphertext, b("")) + self.assertRaises(TypeError, rsaObj.decrypt, ciphertext) + + # Check __eq__ and __ne__ + self.assertEqual(rsaObj.publickey() == rsaObj.publickey(),True) # assert_ + self.assertEqual(rsaObj.publickey() != rsaObj.publickey(),False) # failIf + + def _exercise_primitive(self, rsaObj): + # Since we're using a randomly-generated key, we can't check the test + # vector, but we can make sure encryption and decryption are inverse + # operations. + ciphertext = a2b_hex(self.ciphertext) + + # Test decryption + plaintext = rsaObj.decrypt((ciphertext,)) + + # Test encryption (2 arguments) + (new_ciphertext2,) = rsaObj.encrypt(plaintext, b("")) + self.assertEqual(b2a_hex(ciphertext), b2a_hex(new_ciphertext2)) + + # Test blinded decryption + blinding_factor = Random.new().read(len(ciphertext)-1) + blinded_ctext = rsaObj.blind(ciphertext, blinding_factor) + blinded_ptext = rsaObj.decrypt((blinded_ctext,)) + unblinded_plaintext = rsaObj.unblind(blinded_ptext, blinding_factor) + self.assertEqual(b2a_hex(plaintext), b2a_hex(unblinded_plaintext)) + + # Test signing (2 arguments) + signature2 = rsaObj.sign(ciphertext, b("")) + self.assertEqual((bytes_to_long(plaintext),), signature2) + + # Test verification + self.assertEqual(1, rsaObj.verify(ciphertext, (bytes_to_long(plaintext),))) + + def _exercise_public_primitive(self, rsaObj): + plaintext = a2b_hex(self.plaintext) + + # Test encryption (2 arguments) + (new_ciphertext2,) = rsaObj.encrypt(plaintext, b("")) + + # Exercise verification + rsaObj.verify(new_ciphertext2, (bytes_to_long(plaintext),)) + + def _check_encryption(self, rsaObj): + plaintext = a2b_hex(self.plaintext) + ciphertext = a2b_hex(self.ciphertext) + + # Test encryption (2 arguments) + (new_ciphertext2,) = rsaObj.encrypt(plaintext, b("")) + self.assertEqual(b2a_hex(ciphertext), b2a_hex(new_ciphertext2)) + + def _check_decryption(self, rsaObj): + plaintext = a2b_hex(self.plaintext) + ciphertext = a2b_hex(self.ciphertext) + + # Test plain decryption + new_plaintext = rsaObj.decrypt((ciphertext,)) + self.assertEqual(b2a_hex(plaintext), b2a_hex(new_plaintext)) + + # Test blinded decryption + blinding_factor = Random.new().read(len(ciphertext)-1) + blinded_ctext = rsaObj.blind(ciphertext, blinding_factor) + blinded_ptext = rsaObj.decrypt((blinded_ctext,)) + unblinded_plaintext = rsaObj.unblind(blinded_ptext, blinding_factor) + self.assertEqual(b2a_hex(plaintext), b2a_hex(unblinded_plaintext)) + + def _check_verification(self, rsaObj): + signature = bytes_to_long(a2b_hex(self.plaintext)) + message = a2b_hex(self.ciphertext) + + # Test verification + t = (signature,) # rsaObj.verify expects a tuple + self.assertEqual(1, rsaObj.verify(message, t)) + + # Test verification with overlong tuple (this is a + # backward-compatibility hack to support some harmless misuse of the + # API) + t2 = (signature, '') + self.assertEqual(1, rsaObj.verify(message, t2)) # extra garbage at end of tuple + + def _check_signing(self, rsaObj): + signature = bytes_to_long(a2b_hex(self.plaintext)) + message = a2b_hex(self.ciphertext) + + # Test signing (2 argument) + self.assertEqual((signature,), rsaObj.sign(message, b(""))) + +class RSAFastMathTest(RSATest): + def setUp(self): + RSATest.setUp(self) + self.rsa = RSA.RSAImplementation(use_fast_math=True) + + def test_generate_1arg(self): + """RSA (_fastmath implementation) generated key (1 argument)""" + RSATest.test_generate_1arg(self) + + def test_generate_2arg(self): + """RSA (_fastmath implementation) generated key (2 arguments)""" + RSATest.test_generate_2arg(self) + + def test_construct_2tuple(self): + """RSA (_fastmath implementation) constructed key (2-tuple)""" + RSATest.test_construct_2tuple(self) + + def test_construct_3tuple(self): + """RSA (_fastmath implementation) constructed key (3-tuple)""" + RSATest.test_construct_3tuple(self) + + def test_construct_4tuple(self): + """RSA (_fastmath implementation) constructed key (4-tuple)""" + RSATest.test_construct_4tuple(self) + + def test_construct_5tuple(self): + """RSA (_fastmath implementation) constructed key (5-tuple)""" + RSATest.test_construct_5tuple(self) + + def test_construct_6tuple(self): + """RSA (_fastmath implementation) constructed key (6-tuple)""" + RSATest.test_construct_6tuple(self) + + def test_factoring(self): + RSATest.test_factoring(self) + +class RSASlowMathTest(RSATest): + def setUp(self): + RSATest.setUp(self) + self.rsa = RSA.RSAImplementation(use_fast_math=False) + + def test_generate_1arg(self): + """RSA (_slowmath implementation) generated key (1 argument)""" + RSATest.test_generate_1arg(self) + + def test_generate_2arg(self): + """RSA (_slowmath implementation) generated key (2 arguments)""" + RSATest.test_generate_2arg(self) + + def test_construct_2tuple(self): + """RSA (_slowmath implementation) constructed key (2-tuple)""" + RSATest.test_construct_2tuple(self) + + def test_construct_3tuple(self): + """RSA (_slowmath implementation) constructed key (3-tuple)""" + RSATest.test_construct_3tuple(self) + + def test_construct_4tuple(self): + """RSA (_slowmath implementation) constructed key (4-tuple)""" + RSATest.test_construct_4tuple(self) + + def test_construct_5tuple(self): + """RSA (_slowmath implementation) constructed key (5-tuple)""" + RSATest.test_construct_5tuple(self) + + def test_construct_6tuple(self): + """RSA (_slowmath implementation) constructed key (6-tuple)""" + RSATest.test_construct_6tuple(self) + + def test_factoring(self): + RSATest.test_factoring(self) + +def get_tests(config={}): + tests = [] + tests += list_test_cases(RSATest) + try: + from Crypto.PublicKey import _fastmath + tests += list_test_cases(RSAFastMathTest) + except ImportError: + from distutils.sysconfig import get_config_var + import inspect + _fm_path = os.path.normpath(os.path.dirname(os.path.abspath( + inspect.getfile(inspect.currentframe()))) + +"/../../PublicKey/_fastmath"+get_config_var("SO")) + if os.path.exists(_fm_path): + raise ImportError("While the _fastmath module exists, importing "+ + "it failed. This may point to the gmp or mpir shared library "+ + "not being in the path. _fastmath was found at "+_fm_path) + if config.get('slow_tests',1): + tests += list_test_cases(RSASlowMathTest) + return tests + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/PublicKey/test_importKey.py b/lib/Crypto/SelfTest/PublicKey/test_importKey.py new file mode 100644 index 0000000..4710440 --- /dev/null +++ b/lib/Crypto/SelfTest/PublicKey/test_importKey.py @@ -0,0 +1,349 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/PublicKey/test_importKey.py: Self-test for importing RSA keys +# +# =================================================================== +# 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. +# =================================================================== + +from __future__ import nested_scopes + +__revision__ = "$Id$" + +import unittest + +from Crypto.PublicKey import RSA +from Crypto.SelfTest.st_common import * +from Crypto.Util.py3compat import * +from Crypto.Util.number import inverse +from Crypto.Util import asn1 + +def der2pem(der, text='PUBLIC'): + import binascii + chunks = [ binascii.b2a_base64(der[i:i+48]) for i in range(0, len(der), 48) ] + pem = b('-----BEGIN %s KEY-----\n' % text) + pem += b('').join(chunks) + pem += b('-----END %s KEY-----' % text) + return pem + +class ImportKeyTests(unittest.TestCase): + # 512-bit RSA key generated with openssl + rsaKeyPEM = u'''-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBAL8eJ5AKoIsjURpcEoGubZMxLD7+kT+TLr7UkvEtFrRhDDKMtuII +q19FrL4pUIMymPMSLBn3hJLe30Dw48GQM4UCAwEAAQJACUSDEp8RTe32ftq8IwG8 +Wojl5mAd1wFiIOrZ/Uv8b963WJOJiuQcVN29vxU5+My9GPZ7RA3hrDBEAoHUDPrI +OQIhAPIPLz4dphiD9imAkivY31Rc5AfHJiQRA7XixTcjEkojAiEAyh/pJHks/Mlr ++rdPNEpotBjfV4M4BkgGAA/ipcmaAjcCIQCHvhwwKVBLzzTscT2HeUdEeBMoiXXK +JACAr3sJQJGxIQIgarRp+m1WSKV1MciwMaTOnbU7wxFs9DP1pva76lYBzgUCIQC9 +n0CnZCJ6IZYqSt0H5N7+Q+2Ro64nuwV/OSQfM6sBwQ== +-----END RSA PRIVATE KEY-----''' + + # As above, but this is actually an unencrypted PKCS#8 key + rsaKeyPEM8 = u'''-----BEGIN PRIVATE KEY----- +MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAvx4nkAqgiyNRGlwS +ga5tkzEsPv6RP5MuvtSS8S0WtGEMMoy24girX0WsvilQgzKY8xIsGfeEkt7fQPDj +wZAzhQIDAQABAkAJRIMSnxFN7fZ+2rwjAbxaiOXmYB3XAWIg6tn9S/xv3rdYk4mK +5BxU3b2/FTn4zL0Y9ntEDeGsMEQCgdQM+sg5AiEA8g8vPh2mGIP2KYCSK9jfVFzk +B8cmJBEDteLFNyMSSiMCIQDKH+kkeSz8yWv6t080Smi0GN9XgzgGSAYAD+KlyZoC +NwIhAIe+HDApUEvPNOxxPYd5R0R4EyiJdcokAICvewlAkbEhAiBqtGn6bVZIpXUx +yLAxpM6dtTvDEWz0M/Wm9rvqVgHOBQIhAL2fQKdkInohlipK3Qfk3v5D7ZGjrie7 +BX85JB8zqwHB +-----END PRIVATE KEY-----''' + + # The same RSA private key as in rsaKeyPEM, but now encrypted + rsaKeyEncryptedPEM=( + + # With DES and passphrase 'test' + ('test', u'''-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-CBC,AF8F9A40BD2FA2FC + +Ckl9ex1kaVEWhYC2QBmfaF+YPiR4NFkRXA7nj3dcnuFEzBnY5XULupqQpQI3qbfA +u8GYS7+b3toWWiHZivHbAAUBPDIZG9hKDyB9Sq2VMARGsX1yW1zhNvZLIiVJzUHs +C6NxQ1IJWOXzTew/xM2I26kPwHIvadq+/VaT8gLQdjdH0jOiVNaevjWnLgrn1mLP +BCNRMdcexozWtAFNNqSzfW58MJL2OdMi21ED184EFytIc1BlB+FZiGZduwKGuaKy +9bMbdb/1PSvsSzPsqW7KSSrTw6MgJAFJg6lzIYvR5F4poTVBxwBX3+EyEmShiaNY +IRX3TgQI0IjrVuLmvlZKbGWP18FXj7I7k9tSsNOOzllTTdq3ny5vgM3A+ynfAaxp +dysKznQ6P+IoqML1WxAID4aGRMWka+uArOJ148Rbj9s= +-----END RSA PRIVATE KEY-----''', + "\xAF\x8F\x9A\x40\xBD\x2F\xA2\xFC"), + + # With Triple-DES and passphrase 'rocking' + ('rocking', u'''-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,C05D6C07F7FC02F6 + +w4lwQrXaVoTTJ0GgwY566htTA2/t1YlimhxkxYt9AEeCcidS5M0Wq9ClPiPz9O7F +m6K5QpM1rxo1RUE/ZyI85gglRNPdNwkeTOqit+kum7nN73AToX17+irVmOA4Z9E+ +4O07t91GxGMcjUSIFk0ucwEU4jgxRvYscbvOMvNbuZszGdVNzBTVddnShKCsy9i7 +nJbPlXeEKYi/OkRgO4PtfqqWQu5GIEFVUf9ev1QV7AvC+kyWTR1wWYnHX265jU5c +sopxQQtP8XEHIJEdd5/p1oieRcWTCNyY8EkslxDSsrf0OtZp6mZH9N+KU47cgQtt +9qGORmlWnsIoFFKcDohbtOaWBTKhkj5h6OkLjFjfU/sBeV1c+7wDT3dAy5tawXjG +YSxC7qDQIT/RECvV3+oQKEcmpEujn45wAnkTi12BH30= +-----END RSA PRIVATE KEY-----''', + "\xC0\x5D\x6C\x07\xF7\xFC\x02\xF6"), + ) + + rsaPublicKeyPEM = u'''-----BEGIN PUBLIC KEY----- +MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAL8eJ5AKoIsjURpcEoGubZMxLD7+kT+T +Lr7UkvEtFrRhDDKMtuIIq19FrL4pUIMymPMSLBn3hJLe30Dw48GQM4UCAwEAAQ== +-----END PUBLIC KEY-----''' + + # Obtained using 'ssh-keygen -i -m PKCS8 -f rsaPublicKeyPEM' + rsaPublicKeyOpenSSH = b('''ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQC/HieQCqCLI1EaXBKBrm2TMSw+/pE/ky6+1JLxLRa0YQwyjLbiCKtfRay+KVCDMpjzEiwZ94SS3t9A8OPBkDOF comment\n''') + + # The private key, in PKCS#1 format encoded with DER + rsaKeyDER = a2b_hex( + '''3082013b020100024100bf1e27900aa08b23511a5c1281ae6d93312c3efe + 913f932ebed492f12d16b4610c328cb6e208ab5f45acbe2950833298f312 + 2c19f78492dedf40f0e3c190338502030100010240094483129f114dedf6 + 7edabc2301bc5a88e5e6601dd7016220ead9fd4bfc6fdeb75893898ae41c + 54ddbdbf1539f8ccbd18f67b440de1ac30440281d40cfac839022100f20f + 2f3e1da61883f62980922bd8df545ce407c726241103b5e2c53723124a23 + 022100ca1fe924792cfcc96bfab74f344a68b418df578338064806000fe2 + a5c99a023702210087be1c3029504bcf34ec713d877947447813288975ca + 240080af7b094091b12102206ab469fa6d5648a57531c8b031a4ce9db53b + c3116cf433f5a6f6bbea5601ce05022100bd9f40a764227a21962a4add07 + e4defe43ed91a3ae27bb057f39241f33ab01c1 + '''.replace(" ","")) + + # The private key, in unencrypted PKCS#8 format encoded with DER + rsaKeyDER8 = a2b_hex( + '''30820155020100300d06092a864886f70d01010105000482013f3082013 + b020100024100bf1e27900aa08b23511a5c1281ae6d93312c3efe913f932 + ebed492f12d16b4610c328cb6e208ab5f45acbe2950833298f3122c19f78 + 492dedf40f0e3c190338502030100010240094483129f114dedf67edabc2 + 301bc5a88e5e6601dd7016220ead9fd4bfc6fdeb75893898ae41c54ddbdb + f1539f8ccbd18f67b440de1ac30440281d40cfac839022100f20f2f3e1da + 61883f62980922bd8df545ce407c726241103b5e2c53723124a23022100c + a1fe924792cfcc96bfab74f344a68b418df578338064806000fe2a5c99a0 + 23702210087be1c3029504bcf34ec713d877947447813288975ca240080a + f7b094091b12102206ab469fa6d5648a57531c8b031a4ce9db53bc3116cf + 433f5a6f6bbea5601ce05022100bd9f40a764227a21962a4add07e4defe4 + 3ed91a3ae27bb057f39241f33ab01c1 + '''.replace(" ","")) + + rsaPublicKeyDER = a2b_hex( + '''305c300d06092a864886f70d0101010500034b003048024100bf1e27900a + a08b23511a5c1281ae6d93312c3efe913f932ebed492f12d16b4610c328c + b6e208ab5f45acbe2950833298f3122c19f78492dedf40f0e3c190338502 + 03010001 + '''.replace(" ","")) + + n = long('BF 1E 27 90 0A A0 8B 23 51 1A 5C 12 81 AE 6D 93 31 2C 3E FE 91 3F 93 2E BE D4 92 F1 2D 16 B4 61 0C 32 8C B6 E2 08 AB 5F 45 AC BE 29 50 83 32 98 F3 12 2C 19 F7 84 92 DE DF 40 F0 E3 C1 90 33 85'.replace(" ",""),16) + e = 65537L + d = long('09 44 83 12 9F 11 4D ED F6 7E DA BC 23 01 BC 5A 88 E5 E6 60 1D D7 01 62 20 EA D9 FD 4B FC 6F DE B7 58 93 89 8A E4 1C 54 DD BD BF 15 39 F8 CC BD 18 F6 7B 44 0D E1 AC 30 44 02 81 D4 0C FA C8 39'.replace(" ",""),16) + p = long('00 F2 0F 2F 3E 1D A6 18 83 F6 29 80 92 2B D8 DF 54 5C E4 07 C7 26 24 11 03 B5 E2 C5 37 23 12 4A 23'.replace(" ",""),16) + q = long('00 CA 1F E9 24 79 2C FC C9 6B FA B7 4F 34 4A 68 B4 18 DF 57 83 38 06 48 06 00 0F E2 A5 C9 9A 02 37'.replace(" ",""),16) + + # This is q^{-1} mod p). fastmath and slowmath use pInv (p^{-1} + # mod q) instead! + qInv = long('00 BD 9F 40 A7 64 22 7A 21 96 2A 4A DD 07 E4 DE FE 43 ED 91 A3 AE 27 BB 05 7F 39 24 1F 33 AB 01 C1'.replace(" ",""),16) + pInv = inverse(p,q) + + def testImportKey1(self): + """Verify import of RSAPrivateKey DER SEQUENCE""" + key = self.rsa.importKey(self.rsaKeyDER) + self.failUnless(key.has_private()) + self.assertEqual(key.n, self.n) + self.assertEqual(key.e, self.e) + self.assertEqual(key.d, self.d) + self.assertEqual(key.p, self.p) + self.assertEqual(key.q, self.q) + + def testImportKey2(self): + """Verify import of SubjectPublicKeyInfo DER SEQUENCE""" + key = self.rsa.importKey(self.rsaPublicKeyDER) + self.failIf(key.has_private()) + self.assertEqual(key.n, self.n) + self.assertEqual(key.e, self.e) + + def testImportKey3unicode(self): + """Verify import of RSAPrivateKey DER SEQUENCE, encoded with PEM as unicode""" + key = RSA.importKey(self.rsaKeyPEM) + self.assertEqual(key.has_private(),True) # assert_ + self.assertEqual(key.n, self.n) + self.assertEqual(key.e, self.e) + self.assertEqual(key.d, self.d) + self.assertEqual(key.p, self.p) + self.assertEqual(key.q, self.q) + + def testImportKey3bytes(self): + """Verify import of RSAPrivateKey DER SEQUENCE, encoded with PEM as byte string""" + key = RSA.importKey(b(self.rsaKeyPEM)) + self.assertEqual(key.has_private(),True) # assert_ + self.assertEqual(key.n, self.n) + self.assertEqual(key.e, self.e) + self.assertEqual(key.d, self.d) + self.assertEqual(key.p, self.p) + self.assertEqual(key.q, self.q) + + def testImportKey4unicode(self): + """Verify import of RSAPrivateKey DER SEQUENCE, encoded with PEM as unicode""" + key = RSA.importKey(self.rsaPublicKeyPEM) + self.assertEqual(key.has_private(),False) # failIf + self.assertEqual(key.n, self.n) + self.assertEqual(key.e, self.e) + + def testImportKey4bytes(self): + """Verify import of SubjectPublicKeyInfo DER SEQUENCE, encoded with PEM as byte string""" + key = RSA.importKey(b(self.rsaPublicKeyPEM)) + self.assertEqual(key.has_private(),False) # failIf + self.assertEqual(key.n, self.n) + self.assertEqual(key.e, self.e) + + def testImportKey5(self): + """Verifies that the imported key is still a valid RSA pair""" + key = RSA.importKey(self.rsaKeyPEM) + idem = key.encrypt(key.decrypt(b("Test")),0) + self.assertEqual(idem[0],b("Test")) + + def testImportKey6(self): + """Verifies that the imported key is still a valid RSA pair""" + key = RSA.importKey(self.rsaKeyDER) + idem = key.encrypt(key.decrypt(b("Test")),0) + self.assertEqual(idem[0],b("Test")) + + def testImportKey7(self): + """Verify import of OpenSSH public key""" + key = self.rsa.importKey(self.rsaPublicKeyOpenSSH) + self.assertEqual(key.n, self.n) + self.assertEqual(key.e, self.e) + + def testImportKey8(self): + """Verify import of encrypted PrivateKeyInfo DER SEQUENCE""" + for t in self.rsaKeyEncryptedPEM: + key = self.rsa.importKey(t[1], t[0]) + self.failUnless(key.has_private()) + self.assertEqual(key.n, self.n) + self.assertEqual(key.e, self.e) + self.assertEqual(key.d, self.d) + self.assertEqual(key.p, self.p) + self.assertEqual(key.q, self.q) + + def testImportKey9(self): + """Verify import of unencrypted PrivateKeyInfo DER SEQUENCE""" + key = self.rsa.importKey(self.rsaKeyDER8) + self.failUnless(key.has_private()) + self.assertEqual(key.n, self.n) + self.assertEqual(key.e, self.e) + self.assertEqual(key.d, self.d) + self.assertEqual(key.p, self.p) + self.assertEqual(key.q, self.q) + + def testImportKey10(self): + """Verify import of unencrypted PrivateKeyInfo DER SEQUENCE, encoded with PEM""" + key = self.rsa.importKey(self.rsaKeyPEM8) + self.failUnless(key.has_private()) + self.assertEqual(key.n, self.n) + self.assertEqual(key.e, self.e) + self.assertEqual(key.d, self.d) + self.assertEqual(key.p, self.p) + self.assertEqual(key.q, self.q) + + def testImportKey11(self): + """Verify import of RSAPublicKey DER SEQUENCE""" + der = asn1.DerSequence([17, 3]).encode() + key = self.rsa.importKey(der) + self.assertEqual(key.n, 17) + self.assertEqual(key.e, 3) + + def testImportKey12(self): + """Verify import of RSAPublicKey DER SEQUENCE, encoded with PEM""" + der = asn1.DerSequence([17, 3]).encode() + pem = der2pem(der) + key = self.rsa.importKey(pem) + self.assertEqual(key.n, 17) + self.assertEqual(key.e, 3) + + ### + def testExportKey1(self): + key = self.rsa.construct([self.n, self.e, self.d, self.p, self.q, self.pInv]) + derKey = key.exportKey("DER") + self.assertEqual(derKey, self.rsaKeyDER) + + def testExportKey2(self): + key = self.rsa.construct([self.n, self.e]) + derKey = key.exportKey("DER") + self.assertEqual(derKey, self.rsaPublicKeyDER) + + def testExportKey3(self): + key = self.rsa.construct([self.n, self.e, self.d, self.p, self.q, self.pInv]) + pemKey = key.exportKey("PEM") + self.assertEqual(pemKey, b(self.rsaKeyPEM)) + + def testExportKey4(self): + key = self.rsa.construct([self.n, self.e]) + pemKey = key.exportKey("PEM") + self.assertEqual(pemKey, b(self.rsaPublicKeyPEM)) + + def testExportKey5(self): + key = self.rsa.construct([self.n, self.e]) + openssh_1 = key.exportKey("OpenSSH").split() + openssh_2 = self.rsaPublicKeyOpenSSH.split() + self.assertEqual(openssh_1[0], openssh_2[0]) + self.assertEqual(openssh_1[1], openssh_2[1]) + + def testExportKey6(self): + key = self.rsa.construct([self.n, self.e, self.d, self.p, self.q, self.pInv]) + # Tuple with index #1 is encrypted with 3DES + t = map(b,self.rsaKeyEncryptedPEM[1]) + # Force the salt being used when exporting + key._randfunc = lambda N: (t[2]*divmod(N+len(t[2]),len(t[2]))[0])[:N] + pemKey = key.exportKey("PEM", t[0]) + self.assertEqual(pemKey, t[1]) + + def testExportKey7(self): + key = self.rsa.construct([self.n, self.e, self.d, self.p, self.q, self.pInv]) + derKey = key.exportKey("DER", pkcs=8) + self.assertEqual(derKey, self.rsaKeyDER8) + + def testExportKey8(self): + key = self.rsa.construct([self.n, self.e, self.d, self.p, self.q, self.pInv]) + pemKey = key.exportKey("PEM", pkcs=8) + self.assertEqual(pemKey, b(self.rsaKeyPEM8)) + + def testExportKey9(self): + key = self.rsa.construct([self.n, self.e, self.d, self.p, self.q, self.pInv]) + self.assertRaises(ValueError, key.exportKey, "invalid-format") + +class ImportKeyTestsSlow(ImportKeyTests): + def setUp(self): + self.rsa = RSA.RSAImplementation(use_fast_math=0) + +class ImportKeyTestsFast(ImportKeyTests): + def setUp(self): + self.rsa = RSA.RSAImplementation(use_fast_math=1) + +if __name__ == '__main__': + unittest.main() + +def get_tests(config={}): + tests = [] + try: + from Crypto.PublicKey import _fastmath + tests += list_test_cases(ImportKeyTestsFast) + except ImportError: + pass + tests += list_test_cases(ImportKeyTestsSlow) + return tests + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Random/Fortuna/__init__.py b/lib/Crypto/SelfTest/Random/Fortuna/__init__.py new file mode 100644 index 0000000..81a0e13 --- /dev/null +++ b/lib/Crypto/SelfTest/Random/Fortuna/__init__.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Random/Fortuna/__init__.py: Self-test for Fortuna modules +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test for the Crypto.Random.Fortuna package""" + +__revision__ = "$Id$" + +import os + +def get_tests(config={}): + tests = [] + from Crypto.SelfTest.Random.Fortuna import test_FortunaAccumulator; tests += test_FortunaAccumulator.get_tests(config=config) + from Crypto.SelfTest.Random.Fortuna import test_FortunaGenerator; tests += test_FortunaGenerator.get_tests(config=config) + from Crypto.SelfTest.Random.Fortuna import test_SHAd256; tests += test_SHAd256.get_tests(config=config) + return tests + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Random/Fortuna/test_FortunaAccumulator.py b/lib/Crypto/SelfTest/Random/Fortuna/test_FortunaAccumulator.py new file mode 100644 index 0000000..c4e6ccf --- /dev/null +++ b/lib/Crypto/SelfTest/Random/Fortuna/test_FortunaAccumulator.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Random/Fortuna/test_FortunaAccumulator.py: Self-test for the FortunaAccumulator module +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-tests for Crypto.Random.Fortuna.FortunaAccumulator""" + +__revision__ = "$Id$" + +import sys +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * +from Crypto.Util.py3compat import * + +import unittest +from binascii import b2a_hex + +class FortunaAccumulatorTests(unittest.TestCase): + def setUp(self): + global FortunaAccumulator + from Crypto.Random.Fortuna import FortunaAccumulator + + def test_FortunaPool(self): + """FortunaAccumulator.FortunaPool""" + pool = FortunaAccumulator.FortunaPool() + self.assertEqual(0, pool.length) + self.assertEqual("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456", pool.hexdigest()) + + pool.append(b('abc')) + + self.assertEqual(3, pool.length) + self.assertEqual("4f8b42c22dd3729b519ba6f68d2da7cc5b2d606d05daed5ad5128cc03e6c6358", pool.hexdigest()) + + pool.append(b("dbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")) + + self.assertEqual(56, pool.length) + self.assertEqual(b('0cffe17f68954dac3a84fb1458bd5ec99209449749b2b308b7cb55812f9563af'), b2a_hex(pool.digest())) + + pool.reset() + + self.assertEqual(0, pool.length) + + pool.append(b('a') * 10**6) + + self.assertEqual(10**6, pool.length) + self.assertEqual(b('80d1189477563e1b5206b2749f1afe4807e5705e8bd77887a60187a712156688'), b2a_hex(pool.digest())) + + def test_which_pools(self): + """FortunaAccumulator.which_pools""" + + # which_pools(0) should fail + self.assertRaises(AssertionError, FortunaAccumulator.which_pools, 0) + + self.assertEqual(FortunaAccumulator.which_pools(1), [0]) + self.assertEqual(FortunaAccumulator.which_pools(2), [0, 1]) + self.assertEqual(FortunaAccumulator.which_pools(3), [0]) + self.assertEqual(FortunaAccumulator.which_pools(4), [0, 1, 2]) + self.assertEqual(FortunaAccumulator.which_pools(5), [0]) + self.assertEqual(FortunaAccumulator.which_pools(6), [0, 1]) + self.assertEqual(FortunaAccumulator.which_pools(7), [0]) + self.assertEqual(FortunaAccumulator.which_pools(8), [0, 1, 2, 3]) + for i in range(1, 32): + self.assertEqual(FortunaAccumulator.which_pools(2L**i-1), [0]) + self.assertEqual(FortunaAccumulator.which_pools(2L**i), range(i+1)) + self.assertEqual(FortunaAccumulator.which_pools(2L**i+1), [0]) + self.assertEqual(FortunaAccumulator.which_pools(2L**31), range(32)) + self.assertEqual(FortunaAccumulator.which_pools(2L**32), range(32)) + self.assertEqual(FortunaAccumulator.which_pools(2L**33), range(32)) + self.assertEqual(FortunaAccumulator.which_pools(2L**34), range(32)) + self.assertEqual(FortunaAccumulator.which_pools(2L**35), range(32)) + self.assertEqual(FortunaAccumulator.which_pools(2L**36), range(32)) + self.assertEqual(FortunaAccumulator.which_pools(2L**64), range(32)) + self.assertEqual(FortunaAccumulator.which_pools(2L**128), range(32)) + + def test_accumulator(self): + """FortunaAccumulator.FortunaAccumulator""" + fa = FortunaAccumulator.FortunaAccumulator() + + # This should fail, because we haven't seeded the PRNG yet + self.assertRaises(AssertionError, fa.random_data, 1) + + # Spread some test data across the pools (source number 42) + # This would be horribly insecure in a real system. + for p in range(32): + fa.add_random_event(42, p, b("X") * 32) + self.assertEqual(32+2, fa.pools[p].length) + + # This should still fail, because we haven't seeded the PRNG with 64 bytes yet + self.assertRaises(AssertionError, fa.random_data, 1) + + # Add more data + for p in range(32): + fa.add_random_event(42, p, b("X") * 32) + self.assertEqual((32+2)*2, fa.pools[p].length) + + # The underlying RandomGenerator should get seeded with Pool 0 + # s = SHAd256(chr(42) + chr(32) + "X"*32 + chr(42) + chr(32) + "X"*32) + # = SHA256(h'edd546f057b389155a31c32e3975e736c1dec030ddebb137014ecbfb32ed8c6f') + # = h'aef42a5dcbddab67e8efa118e1b47fde5d697f89beb971b99e6e8e5e89fbf064' + # The counter and the key before reseeding is: + # C_0 = 0 + # K_0 = "\x00" * 32 + # The counter after reseeding is 1, and the new key after reseeding is + # C_1 = 1 + # K_1 = SHAd256(K_0 || s) + # = SHA256(h'0eae3e401389fab86640327ac919ecfcb067359d95469e18995ca889abc119a6') + # = h'aafe9d0409fbaaafeb0a1f2ef2014a20953349d3c1c6e6e3b962953bea6184dd' + # The first block of random data, therefore, is + # r_1 = AES-256(K_1, 1) + # = AES-256(K_1, h'01000000000000000000000000000000') + # = h'b7b86bd9a27d96d7bb4add1b6b10d157' + # The second block of random data is + # r_2 = AES-256(K_1, 2) + # = AES-256(K_1, h'02000000000000000000000000000000') + # = h'2350b1c61253db2f8da233be726dc15f' + # The third and fourth blocks of random data (which become the new key) are + # r_3 = AES-256(K_1, 3) + # = AES-256(K_1, h'03000000000000000000000000000000') + # = h'f23ad749f33066ff53d307914fbf5b21' + # r_4 = AES-256(K_1, 4) + # = AES-256(K_1, h'04000000000000000000000000000000') + # = h'da9667c7e86ba247655c9490e9d94a7c' + # K_2 = r_3 || r_4 + # = h'f23ad749f33066ff53d307914fbf5b21da9667c7e86ba247655c9490e9d94a7c' + # The final counter value is 5. + self.assertEqual("aef42a5dcbddab67e8efa118e1b47fde5d697f89beb971b99e6e8e5e89fbf064", + fa.pools[0].hexdigest()) + self.assertEqual(None, fa.generator.key) + self.assertEqual(0, fa.generator.counter.next_value()) + + result = fa.random_data(32) + + self.assertEqual(b("b7b86bd9a27d96d7bb4add1b6b10d157" "2350b1c61253db2f8da233be726dc15f"), b2a_hex(result)) + self.assertEqual(b("f23ad749f33066ff53d307914fbf5b21da9667c7e86ba247655c9490e9d94a7c"), b2a_hex(fa.generator.key)) + self.assertEqual(5, fa.generator.counter.next_value()) + + def test_accumulator_pool_length(self): + """FortunaAccumulator.FortunaAccumulator minimum pool length""" + fa = FortunaAccumulator.FortunaAccumulator() + + # This test case is hard-coded to assume that FortunaAccumulator.min_pool_size is 64. + self.assertEqual(fa.min_pool_size, 64) + + # The PRNG should not allow us to get random data from it yet + self.assertRaises(AssertionError, fa.random_data, 1) + + # Add 60 bytes, 4 at a time (2 header + 2 payload) to each of the 32 pools + for i in range(15): + for p in range(32): + # Add the bytes to the pool + fa.add_random_event(2, p, b("XX")) + + # The PRNG should not allow us to get random data from it yet + self.assertRaises(AssertionError, fa.random_data, 1) + + # Add 4 more bytes to pool 0 + fa.add_random_event(2, 0, b("XX")) + + # We should now be able to get data from the accumulator + fa.random_data(1) + +def get_tests(config={}): + from Crypto.SelfTest.st_common import list_test_cases + return list_test_cases(FortunaAccumulatorTests) + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Random/Fortuna/test_FortunaGenerator.py b/lib/Crypto/SelfTest/Random/Fortuna/test_FortunaGenerator.py new file mode 100644 index 0000000..d41bb02 --- /dev/null +++ b/lib/Crypto/SelfTest/Random/Fortuna/test_FortunaGenerator.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Random/Fortuna/test_FortunaGenerator.py: Self-test for the FortunaGenerator module +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-tests for Crypto.Random.Fortuna.FortunaGenerator""" + +__revision__ = "$Id$" + +import sys +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * +from Crypto.Util.py3compat import * + +import unittest +from binascii import b2a_hex + +class FortunaGeneratorTests(unittest.TestCase): + def setUp(self): + global FortunaGenerator + from Crypto.Random.Fortuna import FortunaGenerator + + def test_generator(self): + """FortunaGenerator.AESGenerator""" + fg = FortunaGenerator.AESGenerator() + + # We shouldn't be able to read data until we've seeded the generator + self.assertRaises(Exception, fg.pseudo_random_data, 1) + self.assertEqual(0, fg.counter.next_value()) + + # Seed the generator, which should set the key and increment the counter. + fg.reseed(b("Hello")) + self.assertEqual(b("0ea6919d4361551364242a4ba890f8f073676e82cf1a52bb880f7e496648b565"), b2a_hex(fg.key)) + self.assertEqual(1, fg.counter.next_value()) + + # Read 2 full blocks from the generator + self.assertEqual(b("7cbe2c17684ac223d08969ee8b565616") + # counter=1 + b("717661c0d2f4758bd6ba140bf3791abd"), # counter=2 + b2a_hex(fg.pseudo_random_data(32))) + + # Meanwhile, the generator will have re-keyed itself and incremented its counter + self.assertEqual(b("33a1bb21987859caf2bbfc5615bef56d") + # counter=3 + b("e6b71ff9f37112d0c193a135160862b7"), # counter=4 + b2a_hex(fg.key)) + self.assertEqual(5, fg.counter.next_value()) + + # Read another 2 blocks from the generator + self.assertEqual(b("fd6648ba3086e919cee34904ef09a7ff") + # counter=5 + b("021f77580558b8c3e9248275f23042bf"), # counter=6 + b2a_hex(fg.pseudo_random_data(32))) + + + # Try to read more than 2**20 bytes using the internal function. This should fail. + self.assertRaises(AssertionError, fg._pseudo_random_data, 2**20+1) + +def get_tests(config={}): + from Crypto.SelfTest.st_common import list_test_cases + return list_test_cases(FortunaGeneratorTests) + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Random/Fortuna/test_SHAd256.py b/lib/Crypto/SelfTest/Random/Fortuna/test_SHAd256.py new file mode 100644 index 0000000..f94db8a --- /dev/null +++ b/lib/Crypto/SelfTest/Random/Fortuna/test_SHAd256.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Random/Fortuna/test_SHAd256.py: Self-test for the SHAd256 hash function +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Random.Fortuna.SHAd256""" + +__revision__ = "$Id$" +from Crypto.Util.py3compat import * + +# This is a list of (expected_result, input[, description]) tuples. +test_data = [ + # I could not find any test vectors for SHAd256, so I made these vectors by + # feeding some sample data into several plain SHA256 implementations + # (including OpenSSL, the "sha256sum" tool, and this implementation). + # This is a subset of the resulting test vectors. The complete list can be + # found at: http://www.dlitz.net/crypto/shad256-test-vectors/ + ('5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456', + '', "'' (empty string)"), + ('4f8b42c22dd3729b519ba6f68d2da7cc5b2d606d05daed5ad5128cc03e6c6358', + 'abc'), + ('0cffe17f68954dac3a84fb1458bd5ec99209449749b2b308b7cb55812f9563af', + 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq') +] + +def get_tests(config={}): + from Crypto.Random.Fortuna import SHAd256 + from Crypto.SelfTest.Hash.common import make_hash_tests + return make_hash_tests(SHAd256, "SHAd256", test_data, 32) + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Random/OSRNG/__init__.py b/lib/Crypto/SelfTest/Random/OSRNG/__init__.py new file mode 100644 index 0000000..44b3fa1 --- /dev/null +++ b/lib/Crypto/SelfTest/Random/OSRNG/__init__.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Random/OSRNG/__init__.py: Self-test for OSRNG modules +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test for Crypto.Random.OSRNG package""" + +__revision__ = "$Id$" + +import os + +def get_tests(config={}): + tests = [] + if os.name == 'nt': + from Crypto.SelfTest.Random.OSRNG import test_nt; tests += test_nt.get_tests(config=config) + from Crypto.SelfTest.Random.OSRNG import test_winrandom; tests += test_winrandom.get_tests(config=config) + elif os.name == 'posix': + from Crypto.SelfTest.Random.OSRNG import test_posix; tests += test_posix.get_tests(config=config) + if hasattr(os, 'urandom'): + from Crypto.SelfTest.Random.OSRNG import test_fallback; tests += test_fallback.get_tests(config=config) + from Crypto.SelfTest.Random.OSRNG import test_generic; tests += test_generic.get_tests(config=config) + return tests + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Random/OSRNG/test_fallback.py b/lib/Crypto/SelfTest/Random/OSRNG/test_fallback.py new file mode 100644 index 0000000..41909b0 --- /dev/null +++ b/lib/Crypto/SelfTest/Random/OSRNG/test_fallback.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Util/test_fallback.py: Self-test for the OSRNG.fallback.new() function +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Random.OSRNG.fallback""" + +__revision__ = "$Id$" + +import unittest + +class SimpleTest(unittest.TestCase): + def runTest(self): + """Crypto.Random.OSRNG.fallback.new()""" + # Import the OSRNG.nt module and try to use it + import Crypto.Random.OSRNG.fallback + randobj = Crypto.Random.OSRNG.fallback.new() + x = randobj.read(16) + y = randobj.read(16) + self.assertNotEqual(x, y) + +def get_tests(config={}): + return [SimpleTest()] + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Random/OSRNG/test_generic.py b/lib/Crypto/SelfTest/Random/OSRNG/test_generic.py new file mode 100644 index 0000000..2a40974 --- /dev/null +++ b/lib/Crypto/SelfTest/Random/OSRNG/test_generic.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Util/test_generic.py: Self-test for the OSRNG.new() function +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Random.OSRNG""" + +__revision__ = "$Id$" + +import unittest + +class SimpleTest(unittest.TestCase): + def runTest(self): + """Crypto.Random.OSRNG.new()""" + # Import the OSRNG module and try to use it + import Crypto.Random.OSRNG + randobj = Crypto.Random.OSRNG.new() + x = randobj.read(16) + y = randobj.read(16) + self.assertNotEqual(x, y) + +def get_tests(config={}): + return [SimpleTest()] + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Random/OSRNG/test_nt.py b/lib/Crypto/SelfTest/Random/OSRNG/test_nt.py new file mode 100644 index 0000000..a7a8338 --- /dev/null +++ b/lib/Crypto/SelfTest/Random/OSRNG/test_nt.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Util/test_generic.py: Self-test for the OSRNG.nt.new() function +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Random.OSRNG.nt""" + +__revision__ = "$Id$" + +import unittest + +class SimpleTest(unittest.TestCase): + def runTest(self): + """Crypto.Random.OSRNG.nt.new()""" + # Import the OSRNG.nt module and try to use it + import Crypto.Random.OSRNG.nt + randobj = Crypto.Random.OSRNG.nt.new() + x = randobj.read(16) + y = randobj.read(16) + self.assertNotEqual(x, y) + +def get_tests(config={}): + return [SimpleTest()] + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Random/OSRNG/test_posix.py b/lib/Crypto/SelfTest/Random/OSRNG/test_posix.py new file mode 100644 index 0000000..2224afe --- /dev/null +++ b/lib/Crypto/SelfTest/Random/OSRNG/test_posix.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Util/test_posix.py: Self-test for the OSRNG.posix.new() function +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Random.OSRNG.posix""" + +__revision__ = "$Id$" + +import unittest + +class SimpleTest(unittest.TestCase): + def runTest(self): + """Crypto.Random.OSRNG.posix.new()""" + # Import the OSRNG.nt module and try to use it + import Crypto.Random.OSRNG.posix + randobj = Crypto.Random.OSRNG.posix.new() + x = randobj.read(16) + y = randobj.read(16) + self.assertNotEqual(x, y) + +def get_tests(config={}): + return [SimpleTest()] + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Random/OSRNG/test_winrandom.py b/lib/Crypto/SelfTest/Random/OSRNG/test_winrandom.py new file mode 100644 index 0000000..3010eb7 --- /dev/null +++ b/lib/Crypto/SelfTest/Random/OSRNG/test_winrandom.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Util/test_winrandom.py: Self-test for the winrandom module +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Random.OSRNG.winrandom""" + +__revision__ = "$Id$" + +import unittest + +class SimpleTest(unittest.TestCase): + def runTest(self): + """Crypto.Random.OSRNG.winrandom""" + # Import the winrandom module and try to use it + from Crypto.Random.OSRNG import winrandom + randobj = winrandom.new() + x = randobj.get_bytes(16) + y = randobj.get_bytes(16) + self.assertNotEqual(x, y) + +def get_tests(config={}): + return [SimpleTest()] + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Random/__init__.py b/lib/Crypto/SelfTest/Random/__init__.py new file mode 100644 index 0000000..48d84ff --- /dev/null +++ b/lib/Crypto/SelfTest/Random/__init__.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Random/__init__.py: Self-test for random number generation modules +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test for random number generators""" + +__revision__ = "$Id$" + +def get_tests(config={}): + tests = [] + from Crypto.SelfTest.Random import Fortuna; tests += Fortuna.get_tests(config=config) + from Crypto.SelfTest.Random import OSRNG; tests += OSRNG.get_tests(config=config) + from Crypto.SelfTest.Random import test_random; tests += test_random.get_tests(config=config) + from Crypto.SelfTest.Random import test_rpoolcompat; tests += test_rpoolcompat.get_tests(config=config) + return tests + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Random/test_random.py b/lib/Crypto/SelfTest/Random/test_random.py new file mode 100644 index 0000000..f9ffc66 --- /dev/null +++ b/lib/Crypto/SelfTest/Random/test_random.py @@ -0,0 +1,171 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Util/test_generic.py: Self-test for the Crypto.Random.new() function +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Random.new()""" + +__revision__ = "$Id$" + +import unittest +import sys +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * +from Crypto.Util.py3compat import * + +class SimpleTest(unittest.TestCase): + def runTest(self): + """Crypto.Random.new()""" + # Import the Random module and try to use it + from Crypto import Random + randobj = Random.new() + x = randobj.read(16) + y = randobj.read(16) + self.assertNotEqual(x, y) + z = Random.get_random_bytes(16) + self.assertNotEqual(x, z) + self.assertNotEqual(y, z) + # Test the Random.random module, which + # implements a subset of Python's random API + # Not implemented: + # seed(), getstate(), setstate(), jumpahead() + # random(), uniform(), triangular(), betavariate() + # expovariate(), gammavariate(), gauss(), + # longnormvariate(), normalvariate(), + # vonmisesvariate(), paretovariate() + # weibullvariate() + # WichmannHill(), whseed(), SystemRandom() + from Crypto.Random import random + x = random.getrandbits(16*8) + y = random.getrandbits(16*8) + self.assertNotEqual(x, y) + # Test randrange + if x>y: + start = y + stop = x + else: + start = x + stop = y + for step in range(1,10): + x = random.randrange(start,stop,step) + y = random.randrange(start,stop,step) + self.assertNotEqual(x, y) + self.assertEqual(start <= x < stop, True) + self.assertEqual(start <= y < stop, True) + self.assertEqual((x - start) % step, 0) + self.assertEqual((y - start) % step, 0) + for i in range(10): + self.assertEqual(random.randrange(1,2), 1) + self.assertRaises(ValueError, random.randrange, start, start) + self.assertRaises(ValueError, random.randrange, stop, start, step) + self.assertRaises(TypeError, random.randrange, start, stop, step, step) + self.assertRaises(TypeError, random.randrange, start, stop, "1") + self.assertRaises(TypeError, random.randrange, "1", stop, step) + self.assertRaises(TypeError, random.randrange, 1, "2", step) + self.assertRaises(ValueError, random.randrange, start, stop, 0) + # Test randint + x = random.randint(start,stop) + y = random.randint(start,stop) + self.assertNotEqual(x, y) + self.assertEqual(start <= x <= stop, True) + self.assertEqual(start <= y <= stop, True) + for i in range(10): + self.assertEqual(random.randint(1,1), 1) + self.assertRaises(ValueError, random.randint, stop, start) + self.assertRaises(TypeError, random.randint, start, stop, step) + self.assertRaises(TypeError, random.randint, "1", stop) + self.assertRaises(TypeError, random.randint, 1, "2") + # Test choice + seq = range(10000) + x = random.choice(seq) + y = random.choice(seq) + self.assertNotEqual(x, y) + self.assertEqual(x in seq, True) + self.assertEqual(y in seq, True) + for i in range(10): + self.assertEqual(random.choice((1,2,3)) in (1,2,3), True) + self.assertEqual(random.choice([1,2,3]) in [1,2,3], True) + if sys.version_info[0] is 3: + self.assertEqual(random.choice(bytearray(b('123'))) in bytearray(b('123')), True) + self.assertEqual(1, random.choice([1])) + self.assertRaises(IndexError, random.choice, []) + self.assertRaises(TypeError, random.choice, 1) + # Test shuffle. Lacks random parameter to specify function. + # Make copies of seq + seq = range(500) + x = list(seq) + y = list(seq) + random.shuffle(x) + random.shuffle(y) + self.assertNotEqual(x, y) + self.assertEqual(len(seq), len(x)) + self.assertEqual(len(seq), len(y)) + for i in range(len(seq)): + self.assertEqual(x[i] in seq, True) + self.assertEqual(y[i] in seq, True) + self.assertEqual(seq[i] in x, True) + self.assertEqual(seq[i] in y, True) + z = [1] + random.shuffle(z) + self.assertEqual(z, [1]) + if sys.version_info[0] == 3: + z = bytearray(b('12')) + random.shuffle(z) + self.assertEqual(b('1') in z, True) + self.assertRaises(TypeError, random.shuffle, b('12')) + self.assertRaises(TypeError, random.shuffle, 1) + self.assertRaises(TypeError, random.shuffle, "1") + self.assertRaises(TypeError, random.shuffle, (1,2)) + # 2to3 wraps a list() around it, alas - but I want to shoot + # myself in the foot here! :D + # if sys.version_info[0] == 3: + # self.assertRaises(TypeError, random.shuffle, range(3)) + # Test sample + x = random.sample(seq, 20) + y = random.sample(seq, 20) + self.assertNotEqual(x, y) + for i in range(20): + self.assertEqual(x[i] in seq, True) + self.assertEqual(y[i] in seq, True) + z = random.sample([1], 1) + self.assertEqual(z, [1]) + z = random.sample((1,2,3), 1) + self.assertEqual(z[0] in (1,2,3), True) + z = random.sample("123", 1) + self.assertEqual(z[0] in "123", True) + z = random.sample(range(3), 1) + self.assertEqual(z[0] in range(3), True) + if sys.version_info[0] == 3: + z = random.sample(b("123"), 1) + self.assertEqual(z[0] in b("123"), True) + z = random.sample(bytearray(b("123")), 1) + self.assertEqual(z[0] in bytearray(b("123")), True) + self.assertRaises(TypeError, random.sample, 1) + +def get_tests(config={}): + return [SimpleTest()] + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Random/test_rpoolcompat.py b/lib/Crypto/SelfTest/Random/test_rpoolcompat.py new file mode 100644 index 0000000..be538da --- /dev/null +++ b/lib/Crypto/SelfTest/Random/test_rpoolcompat.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Util/test_winrandom.py: Self-test for the winrandom module +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test for the Crypto.Util.randpool.RandomPool wrapper class""" + +__revision__ = "$Id$" + +import sys +import unittest + +class SimpleTest(unittest.TestCase): + def runTest(self): + """Crypto.Util.randpool.RandomPool""" + # Import the winrandom module and try to use it + from Crypto.Util.randpool import RandomPool + sys.stderr.write("SelfTest: You can ignore the RandomPool_DeprecationWarning that follows.\n") + rpool = RandomPool() + x = rpool.get_bytes(16) + y = rpool.get_bytes(16) + self.assertNotEqual(x, y) + self.assertNotEqual(rpool.entropy, 0) + + rpool.randomize() + rpool.stir('foo') + rpool.add_event('foo') + +def get_tests(config={}): + return [SimpleTest()] + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Signature/__init__.py b/lib/Crypto/SelfTest/Signature/__init__.py new file mode 100644 index 0000000..653b66c --- /dev/null +++ b/lib/Crypto/SelfTest/Signature/__init__.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Signature/__init__.py: Self-test for signature modules +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test for signature modules""" + +__revision__ = "$Id$" + +import os + +def get_tests(config={}): + tests = [] + import test_pkcs1_15; tests += test_pkcs1_15.get_tests(config=config) + import test_pkcs1_pss; tests += test_pkcs1_pss.get_tests(config=config) + return tests + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Signature/test_pkcs1_15.py b/lib/Crypto/SelfTest/Signature/test_pkcs1_15.py new file mode 100644 index 0000000..bc36696 --- /dev/null +++ b/lib/Crypto/SelfTest/Signature/test_pkcs1_15.py @@ -0,0 +1,219 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Signature/test_pkcs1_15.py: Self-test for PKCS#1 v1.5 signatures +# +# =================================================================== +# 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. +# =================================================================== + +__revision__ = "$Id$" + +import unittest + +from Crypto.PublicKey import RSA +from Crypto.SelfTest.st_common import list_test_cases, a2b_hex, b2a_hex +from Crypto.Hash import * +from Crypto import Random +from Crypto.Signature import PKCS1_v1_5 as PKCS +from Crypto.Util.py3compat import * + +def isStr(s): + t = '' + try: + t += s + except TypeError: + return 0 + return 1 + +def rws(t): + """Remove white spaces, tabs, and new lines from a string""" + for c in ['\n', '\t', ' ']: + t = t.replace(c,'') + return t + +def t2b(t): + """Convert a text string with bytes in hex form to a byte string""" + clean = b(rws(t)) + if len(clean)%2 == 1: + raise ValueError("Even number of characters expected") + return a2b_hex(clean) + +class PKCS1_15_Tests(unittest.TestCase): + + # List of tuples with test data for PKCS#1 v1.5. + # Each tuple is made up by: + # Item #0: dictionary with RSA key component, or key to import + # Item #1: data to hash and sign + # Item #2: signature of the data #1, done with the key #0, after + # hashing it with #3 + # Item #3: hash object generator + + _testData = ( + + # + # Taken from ftp://ftp.rsa.com/pub/pkcs/ascii/examples.asc + # "Some Examples of the PKCS Standards", 1999 + # + ( + + # Private key, from 2.1 + { + 'n':'''0a 66 79 1d c6 98 81 68 de 7a b7 74 19 bb 7f b0 c0 01 c6 + 27 10 27 00 75 14 29 42 e1 9a 8d 8c 51 d0 53 b3 e3 78 2a 1d + e5 dc 5a f4 eb e9 94 68 17 01 14 a1 df e6 7c dc 9a 9a f5 5d + 65 56 20 bb ab''', + 'e':'''01 00 + 01''', + 'd':'''01 23 c5 b6 1b a3 6e db 1d 36 79 90 41 99 a8 9e a8 0c 09 + b9 12 2e 14 00 c0 9a dc f7 78 46 76 d0 1d 23 35 6a 7d 44 d6 + bd 8b d5 0e 94 bf c7 23 fa 87 d8 86 2b 75 17 76 91 c1 1d 75 + 76 92 df 88 81''' + }, + # Data to sign, from 3.1 + '''30 81 a4 02 01 00 30 42 31 0b 30 09 06 + 03 55 04 06 13 02 55 53 31 1d 30 1b 06 03 55 04 0a 13 14 + 45 78 61 6d 70 6c 65 20 4f 72 67 61 6e 69 7a 61 74 69 6f + 6e 31 14 30 12 06 03 55 04 03 13 0b 54 65 73 74 20 55 73 + 65 72 20 31 30 5b 30 0d 06 09 2a 86 48 86 f7 0d 01 01 01 + 05 00 03 4a 00 30 47 02 40 + 0a 66 79 1d c6 98 81 68 de 7a b7 74 19 bb 7f b0 + c0 01 c6 27 10 27 00 75 14 29 42 e1 9a 8d 8c 51 + d0 53 b3 e3 78 2a 1d e5 dc 5a f4 eb e9 94 68 17 + 01 14 a1 df e6 7c dc 9a 9a f5 5d 65 56 20 bb ab + 02 03 01 00 01''', + # Signature, from 3.2 (at the very end) + '''06 db 36 cb 18 d3 47 5b 9c 01 db 3c 78 95 28 08 + 02 79 bb ae ff 2b 7d 55 8e d6 61 59 87 c8 51 86 + 3f 8a 6c 2c ff bc 89 c3 f7 5a 18 d9 6b 12 7c 71 + 7d 54 d0 d8 04 8d a8 a0 54 46 26 d1 7a 2a 8f be''', + MD2 + ), + + # + # RSA keypair generated with openssl + # + ( + """-----BEGIN RSA PRIVATE KEY----- + MIIBOwIBAAJBAL8eJ5AKoIsjURpcEoGubZMxLD7+kT+TLr7UkvEtFrRhDDKMtuII + q19FrL4pUIMymPMSLBn3hJLe30Dw48GQM4UCAwEAAQJACUSDEp8RTe32ftq8IwG8 + Wojl5mAd1wFiIOrZ/Uv8b963WJOJiuQcVN29vxU5+My9GPZ7RA3hrDBEAoHUDPrI + OQIhAPIPLz4dphiD9imAkivY31Rc5AfHJiQRA7XixTcjEkojAiEAyh/pJHks/Mlr + +rdPNEpotBjfV4M4BkgGAA/ipcmaAjcCIQCHvhwwKVBLzzTscT2HeUdEeBMoiXXK + JACAr3sJQJGxIQIgarRp+m1WSKV1MciwMaTOnbU7wxFs9DP1pva76lYBzgUCIQC9 + n0CnZCJ6IZYqSt0H5N7+Q+2Ro64nuwV/OSQfM6sBwQ== + -----END RSA PRIVATE KEY-----""", + "This is a test\x0a", + # + # PKCS#1 signature computed with openssl + # + '''4a700a16432a291a3194646952687d5316458b8b86fb0a25aa30e0dcecdb + 442676759ac63d56ec1499c3ae4c0013c2053cabd5b5804848994541ac16 + fa243a4d''', + SHA + ), + + # + # Test vector from http://www.di-mgt.com.au/rsa_alg.html#signpkcs1 + # + ( + { + 'n':'''E08973398DD8F5F5E88776397F4EB005BB5383DE0FB7ABDC7DC775290D052E6D + 12DFA68626D4D26FAA5829FC97ECFA82510F3080BEB1509E4644F12CBBD832CF + C6686F07D9B060ACBEEE34096A13F5F7050593DF5EBA3556D961FF197FC981E6 + F86CEA874070EFAC6D2C749F2DFA553AB9997702A648528C4EF357385774575F''', + 'e':'''010001''', + 'd':'''00A403C327477634346CA686B57949014B2E8AD2C862B2C7D748096A8B91F736 + F275D6E8CD15906027314735644D95CD6763CEB49F56AC2F376E1CEE0EBF282D + F439906F34D86E085BD5656AD841F313D72D395EFE33CBFF29E4030B3D05A28F + B7F18EA27637B07957D32F2BDE8706227D04665EC91BAF8B1AC3EC9144AB7F21''' + }, + "abc", + '''60AD5A78FB4A4030EC542C8974CD15F55384E836554CEDD9A322D5F4135C6267 + A9D20970C54E6651070B0144D43844C899320DD8FA7819F7EBC6A7715287332E + C8675C136183B3F8A1F81EF969418267130A756FDBB2C71D9A667446E34E0EAD + 9CF31BFB66F816F319D0B7E430A5F2891553986E003720261C7E9022C0D9F11F''', + SHA + ) + + ) + + def testSign1(self): + for i in range(len(self._testData)): + row = self._testData[i] + # Build the key + if isStr(row[0]): + key = RSA.importKey(row[0]) + else: + comps = [ long(rws(row[0][x]),16) for x in ('n','e','d') ] + key = RSA.construct(comps) + h = row[3].new() + # Data to sign can either be in hex form or not + try: + h.update(t2b(row[1])) + except: + h.update(b(row[1])) + # The real test + signer = PKCS.new(key) + self.failUnless(signer.can_sign()) + s = signer.sign(h) + self.assertEqual(s, t2b(row[2])) + + def testVerify1(self): + for i in range(len(self._testData)): + row = self._testData[i] + # Build the key + if isStr(row[0]): + key = RSA.importKey(row[0]).publickey() + else: + comps = [ long(rws(row[0][x]),16) for x in ('n','e') ] + key = RSA.construct(comps) + h = row[3].new() + # Data to sign can either be in hex form or not + try: + h.update(t2b(row[1])) + except: + h.update(b(row[1])) + # The real test + verifier = PKCS.new(key) + self.failIf(verifier.can_sign()) + result = verifier.verify(h, t2b(row[2])) + self.failUnless(result) + + def testSignVerify(self): + rng = Random.new().read + key = RSA.generate(1024, rng) + + for hashmod in (MD2,MD5,SHA,SHA224,SHA256,SHA384,SHA512,RIPEMD): + h = hashmod.new() + h.update(b('blah blah blah')) + + signer = PKCS.new(key) + s = signer.sign(h) + result = signer.verify(h, s) + self.failUnless(result) + + +def get_tests(config={}): + tests = [] + tests += list_test_cases(PKCS1_15_Tests) + return tests + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Signature/test_pkcs1_pss.py b/lib/Crypto/SelfTest/Signature/test_pkcs1_pss.py new file mode 100644 index 0000000..f5256a5 --- /dev/null +++ b/lib/Crypto/SelfTest/Signature/test_pkcs1_pss.py @@ -0,0 +1,446 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Signature/test_pkcs1_pss.py: Self-test for PKCS#1 PSS signatures +# +# =================================================================== +# 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. +# =================================================================== + +from __future__ import nested_scopes + +__revision__ = "$Id$" + +import unittest + +from Crypto.PublicKey import RSA +from Crypto import Random +from Crypto.SelfTest.st_common import list_test_cases, a2b_hex, b2a_hex +from Crypto.Hash import * +from Crypto.Signature import PKCS1_PSS as PKCS +from Crypto.Util.py3compat import * + +def isStr(s): + t = '' + try: + t += s + except TypeError: + return 0 + return 1 + +def rws(t): + """Remove white spaces, tabs, and new lines from a string""" + for c in ['\t', '\n', ' ']: + t = t.replace(c,'') + return t + +def t2b(t): + """Convert a text string with bytes in hex form to a byte string""" + clean = b(rws(t)) + if len(clean)%2 == 1: + raise ValueError("Even number of characters expected") + return a2b_hex(clean) + +# Helper class to count how many bytes have been requested +# from the key's private RNG, w/o counting those used for blinding +class MyKey: + def __init__(self, key): + self._key = key + self.n = key.n + self.asked = 0 + def _randfunc(self, N): + self.asked += N + return self._key._randfunc(N) + def sign(self, m): + return self._key.sign(m) + def has_private(self): + return self._key.has_private() + def decrypt(self, m): + return self._key.decrypt(m) + def verify(self, m, p): + return self._key.verify(m, p) + def encrypt(self, m, p): + return self._key.encrypt(m, p) + +class PKCS1_PSS_Tests(unittest.TestCase): + + # List of tuples with test data for PKCS#1 PSS + # Each tuple is made up by: + # Item #0: dictionary with RSA key component, or key to import + # Item #1: data to hash and sign + # Item #2: signature of the data #1, done with the key #0, + # and salt #3 after hashing it with #4 + # Item #3: salt + # Item #4: hash object generator + + _testData = ( + + # + # From in pss-vect.txt to be found in + # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip + # + ( + # Private key + { + 'n':'''a2 ba 40 ee 07 e3 b2 bd 2f 02 ce 22 7f 36 a1 95 + 02 44 86 e4 9c 19 cb 41 bb bd fb ba 98 b2 2b 0e + 57 7c 2e ea ff a2 0d 88 3a 76 e6 5e 39 4c 69 d4 + b3 c0 5a 1e 8f ad da 27 ed b2 a4 2b c0 00 fe 88 + 8b 9b 32 c2 2d 15 ad d0 cd 76 b3 e7 93 6e 19 95 + 5b 22 0d d1 7d 4e a9 04 b1 ec 10 2b 2e 4d e7 75 + 12 22 aa 99 15 10 24 c7 cb 41 cc 5e a2 1d 00 ee + b4 1f 7c 80 08 34 d2 c6 e0 6b ce 3b ce 7e a9 a5''', + 'e':'''01 00 01''', + # In the test vector, only p and q were given... + # d is computed offline as e^{-1} mod (p-1)(q-1) + 'd':'''50e2c3e38d886110288dfc68a9533e7e12e27d2aa56 + d2cdb3fb6efa990bcff29e1d2987fb711962860e7391b1ce01 + ebadb9e812d2fbdfaf25df4ae26110a6d7a26f0b810f54875e + 17dd5c9fb6d641761245b81e79f8c88f0e55a6dcd5f133abd3 + 5f8f4ec80adf1bf86277a582894cb6ebcd2162f1c7534f1f49 + 47b129151b71''' + }, + + # Data to sign + '''85 9e ef 2f d7 8a ca 00 30 8b dc 47 11 93 bf 55 + bf 9d 78 db 8f 8a 67 2b 48 46 34 f3 c9 c2 6e 64 + 78 ae 10 26 0f e0 dd 8c 08 2e 53 a5 29 3a f2 17 + 3c d5 0c 6d 5d 35 4f eb f7 8b 26 02 1c 25 c0 27 + 12 e7 8c d4 69 4c 9f 46 97 77 e4 51 e7 f8 e9 e0 + 4c d3 73 9c 6b bf ed ae 48 7f b5 56 44 e9 ca 74 + ff 77 a5 3c b7 29 80 2f 6e d4 a5 ff a8 ba 15 98 + 90 fc''', + # Signature + '''8d aa 62 7d 3d e7 59 5d 63 05 6c 7e c6 59 e5 44 + 06 f1 06 10 12 8b aa e8 21 c8 b2 a0 f3 93 6d 54 + dc 3b dc e4 66 89 f6 b7 95 1b b1 8e 84 05 42 76 + 97 18 d5 71 5d 21 0d 85 ef bb 59 61 92 03 2c 42 + be 4c 29 97 2c 85 62 75 eb 6d 5a 45 f0 5f 51 87 + 6f c6 74 3d ed dd 28 ca ec 9b b3 0e a9 9e 02 c3 + 48 82 69 60 4f e4 97 f7 4c cd 7c 7f ca 16 71 89 + 71 23 cb d3 0d ef 5d 54 a2 b5 53 6a d9 0a 74 7e''', + # Salt + '''e3 b5 d5 d0 02 c1 bc e5 0c 2b 65 ef 88 a1 88 d8 + 3b ce 7e 61''', + # Hash algorithm + SHA + ), + + # + # Example 1.1 to be found in + # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip + # + ( + # Private key + { + 'n':'''a5 6e 4a 0e 70 10 17 58 9a 51 87 dc 7e a8 41 d1 + 56 f2 ec 0e 36 ad 52 a4 4d fe b1 e6 1f 7a d9 91 + d8 c5 10 56 ff ed b1 62 b4 c0 f2 83 a1 2a 88 a3 + 94 df f5 26 ab 72 91 cb b3 07 ce ab fc e0 b1 df + d5 cd 95 08 09 6d 5b 2b 8b 6d f5 d6 71 ef 63 77 + c0 92 1c b2 3c 27 0a 70 e2 59 8e 6f f8 9d 19 f1 + 05 ac c2 d3 f0 cb 35 f2 92 80 e1 38 6b 6f 64 c4 + ef 22 e1 e1 f2 0d 0c e8 cf fb 22 49 bd 9a 21 37''', + 'e':'''01 00 01''', + 'd':'''33 a5 04 2a 90 b2 7d 4f 54 51 ca 9b bb d0 b4 47 + 71 a1 01 af 88 43 40 ae f9 88 5f 2a 4b be 92 e8 + 94 a7 24 ac 3c 56 8c 8f 97 85 3a d0 7c 02 66 c8 + c6 a3 ca 09 29 f1 e8 f1 12 31 88 44 29 fc 4d 9a + e5 5f ee 89 6a 10 ce 70 7c 3e d7 e7 34 e4 47 27 + a3 95 74 50 1a 53 26 83 10 9c 2a ba ca ba 28 3c + 31 b4 bd 2f 53 c3 ee 37 e3 52 ce e3 4f 9e 50 3b + d8 0c 06 22 ad 79 c6 dc ee 88 35 47 c6 a3 b3 25''' + }, + # Message + '''cd c8 7d a2 23 d7 86 df 3b 45 e0 bb bc 72 13 26 + d1 ee 2a f8 06 cc 31 54 75 cc 6f 0d 9c 66 e1 b6 + 23 71 d4 5c e2 39 2e 1a c9 28 44 c3 10 10 2f 15 + 6a 0d 8d 52 c1 f4 c4 0b a3 aa 65 09 57 86 cb 76 + 97 57 a6 56 3b a9 58 fe d0 bc c9 84 e8 b5 17 a3 + d5 f5 15 b2 3b 8a 41 e7 4a a8 67 69 3f 90 df b0 + 61 a6 e8 6d fa ae e6 44 72 c0 0e 5f 20 94 57 29 + cb eb e7 7f 06 ce 78 e0 8f 40 98 fb a4 1f 9d 61 + 93 c0 31 7e 8b 60 d4 b6 08 4a cb 42 d2 9e 38 08 + a3 bc 37 2d 85 e3 31 17 0f cb f7 cc 72 d0 b7 1c + 29 66 48 b3 a4 d1 0f 41 62 95 d0 80 7a a6 25 ca + b2 74 4f d9 ea 8f d2 23 c4 25 37 02 98 28 bd 16 + be 02 54 6f 13 0f d2 e3 3b 93 6d 26 76 e0 8a ed + 1b 73 31 8b 75 0a 01 67 d0''', + # Signature + '''90 74 30 8f b5 98 e9 70 1b 22 94 38 8e 52 f9 71 + fa ac 2b 60 a5 14 5a f1 85 df 52 87 b5 ed 28 87 + e5 7c e7 fd 44 dc 86 34 e4 07 c8 e0 e4 36 0b c2 + 26 f3 ec 22 7f 9d 9e 54 63 8e 8d 31 f5 05 12 15 + df 6e bb 9c 2f 95 79 aa 77 59 8a 38 f9 14 b5 b9 + c1 bd 83 c4 e2 f9 f3 82 a0 d0 aa 35 42 ff ee 65 + 98 4a 60 1b c6 9e b2 8d eb 27 dc a1 2c 82 c2 d4 + c3 f6 6c d5 00 f1 ff 2b 99 4d 8a 4e 30 cb b3 3c''', + # Salt + '''de e9 59 c7 e0 64 11 36 14 20 ff 80 18 5e d5 7f + 3e 67 76 af''', + # Hash + SHA + ), + + # + # Example 1.2 to be found in + # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip + # + ( + # Private key + { + 'n':'''a5 6e 4a 0e 70 10 17 58 9a 51 87 dc 7e a8 41 d1 + 56 f2 ec 0e 36 ad 52 a4 4d fe b1 e6 1f 7a d9 91 + d8 c5 10 56 ff ed b1 62 b4 c0 f2 83 a1 2a 88 a3 + 94 df f5 26 ab 72 91 cb b3 07 ce ab fc e0 b1 df + d5 cd 95 08 09 6d 5b 2b 8b 6d f5 d6 71 ef 63 77 + c0 92 1c b2 3c 27 0a 70 e2 59 8e 6f f8 9d 19 f1 + 05 ac c2 d3 f0 cb 35 f2 92 80 e1 38 6b 6f 64 c4 + ef 22 e1 e1 f2 0d 0c e8 cf fb 22 49 bd 9a 21 37''', + 'e':'''01 00 01''', + 'd':'''33 a5 04 2a 90 b2 7d 4f 54 51 ca 9b bb d0 b4 47 + 71 a1 01 af 88 43 40 ae f9 88 5f 2a 4b be 92 e8 + 94 a7 24 ac 3c 56 8c 8f 97 85 3a d0 7c 02 66 c8 + c6 a3 ca 09 29 f1 e8 f1 12 31 88 44 29 fc 4d 9a + e5 5f ee 89 6a 10 ce 70 7c 3e d7 e7 34 e4 47 27 + a3 95 74 50 1a 53 26 83 10 9c 2a ba ca ba 28 3c + 31 b4 bd 2f 53 c3 ee 37 e3 52 ce e3 4f 9e 50 3b + d8 0c 06 22 ad 79 c6 dc ee 88 35 47 c6 a3 b3 25''' + }, + # Message + '''85 13 84 cd fe 81 9c 22 ed 6c 4c cb 30 da eb 5c + f0 59 bc 8e 11 66 b7 e3 53 0c 4c 23 3e 2b 5f 8f + 71 a1 cc a5 82 d4 3e cc 72 b1 bc a1 6d fc 70 13 + 22 6b 9e''', + # Signature + '''3e f7 f4 6e 83 1b f9 2b 32 27 41 42 a5 85 ff ce + fb dc a7 b3 2a e9 0d 10 fb 0f 0c 72 99 84 f0 4e + f2 9a 9d f0 78 07 75 ce 43 73 9b 97 83 83 90 db + 0a 55 05 e6 3d e9 27 02 8d 9d 29 b2 19 ca 2c 45 + 17 83 25 58 a5 5d 69 4a 6d 25 b9 da b6 60 03 c4 + cc cd 90 78 02 19 3b e5 17 0d 26 14 7d 37 b9 35 + 90 24 1b e5 1c 25 05 5f 47 ef 62 75 2c fb e2 14 + 18 fa fe 98 c2 2c 4d 4d 47 72 4f db 56 69 e8 43''', + # Salt + '''ef 28 69 fa 40 c3 46 cb 18 3d ab 3d 7b ff c9 8f + d5 6d f4 2d''', + # Hash + SHA + ), + + # + # Example 2.1 to be found in + # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip + # + ( + # Private key + { + 'n':'''01 d4 0c 1b cf 97 a6 8a e7 cd bd 8a 7b f3 e3 4f + a1 9d cc a4 ef 75 a4 74 54 37 5f 94 51 4d 88 fe + d0 06 fb 82 9f 84 19 ff 87 d6 31 5d a6 8a 1f f3 + a0 93 8e 9a bb 34 64 01 1c 30 3a d9 91 99 cf 0c + 7c 7a 8b 47 7d ce 82 9e 88 44 f6 25 b1 15 e5 e9 + c4 a5 9c f8 f8 11 3b 68 34 33 6a 2f d2 68 9b 47 + 2c bb 5e 5c ab e6 74 35 0c 59 b6 c1 7e 17 68 74 + fb 42 f8 fc 3d 17 6a 01 7e dc 61 fd 32 6c 4b 33 + c9''', + 'e':'''01 00 01''', + 'd':'''02 7d 14 7e 46 73 05 73 77 fd 1e a2 01 56 57 72 + 17 6a 7d c3 83 58 d3 76 04 56 85 a2 e7 87 c2 3c + 15 57 6b c1 6b 9f 44 44 02 d6 bf c5 d9 8a 3e 88 + ea 13 ef 67 c3 53 ec a0 c0 dd ba 92 55 bd 7b 8b + b5 0a 64 4a fd fd 1d d5 16 95 b2 52 d2 2e 73 18 + d1 b6 68 7a 1c 10 ff 75 54 5f 3d b0 fe 60 2d 5f + 2b 7f 29 4e 36 01 ea b7 b9 d1 ce cd 76 7f 64 69 + 2e 3e 53 6c a2 84 6c b0 c2 dd 48 6a 39 fa 75 b1''' + }, + # Message + '''da ba 03 20 66 26 3f ae db 65 98 48 11 52 78 a5 + 2c 44 fa a3 a7 6f 37 51 5e d3 36 32 10 72 c4 0a + 9d 9b 53 bc 05 01 40 78 ad f5 20 87 51 46 aa e7 + 0f f0 60 22 6d cb 7b 1f 1f c2 7e 93 60''', + # Signature + '''01 4c 5b a5 33 83 28 cc c6 e7 a9 0b f1 c0 ab 3f + d6 06 ff 47 96 d3 c1 2e 4b 63 9e d9 13 6a 5f ec + 6c 16 d8 88 4b dd 99 cf dc 52 14 56 b0 74 2b 73 + 68 68 cf 90 de 09 9a db 8d 5f fd 1d ef f3 9b a4 + 00 7a b7 46 ce fd b2 2d 7d f0 e2 25 f5 46 27 dc + 65 46 61 31 72 1b 90 af 44 53 63 a8 35 8b 9f 60 + 76 42 f7 8f ab 0a b0 f4 3b 71 68 d6 4b ae 70 d8 + 82 78 48 d8 ef 1e 42 1c 57 54 dd f4 2c 25 89 b5 + b3''', + # Salt + '''57 bf 16 0b cb 02 bb 1d c7 28 0c f0 45 85 30 b7 + d2 83 2f f7''', + SHA + ), + + # + # Example 8.1 to be found in + # ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip + # + ( + # Private key + { + 'n':'''49 53 70 a1 fb 18 54 3c 16 d3 63 1e 31 63 25 5d + f6 2b e6 ee e8 90 d5 f2 55 09 e4 f7 78 a8 ea 6f + bb bc df 85 df f6 4e 0d 97 20 03 ab 36 81 fb ba + 6d d4 1f d5 41 82 9b 2e 58 2d e9 f2 a4 a4 e0 a2 + d0 90 0b ef 47 53 db 3c ee 0e e0 6c 7d fa e8 b1 + d5 3b 59 53 21 8f 9c ce ea 69 5b 08 66 8e de aa + dc ed 94 63 b1 d7 90 d5 eb f2 7e 91 15 b4 6c ad + 4d 9a 2b 8e fa b0 56 1b 08 10 34 47 39 ad a0 73 + 3f''', + 'e':'''01 00 01''', + 'd':'''6c 66 ff e9 89 80 c3 8f cd ea b5 15 98 98 83 61 + 65 f4 b4 b8 17 c4 f6 a8 d4 86 ee 4e a9 13 0f e9 + b9 09 2b d1 36 d1 84 f9 5f 50 4a 60 7e ac 56 58 + 46 d2 fd d6 59 7a 89 67 c7 39 6e f9 5a 6e ee bb + 45 78 a6 43 96 6d ca 4d 8e e3 de 84 2d e6 32 79 + c6 18 15 9c 1a b5 4a 89 43 7b 6a 61 20 e4 93 0a + fb 52 a4 ba 6c ed 8a 49 47 ac 64 b3 0a 34 97 cb + e7 01 c2 d6 26 6d 51 72 19 ad 0e c6 d3 47 db e9''' + }, + # Message + '''81 33 2f 4b e6 29 48 41 5e a1 d8 99 79 2e ea cf + 6c 6e 1d b1 da 8b e1 3b 5c ea 41 db 2f ed 46 70 + 92 e1 ff 39 89 14 c7 14 25 97 75 f5 95 f8 54 7f + 73 56 92 a5 75 e6 92 3a f7 8f 22 c6 99 7d db 90 + fb 6f 72 d7 bb 0d d5 74 4a 31 de cd 3d c3 68 58 + 49 83 6e d3 4a ec 59 63 04 ad 11 84 3c 4f 88 48 + 9f 20 97 35 f5 fb 7f da f7 ce c8 ad dc 58 18 16 + 8f 88 0a cb f4 90 d5 10 05 b7 a8 e8 4e 43 e5 42 + 87 97 75 71 dd 99 ee a4 b1 61 eb 2d f1 f5 10 8f + 12 a4 14 2a 83 32 2e db 05 a7 54 87 a3 43 5c 9a + 78 ce 53 ed 93 bc 55 08 57 d7 a9 fb''', + # Signature + '''02 62 ac 25 4b fa 77 f3 c1 ac a2 2c 51 79 f8 f0 + 40 42 2b 3c 5b af d4 0a 8f 21 cf 0f a5 a6 67 cc + d5 99 3d 42 db af b4 09 c5 20 e2 5f ce 2b 1e e1 + e7 16 57 7f 1e fa 17 f3 da 28 05 2f 40 f0 41 9b + 23 10 6d 78 45 aa f0 11 25 b6 98 e7 a4 df e9 2d + 39 67 bb 00 c4 d0 d3 5b a3 55 2a b9 a8 b3 ee f0 + 7c 7f ec db c5 42 4a c4 db 1e 20 cb 37 d0 b2 74 + 47 69 94 0e a9 07 e1 7f bb ca 67 3b 20 52 23 80 + c5''', + # Salt + '''1d 65 49 1d 79 c8 64 b3 73 00 9b e6 f6 f2 46 7b + ac 4c 78 fa''', + SHA + ) + ) + + def testSign1(self): + for i in range(len(self._testData)): + # Build the key + comps = [ long(rws(self._testData[i][0][x]),16) for x in ('n','e','d') ] + key = MyKey(RSA.construct(comps)) + # Hash function + h = self._testData[i][4].new() + # Data to sign + h.update(t2b(self._testData[i][1])) + # Salt + test_salt = t2b(self._testData[i][3]) + key._randfunc = lambda N: test_salt + # The real test + signer = PKCS.new(key) + self.failUnless(signer.can_sign()) + s = signer.sign(h) + self.assertEqual(s, t2b(self._testData[i][2])) + + def testVerify1(self): + for i in range(len(self._testData)): + # Build the key + comps = [ long(rws(self._testData[i][0][x]),16) for x in ('n','e') ] + key = MyKey(RSA.construct(comps)) + # Hash function + h = self._testData[i][4].new() + # Data to sign + h.update(t2b(self._testData[i][1])) + # Salt + test_salt = t2b(self._testData[i][3]) + # The real test + key._randfunc = lambda N: test_salt + verifier = PKCS.new(key) + self.failIf(verifier.can_sign()) + result = verifier.verify(h, t2b(self._testData[i][2])) + self.failUnless(result) + + def testSignVerify(self): + h = SHA.new() + h.update(b('blah blah blah')) + + rng = Random.new().read + key = MyKey(RSA.generate(1024,rng)) + + # Helper function to monitor what's request from MGF + global mgfcalls + def newMGF(seed,maskLen): + global mgfcalls + mgfcalls += 1 + return bchr(0x00)*maskLen + + # Verify that PSS is friendly to all ciphers + for hashmod in (MD2,MD5,SHA,SHA224,SHA256,SHA384,RIPEMD): + h = hashmod.new() + h.update(b('blah blah blah')) + + # Verify that sign() asks for as many random bytes + # as the hash output size + key.asked = 0 + signer = PKCS.new(key) + s = signer.sign(h) + self.failUnless(signer.verify(h, s)) + self.assertEqual(key.asked, h.digest_size) + + h = SHA.new() + h.update(b('blah blah blah')) + + # Verify that sign() uses a different salt length + for sLen in (0,3,21): + key.asked = 0 + signer = PKCS.new(key, saltLen=sLen) + s = signer.sign(h) + self.assertEqual(key.asked, sLen) + self.failUnless(signer.verify(h, s)) + + # Verify that sign() uses the custom MGF + mgfcalls = 0 + signer = PKCS.new(key, newMGF) + s = signer.sign(h) + self.assertEqual(mgfcalls, 1) + self.failUnless(signer.verify(h, s)) + + # Verify that sign() does not call the RNG + # when salt length is 0, even when a new MGF is provided + key.asked = 0 + mgfcalls = 0 + signer = PKCS.new(key, newMGF, 0) + s = signer.sign(h) + self.assertEqual(key.asked,0) + self.assertEqual(mgfcalls, 1) + self.failUnless(signer.verify(h, s)) + +def get_tests(config={}): + tests = [] + tests += list_test_cases(PKCS1_PSS_Tests) + return tests + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 diff --git a/lib/Crypto/SelfTest/Util/__init__.py b/lib/Crypto/SelfTest/Util/__init__.py new file mode 100644 index 0000000..abd640a --- /dev/null +++ b/lib/Crypto/SelfTest/Util/__init__.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Util/__init__.py: Self-test for utility modules +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test for utility modules""" + +__revision__ = "$Id$" + +import os + +def get_tests(config={}): + tests = [] + if os.name == 'nt': + from Crypto.SelfTest.Util import test_winrandom; tests += test_winrandom.get_tests(config=config) + from Crypto.SelfTest.Util import test_number; tests += test_number.get_tests(config=config) + from Crypto.SelfTest.Util import test_Counter; tests += test_Counter.get_tests(config=config) + return tests + +if __name__ == '__main__': + import unittest + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Util/test_Counter.py b/lib/Crypto/SelfTest/Util/test_Counter.py new file mode 100644 index 0000000..33c9bd7 --- /dev/null +++ b/lib/Crypto/SelfTest/Util/test_Counter.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Util/test_Counter: Self-test for the Crypto.Util.Counter module +# +# Written in 2009 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-tests for Crypto.Util.Counter""" + +__revision__ = "$Id$" + +import sys +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * +from Crypto.Util.py3compat import * + +import unittest + +class CounterTests(unittest.TestCase): + def setUp(self): + global Counter + from Crypto.Util import Counter + + def test_BE_shortcut(self): + """Big endian, shortcut enabled""" + c = Counter.new(128) + self.assertEqual(c.__PCT_CTR_SHORTCUT__,True) # assert_ + c = Counter.new(128, little_endian=False) + self.assertEqual(c.__PCT_CTR_SHORTCUT__,True) # assert_ + c = Counter.new(128, disable_shortcut=False) + self.assertEqual(c.__PCT_CTR_SHORTCUT__,True) # assert_ + c = Counter.new(128, little_endian=False, disable_shortcut=False) + self.assertEqual(c.__PCT_CTR_SHORTCUT__,True) # assert_ + + def test_LE_shortcut(self): + """Little endian, shortcut enabled""" + c = Counter.new(128, little_endian=True) + self.assertEqual(c.__PCT_CTR_SHORTCUT__,True) # assert_ + c = Counter.new(128, little_endian=True, disable_shortcut=False) + self.assertEqual(c.__PCT_CTR_SHORTCUT__,True) # assert_ + + def test_BE_no_shortcut(self): + """Big endian, shortcut disabled""" + c = Counter.new(128, disable_shortcut=True) + self.assertRaises(AttributeError, getattr, c, '__PCT_CTR_SHORTCUT__') + c = Counter.new(128, little_endian=False, disable_shortcut=True) + self.assertRaises(AttributeError, getattr, c, '__PCT_CTR_SHORTCUT__') + + def test_LE_no_shortcut(self): + """Little endian, shortcut disabled""" + c = Counter.new(128, little_endian=True, disable_shortcut=True) + self.assertRaises(AttributeError, getattr, c, '__PCT_CTR_SHORTCUT__') + + def test_BE_defaults(self): + """128-bit, Big endian, defaults""" + c = Counter.new(128) + self.assertEqual(1, c.next_value()) + self.assertEqual(b("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"), c()) + self.assertEqual(2, c.next_value()) + self.assertEqual(b("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"), c()) + for i in xrange(3, 256): + self.assertEqual(i, c.next_value()) + self.assertEqual(b("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")+bchr(i), c()) + self.assertEqual(256, c.next_value()) + self.assertEqual(b("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00"), c()) + + def test_LE_defaults(self): + """128-bit, Little endian, defaults""" + c = Counter.new(128, little_endian=True) + self.assertEqual(1, c.next_value()) + self.assertEqual(b("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), c()) + self.assertEqual(2, c.next_value()) + self.assertEqual(b("\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), c()) + for i in xrange(3, 256): + self.assertEqual(i, c.next_value()) + self.assertEqual(bchr(i)+b("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), c()) + self.assertEqual(256, c.next_value()) + self.assertEqual(b("\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), c()) + + def test_BE8_wraparound(self): + """8-bit, Big endian, wraparound""" + c = Counter.new(8) + for i in xrange(1, 256): + self.assertEqual(i, c.next_value()) + self.assertEqual(bchr(i), c()) + self.assertRaises(OverflowError, c.next_value) + self.assertRaises(OverflowError, c) + self.assertRaises(OverflowError, c.next_value) + self.assertRaises(OverflowError, c) + + def test_LE8_wraparound(self): + """8-bit, Little endian, wraparound""" + c = Counter.new(8, little_endian=True) + for i in xrange(1, 256): + self.assertEqual(i, c.next_value()) + self.assertEqual(bchr(i), c()) + self.assertRaises(OverflowError, c.next_value) + self.assertRaises(OverflowError, c) + self.assertRaises(OverflowError, c.next_value) + self.assertRaises(OverflowError, c) + + def test_BE8_wraparound_allowed(self): + """8-bit, Big endian, wraparound with allow_wraparound=True""" + c = Counter.new(8, allow_wraparound=True) + for i in xrange(1, 256): + self.assertEqual(i, c.next_value()) + self.assertEqual(bchr(i), c()) + self.assertEqual(0, c.next_value()) + self.assertEqual(b("\x00"), c()) + self.assertEqual(1, c.next_value()) + + def test_LE8_wraparound_allowed(self): + """8-bit, Little endian, wraparound with allow_wraparound=True""" + c = Counter.new(8, little_endian=True, allow_wraparound=True) + for i in xrange(1, 256): + self.assertEqual(i, c.next_value()) + self.assertEqual(bchr(i), c()) + self.assertEqual(0, c.next_value()) + self.assertEqual(b("\x00"), c()) + self.assertEqual(1, c.next_value()) + + def test_BE8_carry(self): + """8-bit, Big endian, carry attribute""" + c = Counter.new(8) + for i in xrange(1, 256): + self.assertEqual(0, c.carry) + self.assertEqual(i, c.next_value()) + self.assertEqual(bchr(i), c()) + self.assertEqual(1, c.carry) + + def test_LE8_carry(self): + """8-bit, Little endian, carry attribute""" + c = Counter.new(8, little_endian=True) + for i in xrange(1, 256): + self.assertEqual(0, c.carry) + self.assertEqual(i, c.next_value()) + self.assertEqual(bchr(i), c()) + self.assertEqual(1, c.carry) + +def get_tests(config={}): + from Crypto.SelfTest.st_common import list_test_cases + return list_test_cases(CounterTests) + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Util/test_asn1.py b/lib/Crypto/SelfTest/Util/test_asn1.py new file mode 100644 index 0000000..bbd0a39 --- /dev/null +++ b/lib/Crypto/SelfTest/Util/test_asn1.py @@ -0,0 +1,293 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Util/test_asn.py: Self-test for the Crypto.Util.asn1 module +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-tests for Crypto.Util.asn1""" + +__revision__ = "$Id$" + +import unittest +import sys + +from Crypto.Util.py3compat import * +from Crypto.Util.asn1 import DerSequence, DerObject + +class DerObjectTests(unittest.TestCase): + + def testObjEncode1(self): + # No payload + der = DerObject(b('\x33')) + self.assertEquals(der.encode(), b('\x33\x00')) + # Small payload + der.payload = b('\x45') + self.assertEquals(der.encode(), b('\x33\x01\x45')) + # Invariant + self.assertEquals(der.encode(), b('\x33\x01\x45')) + # Initialize with numerical tag + der = DerObject(b(0x33)) + der.payload = b('\x45') + self.assertEquals(der.encode(), b('\x33\x01\x45')) + + def testObjEncode2(self): + # Known types + der = DerObject('SEQUENCE') + self.assertEquals(der.encode(), b('\x30\x00')) + der = DerObject('BIT STRING') + self.assertEquals(der.encode(), b('\x03\x00')) + + def testObjEncode3(self): + # Long payload + der = DerObject(b('\x34')) + der.payload = b("0")*128 + self.assertEquals(der.encode(), b('\x34\x81\x80' + "0"*128)) + + def testObjDecode1(self): + # Decode short payload + der = DerObject() + der.decode(b('\x20\x02\x01\x02')) + self.assertEquals(der.payload, b("\x01\x02")) + self.assertEquals(der.typeTag, 0x20) + + def testObjDecode2(self): + # Decode short payload + der = DerObject() + der.decode(b('\x22\x81\x80' + "1"*128)) + self.assertEquals(der.payload, b("1")*128) + self.assertEquals(der.typeTag, 0x22) + +class DerSequenceTests(unittest.TestCase): + + def testEncode1(self): + # Empty sequence + der = DerSequence() + self.assertEquals(der.encode(), b('0\x00')) + self.failIf(der.hasOnlyInts()) + # One single-byte integer (zero) + der.append(0) + self.assertEquals(der.encode(), b('0\x03\x02\x01\x00')) + self.failUnless(der.hasOnlyInts()) + # Invariant + self.assertEquals(der.encode(), b('0\x03\x02\x01\x00')) + + def testEncode2(self): + # One single-byte integer (non-zero) + der = DerSequence() + der.append(127) + self.assertEquals(der.encode(), b('0\x03\x02\x01\x7f')) + # Indexing + der[0] = 1 + self.assertEquals(len(der),1) + self.assertEquals(der[0],1) + self.assertEquals(der[-1],1) + self.assertEquals(der.encode(), b('0\x03\x02\x01\x01')) + # + der[:] = [1] + self.assertEquals(len(der),1) + self.assertEquals(der[0],1) + self.assertEquals(der.encode(), b('0\x03\x02\x01\x01')) + + def testEncode3(self): + # One multi-byte integer (non-zero) + der = DerSequence() + der.append(0x180L) + self.assertEquals(der.encode(), b('0\x04\x02\x02\x01\x80')) + + def testEncode4(self): + # One very long integer + der = DerSequence() + der.append(2**2048) + self.assertEquals(der.encode(), b('0\x82\x01\x05')+ + b('\x02\x82\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00')) + + def testEncode5(self): + # One single-byte integer (looks negative) + der = DerSequence() + der.append(0xFFL) + self.assertEquals(der.encode(), b('0\x04\x02\x02\x00\xff')) + + def testEncode6(self): + # Two integers + der = DerSequence() + der.append(0x180L) + der.append(0xFFL) + self.assertEquals(der.encode(), b('0\x08\x02\x02\x01\x80\x02\x02\x00\xff')) + self.failUnless(der.hasOnlyInts()) + # + der.append(0x01) + der[1:] = [9,8] + self.assertEquals(len(der),3) + self.assertEqual(der[1:],[9,8]) + self.assertEqual(der[1:-1],[9]) + self.assertEquals(der.encode(), b('0\x0A\x02\x02\x01\x80\x02\x01\x09\x02\x01\x08')) + + def testEncode7(self): + # One integer and another type (no matter what it is) + der = DerSequence() + der.append(0x180L) + der.append(b('\x00\x02\x00\x00')) + self.assertEquals(der.encode(), b('0\x08\x02\x02\x01\x80\x00\x02\x00\x00')) + self.failIf(der.hasOnlyInts()) + + #### + + def testDecode1(self): + # Empty sequence + der = DerSequence() + der.decode(b('0\x00')) + self.assertEquals(len(der),0) + # One single-byte integer (zero) + der.decode(b('0\x03\x02\x01\x00')) + self.assertEquals(len(der),1) + self.assertEquals(der[0],0) + # Invariant + der.decode(b('0\x03\x02\x01\x00')) + self.assertEquals(len(der),1) + self.assertEquals(der[0],0) + + def testDecode2(self): + # One single-byte integer (non-zero) + der = DerSequence() + der.decode(b('0\x03\x02\x01\x7f')) + self.assertEquals(len(der),1) + self.assertEquals(der[0],127) + + def testDecode3(self): + # One multi-byte integer (non-zero) + der = DerSequence() + der.decode(b('0\x04\x02\x02\x01\x80')) + self.assertEquals(len(der),1) + self.assertEquals(der[0],0x180L) + + def testDecode4(self): + # One very long integer + der = DerSequence() + der.decode(b('0\x82\x01\x05')+ + b('\x02\x82\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')+ + b('\x00\x00\x00\x00\x00\x00\x00\x00\x00')) + self.assertEquals(len(der),1) + self.assertEquals(der[0],2**2048) + + def testDecode5(self): + # One single-byte integer (looks negative) + der = DerSequence() + der.decode(b('0\x04\x02\x02\x00\xff')) + self.assertEquals(len(der),1) + self.assertEquals(der[0],0xFFL) + + def testDecode6(self): + # Two integers + der = DerSequence() + der.decode(b('0\x08\x02\x02\x01\x80\x02\x02\x00\xff')) + self.assertEquals(len(der),2) + self.assertEquals(der[0],0x180L) + self.assertEquals(der[1],0xFFL) + + def testDecode7(self): + # One integer and 2 other types + der = DerSequence() + der.decode(b('0\x0A\x02\x02\x01\x80\x24\x02\xb6\x63\x12\x00')) + self.assertEquals(len(der),3) + self.assertEquals(der[0],0x180L) + self.assertEquals(der[1],b('\x24\x02\xb6\x63')) + self.assertEquals(der[2],b('\x12\x00')) + + def testDecode8(self): + # Only 2 other types + der = DerSequence() + der.decode(b('0\x06\x24\x02\xb6\x63\x12\x00')) + self.assertEquals(len(der),2) + self.assertEquals(der[0],b('\x24\x02\xb6\x63')) + self.assertEquals(der[1],b('\x12\x00')) + + def testErrDecode1(self): + # Not a sequence + der = DerSequence() + self.assertRaises(ValueError, der.decode, b('')) + self.assertRaises(ValueError, der.decode, b('\x00')) + self.assertRaises(ValueError, der.decode, b('\x30')) + + def testErrDecode2(self): + # Wrong payload type + der = DerSequence() + self.assertRaises(ValueError, der.decode, b('\x30\x00\x00'), True) + + def testErrDecode3(self): + # Wrong length format + der = DerSequence() + self.assertRaises(ValueError, der.decode, b('\x30\x04\x02\x01\x01\x00')) + self.assertRaises(ValueError, der.decode, b('\x30\x81\x03\x02\x01\x01')) + self.assertRaises(ValueError, der.decode, b('\x30\x04\x02\x81\x01\x01')) + + def testErrDecode4(self): + # Wrong integer format + der = DerSequence() + # Multi-byte encoding for zero + #self.assertRaises(ValueError, der.decode, '\x30\x04\x02\x02\x00\x00') + # Negative integer + self.assertRaises(ValueError, der.decode, b('\x30\x04\x02\x01\xFF')) + +def get_tests(config={}): + from Crypto.SelfTest.st_common import list_test_cases + listTests = [] + listTests += list_test_cases(DerObjectTests) + listTests += list_test_cases(DerSequenceTests) + return listTests + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Util/test_number.py b/lib/Crypto/SelfTest/Util/test_number.py new file mode 100644 index 0000000..2201a93 --- /dev/null +++ b/lib/Crypto/SelfTest/Util/test_number.py @@ -0,0 +1,343 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Util/test_number.py: Self-test for parts of the Crypto.Util.number module +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-tests for (some of) Crypto.Util.number""" + +__revision__ = "$Id$" + +import sys +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * + +import unittest + +class MyError(Exception): + """Dummy exception used for tests""" + +# NB: In some places, we compare tuples instead of just output values so that +# if any inputs cause a test failure, we'll be able to tell which ones. + +class MiscTests(unittest.TestCase): + def setUp(self): + global number, math + from Crypto.Util import number + import math + + def test_ceil_shift(self): + """Util.number.ceil_shift""" + self.assertRaises(AssertionError, number.ceil_shift, -1, 1) + self.assertRaises(AssertionError, number.ceil_shift, 1, -1) + + # b = 0 + self.assertEqual(0, number.ceil_shift(0, 0)) + self.assertEqual(1, number.ceil_shift(1, 0)) + self.assertEqual(2, number.ceil_shift(2, 0)) + self.assertEqual(3, number.ceil_shift(3, 0)) + + # b = 1 + self.assertEqual(0, number.ceil_shift(0, 1)) + self.assertEqual(1, number.ceil_shift(1, 1)) + self.assertEqual(1, number.ceil_shift(2, 1)) + self.assertEqual(2, number.ceil_shift(3, 1)) + + # b = 2 + self.assertEqual(0, number.ceil_shift(0, 2)) + self.assertEqual(1, number.ceil_shift(1, 2)) + self.assertEqual(1, number.ceil_shift(2, 2)) + self.assertEqual(1, number.ceil_shift(3, 2)) + self.assertEqual(1, number.ceil_shift(4, 2)) + self.assertEqual(2, number.ceil_shift(5, 2)) + self.assertEqual(2, number.ceil_shift(6, 2)) + self.assertEqual(2, number.ceil_shift(7, 2)) + self.assertEqual(2, number.ceil_shift(8, 2)) + self.assertEqual(3, number.ceil_shift(9, 2)) + + for b in range(3, 1+129, 3): # 3, 6, ... , 129 + self.assertEqual(0, number.ceil_shift(0, b)) + + n = 1L + while n <= 2L**(b+2): + (q, r) = divmod(n-1, 2L**b) + expected = q + int(not not r) + self.assertEqual((n-1, b, expected), + (n-1, b, number.ceil_shift(n-1, b))) + + (q, r) = divmod(n, 2L**b) + expected = q + int(not not r) + self.assertEqual((n, b, expected), + (n, b, number.ceil_shift(n, b))) + + (q, r) = divmod(n+1, 2L**b) + expected = q + int(not not r) + self.assertEqual((n+1, b, expected), + (n+1, b, number.ceil_shift(n+1, b))) + + n *= 2 + + def test_ceil_div(self): + """Util.number.ceil_div""" + self.assertRaises(TypeError, number.ceil_div, "1", 1) + self.assertRaises(ZeroDivisionError, number.ceil_div, 1, 0) + self.assertRaises(ZeroDivisionError, number.ceil_div, -1, 0) + + # b = -1 + self.assertEqual(0, number.ceil_div(0, -1)) + self.assertEqual(-1, number.ceil_div(1, -1)) + self.assertEqual(-2, number.ceil_div(2, -1)) + self.assertEqual(-3, number.ceil_div(3, -1)) + + # b = 1 + self.assertEqual(0, number.ceil_div(0, 1)) + self.assertEqual(1, number.ceil_div(1, 1)) + self.assertEqual(2, number.ceil_div(2, 1)) + self.assertEqual(3, number.ceil_div(3, 1)) + + # b = 2 + self.assertEqual(0, number.ceil_div(0, 2)) + self.assertEqual(1, number.ceil_div(1, 2)) + self.assertEqual(1, number.ceil_div(2, 2)) + self.assertEqual(2, number.ceil_div(3, 2)) + self.assertEqual(2, number.ceil_div(4, 2)) + self.assertEqual(3, number.ceil_div(5, 2)) + + # b = 3 + self.assertEqual(0, number.ceil_div(0, 3)) + self.assertEqual(1, number.ceil_div(1, 3)) + self.assertEqual(1, number.ceil_div(2, 3)) + self.assertEqual(1, number.ceil_div(3, 3)) + self.assertEqual(2, number.ceil_div(4, 3)) + self.assertEqual(2, number.ceil_div(5, 3)) + self.assertEqual(2, number.ceil_div(6, 3)) + self.assertEqual(3, number.ceil_div(7, 3)) + + # b = 4 + self.assertEqual(0, number.ceil_div(0, 4)) + self.assertEqual(1, number.ceil_div(1, 4)) + self.assertEqual(1, number.ceil_div(2, 4)) + self.assertEqual(1, number.ceil_div(3, 4)) + self.assertEqual(1, number.ceil_div(4, 4)) + self.assertEqual(2, number.ceil_div(5, 4)) + self.assertEqual(2, number.ceil_div(6, 4)) + self.assertEqual(2, number.ceil_div(7, 4)) + self.assertEqual(2, number.ceil_div(8, 4)) + self.assertEqual(3, number.ceil_div(9, 4)) + + # b = -4 + self.assertEqual(3, number.ceil_div(-9, -4)) + self.assertEqual(2, number.ceil_div(-8, -4)) + self.assertEqual(2, number.ceil_div(-7, -4)) + self.assertEqual(2, number.ceil_div(-6, -4)) + self.assertEqual(2, number.ceil_div(-5, -4)) + self.assertEqual(1, number.ceil_div(-4, -4)) + self.assertEqual(1, number.ceil_div(-3, -4)) + self.assertEqual(1, number.ceil_div(-2, -4)) + self.assertEqual(1, number.ceil_div(-1, -4)) + self.assertEqual(0, number.ceil_div(0, -4)) + self.assertEqual(0, number.ceil_div(1, -4)) + self.assertEqual(0, number.ceil_div(2, -4)) + self.assertEqual(0, number.ceil_div(3, -4)) + self.assertEqual(-1, number.ceil_div(4, -4)) + self.assertEqual(-1, number.ceil_div(5, -4)) + self.assertEqual(-1, number.ceil_div(6, -4)) + self.assertEqual(-1, number.ceil_div(7, -4)) + self.assertEqual(-2, number.ceil_div(8, -4)) + self.assertEqual(-2, number.ceil_div(9, -4)) + + def test_exact_log2(self): + """Util.number.exact_log2""" + self.assertRaises(TypeError, number.exact_log2, "0") + self.assertRaises(ValueError, number.exact_log2, -1) + self.assertRaises(ValueError, number.exact_log2, 0) + self.assertEqual(0, number.exact_log2(1)) + self.assertEqual(1, number.exact_log2(2)) + self.assertRaises(ValueError, number.exact_log2, 3) + self.assertEqual(2, number.exact_log2(4)) + self.assertRaises(ValueError, number.exact_log2, 5) + self.assertRaises(ValueError, number.exact_log2, 6) + self.assertRaises(ValueError, number.exact_log2, 7) + e = 3 + n = 8 + while e < 16: + if n == 2**e: + self.assertEqual(e, number.exact_log2(n), "expected=2**%d, n=%d" % (e, n)) + e += 1 + else: + self.assertRaises(ValueError, number.exact_log2, n) + n += 1 + + for e in range(16, 1+64, 2): + self.assertRaises(ValueError, number.exact_log2, 2L**e-1) + self.assertEqual(e, number.exact_log2(2L**e)) + self.assertRaises(ValueError, number.exact_log2, 2L**e+1) + + def test_exact_div(self): + """Util.number.exact_div""" + + # Positive numbers + self.assertEqual(1, number.exact_div(1, 1)) + self.assertRaises(ValueError, number.exact_div, 1, 2) + self.assertEqual(1, number.exact_div(2, 2)) + self.assertRaises(ValueError, number.exact_div, 3, 2) + self.assertEqual(2, number.exact_div(4, 2)) + + # Negative numbers + self.assertEqual(-1, number.exact_div(-1, 1)) + self.assertEqual(-1, number.exact_div(1, -1)) + self.assertRaises(ValueError, number.exact_div, -1, 2) + self.assertEqual(1, number.exact_div(-2, -2)) + self.assertEqual(-2, number.exact_div(-4, 2)) + + # Zero dividend + self.assertEqual(0, number.exact_div(0, 1)) + self.assertEqual(0, number.exact_div(0, 2)) + + # Zero divisor (allow_divzero == False) + self.assertRaises(ZeroDivisionError, number.exact_div, 0, 0) + self.assertRaises(ZeroDivisionError, number.exact_div, 1, 0) + + # Zero divisor (allow_divzero == True) + self.assertEqual(0, number.exact_div(0, 0, allow_divzero=True)) + self.assertRaises(ValueError, number.exact_div, 1, 0, allow_divzero=True) + + def test_floor_div(self): + """Util.number.floor_div""" + self.assertRaises(TypeError, number.floor_div, "1", 1) + for a in range(-10, 10): + for b in range(-10, 10): + if b == 0: + self.assertRaises(ZeroDivisionError, number.floor_div, a, b) + else: + self.assertEqual((a, b, int(math.floor(float(a) / b))), + (a, b, number.floor_div(a, b))) + + def test_getStrongPrime(self): + """Util.number.getStrongPrime""" + self.assertRaises(ValueError, number.getStrongPrime, 256) + self.assertRaises(ValueError, number.getStrongPrime, 513) + bits = 512 + x = number.getStrongPrime(bits) + self.assertNotEqual(x % 2, 0) + self.assertEqual(x > (1L << bits-1)-1, 1) + self.assertEqual(x < (1L << bits), 1) + e = 2**16+1 + x = number.getStrongPrime(bits, e) + self.assertEqual(number.GCD(x-1, e), 1) + self.assertNotEqual(x % 2, 0) + self.assertEqual(x > (1L << bits-1)-1, 1) + self.assertEqual(x < (1L << bits), 1) + e = 2**16+2 + x = number.getStrongPrime(bits, e) + self.assertEqual(number.GCD((x-1)>>1, e), 1) + self.assertNotEqual(x % 2, 0) + self.assertEqual(x > (1L << bits-1)-1, 1) + self.assertEqual(x < (1L << bits), 1) + + def test_isPrime(self): + """Util.number.isPrime""" + self.assertEqual(number.isPrime(-3), False) # Regression test: negative numbers should not be prime + self.assertEqual(number.isPrime(-2), False) # Regression test: negative numbers should not be prime + self.assertEqual(number.isPrime(1), False) # Regression test: isPrime(1) caused some versions of PyCrypto to crash. + self.assertEqual(number.isPrime(2), True) + self.assertEqual(number.isPrime(3), True) + self.assertEqual(number.isPrime(4), False) + self.assertEqual(number.isPrime(2L**1279-1), True) + self.assertEqual(number.isPrime(-(2L**1279-1)), False) # Regression test: negative numbers should not be prime + # test some known gmp pseudo-primes taken from + # http://www.trnicely.net/misc/mpzspsp.html + for composite in (43 * 127 * 211, 61 * 151 * 211, 15259 * 30517, + 346141L * 692281L, 1007119L * 2014237L, 3589477L * 7178953L, + 4859419L * 9718837L, 2730439L * 5460877L, + 245127919L * 490255837L, 963939391L * 1927878781L, + 4186358431L * 8372716861L, 1576820467L * 3153640933L): + self.assertEqual(number.isPrime(long(composite)), False) + + def test_size(self): + self.assertEqual(number.size(2),2) + self.assertEqual(number.size(3),2) + self.assertEqual(number.size(0xa2),8) + self.assertEqual(number.size(0xa2ba40),8*3) + self.assertEqual(number.size(0xa2ba40ee07e3b2bd2f02ce227f36a195024486e49c19cb41bbbdfbba98b22b0e577c2eeaffa20d883a76e65e394c69d4b3c05a1e8fadda27edb2a42bc000fe888b9b32c22d15add0cd76b3e7936e19955b220dd17d4ea904b1ec102b2e4de7751222aa99151024c7cb41cc5ea21d00eeb41f7c800834d2c6e06bce3bce7ea9a5L), 1024) + +class FastmathTests(unittest.TestCase): + def setUp(self): + global number + from Crypto.Util import number + + def test_negative_number_roundtrip_mpzToLongObj_longObjToMPZ(self): + """Test that mpzToLongObj and longObjToMPZ (internal functions) roundtrip negative numbers correctly.""" + n = -100000000000000000000000000000000000L + e = 2L + k = number._fastmath.rsa_construct(n, e) + self.assertEqual(n, k.n) + self.assertEqual(e, k.e) + + def test_isPrime_randfunc_exception(self): + """Test that when isPrime is called, an exception raised in randfunc is propagated.""" + def randfunc(n): + raise MyError + prime = 3536384141L # Needs to be large enough so that rabinMillerTest will be invoked + self.assertRaises(MyError, number._fastmath.isPrime, prime, randfunc=randfunc) + + def test_getStrongPrime_randfunc_exception(self): + """Test that when getStrongPrime is called, an exception raised in randfunc is propagated.""" + def randfunc(n): + raise MyError + self.assertRaises(MyError, number._fastmath.getStrongPrime, 512, randfunc=randfunc) + + def test_isPrime_randfunc_bogus(self): + """Test that when isPrime is called, an exception is raised if randfunc returns something bogus.""" + def randfunc(n): + return None + prime = 3536384141L # Needs to be large enough so that rabinMillerTest will be invoked + self.assertRaises(TypeError, number._fastmath.isPrime, prime, randfunc=randfunc) + + def test_getStrongPrime_randfunc_bogus(self): + """Test that when getStrongPrime is called, an exception is raised if randfunc returns something bogus.""" + def randfunc(n): + return None + self.assertRaises(TypeError, number._fastmath.getStrongPrime, 512, randfunc=randfunc) + +def get_tests(config={}): + from Crypto.SelfTest.st_common import list_test_cases + tests = list_test_cases(MiscTests) + try: + from Crypto.PublicKey import _fastmath + tests += list_test_cases(FastmathTests) + except ImportError: + from distutils.sysconfig import get_config_var + import inspect, os.path + _fm_path = os.path.normpath(os.path.dirname(os.path.abspath( + inspect.getfile(inspect.currentframe()))) + +"/../../PublicKey/_fastmath"+get_config_var("SO")) + if os.path.exists(_fm_path): + raise ImportError("While the _fastmath module exists, importing "+ + "it failed. This may point to the gmp or mpir shared library "+ + "not being in the path. _fastmath was found at "+_fm_path) + return tests + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/Util/test_winrandom.py b/lib/Crypto/SelfTest/Util/test_winrandom.py new file mode 100644 index 0000000..3fc5145 --- /dev/null +++ b/lib/Crypto/SelfTest/Util/test_winrandom.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/Util/test_winrandom.py: Self-test for the winrandom module +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self-test suite for Crypto.Util.winrandom""" + +__revision__ = "$Id$" + +import unittest + +class WinRandomImportTest(unittest.TestCase): + def runTest(self): + """winrandom: simple test""" + # Import the winrandom module and try to use it + from Crypto.Util import winrandom + randobj = winrandom.new() + x = randobj.get_bytes(16) + y = randobj.get_bytes(16) + self.assertNotEqual(x, y) + +def get_tests(config={}): + return [WinRandomImportTest()] + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/__init__.py b/lib/Crypto/SelfTest/__init__.py new file mode 100644 index 0000000..40b3969 --- /dev/null +++ b/lib/Crypto/SelfTest/__init__.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/__init__.py: Self-test for PyCrypto +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Self tests + +These tests should perform quickly and can ideally be used every time an +application runs. +""" + +__revision__ = "$Id$" + +import sys +import unittest +from StringIO import StringIO + +class SelfTestError(Exception): + def __init__(self, message, result): + Exception.__init__(self, message, result) + self.message = message + self.result = result + +def run(module=None, verbosity=0, stream=None, tests=None, config=None, **kwargs): + """Execute self-tests. + + This raises SelfTestError if any test is unsuccessful. + + You may optionally pass in a sub-module of SelfTest if you only want to + perform some of the tests. For example, the following would test only the + hash modules: + + Crypto.SelfTest.run(Crypto.SelfTest.Hash) + + """ + if config is None: + config = {} + suite = unittest.TestSuite() + if module is None: + if tests is None: + tests = get_tests(config=config) + suite.addTests(tests) + else: + if tests is None: + suite.addTests(module.get_tests(config=config)) + else: + raise ValueError("'module' and 'tests' arguments are mutually exclusive") + if stream is None: + kwargs['stream'] = StringIO() + runner = unittest.TextTestRunner(verbosity=verbosity, **kwargs) + result = runner.run(suite) + if not result.wasSuccessful(): + if stream is None: + sys.stderr.write(stream.getvalue()) + raise SelfTestError("Self-test failed", result) + return result + +def get_tests(config={}): + tests = [] + from Crypto.SelfTest import Cipher; tests += Cipher.get_tests(config=config) + from Crypto.SelfTest import Hash; tests += Hash.get_tests(config=config) + from Crypto.SelfTest import Protocol; tests += Protocol.get_tests(config=config) + from Crypto.SelfTest import PublicKey; tests += PublicKey.get_tests(config=config) + from Crypto.SelfTest import Random; tests += Random.get_tests(config=config) + from Crypto.SelfTest import Util; tests += Util.get_tests(config=config) + from Crypto.SelfTest import Signature; tests += Signature.get_tests(config=config) + return tests + +if __name__ == '__main__': + suite = lambda: unittest.TestSuite(get_tests()) + unittest.main(defaultTest='suite') + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/SelfTest/st_common.py b/lib/Crypto/SelfTest/st_common.py new file mode 100644 index 0000000..c56eac5 --- /dev/null +++ b/lib/Crypto/SelfTest/st_common.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# +# SelfTest/st_common.py: Common functions for SelfTest modules +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Common functions for SelfTest modules""" + +__revision__ = "$Id$" + +import unittest +import binascii +import sys +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * +from Crypto.Util.py3compat import * + +class _list_testloader(unittest.TestLoader): + suiteClass = list + +def list_test_cases(class_): + """Return a list of TestCase instances given a TestCase class + + This is useful when you have defined test* methods on your TestCase class. + """ + return _list_testloader().loadTestsFromTestCase(class_) + +def strip_whitespace(s): + """Remove whitespace from a text or byte string""" + if isinstance(s,str): + return b("".join(s.split())) + else: + return b("").join(s.split()) + +def a2b_hex(s): + """Convert hexadecimal to binary, ignoring whitespace""" + return binascii.a2b_hex(strip_whitespace(s)) + +def b2a_hex(s): + """Convert binary to hexadecimal""" + # For completeness + return binascii.b2a_hex(s) + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/Signature/PKCS1_PSS.py b/lib/Crypto/Signature/PKCS1_PSS.py new file mode 100644 index 0000000..7038f4e --- /dev/null +++ b/lib/Crypto/Signature/PKCS1_PSS.py @@ -0,0 +1,355 @@ +# -*- coding: utf-8 -*- +# +# Signature/PKCS1_PSS.py : PKCS#1 PPS +# +# =================================================================== +# 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. +# =================================================================== + +"""RSA digital signature protocol with appendix according to PKCS#1 PSS. + +See RFC3447__ or the `original RSA Labs specification`__. + +This scheme is more properly called ``RSASSA-PSS``. + +For example, a sender may authenticate a message using SHA-1 and PSS like +this: + + >>> from Crypto.Signature import PKCS1_PSS + >>> from Crypto.Hash import SHA + >>> from Crypto.PublicKey import RSA + >>> from Crypto import Random + >>> + >>> message = 'To be signed' + >>> key = RSA.importKey(open('privkey.der').read()) + >>> h = SHA.new() + >>> h.update(message) + >>> signer = PKCS1_PSS.new(key) + >>> signature = signer.sign(key) + +At the receiver side, verification can be done like using the public part of +the RSA key: + + >>> key = RSA.importKey(open('pubkey.der').read()) + >>> h = SHA.new() + >>> h.update(message) + >>> verifier = PKCS1_PSS.new(key) + >>> if verifier.verify(h, signature): + >>> print "The signature is authentic." + >>> else: + >>> print "The signature is not authentic." + +:undocumented: __revision__, __package__ + +.. __: http://www.ietf.org/rfc/rfc3447.txt +.. __: http://www.rsa.com/rsalabs/node.asp?id=2125 +""" + +# Allow nested scopes in Python 2.1 +# See http://oreilly.com/pub/a/python/2001/04/19/pythonnews.html +from __future__ import nested_scopes + +__revision__ = "$Id$" +__all__ = [ 'new', 'PSS_SigScheme' ] + +from Crypto.Util.py3compat import * +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * +import Crypto.Util.number +from Crypto.Util.number import ceil_shift, ceil_div, long_to_bytes +from Crypto.Util.strxor import strxor + +class PSS_SigScheme: + """This signature scheme can perform PKCS#1 PSS RSA signature or verification.""" + + def __init__(self, key, mgfunc, saltLen): + """Initialize this PKCS#1 PSS signature scheme object. + + :Parameters: + key : an RSA key object + If a private half is given, both signature and verification are possible. + If a public half is given, only verification is possible. + mgfunc : callable + A mask generation function that accepts two parameters: a string to + use as seed, and the lenth of the mask to generate, in bytes. + saltLen : int + Length of the salt, in bytes. + """ + self._key = key + self._saltLen = saltLen + self._mgfunc = mgfunc + + def can_sign(self): + """Return True if this cipher object can be used for signing messages.""" + return self._key.has_private() + + def sign(self, mhash): + """Produce the PKCS#1 PSS signature of a message. + + This function is named ``RSASSA-PSS-SIGN``, and is specified in + section 8.1.1 of RFC3447. + + :Parameters: + mhash : hash object + The hash that was carried out over the message. This is an object + belonging to the `Crypto.Hash` module. + + :Return: The PSS signature encoded as a string. + :Raise ValueError: + If the RSA key length is not sufficiently long to deal with the given + hash algorithm. + :Raise TypeError: + If the RSA key has no private half. + + :attention: Modify the salt length and the mask generation function only + if you know what you are doing. + The receiver must use the same parameters too. + """ + # TODO: Verify the key is RSA + + randfunc = self._key._randfunc + + # Set defaults for salt length and mask generation function + if self._saltLen == None: + sLen = mhash.digest_size + else: + sLen = self._saltLen + if self._mgfunc: + mgf = self._mgfunc + else: + mgf = lambda x,y: MGF1(x,y,mhash) + + modBits = Crypto.Util.number.size(self._key.n) + + # See 8.1.1 in RFC3447 + k = ceil_div(modBits,8) # Convert from bits to bytes + # Step 1 + em = EMSA_PSS_ENCODE(mhash, modBits-1, randfunc, mgf, sLen) + # Step 2a (OS2IP) and 2b (RSASP1) + m = self._key.decrypt(em) + # Step 2c (I2OSP) + S = bchr(0x00)*(k-len(m)) + m + return S + + def verify(self, mhash, S): + """Verify that a certain PKCS#1 PSS signature is authentic. + + This function checks if the party holding the private half of the given + RSA key has really signed the message. + + This function is called ``RSASSA-PSS-VERIFY``, and is specified in section + 8.1.2 of RFC3447. + + :Parameters: + mhash : hash object + The hash that was carried out over the message. This is an object + belonging to the `Crypto.Hash` module. + S : string + The signature that needs to be validated. + + :Return: True if verification is correct. False otherwise. + """ + # TODO: Verify the key is RSA + + # Set defaults for salt length and mask generation function + if self._saltLen == None: + sLen = mhash.digest_size + else: + sLen = self._saltLen + if self._mgfunc: + mgf = self._mgfunc + else: + mgf = lambda x,y: MGF1(x,y,mhash) + + modBits = Crypto.Util.number.size(self._key.n) + + # See 8.1.2 in RFC3447 + k = ceil_div(modBits,8) # Convert from bits to bytes + # Step 1 + if len(S) != k: + return False + # Step 2a (O2SIP), 2b (RSAVP1), and partially 2c (I2OSP) + # Note that signature must be smaller than the module + # but RSA.py won't complain about it. + # TODO: Fix RSA object; don't do it here. + em = self._key.encrypt(S, 0)[0] + # Step 2c + emLen = ceil_div(modBits-1,8) + em = bchr(0x00)*(emLen-len(em)) + em + # Step 3 + try: + result = EMSA_PSS_VERIFY(mhash, em, modBits-1, mgf, sLen) + except ValueError: + return False + # Step 4 + return result + +def MGF1(mgfSeed, maskLen, hash): + """Mask Generation Function, described in B.2.1""" + T = b("") + for counter in xrange(ceil_div(maskLen, hash.digest_size)): + c = long_to_bytes(counter, 4) + T = T + hash.new(mgfSeed + c).digest() + assert(len(T)>=maskLen) + return T[:maskLen] + +def EMSA_PSS_ENCODE(mhash, emBits, randFunc, mgf, sLen): + """ + Implement the ``EMSA-PSS-ENCODE`` function, as defined + in PKCS#1 v2.1 (RFC3447, 9.1.1). + + The original ``EMSA-PSS-ENCODE`` actually accepts the message ``M`` as input, + and hash it internally. Here, we expect that the message has already + been hashed instead. + + :Parameters: + mhash : hash object + The hash object that holds the digest of the message being signed. + emBits : int + Maximum length of the final encoding, in bits. + randFunc : callable + An RNG function that accepts as only parameter an int, and returns + a string of random bytes, to be used as salt. + mgf : callable + A mask generation function that accepts two parameters: a string to + use as seed, and the lenth of the mask to generate, in bytes. + sLen : int + Length of the salt, in bytes. + + :Return: An ``emLen`` byte long string that encodes the hash + (with ``emLen = \ceil(emBits/8)``). + + :Raise ValueError: + When digest or salt length are too big. + """ + + emLen = ceil_div(emBits,8) + + # Bitmask of digits that fill up + lmask = 0 + for i in xrange(8*emLen-emBits): + lmask = lmask>>1 | 0x80 + + # Step 1 and 2 have been already done + # Step 3 + if emLen < mhash.digest_size+sLen+2: + raise ValueError("Digest or salt length are too long for given key size.") + # Step 4 + salt = b("") + if randFunc and sLen>0: + salt = randFunc(sLen) + # Step 5 and 6 + h = mhash.new(bchr(0x00)*8 + mhash.digest() + salt) + # Step 7 and 8 + db = bchr(0x00)*(emLen-sLen-mhash.digest_size-2) + bchr(0x01) + salt + # Step 9 + dbMask = mgf(h.digest(), emLen-mhash.digest_size-1) + # Step 10 + maskedDB = strxor(db,dbMask) + # Step 11 + maskedDB = bchr(bord(maskedDB[0]) & ~lmask) + maskedDB[1:] + # Step 12 + em = maskedDB + h.digest() + bchr(0xBC) + return em + +def EMSA_PSS_VERIFY(mhash, em, emBits, mgf, sLen): + """ + Implement the ``EMSA-PSS-VERIFY`` function, as defined + in PKCS#1 v2.1 (RFC3447, 9.1.2). + + ``EMSA-PSS-VERIFY`` actually accepts the message ``M`` as input, + and hash it internally. Here, we expect that the message has already + been hashed instead. + + :Parameters: + mhash : hash object + The hash object that holds the digest of the message to be verified. + em : string + The signature to verify, therefore proving that the sender really signed + the message that was received. + emBits : int + Length of the final encoding (em), in bits. + mgf : callable + A mask generation function that accepts two parameters: a string to + use as seed, and the lenth of the mask to generate, in bytes. + sLen : int + Length of the salt, in bytes. + + :Return: 0 if the encoding is consistent, 1 if it is inconsistent. + + :Raise ValueError: + When digest or salt length are too big. + """ + + emLen = ceil_div(emBits,8) + + # Bitmask of digits that fill up + lmask = 0 + for i in xrange(8*emLen-emBits): + lmask = lmask>>1 | 0x80 + + # Step 1 and 2 have been already done + # Step 3 + if emLen < mhash.digest_size+sLen+2: + return False + # Step 4 + if ord(em[-1:])!=0xBC: + return False + # Step 5 + maskedDB = em[:emLen-mhash.digest_size-1] + h = em[emLen-mhash.digest_size-1:-1] + # Step 6 + if lmask & bord(em[0]): + return False + # Step 7 + dbMask = mgf(h, emLen-mhash.digest_size-1) + # Step 8 + db = strxor(maskedDB, dbMask) + # Step 9 + db = bchr(bord(db[0]) & ~lmask) + db[1:] + # Step 10 + if not db.startswith(bchr(0x00)*(emLen-mhash.digest_size-sLen-2) + bchr(0x01)): + return False + # Step 11 + salt = b("") + if sLen: salt = db[-sLen:] + # Step 12 and 13 + hp = mhash.new(bchr(0x00)*8 + mhash.digest() + salt).digest() + # Step 14 + if h!=hp: + return False + return True + +def new(key, mgfunc=None, saltLen=None): + """Return a signature scheme object `PSS_SigScheme` that + can be used to perform PKCS#1 PSS signature or verification. + + :Parameters: + key : RSA key object + The key to use to sign or verify the message. This is a `Crypto.PublicKey.RSA` object. + Signing is only possible if *key* is a private RSA key. + mgfunc : callable + A mask generation function that accepts two parameters: a string to + use as seed, and the lenth of the mask to generate, in bytes. + If not specified, the standard MGF1 is used. + saltLen : int + Length of the salt, in bytes. If not specified, it matches the output + size of the hash function. + + """ + return PSS_SigScheme(key, mgfunc, saltLen) + diff --git a/lib/Crypto/Signature/PKCS1_v1_5.py b/lib/Crypto/Signature/PKCS1_v1_5.py new file mode 100644 index 0000000..73ac251 --- /dev/null +++ b/lib/Crypto/Signature/PKCS1_v1_5.py @@ -0,0 +1,236 @@ +# -*- coding: utf-8 -*- +# +# Signature/PKCS1-v1_5.py : PKCS#1 v1.5 +# +# =================================================================== +# 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. +# =================================================================== + +""" +RSA digital signature protocol according to PKCS#1 v1.5 + +See RFC3447__ or the `original RSA Labs specification`__. + +This scheme is more properly called ``RSASSA-PKCS1-v1_5``. + +For example, a sender may authenticate a message using SHA-1 like +this: + + >>> from Crypto.Signature import PKCS1_v1_5 + >>> from Crypto.Hash import SHA + >>> from Crypto.PublicKey import RSA + >>> + >>> message = 'To be signed' + >>> key = RSA.importKey(open('privkey.der').read()) + >>> h = SHA.new(message) + >>> signer = PKCS1_v1_5.new(key) + >>> signature = signer.sign(h) + +At the receiver side, verification can be done using the public part of +the RSA key: + + >>> key = RSA.importKey(open('pubkey.der').read()) + >>> h = SHA.new(message) + >>> verifier = PKCS1_v1_5.new(key) + >>> if verifier.verify(h, signature): + >>> print "The signature is authentic." + >>> else: + >>> print "The signature is not authentic." + +:undocumented: __revision__, __package__ + +.. __: http://www.ietf.org/rfc/rfc3447.txt +.. __: http://www.rsa.com/rsalabs/node.asp?id=2125 +""" + +__revision__ = "$Id$" +__all__ = [ 'new', 'PKCS115_SigScheme' ] + +import Crypto.Util.number +from Crypto.Util.number import ceil_div +from Crypto.Util.asn1 import DerSequence, DerNull, DerOctetString +from Crypto.Util.py3compat import * + +class PKCS115_SigScheme: + """This signature scheme can perform PKCS#1 v1.5 RSA signature or verification.""" + + def __init__(self, key): + """Initialize this PKCS#1 v1.5 signature scheme object. + + :Parameters: + key : an RSA key object + If a private half is given, both signature and verification are possible. + If a public half is given, only verification is possible. + """ + self._key = key + + def can_sign(self): + """Return True if this cipher object can be used for signing messages.""" + return self._key.has_private() + + def sign(self, mhash): + """Produce the PKCS#1 v1.5 signature of a message. + + This function is named ``RSASSA-PKCS1-V1_5-SIGN``, and is specified in + section 8.2.1 of RFC3447. + + :Parameters: + mhash : hash object + The hash that was carried out over the message. This is an object + belonging to the `Crypto.Hash` module. + + :Return: The signature encoded as a string. + :Raise ValueError: + If the RSA key length is not sufficiently long to deal with the given + hash algorithm. + :Raise TypeError: + If the RSA key has no private half. + """ + # TODO: Verify the key is RSA + + # See 8.2.1 in RFC3447 + modBits = Crypto.Util.number.size(self._key.n) + k = ceil_div(modBits,8) # Convert from bits to bytes + + # Step 1 + em = EMSA_PKCS1_V1_5_ENCODE(mhash, k) + # Step 2a (OS2IP) and 2b (RSASP1) + m = self._key.decrypt(em) + # Step 2c (I2OSP) + S = bchr(0x00)*(k-len(m)) + m + return S + + def verify(self, mhash, S): + """Verify that a certain PKCS#1 v1.5 signature is authentic. + + This function checks if the party holding the private half of the key + really signed the message. + + This function is named ``RSASSA-PKCS1-V1_5-VERIFY``, and is specified in + section 8.2.2 of RFC3447. + + :Parameters: + mhash : hash object + The hash that was carried out over the message. This is an object + belonging to the `Crypto.Hash` module. + S : string + The signature that needs to be validated. + + :Return: True if verification is correct. False otherwise. + """ + # TODO: Verify the key is RSA + + # See 8.2.2 in RFC3447 + modBits = Crypto.Util.number.size(self._key.n) + k = ceil_div(modBits,8) # Convert from bits to bytes + + # Step 1 + if len(S) != k: + return 0 + # Step 2a (O2SIP) and 2b (RSAVP1) + # Note that signature must be smaller than the module + # but RSA.py won't complain about it. + # TODO: Fix RSA object; don't do it here. + m = self._key.encrypt(S, 0)[0] + # Step 2c (I2OSP) + em1 = bchr(0x00)*(k-len(m)) + m + # Step 3 + try: + em2 = EMSA_PKCS1_V1_5_ENCODE(mhash, k) + except ValueError: + return 0 + # Step 4 + # By comparing the full encodings (as opposed to checking each + # of its components one at a time) we avoid attacks to the padding + # scheme like Bleichenbacher's (see http://www.mail-archive.com/cryptography@metzdowd.com/msg06537). + # + return em1==em2 + +def EMSA_PKCS1_V1_5_ENCODE(hash, emLen): + """ + Implement the ``EMSA-PKCS1-V1_5-ENCODE`` function, as defined + in PKCS#1 v2.1 (RFC3447, 9.2). + + ``EMSA-PKCS1-V1_5-ENCODE`` actually accepts the message ``M`` as input, + and hash it internally. Here, we expect that the message has already + been hashed instead. + + :Parameters: + hash : hash object + The hash object that holds the digest of the message being signed. + emLen : int + The length the final encoding must have, in bytes. + + :attention: the early standard (RFC2313) stated that ``DigestInfo`` + had to be BER-encoded. This means that old signatures + might have length tags in indefinite form, which + is not supported in DER. Such encoding cannot be + reproduced by this function. + + :attention: the same standard defined ``DigestAlgorithm`` to be + of ``AlgorithmIdentifier`` type, where the PARAMETERS + item is optional. Encodings for ``MD2/4/5`` without + ``PARAMETERS`` cannot be reproduced by this function. + + :Return: An ``emLen`` byte long string that encodes the hash. + """ + + # First, build the ASN.1 DER object DigestInfo: + # + # DigestInfo ::= SEQUENCE { + # digestAlgorithm AlgorithmIdentifier, + # digest OCTET STRING + # } + # + # where digestAlgorithm identifies the hash function and shall be an + # algorithm ID with an OID in the set PKCS1-v1-5DigestAlgorithms. + # + # PKCS1-v1-5DigestAlgorithms ALGORITHM-IDENTIFIER ::= { + # { OID id-md2 PARAMETERS NULL }| + # { OID id-md5 PARAMETERS NULL }| + # { OID id-sha1 PARAMETERS NULL }| + # { OID id-sha256 PARAMETERS NULL }| + # { OID id-sha384 PARAMETERS NULL }| + # { OID id-sha512 PARAMETERS NULL } + # } + # + digestAlgo = DerSequence([hash.oid, DerNull().encode()]) + digest = DerOctetString(hash.digest()) + digestInfo = DerSequence([ + digestAlgo.encode(), + digest.encode() + ]).encode() + + # We need at least 11 bytes for the remaining data: 3 fixed bytes and + # at least 8 bytes of padding). + if emLen +# +# =================================================================== +# 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. +# =================================================================== +"""Fast counter functions for CTR cipher modes. + +CTR is a chaining mode for symmetric block encryption or decryption. +Messages are divideded into blocks, and the cipher operation takes +place on each block using the secret key and a unique *counter block*. + +The most straightforward way to fulfil the uniqueness property is +to start with an initial, random *counter block* value, and increment it as +the next block is processed. + +The block ciphers from `Crypto.Cipher` (when configured in *MODE_CTR* mode) +invoke a callable object (the *counter* parameter) to get the next *counter block*. +Unfortunately, the Python calling protocol leads to major performance degradations. + +The counter functions instantiated by this module will be invoked directly +by the ciphers in `Crypto.Cipher`. The fact that the Python layer is bypassed +lead to more efficient (and faster) execution of CTR cipher modes. + +An example of usage is the following: + + >>> from Crypto.Cipher import AES + >>> from Crypto.Util import Counter + >>> + >>> pt = b'X'*1000000 + >>> ctr = Counter.new(128) + >>> key = b'AES-128 symm key' + >>> cipher = AES.new(key, AES.MODE_CTR, counter=ctr) + >>> ct = cipher.encrypt(pt) + +:undocumented: __package__ +""" +import sys +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * +from Crypto.Util.py3compat import * + +from Crypto.Util import _counter +import struct + +# Factory function +def new(nbits, prefix=b(""), suffix=b(""), initial_value=1, overflow=0, little_endian=False, allow_wraparound=False, disable_shortcut=False): + """Create a stateful counter block function suitable for CTR encryption modes. + + Each call to the function returns the next counter block. + Each counter block is made up by three parts:: + + prefix || counter value || postfix + + The counter value is incremented by one at each call. + + :Parameters: + nbits : integer + Length of the desired counter, in bits. It must be a multiple of 8. + prefix : byte string + The constant prefix of the counter block. By default, no prefix is + used. + suffix : byte string + The constant postfix of the counter block. By default, no suffix is + used. + initial_value : integer + The initial value of the counter. Default value is 1. + little_endian : boolean + If True, the counter number will be encoded in little endian format. + If False (default), in big endian format. + allow_wraparound : boolean + If True, the function will raise an *OverflowError* exception as soon + as the counter wraps around. If False (default), the counter will + simply restart from zero. + disable_shortcut : boolean + If True, do not make ciphers from `Crypto.Cipher` bypass the Python + layer when invoking the counter block function. + If False (default), bypass the Python layer. + :Returns: + The counter block function. + """ + + # Sanity-check the message size + (nbytes, remainder) = divmod(nbits, 8) + if remainder != 0: + # In the future, we might support arbitrary bit lengths, but for now we don't. + raise ValueError("nbits must be a multiple of 8; got %d" % (nbits,)) + if nbytes < 1: + raise ValueError("nbits too small") + elif nbytes > 0xffff: + raise ValueError("nbits too large") + + initval = _encode(initial_value, nbytes, little_endian) + + if little_endian: + return _counter._newLE(bstr(prefix), bstr(suffix), initval, allow_wraparound=allow_wraparound, disable_shortcut=disable_shortcut) + else: + return _counter._newBE(bstr(prefix), bstr(suffix), initval, allow_wraparound=allow_wraparound, disable_shortcut=disable_shortcut) + +def _encode(n, nbytes, little_endian=False): + retval = [] + n = long(n) + for i in range(nbytes): + if little_endian: + retval.append(bchr(n & 0xff)) + else: + retval.insert(0, bchr(n & 0xff)) + n >>= 8 + return b("").join(retval) + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/Util/RFC1751.py b/lib/Crypto/Util/RFC1751.py new file mode 100644 index 0000000..9786e6f --- /dev/null +++ b/lib/Crypto/Util/RFC1751.py @@ -0,0 +1,364 @@ +# rfc1751.py : Converts between 128-bit strings and a human-readable +# sequence of words, as defined in RFC1751: "A Convention for +# Human-Readable 128-bit Keys", by Daniel L. McDonald. +# +# Part of the Python Cryptography Toolkit +# +# Written by Andrew M. Kuchling and others +# +# =================================================================== +# 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. +# =================================================================== + +__revision__ = "$Id$" + + +import binascii +from Crypto.Util.py3compat import * + +binary={0:'0000', 1:'0001', 2:'0010', 3:'0011', 4:'0100', 5:'0101', + 6:'0110', 7:'0111', 8:'1000', 9:'1001', 10:'1010', 11:'1011', + 12:'1100', 13:'1101', 14:'1110', 15:'1111'} + +def _key2bin(s): + "Convert a key into a string of binary digits" + kl=map(lambda x: bord(x), s) + kl=map(lambda x: binary[x>>4]+binary[x&15], kl) + return ''.join(kl) + +def _extract(key, start, length): + """Extract a bitstring(2.x)/bytestring(2.x) from a string of binary digits, and return its + numeric value.""" + k=key[start:start+length] + return reduce(lambda x,y: x*2+ord(y)-48, k, 0) + +def key_to_english (key): + """key_to_english(key:string(2.x)/bytes(3.x)) : string + Transform an arbitrary key into a string containing English words. + The key length must be a multiple of 8. + """ + english='' + for index in range(0, len(key), 8): # Loop over 8-byte subkeys + subkey=key[index:index+8] + # Compute the parity of the key + skbin=_key2bin(subkey) ; p=0 + for i in range(0, 64, 2): p=p+_extract(skbin, i, 2) + # Append parity bits to the subkey + skbin=_key2bin(subkey+bchr((p<<6) & 255)) + for i in range(0, 64, 11): + english=english+wordlist[_extract(skbin, i, 11)]+' ' + + return english[:-1] # Remove the trailing space + +def english_to_key (s): + """english_to_key(string):string(2.x)/bytes(2.x) + Transform a string into a corresponding key. + The string must contain words separated by whitespace; the number + of words must be a multiple of 6. + """ + + L=s.upper().split() ; key=b('') + for index in range(0, len(L), 6): + sublist=L[index:index+6] ; char=9*[0] ; bits=0 + for i in sublist: + index = wordlist.index(i) + shift = (8-(bits+11)%8) %8 + y = index << shift + cl, cc, cr = (y>>16), (y>>8)&0xff, y & 0xff + if (shift>5): + char[bits>>3] = char[bits>>3] | cl + char[(bits>>3)+1] = char[(bits>>3)+1] | cc + char[(bits>>3)+2] = char[(bits>>3)+2] | cr + elif shift>-3: + char[bits>>3] = char[bits>>3] | cc + char[(bits>>3)+1] = char[(bits>>3)+1] | cr + else: char[bits>>3] = char[bits>>3] | cr + bits=bits+11 + subkey=reduce(lambda x,y:x+bchr(y), char, b('')) + + # Check the parity of the resulting key + skbin=_key2bin(subkey) + p=0 + for i in range(0, 64, 2): p=p+_extract(skbin, i, 2) + if (p&3) != _extract(skbin, 64, 2): + raise ValueError, "Parity error in resulting key" + key=key+subkey[0:8] + return key + +wordlist=[ "A", "ABE", "ACE", "ACT", "AD", "ADA", "ADD", + "AGO", "AID", "AIM", "AIR", "ALL", "ALP", "AM", "AMY", "AN", "ANA", + "AND", "ANN", "ANT", "ANY", "APE", "APS", "APT", "ARC", "ARE", "ARK", + "ARM", "ART", "AS", "ASH", "ASK", "AT", "ATE", "AUG", "AUK", "AVE", + "AWE", "AWK", "AWL", "AWN", "AX", "AYE", "BAD", "BAG", "BAH", "BAM", + "BAN", "BAR", "BAT", "BAY", "BE", "BED", "BEE", "BEG", "BEN", "BET", + "BEY", "BIB", "BID", "BIG", "BIN", "BIT", "BOB", "BOG", "BON", "BOO", + "BOP", "BOW", "BOY", "BUB", "BUD", "BUG", "BUM", "BUN", "BUS", "BUT", + "BUY", "BY", "BYE", "CAB", "CAL", "CAM", "CAN", "CAP", "CAR", "CAT", + "CAW", "COD", "COG", "COL", "CON", "COO", "COP", "COT", "COW", "COY", + "CRY", "CUB", "CUE", "CUP", "CUR", "CUT", "DAB", "DAD", "DAM", "DAN", + "DAR", "DAY", "DEE", "DEL", "DEN", "DES", "DEW", "DID", "DIE", "DIG", + "DIN", "DIP", "DO", "DOE", "DOG", "DON", "DOT", "DOW", "DRY", "DUB", + "DUD", "DUE", "DUG", "DUN", "EAR", "EAT", "ED", "EEL", "EGG", "EGO", + "ELI", "ELK", "ELM", "ELY", "EM", "END", "EST", "ETC", "EVA", "EVE", + "EWE", "EYE", "FAD", "FAN", "FAR", "FAT", "FAY", "FED", "FEE", "FEW", + "FIB", "FIG", "FIN", "FIR", "FIT", "FLO", "FLY", "FOE", "FOG", "FOR", + "FRY", "FUM", "FUN", "FUR", "GAB", "GAD", "GAG", "GAL", "GAM", "GAP", + "GAS", "GAY", "GEE", "GEL", "GEM", "GET", "GIG", "GIL", "GIN", "GO", + "GOT", "GUM", "GUN", "GUS", "GUT", "GUY", "GYM", "GYP", "HA", "HAD", + "HAL", "HAM", "HAN", "HAP", "HAS", "HAT", "HAW", "HAY", "HE", "HEM", + "HEN", "HER", "HEW", "HEY", "HI", "HID", "HIM", "HIP", "HIS", "HIT", + "HO", "HOB", "HOC", "HOE", "HOG", "HOP", "HOT", "HOW", "HUB", "HUE", + "HUG", "HUH", "HUM", "HUT", "I", "ICY", "IDA", "IF", "IKE", "ILL", + "INK", "INN", "IO", "ION", "IQ", "IRA", "IRE", "IRK", "IS", "IT", + "ITS", "IVY", "JAB", "JAG", "JAM", "JAN", "JAR", "JAW", "JAY", "JET", + "JIG", "JIM", "JO", "JOB", "JOE", "JOG", "JOT", "JOY", "JUG", "JUT", + "KAY", "KEG", "KEN", "KEY", "KID", "KIM", "KIN", "KIT", "LA", "LAB", + "LAC", "LAD", "LAG", "LAM", "LAP", "LAW", "LAY", "LEA", "LED", "LEE", + "LEG", "LEN", "LEO", "LET", "LEW", "LID", "LIE", "LIN", "LIP", "LIT", + "LO", "LOB", "LOG", "LOP", "LOS", "LOT", "LOU", "LOW", "LOY", "LUG", + "LYE", "MA", "MAC", "MAD", "MAE", "MAN", "MAO", "MAP", "MAT", "MAW", + "MAY", "ME", "MEG", "MEL", "MEN", "MET", "MEW", "MID", "MIN", "MIT", + "MOB", "MOD", "MOE", "MOO", "MOP", "MOS", "MOT", "MOW", "MUD", "MUG", + "MUM", "MY", "NAB", "NAG", "NAN", "NAP", "NAT", "NAY", "NE", "NED", + "NEE", "NET", "NEW", "NIB", "NIL", "NIP", "NIT", "NO", "NOB", "NOD", + "NON", "NOR", "NOT", "NOV", "NOW", "NU", "NUN", "NUT", "O", "OAF", + "OAK", "OAR", "OAT", "ODD", "ODE", "OF", "OFF", "OFT", "OH", "OIL", + "OK", "OLD", "ON", "ONE", "OR", "ORB", "ORE", "ORR", "OS", "OTT", + "OUR", "OUT", "OVA", "OW", "OWE", "OWL", "OWN", "OX", "PA", "PAD", + "PAL", "PAM", "PAN", "PAP", "PAR", "PAT", "PAW", "PAY", "PEA", "PEG", + "PEN", "PEP", "PER", "PET", "PEW", "PHI", "PI", "PIE", "PIN", "PIT", + "PLY", "PO", "POD", "POE", "POP", "POT", "POW", "PRO", "PRY", "PUB", + "PUG", "PUN", "PUP", "PUT", "QUO", "RAG", "RAM", "RAN", "RAP", "RAT", + "RAW", "RAY", "REB", "RED", "REP", "RET", "RIB", "RID", "RIG", "RIM", + "RIO", "RIP", "ROB", "ROD", "ROE", "RON", "ROT", "ROW", "ROY", "RUB", + "RUE", "RUG", "RUM", "RUN", "RYE", "SAC", "SAD", "SAG", "SAL", "SAM", + "SAN", "SAP", "SAT", "SAW", "SAY", "SEA", "SEC", "SEE", "SEN", "SET", + "SEW", "SHE", "SHY", "SIN", "SIP", "SIR", "SIS", "SIT", "SKI", "SKY", + "SLY", "SO", "SOB", "SOD", "SON", "SOP", "SOW", "SOY", "SPA", "SPY", + "SUB", "SUD", "SUE", "SUM", "SUN", "SUP", "TAB", "TAD", "TAG", "TAN", + "TAP", "TAR", "TEA", "TED", "TEE", "TEN", "THE", "THY", "TIC", "TIE", + "TIM", "TIN", "TIP", "TO", "TOE", "TOG", "TOM", "TON", "TOO", "TOP", + "TOW", "TOY", "TRY", "TUB", "TUG", "TUM", "TUN", "TWO", "UN", "UP", + "US", "USE", "VAN", "VAT", "VET", "VIE", "WAD", "WAG", "WAR", "WAS", + "WAY", "WE", "WEB", "WED", "WEE", "WET", "WHO", "WHY", "WIN", "WIT", + "WOK", "WON", "WOO", "WOW", "WRY", "WU", "YAM", "YAP", "YAW", "YE", + "YEA", "YES", "YET", "YOU", "ABED", "ABEL", "ABET", "ABLE", "ABUT", + "ACHE", "ACID", "ACME", "ACRE", "ACTA", "ACTS", "ADAM", "ADDS", + "ADEN", "AFAR", "AFRO", "AGEE", "AHEM", "AHOY", "AIDA", "AIDE", + "AIDS", "AIRY", "AJAR", "AKIN", "ALAN", "ALEC", "ALGA", "ALIA", + "ALLY", "ALMA", "ALOE", "ALSO", "ALTO", "ALUM", "ALVA", "AMEN", + "AMES", "AMID", "AMMO", "AMOK", "AMOS", "AMRA", "ANDY", "ANEW", + "ANNA", "ANNE", "ANTE", "ANTI", "AQUA", "ARAB", "ARCH", "AREA", + "ARGO", "ARID", "ARMY", "ARTS", "ARTY", "ASIA", "ASKS", "ATOM", + "AUNT", "AURA", "AUTO", "AVER", "AVID", "AVIS", "AVON", "AVOW", + "AWAY", "AWRY", "BABE", "BABY", "BACH", "BACK", "BADE", "BAIL", + "BAIT", "BAKE", "BALD", "BALE", "BALI", "BALK", "BALL", "BALM", + "BAND", "BANE", "BANG", "BANK", "BARB", "BARD", "BARE", "BARK", + "BARN", "BARR", "BASE", "BASH", "BASK", "BASS", "BATE", "BATH", + "BAWD", "BAWL", "BEAD", "BEAK", "BEAM", "BEAN", "BEAR", "BEAT", + "BEAU", "BECK", "BEEF", "BEEN", "BEER", + "BEET", "BELA", "BELL", "BELT", "BEND", "BENT", "BERG", "BERN", + "BERT", "BESS", "BEST", "BETA", "BETH", "BHOY", "BIAS", "BIDE", + "BIEN", "BILE", "BILK", "BILL", "BIND", "BING", "BIRD", "BITE", + "BITS", "BLAB", "BLAT", "BLED", "BLEW", "BLOB", "BLOC", "BLOT", + "BLOW", "BLUE", "BLUM", "BLUR", "BOAR", "BOAT", "BOCA", "BOCK", + "BODE", "BODY", "BOGY", "BOHR", "BOIL", "BOLD", "BOLO", "BOLT", + "BOMB", "BONA", "BOND", "BONE", "BONG", "BONN", "BONY", "BOOK", + "BOOM", "BOON", "BOOT", "BORE", "BORG", "BORN", "BOSE", "BOSS", + "BOTH", "BOUT", "BOWL", "BOYD", "BRAD", "BRAE", "BRAG", "BRAN", + "BRAY", "BRED", "BREW", "BRIG", "BRIM", "BROW", "BUCK", "BUDD", + "BUFF", "BULB", "BULK", "BULL", "BUNK", "BUNT", "BUOY", "BURG", + "BURL", "BURN", "BURR", "BURT", "BURY", "BUSH", "BUSS", "BUST", + "BUSY", "BYTE", "CADY", "CAFE", "CAGE", "CAIN", "CAKE", "CALF", + "CALL", "CALM", "CAME", "CANE", "CANT", "CARD", "CARE", "CARL", + "CARR", "CART", "CASE", "CASH", "CASK", "CAST", "CAVE", "CEIL", + "CELL", "CENT", "CERN", "CHAD", "CHAR", "CHAT", "CHAW", "CHEF", + "CHEN", "CHEW", "CHIC", "CHIN", "CHOU", "CHOW", "CHUB", "CHUG", + "CHUM", "CITE", "CITY", "CLAD", "CLAM", "CLAN", "CLAW", "CLAY", + "CLOD", "CLOG", "CLOT", "CLUB", "CLUE", "COAL", "COAT", "COCA", + "COCK", "COCO", "CODA", "CODE", "CODY", "COED", "COIL", "COIN", + "COKE", "COLA", "COLD", "COLT", "COMA", "COMB", "COME", "COOK", + "COOL", "COON", "COOT", "CORD", "CORE", "CORK", "CORN", "COST", + "COVE", "COWL", "CRAB", "CRAG", "CRAM", "CRAY", "CREW", "CRIB", + "CROW", "CRUD", "CUBA", "CUBE", "CUFF", "CULL", "CULT", "CUNY", + "CURB", "CURD", "CURE", "CURL", "CURT", "CUTS", "DADE", "DALE", + "DAME", "DANA", "DANE", "DANG", "DANK", "DARE", "DARK", "DARN", + "DART", "DASH", "DATA", "DATE", "DAVE", "DAVY", "DAWN", "DAYS", + "DEAD", "DEAF", "DEAL", "DEAN", "DEAR", "DEBT", "DECK", "DEED", + "DEEM", "DEER", "DEFT", "DEFY", "DELL", "DENT", "DENY", "DESK", + "DIAL", "DICE", "DIED", "DIET", "DIME", "DINE", "DING", "DINT", + "DIRE", "DIRT", "DISC", "DISH", "DISK", "DIVE", "DOCK", "DOES", + "DOLE", "DOLL", "DOLT", "DOME", "DONE", "DOOM", "DOOR", "DORA", + "DOSE", "DOTE", "DOUG", "DOUR", "DOVE", "DOWN", "DRAB", "DRAG", + "DRAM", "DRAW", "DREW", "DRUB", "DRUG", "DRUM", "DUAL", "DUCK", + "DUCT", "DUEL", "DUET", "DUKE", "DULL", "DUMB", "DUNE", "DUNK", + "DUSK", "DUST", "DUTY", "EACH", "EARL", "EARN", "EASE", "EAST", + "EASY", "EBEN", "ECHO", "EDDY", "EDEN", "EDGE", "EDGY", "EDIT", + "EDNA", "EGAN", "ELAN", "ELBA", "ELLA", "ELSE", "EMIL", "EMIT", + "EMMA", "ENDS", "ERIC", "EROS", "EVEN", "EVER", "EVIL", "EYED", + "FACE", "FACT", "FADE", "FAIL", "FAIN", "FAIR", "FAKE", "FALL", + "FAME", "FANG", "FARM", "FAST", "FATE", "FAWN", "FEAR", "FEAT", + "FEED", "FEEL", "FEET", "FELL", "FELT", "FEND", "FERN", "FEST", + "FEUD", "FIEF", "FIGS", "FILE", "FILL", "FILM", "FIND", "FINE", + "FINK", "FIRE", "FIRM", "FISH", "FISK", "FIST", "FITS", "FIVE", + "FLAG", "FLAK", "FLAM", "FLAT", "FLAW", "FLEA", "FLED", "FLEW", + "FLIT", "FLOC", "FLOG", "FLOW", "FLUB", "FLUE", "FOAL", "FOAM", + "FOGY", "FOIL", "FOLD", "FOLK", "FOND", "FONT", "FOOD", "FOOL", + "FOOT", "FORD", "FORE", "FORK", "FORM", "FORT", "FOSS", "FOUL", + "FOUR", "FOWL", "FRAU", "FRAY", "FRED", "FREE", "FRET", "FREY", + "FROG", "FROM", "FUEL", "FULL", "FUME", "FUND", "FUNK", "FURY", + "FUSE", "FUSS", "GAFF", "GAGE", "GAIL", "GAIN", "GAIT", "GALA", + "GALE", "GALL", "GALT", "GAME", "GANG", "GARB", "GARY", "GASH", + "GATE", "GAUL", "GAUR", "GAVE", "GAWK", "GEAR", "GELD", "GENE", + "GENT", "GERM", "GETS", "GIBE", "GIFT", "GILD", "GILL", "GILT", + "GINA", "GIRD", "GIRL", "GIST", "GIVE", "GLAD", "GLEE", "GLEN", + "GLIB", "GLOB", "GLOM", "GLOW", "GLUE", "GLUM", "GLUT", "GOAD", + "GOAL", "GOAT", "GOER", "GOES", "GOLD", "GOLF", "GONE", "GONG", + "GOOD", "GOOF", "GORE", "GORY", "GOSH", "GOUT", "GOWN", "GRAB", + "GRAD", "GRAY", "GREG", "GREW", "GREY", "GRID", "GRIM", "GRIN", + "GRIT", "GROW", "GRUB", "GULF", "GULL", "GUNK", "GURU", "GUSH", + "GUST", "GWEN", "GWYN", "HAAG", "HAAS", "HACK", "HAIL", "HAIR", + "HALE", "HALF", "HALL", "HALO", "HALT", "HAND", "HANG", "HANK", + "HANS", "HARD", "HARK", "HARM", "HART", "HASH", "HAST", "HATE", + "HATH", "HAUL", "HAVE", "HAWK", "HAYS", "HEAD", "HEAL", "HEAR", + "HEAT", "HEBE", "HECK", "HEED", "HEEL", "HEFT", "HELD", "HELL", + "HELM", "HERB", "HERD", "HERE", "HERO", "HERS", "HESS", "HEWN", + "HICK", "HIDE", "HIGH", "HIKE", "HILL", "HILT", "HIND", "HINT", + "HIRE", "HISS", "HIVE", "HOBO", "HOCK", "HOFF", "HOLD", "HOLE", + "HOLM", "HOLT", "HOME", "HONE", "HONK", "HOOD", "HOOF", "HOOK", + "HOOT", "HORN", "HOSE", "HOST", "HOUR", "HOVE", "HOWE", "HOWL", + "HOYT", "HUCK", "HUED", "HUFF", "HUGE", "HUGH", "HUGO", "HULK", + "HULL", "HUNK", "HUNT", "HURD", "HURL", "HURT", "HUSH", "HYDE", + "HYMN", "IBIS", "ICON", "IDEA", "IDLE", "IFFY", "INCA", "INCH", + "INTO", "IONS", "IOTA", "IOWA", "IRIS", "IRMA", "IRON", "ISLE", + "ITCH", "ITEM", "IVAN", "JACK", "JADE", "JAIL", "JAKE", "JANE", + "JAVA", "JEAN", "JEFF", "JERK", "JESS", "JEST", "JIBE", "JILL", + "JILT", "JIVE", "JOAN", "JOBS", "JOCK", "JOEL", "JOEY", "JOHN", + "JOIN", "JOKE", "JOLT", "JOVE", "JUDD", "JUDE", "JUDO", "JUDY", + "JUJU", "JUKE", "JULY", "JUNE", "JUNK", "JUNO", "JURY", "JUST", + "JUTE", "KAHN", "KALE", "KANE", "KANT", "KARL", "KATE", "KEEL", + "KEEN", "KENO", "KENT", "KERN", "KERR", "KEYS", "KICK", "KILL", + "KIND", "KING", "KIRK", "KISS", "KITE", "KLAN", "KNEE", "KNEW", + "KNIT", "KNOB", "KNOT", "KNOW", "KOCH", "KONG", "KUDO", "KURD", + "KURT", "KYLE", "LACE", "LACK", "LACY", "LADY", "LAID", "LAIN", + "LAIR", "LAKE", "LAMB", "LAME", "LAND", "LANE", "LANG", "LARD", + "LARK", "LASS", "LAST", "LATE", "LAUD", "LAVA", "LAWN", "LAWS", + "LAYS", "LEAD", "LEAF", "LEAK", "LEAN", "LEAR", "LEEK", "LEER", + "LEFT", "LEND", "LENS", "LENT", "LEON", "LESK", "LESS", "LEST", + "LETS", "LIAR", "LICE", "LICK", "LIED", "LIEN", "LIES", "LIEU", + "LIFE", "LIFT", "LIKE", "LILA", "LILT", "LILY", "LIMA", "LIMB", + "LIME", "LIND", "LINE", "LINK", "LINT", "LION", "LISA", "LIST", + "LIVE", "LOAD", "LOAF", "LOAM", "LOAN", "LOCK", "LOFT", "LOGE", + "LOIS", "LOLA", "LONE", "LONG", "LOOK", "LOON", "LOOT", "LORD", + "LORE", "LOSE", "LOSS", "LOST", "LOUD", "LOVE", "LOWE", "LUCK", + "LUCY", "LUGE", "LUKE", "LULU", "LUND", "LUNG", "LURA", "LURE", + "LURK", "LUSH", "LUST", "LYLE", "LYNN", "LYON", "LYRA", "MACE", + "MADE", "MAGI", "MAID", "MAIL", "MAIN", "MAKE", "MALE", "MALI", + "MALL", "MALT", "MANA", "MANN", "MANY", "MARC", "MARE", "MARK", + "MARS", "MART", "MARY", "MASH", "MASK", "MASS", "MAST", "MATE", + "MATH", "MAUL", "MAYO", "MEAD", "MEAL", "MEAN", "MEAT", "MEEK", + "MEET", "MELD", "MELT", "MEMO", "MEND", "MENU", "MERT", "MESH", + "MESS", "MICE", "MIKE", "MILD", "MILE", "MILK", "MILL", "MILT", + "MIMI", "MIND", "MINE", "MINI", "MINK", "MINT", "MIRE", "MISS", + "MIST", "MITE", "MITT", "MOAN", "MOAT", "MOCK", "MODE", "MOLD", + "MOLE", "MOLL", "MOLT", "MONA", "MONK", "MONT", "MOOD", "MOON", + "MOOR", "MOOT", "MORE", "MORN", "MORT", "MOSS", "MOST", "MOTH", + "MOVE", "MUCH", "MUCK", "MUDD", "MUFF", "MULE", "MULL", "MURK", + "MUSH", "MUST", "MUTE", "MUTT", "MYRA", "MYTH", "NAGY", "NAIL", + "NAIR", "NAME", "NARY", "NASH", "NAVE", "NAVY", "NEAL", "NEAR", + "NEAT", "NECK", "NEED", "NEIL", "NELL", "NEON", "NERO", "NESS", + "NEST", "NEWS", "NEWT", "NIBS", "NICE", "NICK", "NILE", "NINA", + "NINE", "NOAH", "NODE", "NOEL", "NOLL", "NONE", "NOOK", "NOON", + "NORM", "NOSE", "NOTE", "NOUN", "NOVA", "NUDE", "NULL", "NUMB", + "OATH", "OBEY", "OBOE", "ODIN", "OHIO", "OILY", "OINT", "OKAY", + "OLAF", "OLDY", "OLGA", "OLIN", "OMAN", "OMEN", "OMIT", "ONCE", + "ONES", "ONLY", "ONTO", "ONUS", "ORAL", "ORGY", "OSLO", "OTIS", + "OTTO", "OUCH", "OUST", "OUTS", "OVAL", "OVEN", "OVER", "OWLY", + "OWNS", "QUAD", "QUIT", "QUOD", "RACE", "RACK", "RACY", "RAFT", + "RAGE", "RAID", "RAIL", "RAIN", "RAKE", "RANK", "RANT", "RARE", + "RASH", "RATE", "RAVE", "RAYS", "READ", "REAL", "REAM", "REAR", + "RECK", "REED", "REEF", "REEK", "REEL", "REID", "REIN", "RENA", + "REND", "RENT", "REST", "RICE", "RICH", "RICK", "RIDE", "RIFT", + "RILL", "RIME", "RING", "RINK", "RISE", "RISK", "RITE", "ROAD", + "ROAM", "ROAR", "ROBE", "ROCK", "RODE", "ROIL", "ROLL", "ROME", + "ROOD", "ROOF", "ROOK", "ROOM", "ROOT", "ROSA", "ROSE", "ROSS", + "ROSY", "ROTH", "ROUT", "ROVE", "ROWE", "ROWS", "RUBE", "RUBY", + "RUDE", "RUDY", "RUIN", "RULE", "RUNG", "RUNS", "RUNT", "RUSE", + "RUSH", "RUSK", "RUSS", "RUST", "RUTH", "SACK", "SAFE", "SAGE", + "SAID", "SAIL", "SALE", "SALK", "SALT", "SAME", "SAND", "SANE", + "SANG", "SANK", "SARA", "SAUL", "SAVE", "SAYS", "SCAN", "SCAR", + "SCAT", "SCOT", "SEAL", "SEAM", "SEAR", "SEAT", "SEED", "SEEK", + "SEEM", "SEEN", "SEES", "SELF", "SELL", "SEND", "SENT", "SETS", + "SEWN", "SHAG", "SHAM", "SHAW", "SHAY", "SHED", "SHIM", "SHIN", + "SHOD", "SHOE", "SHOT", "SHOW", "SHUN", "SHUT", "SICK", "SIDE", + "SIFT", "SIGH", "SIGN", "SILK", "SILL", "SILO", "SILT", "SINE", + "SING", "SINK", "SIRE", "SITE", "SITS", "SITU", "SKAT", "SKEW", + "SKID", "SKIM", "SKIN", "SKIT", "SLAB", "SLAM", "SLAT", "SLAY", + "SLED", "SLEW", "SLID", "SLIM", "SLIT", "SLOB", "SLOG", "SLOT", + "SLOW", "SLUG", "SLUM", "SLUR", "SMOG", "SMUG", "SNAG", "SNOB", + "SNOW", "SNUB", "SNUG", "SOAK", "SOAR", "SOCK", "SODA", "SOFA", + "SOFT", "SOIL", "SOLD", "SOME", "SONG", "SOON", "SOOT", "SORE", + "SORT", "SOUL", "SOUR", "SOWN", "STAB", "STAG", "STAN", "STAR", + "STAY", "STEM", "STEW", "STIR", "STOW", "STUB", "STUN", "SUCH", + "SUDS", "SUIT", "SULK", "SUMS", "SUNG", "SUNK", "SURE", "SURF", + "SWAB", "SWAG", "SWAM", "SWAN", "SWAT", "SWAY", "SWIM", "SWUM", + "TACK", "TACT", "TAIL", "TAKE", "TALE", "TALK", "TALL", "TANK", + "TASK", "TATE", "TAUT", "TEAL", "TEAM", "TEAR", "TECH", "TEEM", + "TEEN", "TEET", "TELL", "TEND", "TENT", "TERM", "TERN", "TESS", + "TEST", "THAN", "THAT", "THEE", "THEM", "THEN", "THEY", "THIN", + "THIS", "THUD", "THUG", "TICK", "TIDE", "TIDY", "TIED", "TIER", + "TILE", "TILL", "TILT", "TIME", "TINA", "TINE", "TINT", "TINY", + "TIRE", "TOAD", "TOGO", "TOIL", "TOLD", "TOLL", "TONE", "TONG", + "TONY", "TOOK", "TOOL", "TOOT", "TORE", "TORN", "TOTE", "TOUR", + "TOUT", "TOWN", "TRAG", "TRAM", "TRAY", "TREE", "TREK", "TRIG", + "TRIM", "TRIO", "TROD", "TROT", "TROY", "TRUE", "TUBA", "TUBE", + "TUCK", "TUFT", "TUNA", "TUNE", "TUNG", "TURF", "TURN", "TUSK", + "TWIG", "TWIN", "TWIT", "ULAN", "UNIT", "URGE", "USED", "USER", + "USES", "UTAH", "VAIL", "VAIN", "VALE", "VARY", "VASE", "VAST", + "VEAL", "VEDA", "VEIL", "VEIN", "VEND", "VENT", "VERB", "VERY", + "VETO", "VICE", "VIEW", "VINE", "VISE", "VOID", "VOLT", "VOTE", + "WACK", "WADE", "WAGE", "WAIL", "WAIT", "WAKE", "WALE", "WALK", + "WALL", "WALT", "WAND", "WANE", "WANG", "WANT", "WARD", "WARM", + "WARN", "WART", "WASH", "WAST", "WATS", "WATT", "WAVE", "WAVY", + "WAYS", "WEAK", "WEAL", "WEAN", "WEAR", "WEED", "WEEK", "WEIR", + "WELD", "WELL", "WELT", "WENT", "WERE", "WERT", "WEST", "WHAM", + "WHAT", "WHEE", "WHEN", "WHET", "WHOA", "WHOM", "WICK", "WIFE", + "WILD", "WILL", "WIND", "WINE", "WING", "WINK", "WINO", "WIRE", + "WISE", "WISH", "WITH", "WOLF", "WONT", "WOOD", "WOOL", "WORD", + "WORE", "WORK", "WORM", "WORN", "WOVE", "WRIT", "WYNN", "YALE", + "YANG", "YANK", "YARD", "YARN", "YAWL", "YAWN", "YEAH", "YEAR", + "YELL", "YOGA", "YOKE" ] + +if __name__=='__main__': + data = [('EB33F77EE73D4053', 'TIDE ITCH SLOW REIN RULE MOT'), + ('CCAC2AED591056BE4F90FD441C534766', + 'RASH BUSH MILK LOOK BAD BRIM AVID GAFF BAIT ROT POD LOVE'), + ('EFF81F9BFBC65350920CDD7416DE8009', + 'TROD MUTE TAIL WARM CHAR KONG HAAG CITY BORE O TEAL AWL') + ] + + for key, words in data: + print 'Trying key', key + key=binascii.a2b_hex(key) + w2=key_to_english(key) + if w2!=words: + print 'key_to_english fails on key', repr(key), ', producing', str(w2) + k2=english_to_key(words) + if k2!=key: + print 'english_to_key fails on key', repr(key), ', producing', repr(k2) + + diff --git a/lib/Crypto/Util/__init__.py b/lib/Crypto/Util/__init__.py new file mode 100644 index 0000000..18df5e0 --- /dev/null +++ b/lib/Crypto/Util/__init__.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# +# =================================================================== +# 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. +# =================================================================== + +"""Miscellaneous modules + +Contains useful modules that don't belong into any of the +other Crypto.* subpackages. + +Crypto.Util.number Number-theoretic functions (primality testing, etc.) +Crypto.Util.Counter Fast counter functions for CTR cipher modes. +Crypto.Util.randpool Random number generation +Crypto.Util.RFC1751 Converts between 128-bit keys and human-readable + strings of words. +Crypto.Util.asn1 Minimal support for ASN.1 DER encoding + +""" + +__all__ = ['randpool', 'RFC1751', 'number', 'strxor', 'asn1', 'Counter' ] + +__revision__ = "$Id$" + diff --git a/lib/Crypto/Util/_counter.so b/lib/Crypto/Util/_counter.so new file mode 100755 index 0000000000000000000000000000000000000000..2533026f9eb79ff921a43cab65fa9e0c80a30171 GIT binary patch literal 35072 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

Zur3!QtqVoQ5|OzMk6tW5 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 +# +# =================================================================== +# 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. +# =================================================================== + +## NOTE: Do not import this module directly. Import these functions from Crypto.Util.number. + +__revision__ = "$Id$" +__all__ = ['ceil_shift', 'ceil_div', 'floor_div', 'exact_log2', 'exact_div'] + +import sys +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * + +def ceil_shift(n, b): + """Return ceil(n / 2**b) without performing any floating-point or division operations. + + This is done by right-shifting n by b bits and incrementing the result by 1 + if any '1' bits were shifted out. + """ + if not isinstance(n, (int, long)) or not isinstance(b, (int, long)): + raise TypeError("unsupported operand type(s): %r and %r" % (type(n).__name__, type(b).__name__)) + + assert n >= 0 and b >= 0 # I haven't tested or even thought about negative values + mask = (1L << b) - 1 + if n & mask: + return (n >> b) + 1 + else: + return n >> b + +def ceil_div(a, b): + """Return ceil(a / b) without performing any floating-point operations.""" + + if not isinstance(a, (int, long)) or not isinstance(b, (int, long)): + raise TypeError("unsupported operand type(s): %r and %r" % (type(a).__name__, type(b).__name__)) + + (q, r) = divmod(a, b) + if r: + return q + 1 + else: + return q + +def floor_div(a, b): + if not isinstance(a, (int, long)) or not isinstance(b, (int, long)): + raise TypeError("unsupported operand type(s): %r and %r" % (type(a).__name__, type(b).__name__)) + + (q, r) = divmod(a, b) + return q + +def exact_log2(num): + """Find and return an integer i >= 0 such that num == 2**i. + + If no such integer exists, this function raises ValueError. + """ + + if not isinstance(num, (int, long)): + raise TypeError("unsupported operand type: %r" % (type(num).__name__,)) + + n = long(num) + if n <= 0: + raise ValueError("cannot compute logarithm of non-positive number") + + i = 0 + while n != 0: + if (n & 1) and n != 1: + raise ValueError("No solution could be found") + i += 1 + n >>= 1 + i -= 1 + + assert num == (1L << i) + return i + +def exact_div(p, d, allow_divzero=False): + """Find and return an integer n such that p == n * d + + If no such integer exists, this function raises ValueError. + + Both operands must be integers. + + If the second operand is zero, this function will raise ZeroDivisionError + unless allow_divzero is true (default: False). + """ + + if not isinstance(p, (int, long)) or not isinstance(d, (int, long)): + raise TypeError("unsupported operand type(s): %r and %r" % (type(p).__name__, type(d).__name__)) + + if d == 0 and allow_divzero: + n = 0 + if p != n * d: + raise ValueError("No solution could be found") + else: + (n, r) = divmod(p, d) + if r != 0: + raise ValueError("No solution could be found") + + assert p == n * d + return n + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/Util/asn1.py b/lib/Crypto/Util/asn1.py new file mode 100644 index 0000000..dd5ec31 --- /dev/null +++ b/lib/Crypto/Util/asn1.py @@ -0,0 +1,286 @@ +# -*- coding: ascii -*- +# +# Util/asn1.py : Minimal support for ASN.1 DER binary encoding. +# +# =================================================================== +# 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. +# =================================================================== + +from Crypto.Util.number import long_to_bytes, bytes_to_long +import sys +from Crypto.Util.py3compat import * + +__all__ = [ 'DerObject', 'DerInteger', 'DerOctetString', 'DerNull', 'DerSequence', 'DerObjectId' ] + +class DerObject: + """Base class for defining a single DER object. + + Instantiate this class ONLY when you have to decode a DER element. + """ + + # Known TAG types + typeTags = { 'SEQUENCE': 0x30, 'BIT STRING': 0x03, 'INTEGER': 0x02, + 'OCTET STRING': 0x04, 'NULL': 0x05, 'OBJECT IDENTIFIER': 0x06 } + + def __init__(self, ASN1Type=None, payload=b('')): + """Initialize the DER object according to a specific type. + + The ASN.1 type is either specified as the ASN.1 string (e.g. + 'SEQUENCE'), directly with its numerical tag or with no tag + at all (None).""" + if isInt(ASN1Type) or ASN1Type is None: + self.typeTag = ASN1Type + else: + if len(ASN1Type)==1: + self.typeTag = ord(ASN1Type) + else: + self.typeTag = self.typeTags.get(ASN1Type) + self.payload = payload + + def isType(self, ASN1Type): + return self.typeTags[ASN1Type]==self.typeTag + + def _lengthOctets(self, payloadLen): + """Return a byte string that encodes the given payload length (in + bytes) in a format suitable for a DER length tag (L). + """ + if payloadLen>127: + encoding = long_to_bytes(payloadLen) + return bchr(len(encoding)+128) + encoding + return bchr(payloadLen) + + def encode(self): + """Return a complete DER element, fully encoded as a TLV.""" + return bchr(self.typeTag) + self._lengthOctets(len(self.payload)) + self.payload + + def _decodeLen(self, idx, der): + """Given a (part of a) DER element, and an index to the first byte of + a DER length tag (L), return a tuple with the payload size, + and the index of the first byte of the such payload (V). + + Raises a ValueError exception if the DER length is invalid. + Raises an IndexError exception if the DER element is too short. + """ + length = bord(der[idx]) + if length<=127: + return (length,idx+1) + payloadLength = bytes_to_long(der[idx+1:idx+1+(length & 0x7F)]) + if payloadLength<=127: + raise ValueError("Not a DER length tag.") + return (payloadLength, idx+1+(length & 0x7F)) + + def decode(self, derEle, noLeftOvers=0): + """Decode a complete DER element, and re-initializes this + object with it. + + @param derEle A complete DER element. It must start with a DER T + tag. + @param noLeftOvers Indicate whether it is acceptable to complete the + parsing of the DER element and find that not all + bytes in derEle have been used. + @return Index of the first unused byte in the given DER element. + + Raises a ValueError exception in case of parsing errors. + Raises an IndexError exception if the DER element is too short. + """ + try: + self.typeTag = bord(derEle[0]) + if (self.typeTag & 0x1F)==0x1F: + raise ValueError("Unsupported DER tag") + (length,idx) = self._decodeLen(1, derEle) + if noLeftOvers and len(derEle) != (idx+length): + raise ValueError("Not a DER structure") + self.payload = derEle[idx:idx+length] + except IndexError: + raise ValueError("Not a valid DER SEQUENCE.") + return idx+length + +class DerInteger(DerObject): + def __init__(self, value = 0): + """Class to model an INTEGER DER element. + + Limitation: only non-negative values are supported. + """ + DerObject.__init__(self, 'INTEGER') + self.value = value + + def encode(self): + """Return a complete INTEGER DER element, fully encoded as a TLV.""" + self.payload = long_to_bytes(self.value) + if bord(self.payload[0])>127: + self.payload = bchr(0x00) + self.payload + return DerObject.encode(self) + + def decode(self, derEle, noLeftOvers=0): + """Decode a complete INTEGER DER element, and re-initializes this + object with it. + + @param derEle A complete INTEGER DER element. It must start with a DER + INTEGER tag. + @param noLeftOvers Indicate whether it is acceptable to complete the + parsing of the DER element and find that not all + bytes in derEle have been used. + @return Index of the first unused byte in the given DER element. + + Raises a ValueError exception if the DER element is not a + valid non-negative INTEGER. + Raises an IndexError exception if the DER element is too short. + """ + tlvLength = DerObject.decode(self, derEle, noLeftOvers) + if self.typeTag!=self.typeTags['INTEGER']: + raise ValueError ("Not a DER INTEGER.") + if bord(self.payload[0])>127: + raise ValueError ("Negative INTEGER.") + self.value = bytes_to_long(self.payload) + return tlvLength + +class DerSequence(DerObject): + """Class to model a SEQUENCE DER element. + + This object behave like a dynamic Python sequence. + Sub-elements that are INTEGERs, look like Python integers. + Any other sub-element is a binary string encoded as the complete DER + sub-element (TLV). + """ + + def __init__(self, startSeq=None): + """Initialize the SEQUENCE DER object. Always empty + initially.""" + DerObject.__init__(self, 'SEQUENCE') + if startSeq==None: + self._seq = [] + else: + self._seq = startSeq + + ## A few methods to make it behave like a python sequence + + def __delitem__(self, n): + del self._seq[n] + def __getitem__(self, n): + return self._seq[n] + def __setitem__(self, key, value): + self._seq[key] = value + def __setslice__(self,i,j,sequence): + self._seq[i:j] = sequence + def __delslice__(self,i,j): + del self._seq[i:j] + def __getslice__(self, i, j): + return self._seq[max(0, i):max(0, j)] + def __len__(self): + return len(self._seq) + def append(self, item): + return self._seq.append(item) + + def hasInts(self): + """Return the number of items in this sequence that are numbers.""" + return len(filter(isInt, self._seq)) + + def hasOnlyInts(self): + """Return True if all items in this sequence are numbers.""" + return self._seq and self.hasInts()==len(self._seq) + + def encode(self): + """Return the DER encoding for the ASN.1 SEQUENCE, containing + the non-negative integers and longs added to this object. + + Limitation: Raises a ValueError exception if it some elements + in the sequence are neither Python integers nor complete DER INTEGERs. + """ + self.payload = b('') + for item in self._seq: + try: + self.payload += item + except: + try: + self.payload += DerInteger(item).encode() + except: + raise ValueError("Trying to DER encode an unknown object") + return DerObject.encode(self) + + def decode(self, derEle, noLeftOvers=0): + """Decode a complete SEQUENCE DER element, and re-initializes this + object with it. + + @param derEle A complete SEQUENCE DER element. It must start with a DER + SEQUENCE tag. + @param noLeftOvers Indicate whether it is acceptable to complete the + parsing of the DER element and find that not all + bytes in derEle have been used. + @return Index of the first unused byte in the given DER element. + + DER INTEGERs are decoded into Python integers. Any other DER + element is not decoded. Its validity is not checked. + + Raises a ValueError exception if the DER element is not a + valid DER SEQUENCE. + Raises an IndexError exception if the DER element is too short. + """ + + self._seq = [] + try: + tlvLength = DerObject.decode(self, derEle, noLeftOvers) + if self.typeTag!=self.typeTags['SEQUENCE']: + raise ValueError("Not a DER SEQUENCE.") + # Scan one TLV at once + idx = 0 + while idx= 5 to avoid timing attack vulnerability.", PowmInsecureWarning) + +# New functions +from _number_new import * + +# Commented out and replaced with faster versions below +## def long2str(n): +## s='' +## while n>0: +## s=chr(n & 255)+s +## n=n>>8 +## return s + +## import types +## def str2long(s): +## if type(s)!=types.StringType: return s # Integers will be left alone +## return reduce(lambda x,y : x*256+ord(y), s, 0L) + +def size (N): + """size(N:long) : int + Returns the size of the number N in bits. + """ + bits = 0 + while N >> bits: + bits += 1 + return bits + +def getRandomNumber(N, randfunc=None): + """Deprecated. Use getRandomInteger or getRandomNBitInteger instead.""" + warnings.warn("Crypto.Util.number.getRandomNumber has confusing semantics"+ + "and has been deprecated. Use getRandomInteger or getRandomNBitInteger instead.", + GetRandomNumber_DeprecationWarning) + return getRandomNBitInteger(N, randfunc) + +def getRandomInteger(N, randfunc=None): + """getRandomInteger(N:int, randfunc:callable):long + Return a random number with at most N bits. + + If randfunc is omitted, then Random.new().read is used. + + This function is for internal use only and may be renamed or removed in + the future. + """ + if randfunc is None: + _import_Random() + randfunc = Random.new().read + + S = randfunc(N>>3) + odd_bits = N % 8 + if odd_bits != 0: + char = ord(randfunc(1)) >> (8-odd_bits) + S = bchr(char) + S + value = bytes_to_long(S) + return value + +def getRandomRange(a, b, randfunc=None): + """getRandomRange(a:int, b:int, randfunc:callable):long + Return a random number n so that a <= n < b. + + If randfunc is omitted, then Random.new().read is used. + + This function is for internal use only and may be renamed or removed in + the future. + """ + range_ = b - a - 1 + bits = size(range_) + value = getRandomInteger(bits, randfunc) + while value > range_: + value = getRandomInteger(bits, randfunc) + return a + value + +def getRandomNBitInteger(N, randfunc=None): + """getRandomInteger(N:int, randfunc:callable):long + Return a random number with exactly N-bits, i.e. a random number + between 2**(N-1) and (2**N)-1. + + If randfunc is omitted, then Random.new().read is used. + + This function is for internal use only and may be renamed or removed in + the future. + """ + value = getRandomInteger (N-1, randfunc) + value |= 2L ** (N-1) # Ensure high bit is set + assert size(value) >= N + return value + +def GCD(x,y): + """GCD(x:long, y:long): long + Return the GCD of x and y. + """ + x = abs(x) ; y = abs(y) + while x > 0: + x, y = y % x, x + return y + +def inverse(u, v): + """inverse(u:long, v:long):long + Return the inverse of u mod v. + """ + u3, v3 = long(u), long(v) + u1, v1 = 1L, 0L + while v3 > 0: + q=divmod(u3, v3)[0] + u1, v1 = v1, u1 - v1*q + u3, v3 = v3, u3 - v3*q + while u1<0: + u1 = u1 + v + return u1 + +# Given a number of bits to generate and a random generation function, +# find a prime number of the appropriate size. + +def getPrime(N, randfunc=None): + """getPrime(N:int, randfunc:callable):long + Return a random N-bit prime number. + + If randfunc is omitted, then Random.new().read is used. + """ + if randfunc is None: + _import_Random() + randfunc = Random.new().read + + number=getRandomNBitInteger(N, randfunc) | 1 + while (not isPrime(number, randfunc=randfunc)): + number=number+2 + return number + + +def _rabinMillerTest(n, rounds, randfunc=None): + """_rabinMillerTest(n:long, rounds:int, randfunc:callable):int + Tests if n is prime. + Returns 0 when n is definitly composite. + Returns 1 when n is probably prime. + Returns 2 when n is definitly prime. + + If randfunc is omitted, then Random.new().read is used. + + This function is for internal use only and may be renamed or removed in + the future. + """ + # check special cases (n==2, n even, n < 2) + if n < 3 or (n & 1) == 0: + return n == 2 + # n might be very large so it might be beneficial to precalculate n-1 + n_1 = n - 1 + # determine m and b so that 2**b * m = n - 1 and b maximal + b = 0 + m = n_1 + while (m & 1) == 0: + b += 1 + m >>= 1 + + tested = [] + # we need to do at most n-2 rounds. + for i in xrange (min (rounds, n-2)): + # randomly choose a < n and make sure it hasn't been tested yet + a = getRandomRange (2, n, randfunc) + while a in tested: + a = getRandomRange (2, n, randfunc) + tested.append (a) + # do the rabin-miller test + z = pow (a, m, n) # (a**m) % n + if z == 1 or z == n_1: + continue + composite = 1 + for r in xrange (b): + z = (z * z) % n + if z == 1: + return 0 + elif z == n_1: + composite = 0 + break + if composite: + return 0 + return 1 + +def getStrongPrime(N, e=0, false_positive_prob=1e-6, randfunc=None): + """getStrongPrime(N:int, e:int, false_positive_prob:float, randfunc:callable):long + Return a random strong N-bit prime number. + In this context p is a strong prime if p-1 and p+1 have at + least one large prime factor. + N should be a multiple of 128 and > 512. + + If e is provided the returned prime p-1 will be coprime to e + and thus suitable for RSA where e is the public exponent. + + The optional false_positive_prob is the statistical probability + that true is returned even though it is not (pseudo-prime). + It defaults to 1e-6 (less than 1:1000000). + Note that the real probability of a false-positive is far less. This is + just the mathematically provable limit. + + randfunc should take a single int parameter and return that + many random bytes as a string. + If randfunc is omitted, then Random.new().read is used. + """ + # This function was implemented following the + # instructions found in the paper: + # "FAST GENERATION OF RANDOM, STRONG RSA PRIMES" + # by Robert D. Silverman + # RSA Laboratories + # May 17, 1997 + # which by the time of writing could be freely downloaded here: + # http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.17.2713&rep=rep1&type=pdf + + # Use the accelerator if available + if _fastmath is not None: + return _fastmath.getStrongPrime(long(N), long(e), false_positive_prob, + randfunc) + + if (N < 512) or ((N % 128) != 0): + raise ValueError ("bits must be multiple of 128 and > 512") + + rabin_miller_rounds = int(math.ceil(-math.log(false_positive_prob)/math.log(4))) + + # calculate range for X + # lower_bound = sqrt(2) * 2^{511 + 128*x} + # upper_bound = 2^{512 + 128*x} - 1 + x = (N - 512) >> 7; + # We need to approximate the sqrt(2) in the lower_bound by an integer + # expression because floating point math overflows with these numbers + lower_bound = divmod(14142135623730950489L * (2L ** (511 + 128*x)), + 10000000000000000000L)[0] + upper_bound = (1L << (512 + 128*x)) - 1 + # Randomly choose X in calculated range + X = getRandomRange (lower_bound, upper_bound, randfunc) + + # generate p1 and p2 + p = [0, 0] + for i in (0, 1): + # randomly choose 101-bit y + y = getRandomNBitInteger (101, randfunc) + # initialize the field for sieving + field = [0] * 5 * len (sieve_base) + # sieve the field + for prime in sieve_base: + offset = y % prime + for j in xrange ((prime - offset) % prime, len (field), prime): + field[j] = 1 + + # look for suitable p[i] starting at y + result = 0 + for j in range(len(field)): + composite = field[j] + # look for next canidate + if composite: + continue + tmp = y + j + result = _rabinMillerTest (tmp, rabin_miller_rounds) + if result > 0: + p[i] = tmp + break + if result == 0: + raise RuntimeError ("Couln't find prime in field. " + "Developer: Increase field_size") + + # Calculate R + # R = (p2^{-1} mod p1) * p2 - (p1^{-1} mod p2) * p1 + tmp1 = inverse (p[1], p[0]) * p[1] # (p2^-1 mod p1)*p2 + tmp2 = inverse (p[0], p[1]) * p[0] # (p1^-1 mod p2)*p1 + R = tmp1 - tmp2 # (p2^-1 mod p1)*p2 - (p1^-1 mod p2)*p1 + + # search for final prime number starting by Y0 + # Y0 = X + (R - X mod p1p2) + increment = p[0] * p[1] + X = X + (R - (X % increment)) + while 1: + is_possible_prime = 1 + # first check candidate against sieve_base + for prime in sieve_base: + if (X % prime) == 0: + is_possible_prime = 0 + break + # if e is given make sure that e and X-1 are coprime + # this is not necessarily a strong prime criterion but useful when + # creating them for RSA where the p-1 and q-1 should be coprime to + # the public exponent e + if e and is_possible_prime: + if e & 1: + if GCD (e, X-1) != 1: + is_possible_prime = 0 + else: + if GCD (e, divmod((X-1),2)[0]) != 1: + is_possible_prime = 0 + + # do some Rabin-Miller-Tests + if is_possible_prime: + result = _rabinMillerTest (X, rabin_miller_rounds) + if result > 0: + break + X += increment + # abort when X has more bits than requested + # TODO: maybe we shouldn't abort but rather start over. + if X >= 1L << N: + raise RuntimeError ("Couln't find prime in field. " + "Developer: Increase field_size") + return X + +def isPrime(N, false_positive_prob=1e-6, randfunc=None): + """isPrime(N:long, false_positive_prob:float, randfunc:callable):bool + Return true if N is prime. + + The optional false_positive_prob is the statistical probability + that true is returned even though it is not (pseudo-prime). + It defaults to 1e-6 (less than 1:1000000). + Note that the real probability of a false-positive is far less. This is + just the mathematically provable limit. + + If randfunc is omitted, then Random.new().read is used. + """ + if _fastmath is not None: + return _fastmath.isPrime(long(N), false_positive_prob, randfunc) + + if N < 3 or N & 1 == 0: + return N == 2 + for p in sieve_base: + if N == p: + return 1 + if N % p == 0: + return 0 + + rounds = int(math.ceil(-math.log(false_positive_prob)/math.log(4))) + return _rabinMillerTest(N, rounds, randfunc) + + +# Improved conversion functions contributed by Barry Warsaw, after +# careful benchmarking + +import struct + +def long_to_bytes(n, blocksize=0): + """long_to_bytes(n:long, blocksize:int) : string + Convert a long integer to a byte string. + + If optional blocksize is given and greater than zero, pad the front of the + byte string with binary zeros so that the length is a multiple of + blocksize. + """ + # after much testing, this algorithm was deemed to be the fastest + s = b('') + n = long(n) + pack = struct.pack + while n > 0: + s = pack('>I', n & 0xffffffffL) + s + n = n >> 32 + # strip off leading zeros + for i in range(len(s)): + if s[i] != b('\000')[0]: + break + else: + # only happens when n == 0 + s = b('\000') + i = 0 + s = s[i:] + # add back some pad bytes. this could be done more efficiently w.r.t. the + # de-padding being done above, but sigh... + if blocksize > 0 and len(s) % blocksize: + s = (blocksize - len(s) % blocksize) * b('\000') + s + return s + +def bytes_to_long(s): + """bytes_to_long(string) : long + Convert a byte string to a long integer. + + This is (essentially) the inverse of long_to_bytes(). + """ + acc = 0L + unpack = struct.unpack + length = len(s) + if length % 4: + extra = (4 - length % 4) + s = b('\000') * extra + s + length = length + extra + for i in range(0, length, 4): + acc = (acc << 32) + unpack('>I', s[i:i+4])[0] + return acc + +# For backwards compatibility... +import warnings +def long2str(n, blocksize=0): + warnings.warn("long2str() has been replaced by long_to_bytes()") + return long_to_bytes(n, blocksize) +def str2long(s): + warnings.warn("str2long() has been replaced by bytes_to_long()") + return bytes_to_long(s) + +def _import_Random(): + # This is called in a function instead of at the module level in order to + # avoid problems with recursive imports + global Random, StrongRandom + from Crypto import Random + from Crypto.Random.random import StrongRandom + + + +# The first 10000 primes used for checking primality. +# This should be enough to eliminate most of the odd +# numbers before needing to do a Rabin-Miller test at all. +sieve_base = ( + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, + 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, + 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, + 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, + 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, + 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, + 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, + 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, + 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, + 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, + 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, + 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, + 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, + 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, + 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, + 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, + 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, + 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, + 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, + 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, + 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, + 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, + 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, + 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, + 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, + 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, + 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, + 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, + 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, + 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, + 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, + 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, + 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, + 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, + 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, + 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, + 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, + 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, + 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, + 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, + 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, + 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, + 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, + 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, + 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, + 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, + 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, + 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, + 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, + 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, + 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643, + 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, + 3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, + 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, + 3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989, + 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, 4057, + 4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139, + 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231, + 4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297, + 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, + 4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493, + 4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, 4583, + 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657, + 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751, + 4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831, + 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937, + 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, + 5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087, + 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, + 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279, + 5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387, + 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443, + 5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, + 5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639, + 5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, 5693, + 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791, + 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857, + 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939, + 5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053, + 6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133, + 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, + 6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301, + 6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367, + 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473, + 6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, + 6577, 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, 6673, + 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761, + 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833, + 6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917, + 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997, + 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, + 7109, 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207, + 7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297, + 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411, + 7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499, + 7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561, + 7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643, + 7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723, + 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, + 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919, + 7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017, + 8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111, + 8117, 8123, 8147, 8161, 8167, 8171, 8179, 8191, 8209, 8219, + 8221, 8231, 8233, 8237, 8243, 8263, 8269, 8273, 8287, 8291, + 8293, 8297, 8311, 8317, 8329, 8353, 8363, 8369, 8377, 8387, + 8389, 8419, 8423, 8429, 8431, 8443, 8447, 8461, 8467, 8501, + 8513, 8521, 8527, 8537, 8539, 8543, 8563, 8573, 8581, 8597, + 8599, 8609, 8623, 8627, 8629, 8641, 8647, 8663, 8669, 8677, + 8681, 8689, 8693, 8699, 8707, 8713, 8719, 8731, 8737, 8741, + 8747, 8753, 8761, 8779, 8783, 8803, 8807, 8819, 8821, 8831, + 8837, 8839, 8849, 8861, 8863, 8867, 8887, 8893, 8923, 8929, + 8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, 9011, + 9013, 9029, 9041, 9043, 9049, 9059, 9067, 9091, 9103, 9109, + 9127, 9133, 9137, 9151, 9157, 9161, 9173, 9181, 9187, 9199, + 9203, 9209, 9221, 9227, 9239, 9241, 9257, 9277, 9281, 9283, + 9293, 9311, 9319, 9323, 9337, 9341, 9343, 9349, 9371, 9377, + 9391, 9397, 9403, 9413, 9419, 9421, 9431, 9433, 9437, 9439, + 9461, 9463, 9467, 9473, 9479, 9491, 9497, 9511, 9521, 9533, + 9539, 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, 9631, + 9643, 9649, 9661, 9677, 9679, 9689, 9697, 9719, 9721, 9733, + 9739, 9743, 9749, 9767, 9769, 9781, 9787, 9791, 9803, 9811, + 9817, 9829, 9833, 9839, 9851, 9857, 9859, 9871, 9883, 9887, + 9901, 9907, 9923, 9929, 9931, 9941, 9949, 9967, 9973, 10007, + 10009, 10037, 10039, 10061, 10067, 10069, 10079, 10091, 10093, 10099, + 10103, 10111, 10133, 10139, 10141, 10151, 10159, 10163, 10169, 10177, + 10181, 10193, 10211, 10223, 10243, 10247, 10253, 10259, 10267, 10271, + 10273, 10289, 10301, 10303, 10313, 10321, 10331, 10333, 10337, 10343, + 10357, 10369, 10391, 10399, 10427, 10429, 10433, 10453, 10457, 10459, + 10463, 10477, 10487, 10499, 10501, 10513, 10529, 10531, 10559, 10567, + 10589, 10597, 10601, 10607, 10613, 10627, 10631, 10639, 10651, 10657, + 10663, 10667, 10687, 10691, 10709, 10711, 10723, 10729, 10733, 10739, + 10753, 10771, 10781, 10789, 10799, 10831, 10837, 10847, 10853, 10859, + 10861, 10867, 10883, 10889, 10891, 10903, 10909, 10937, 10939, 10949, + 10957, 10973, 10979, 10987, 10993, 11003, 11027, 11047, 11057, 11059, + 11069, 11071, 11083, 11087, 11093, 11113, 11117, 11119, 11131, 11149, + 11159, 11161, 11171, 11173, 11177, 11197, 11213, 11239, 11243, 11251, + 11257, 11261, 11273, 11279, 11287, 11299, 11311, 11317, 11321, 11329, + 11351, 11353, 11369, 11383, 11393, 11399, 11411, 11423, 11437, 11443, + 11447, 11467, 11471, 11483, 11489, 11491, 11497, 11503, 11519, 11527, + 11549, 11551, 11579, 11587, 11593, 11597, 11617, 11621, 11633, 11657, + 11677, 11681, 11689, 11699, 11701, 11717, 11719, 11731, 11743, 11777, + 11779, 11783, 11789, 11801, 11807, 11813, 11821, 11827, 11831, 11833, + 11839, 11863, 11867, 11887, 11897, 11903, 11909, 11923, 11927, 11933, + 11939, 11941, 11953, 11959, 11969, 11971, 11981, 11987, 12007, 12011, + 12037, 12041, 12043, 12049, 12071, 12073, 12097, 12101, 12107, 12109, + 12113, 12119, 12143, 12149, 12157, 12161, 12163, 12197, 12203, 12211, + 12227, 12239, 12241, 12251, 12253, 12263, 12269, 12277, 12281, 12289, + 12301, 12323, 12329, 12343, 12347, 12373, 12377, 12379, 12391, 12401, + 12409, 12413, 12421, 12433, 12437, 12451, 12457, 12473, 12479, 12487, + 12491, 12497, 12503, 12511, 12517, 12527, 12539, 12541, 12547, 12553, + 12569, 12577, 12583, 12589, 12601, 12611, 12613, 12619, 12637, 12641, + 12647, 12653, 12659, 12671, 12689, 12697, 12703, 12713, 12721, 12739, + 12743, 12757, 12763, 12781, 12791, 12799, 12809, 12821, 12823, 12829, + 12841, 12853, 12889, 12893, 12899, 12907, 12911, 12917, 12919, 12923, + 12941, 12953, 12959, 12967, 12973, 12979, 12983, 13001, 13003, 13007, + 13009, 13033, 13037, 13043, 13049, 13063, 13093, 13099, 13103, 13109, + 13121, 13127, 13147, 13151, 13159, 13163, 13171, 13177, 13183, 13187, + 13217, 13219, 13229, 13241, 13249, 13259, 13267, 13291, 13297, 13309, + 13313, 13327, 13331, 13337, 13339, 13367, 13381, 13397, 13399, 13411, + 13417, 13421, 13441, 13451, 13457, 13463, 13469, 13477, 13487, 13499, + 13513, 13523, 13537, 13553, 13567, 13577, 13591, 13597, 13613, 13619, + 13627, 13633, 13649, 13669, 13679, 13681, 13687, 13691, 13693, 13697, + 13709, 13711, 13721, 13723, 13729, 13751, 13757, 13759, 13763, 13781, + 13789, 13799, 13807, 13829, 13831, 13841, 13859, 13873, 13877, 13879, + 13883, 13901, 13903, 13907, 13913, 13921, 13931, 13933, 13963, 13967, + 13997, 13999, 14009, 14011, 14029, 14033, 14051, 14057, 14071, 14081, + 14083, 14087, 14107, 14143, 14149, 14153, 14159, 14173, 14177, 14197, + 14207, 14221, 14243, 14249, 14251, 14281, 14293, 14303, 14321, 14323, + 14327, 14341, 14347, 14369, 14387, 14389, 14401, 14407, 14411, 14419, + 14423, 14431, 14437, 14447, 14449, 14461, 14479, 14489, 14503, 14519, + 14533, 14537, 14543, 14549, 14551, 14557, 14561, 14563, 14591, 14593, + 14621, 14627, 14629, 14633, 14639, 14653, 14657, 14669, 14683, 14699, + 14713, 14717, 14723, 14731, 14737, 14741, 14747, 14753, 14759, 14767, + 14771, 14779, 14783, 14797, 14813, 14821, 14827, 14831, 14843, 14851, + 14867, 14869, 14879, 14887, 14891, 14897, 14923, 14929, 14939, 14947, + 14951, 14957, 14969, 14983, 15013, 15017, 15031, 15053, 15061, 15073, + 15077, 15083, 15091, 15101, 15107, 15121, 15131, 15137, 15139, 15149, + 15161, 15173, 15187, 15193, 15199, 15217, 15227, 15233, 15241, 15259, + 15263, 15269, 15271, 15277, 15287, 15289, 15299, 15307, 15313, 15319, + 15329, 15331, 15349, 15359, 15361, 15373, 15377, 15383, 15391, 15401, + 15413, 15427, 15439, 15443, 15451, 15461, 15467, 15473, 15493, 15497, + 15511, 15527, 15541, 15551, 15559, 15569, 15581, 15583, 15601, 15607, + 15619, 15629, 15641, 15643, 15647, 15649, 15661, 15667, 15671, 15679, + 15683, 15727, 15731, 15733, 15737, 15739, 15749, 15761, 15767, 15773, + 15787, 15791, 15797, 15803, 15809, 15817, 15823, 15859, 15877, 15881, + 15887, 15889, 15901, 15907, 15913, 15919, 15923, 15937, 15959, 15971, + 15973, 15991, 16001, 16007, 16033, 16057, 16061, 16063, 16067, 16069, + 16073, 16087, 16091, 16097, 16103, 16111, 16127, 16139, 16141, 16183, + 16187, 16189, 16193, 16217, 16223, 16229, 16231, 16249, 16253, 16267, + 16273, 16301, 16319, 16333, 16339, 16349, 16361, 16363, 16369, 16381, + 16411, 16417, 16421, 16427, 16433, 16447, 16451, 16453, 16477, 16481, + 16487, 16493, 16519, 16529, 16547, 16553, 16561, 16567, 16573, 16603, + 16607, 16619, 16631, 16633, 16649, 16651, 16657, 16661, 16673, 16691, + 16693, 16699, 16703, 16729, 16741, 16747, 16759, 16763, 16787, 16811, + 16823, 16829, 16831, 16843, 16871, 16879, 16883, 16889, 16901, 16903, + 16921, 16927, 16931, 16937, 16943, 16963, 16979, 16981, 16987, 16993, + 17011, 17021, 17027, 17029, 17033, 17041, 17047, 17053, 17077, 17093, + 17099, 17107, 17117, 17123, 17137, 17159, 17167, 17183, 17189, 17191, + 17203, 17207, 17209, 17231, 17239, 17257, 17291, 17293, 17299, 17317, + 17321, 17327, 17333, 17341, 17351, 17359, 17377, 17383, 17387, 17389, + 17393, 17401, 17417, 17419, 17431, 17443, 17449, 17467, 17471, 17477, + 17483, 17489, 17491, 17497, 17509, 17519, 17539, 17551, 17569, 17573, + 17579, 17581, 17597, 17599, 17609, 17623, 17627, 17657, 17659, 17669, + 17681, 17683, 17707, 17713, 17729, 17737, 17747, 17749, 17761, 17783, + 17789, 17791, 17807, 17827, 17837, 17839, 17851, 17863, 17881, 17891, + 17903, 17909, 17911, 17921, 17923, 17929, 17939, 17957, 17959, 17971, + 17977, 17981, 17987, 17989, 18013, 18041, 18043, 18047, 18049, 18059, + 18061, 18077, 18089, 18097, 18119, 18121, 18127, 18131, 18133, 18143, + 18149, 18169, 18181, 18191, 18199, 18211, 18217, 18223, 18229, 18233, + 18251, 18253, 18257, 18269, 18287, 18289, 18301, 18307, 18311, 18313, + 18329, 18341, 18353, 18367, 18371, 18379, 18397, 18401, 18413, 18427, + 18433, 18439, 18443, 18451, 18457, 18461, 18481, 18493, 18503, 18517, + 18521, 18523, 18539, 18541, 18553, 18583, 18587, 18593, 18617, 18637, + 18661, 18671, 18679, 18691, 18701, 18713, 18719, 18731, 18743, 18749, + 18757, 18773, 18787, 18793, 18797, 18803, 18839, 18859, 18869, 18899, + 18911, 18913, 18917, 18919, 18947, 18959, 18973, 18979, 19001, 19009, + 19013, 19031, 19037, 19051, 19069, 19073, 19079, 19081, 19087, 19121, + 19139, 19141, 19157, 19163, 19181, 19183, 19207, 19211, 19213, 19219, + 19231, 19237, 19249, 19259, 19267, 19273, 19289, 19301, 19309, 19319, + 19333, 19373, 19379, 19381, 19387, 19391, 19403, 19417, 19421, 19423, + 19427, 19429, 19433, 19441, 19447, 19457, 19463, 19469, 19471, 19477, + 19483, 19489, 19501, 19507, 19531, 19541, 19543, 19553, 19559, 19571, + 19577, 19583, 19597, 19603, 19609, 19661, 19681, 19687, 19697, 19699, + 19709, 19717, 19727, 19739, 19751, 19753, 19759, 19763, 19777, 19793, + 19801, 19813, 19819, 19841, 19843, 19853, 19861, 19867, 19889, 19891, + 19913, 19919, 19927, 19937, 19949, 19961, 19963, 19973, 19979, 19991, + 19993, 19997, 20011, 20021, 20023, 20029, 20047, 20051, 20063, 20071, + 20089, 20101, 20107, 20113, 20117, 20123, 20129, 20143, 20147, 20149, + 20161, 20173, 20177, 20183, 20201, 20219, 20231, 20233, 20249, 20261, + 20269, 20287, 20297, 20323, 20327, 20333, 20341, 20347, 20353, 20357, + 20359, 20369, 20389, 20393, 20399, 20407, 20411, 20431, 20441, 20443, + 20477, 20479, 20483, 20507, 20509, 20521, 20533, 20543, 20549, 20551, + 20563, 20593, 20599, 20611, 20627, 20639, 20641, 20663, 20681, 20693, + 20707, 20717, 20719, 20731, 20743, 20747, 20749, 20753, 20759, 20771, + 20773, 20789, 20807, 20809, 20849, 20857, 20873, 20879, 20887, 20897, + 20899, 20903, 20921, 20929, 20939, 20947, 20959, 20963, 20981, 20983, + 21001, 21011, 21013, 21017, 21019, 21023, 21031, 21059, 21061, 21067, + 21089, 21101, 21107, 21121, 21139, 21143, 21149, 21157, 21163, 21169, + 21179, 21187, 21191, 21193, 21211, 21221, 21227, 21247, 21269, 21277, + 21283, 21313, 21317, 21319, 21323, 21341, 21347, 21377, 21379, 21383, + 21391, 21397, 21401, 21407, 21419, 21433, 21467, 21481, 21487, 21491, + 21493, 21499, 21503, 21517, 21521, 21523, 21529, 21557, 21559, 21563, + 21569, 21577, 21587, 21589, 21599, 21601, 21611, 21613, 21617, 21647, + 21649, 21661, 21673, 21683, 21701, 21713, 21727, 21737, 21739, 21751, + 21757, 21767, 21773, 21787, 21799, 21803, 21817, 21821, 21839, 21841, + 21851, 21859, 21863, 21871, 21881, 21893, 21911, 21929, 21937, 21943, + 21961, 21977, 21991, 21997, 22003, 22013, 22027, 22031, 22037, 22039, + 22051, 22063, 22067, 22073, 22079, 22091, 22093, 22109, 22111, 22123, + 22129, 22133, 22147, 22153, 22157, 22159, 22171, 22189, 22193, 22229, + 22247, 22259, 22271, 22273, 22277, 22279, 22283, 22291, 22303, 22307, + 22343, 22349, 22367, 22369, 22381, 22391, 22397, 22409, 22433, 22441, + 22447, 22453, 22469, 22481, 22483, 22501, 22511, 22531, 22541, 22543, + 22549, 22567, 22571, 22573, 22613, 22619, 22621, 22637, 22639, 22643, + 22651, 22669, 22679, 22691, 22697, 22699, 22709, 22717, 22721, 22727, + 22739, 22741, 22751, 22769, 22777, 22783, 22787, 22807, 22811, 22817, + 22853, 22859, 22861, 22871, 22877, 22901, 22907, 22921, 22937, 22943, + 22961, 22963, 22973, 22993, 23003, 23011, 23017, 23021, 23027, 23029, + 23039, 23041, 23053, 23057, 23059, 23063, 23071, 23081, 23087, 23099, + 23117, 23131, 23143, 23159, 23167, 23173, 23189, 23197, 23201, 23203, + 23209, 23227, 23251, 23269, 23279, 23291, 23293, 23297, 23311, 23321, + 23327, 23333, 23339, 23357, 23369, 23371, 23399, 23417, 23431, 23447, + 23459, 23473, 23497, 23509, 23531, 23537, 23539, 23549, 23557, 23561, + 23563, 23567, 23581, 23593, 23599, 23603, 23609, 23623, 23627, 23629, + 23633, 23663, 23669, 23671, 23677, 23687, 23689, 23719, 23741, 23743, + 23747, 23753, 23761, 23767, 23773, 23789, 23801, 23813, 23819, 23827, + 23831, 23833, 23857, 23869, 23873, 23879, 23887, 23893, 23899, 23909, + 23911, 23917, 23929, 23957, 23971, 23977, 23981, 23993, 24001, 24007, + 24019, 24023, 24029, 24043, 24049, 24061, 24071, 24077, 24083, 24091, + 24097, 24103, 24107, 24109, 24113, 24121, 24133, 24137, 24151, 24169, + 24179, 24181, 24197, 24203, 24223, 24229, 24239, 24247, 24251, 24281, + 24317, 24329, 24337, 24359, 24371, 24373, 24379, 24391, 24407, 24413, + 24419, 24421, 24439, 24443, 24469, 24473, 24481, 24499, 24509, 24517, + 24527, 24533, 24547, 24551, 24571, 24593, 24611, 24623, 24631, 24659, + 24671, 24677, 24683, 24691, 24697, 24709, 24733, 24749, 24763, 24767, + 24781, 24793, 24799, 24809, 24821, 24841, 24847, 24851, 24859, 24877, + 24889, 24907, 24917, 24919, 24923, 24943, 24953, 24967, 24971, 24977, + 24979, 24989, 25013, 25031, 25033, 25037, 25057, 25073, 25087, 25097, + 25111, 25117, 25121, 25127, 25147, 25153, 25163, 25169, 25171, 25183, + 25189, 25219, 25229, 25237, 25243, 25247, 25253, 25261, 25301, 25303, + 25307, 25309, 25321, 25339, 25343, 25349, 25357, 25367, 25373, 25391, + 25409, 25411, 25423, 25439, 25447, 25453, 25457, 25463, 25469, 25471, + 25523, 25537, 25541, 25561, 25577, 25579, 25583, 25589, 25601, 25603, + 25609, 25621, 25633, 25639, 25643, 25657, 25667, 25673, 25679, 25693, + 25703, 25717, 25733, 25741, 25747, 25759, 25763, 25771, 25793, 25799, + 25801, 25819, 25841, 25847, 25849, 25867, 25873, 25889, 25903, 25913, + 25919, 25931, 25933, 25939, 25943, 25951, 25969, 25981, 25997, 25999, + 26003, 26017, 26021, 26029, 26041, 26053, 26083, 26099, 26107, 26111, + 26113, 26119, 26141, 26153, 26161, 26171, 26177, 26183, 26189, 26203, + 26209, 26227, 26237, 26249, 26251, 26261, 26263, 26267, 26293, 26297, + 26309, 26317, 26321, 26339, 26347, 26357, 26371, 26387, 26393, 26399, + 26407, 26417, 26423, 26431, 26437, 26449, 26459, 26479, 26489, 26497, + 26501, 26513, 26539, 26557, 26561, 26573, 26591, 26597, 26627, 26633, + 26641, 26647, 26669, 26681, 26683, 26687, 26693, 26699, 26701, 26711, + 26713, 26717, 26723, 26729, 26731, 26737, 26759, 26777, 26783, 26801, + 26813, 26821, 26833, 26839, 26849, 26861, 26863, 26879, 26881, 26891, + 26893, 26903, 26921, 26927, 26947, 26951, 26953, 26959, 26981, 26987, + 26993, 27011, 27017, 27031, 27043, 27059, 27061, 27067, 27073, 27077, + 27091, 27103, 27107, 27109, 27127, 27143, 27179, 27191, 27197, 27211, + 27239, 27241, 27253, 27259, 27271, 27277, 27281, 27283, 27299, 27329, + 27337, 27361, 27367, 27397, 27407, 27409, 27427, 27431, 27437, 27449, + 27457, 27479, 27481, 27487, 27509, 27527, 27529, 27539, 27541, 27551, + 27581, 27583, 27611, 27617, 27631, 27647, 27653, 27673, 27689, 27691, + 27697, 27701, 27733, 27737, 27739, 27743, 27749, 27751, 27763, 27767, + 27773, 27779, 27791, 27793, 27799, 27803, 27809, 27817, 27823, 27827, + 27847, 27851, 27883, 27893, 27901, 27917, 27919, 27941, 27943, 27947, + 27953, 27961, 27967, 27983, 27997, 28001, 28019, 28027, 28031, 28051, + 28057, 28069, 28081, 28087, 28097, 28099, 28109, 28111, 28123, 28151, + 28163, 28181, 28183, 28201, 28211, 28219, 28229, 28277, 28279, 28283, + 28289, 28297, 28307, 28309, 28319, 28349, 28351, 28387, 28393, 28403, + 28409, 28411, 28429, 28433, 28439, 28447, 28463, 28477, 28493, 28499, + 28513, 28517, 28537, 28541, 28547, 28549, 28559, 28571, 28573, 28579, + 28591, 28597, 28603, 28607, 28619, 28621, 28627, 28631, 28643, 28649, + 28657, 28661, 28663, 28669, 28687, 28697, 28703, 28711, 28723, 28729, + 28751, 28753, 28759, 28771, 28789, 28793, 28807, 28813, 28817, 28837, + 28843, 28859, 28867, 28871, 28879, 28901, 28909, 28921, 28927, 28933, + 28949, 28961, 28979, 29009, 29017, 29021, 29023, 29027, 29033, 29059, + 29063, 29077, 29101, 29123, 29129, 29131, 29137, 29147, 29153, 29167, + 29173, 29179, 29191, 29201, 29207, 29209, 29221, 29231, 29243, 29251, + 29269, 29287, 29297, 29303, 29311, 29327, 29333, 29339, 29347, 29363, + 29383, 29387, 29389, 29399, 29401, 29411, 29423, 29429, 29437, 29443, + 29453, 29473, 29483, 29501, 29527, 29531, 29537, 29567, 29569, 29573, + 29581, 29587, 29599, 29611, 29629, 29633, 29641, 29663, 29669, 29671, + 29683, 29717, 29723, 29741, 29753, 29759, 29761, 29789, 29803, 29819, + 29833, 29837, 29851, 29863, 29867, 29873, 29879, 29881, 29917, 29921, + 29927, 29947, 29959, 29983, 29989, 30011, 30013, 30029, 30047, 30059, + 30071, 30089, 30091, 30097, 30103, 30109, 30113, 30119, 30133, 30137, + 30139, 30161, 30169, 30181, 30187, 30197, 30203, 30211, 30223, 30241, + 30253, 30259, 30269, 30271, 30293, 30307, 30313, 30319, 30323, 30341, + 30347, 30367, 30389, 30391, 30403, 30427, 30431, 30449, 30467, 30469, + 30491, 30493, 30497, 30509, 30517, 30529, 30539, 30553, 30557, 30559, + 30577, 30593, 30631, 30637, 30643, 30649, 30661, 30671, 30677, 30689, + 30697, 30703, 30707, 30713, 30727, 30757, 30763, 30773, 30781, 30803, + 30809, 30817, 30829, 30839, 30841, 30851, 30853, 30859, 30869, 30871, + 30881, 30893, 30911, 30931, 30937, 30941, 30949, 30971, 30977, 30983, + 31013, 31019, 31033, 31039, 31051, 31063, 31069, 31079, 31081, 31091, + 31121, 31123, 31139, 31147, 31151, 31153, 31159, 31177, 31181, 31183, + 31189, 31193, 31219, 31223, 31231, 31237, 31247, 31249, 31253, 31259, + 31267, 31271, 31277, 31307, 31319, 31321, 31327, 31333, 31337, 31357, + 31379, 31387, 31391, 31393, 31397, 31469, 31477, 31481, 31489, 31511, + 31513, 31517, 31531, 31541, 31543, 31547, 31567, 31573, 31583, 31601, + 31607, 31627, 31643, 31649, 31657, 31663, 31667, 31687, 31699, 31721, + 31723, 31727, 31729, 31741, 31751, 31769, 31771, 31793, 31799, 31817, + 31847, 31849, 31859, 31873, 31883, 31891, 31907, 31957, 31963, 31973, + 31981, 31991, 32003, 32009, 32027, 32029, 32051, 32057, 32059, 32063, + 32069, 32077, 32083, 32089, 32099, 32117, 32119, 32141, 32143, 32159, + 32173, 32183, 32189, 32191, 32203, 32213, 32233, 32237, 32251, 32257, + 32261, 32297, 32299, 32303, 32309, 32321, 32323, 32327, 32341, 32353, + 32359, 32363, 32369, 32371, 32377, 32381, 32401, 32411, 32413, 32423, + 32429, 32441, 32443, 32467, 32479, 32491, 32497, 32503, 32507, 32531, + 32533, 32537, 32561, 32563, 32569, 32573, 32579, 32587, 32603, 32609, + 32611, 32621, 32633, 32647, 32653, 32687, 32693, 32707, 32713, 32717, + 32719, 32749, 32771, 32779, 32783, 32789, 32797, 32801, 32803, 32831, + 32833, 32839, 32843, 32869, 32887, 32909, 32911, 32917, 32933, 32939, + 32941, 32957, 32969, 32971, 32983, 32987, 32993, 32999, 33013, 33023, + 33029, 33037, 33049, 33053, 33071, 33073, 33083, 33091, 33107, 33113, + 33119, 33149, 33151, 33161, 33179, 33181, 33191, 33199, 33203, 33211, + 33223, 33247, 33287, 33289, 33301, 33311, 33317, 33329, 33331, 33343, + 33347, 33349, 33353, 33359, 33377, 33391, 33403, 33409, 33413, 33427, + 33457, 33461, 33469, 33479, 33487, 33493, 33503, 33521, 33529, 33533, + 33547, 33563, 33569, 33577, 33581, 33587, 33589, 33599, 33601, 33613, + 33617, 33619, 33623, 33629, 33637, 33641, 33647, 33679, 33703, 33713, + 33721, 33739, 33749, 33751, 33757, 33767, 33769, 33773, 33791, 33797, + 33809, 33811, 33827, 33829, 33851, 33857, 33863, 33871, 33889, 33893, + 33911, 33923, 33931, 33937, 33941, 33961, 33967, 33997, 34019, 34031, + 34033, 34039, 34057, 34061, 34123, 34127, 34129, 34141, 34147, 34157, + 34159, 34171, 34183, 34211, 34213, 34217, 34231, 34253, 34259, 34261, + 34267, 34273, 34283, 34297, 34301, 34303, 34313, 34319, 34327, 34337, + 34351, 34361, 34367, 34369, 34381, 34403, 34421, 34429, 34439, 34457, + 34469, 34471, 34483, 34487, 34499, 34501, 34511, 34513, 34519, 34537, + 34543, 34549, 34583, 34589, 34591, 34603, 34607, 34613, 34631, 34649, + 34651, 34667, 34673, 34679, 34687, 34693, 34703, 34721, 34729, 34739, + 34747, 34757, 34759, 34763, 34781, 34807, 34819, 34841, 34843, 34847, + 34849, 34871, 34877, 34883, 34897, 34913, 34919, 34939, 34949, 34961, + 34963, 34981, 35023, 35027, 35051, 35053, 35059, 35069, 35081, 35083, + 35089, 35099, 35107, 35111, 35117, 35129, 35141, 35149, 35153, 35159, + 35171, 35201, 35221, 35227, 35251, 35257, 35267, 35279, 35281, 35291, + 35311, 35317, 35323, 35327, 35339, 35353, 35363, 35381, 35393, 35401, + 35407, 35419, 35423, 35437, 35447, 35449, 35461, 35491, 35507, 35509, + 35521, 35527, 35531, 35533, 35537, 35543, 35569, 35573, 35591, 35593, + 35597, 35603, 35617, 35671, 35677, 35729, 35731, 35747, 35753, 35759, + 35771, 35797, 35801, 35803, 35809, 35831, 35837, 35839, 35851, 35863, + 35869, 35879, 35897, 35899, 35911, 35923, 35933, 35951, 35963, 35969, + 35977, 35983, 35993, 35999, 36007, 36011, 36013, 36017, 36037, 36061, + 36067, 36073, 36083, 36097, 36107, 36109, 36131, 36137, 36151, 36161, + 36187, 36191, 36209, 36217, 36229, 36241, 36251, 36263, 36269, 36277, + 36293, 36299, 36307, 36313, 36319, 36341, 36343, 36353, 36373, 36383, + 36389, 36433, 36451, 36457, 36467, 36469, 36473, 36479, 36493, 36497, + 36523, 36527, 36529, 36541, 36551, 36559, 36563, 36571, 36583, 36587, + 36599, 36607, 36629, 36637, 36643, 36653, 36671, 36677, 36683, 36691, + 36697, 36709, 36713, 36721, 36739, 36749, 36761, 36767, 36779, 36781, + 36787, 36791, 36793, 36809, 36821, 36833, 36847, 36857, 36871, 36877, + 36887, 36899, 36901, 36913, 36919, 36923, 36929, 36931, 36943, 36947, + 36973, 36979, 36997, 37003, 37013, 37019, 37021, 37039, 37049, 37057, + 37061, 37087, 37097, 37117, 37123, 37139, 37159, 37171, 37181, 37189, + 37199, 37201, 37217, 37223, 37243, 37253, 37273, 37277, 37307, 37309, + 37313, 37321, 37337, 37339, 37357, 37361, 37363, 37369, 37379, 37397, + 37409, 37423, 37441, 37447, 37463, 37483, 37489, 37493, 37501, 37507, + 37511, 37517, 37529, 37537, 37547, 37549, 37561, 37567, 37571, 37573, + 37579, 37589, 37591, 37607, 37619, 37633, 37643, 37649, 37657, 37663, + 37691, 37693, 37699, 37717, 37747, 37781, 37783, 37799, 37811, 37813, + 37831, 37847, 37853, 37861, 37871, 37879, 37889, 37897, 37907, 37951, + 37957, 37963, 37967, 37987, 37991, 37993, 37997, 38011, 38039, 38047, + 38053, 38069, 38083, 38113, 38119, 38149, 38153, 38167, 38177, 38183, + 38189, 38197, 38201, 38219, 38231, 38237, 38239, 38261, 38273, 38281, + 38287, 38299, 38303, 38317, 38321, 38327, 38329, 38333, 38351, 38371, + 38377, 38393, 38431, 38447, 38449, 38453, 38459, 38461, 38501, 38543, + 38557, 38561, 38567, 38569, 38593, 38603, 38609, 38611, 38629, 38639, + 38651, 38653, 38669, 38671, 38677, 38693, 38699, 38707, 38711, 38713, + 38723, 38729, 38737, 38747, 38749, 38767, 38783, 38791, 38803, 38821, + 38833, 38839, 38851, 38861, 38867, 38873, 38891, 38903, 38917, 38921, + 38923, 38933, 38953, 38959, 38971, 38977, 38993, 39019, 39023, 39041, + 39043, 39047, 39079, 39089, 39097, 39103, 39107, 39113, 39119, 39133, + 39139, 39157, 39161, 39163, 39181, 39191, 39199, 39209, 39217, 39227, + 39229, 39233, 39239, 39241, 39251, 39293, 39301, 39313, 39317, 39323, + 39341, 39343, 39359, 39367, 39371, 39373, 39383, 39397, 39409, 39419, + 39439, 39443, 39451, 39461, 39499, 39503, 39509, 39511, 39521, 39541, + 39551, 39563, 39569, 39581, 39607, 39619, 39623, 39631, 39659, 39667, + 39671, 39679, 39703, 39709, 39719, 39727, 39733, 39749, 39761, 39769, + 39779, 39791, 39799, 39821, 39827, 39829, 39839, 39841, 39847, 39857, + 39863, 39869, 39877, 39883, 39887, 39901, 39929, 39937, 39953, 39971, + 39979, 39983, 39989, 40009, 40013, 40031, 40037, 40039, 40063, 40087, + 40093, 40099, 40111, 40123, 40127, 40129, 40151, 40153, 40163, 40169, + 40177, 40189, 40193, 40213, 40231, 40237, 40241, 40253, 40277, 40283, + 40289, 40343, 40351, 40357, 40361, 40387, 40423, 40427, 40429, 40433, + 40459, 40471, 40483, 40487, 40493, 40499, 40507, 40519, 40529, 40531, + 40543, 40559, 40577, 40583, 40591, 40597, 40609, 40627, 40637, 40639, + 40693, 40697, 40699, 40709, 40739, 40751, 40759, 40763, 40771, 40787, + 40801, 40813, 40819, 40823, 40829, 40841, 40847, 40849, 40853, 40867, + 40879, 40883, 40897, 40903, 40927, 40933, 40939, 40949, 40961, 40973, + 40993, 41011, 41017, 41023, 41039, 41047, 41051, 41057, 41077, 41081, + 41113, 41117, 41131, 41141, 41143, 41149, 41161, 41177, 41179, 41183, + 41189, 41201, 41203, 41213, 41221, 41227, 41231, 41233, 41243, 41257, + 41263, 41269, 41281, 41299, 41333, 41341, 41351, 41357, 41381, 41387, + 41389, 41399, 41411, 41413, 41443, 41453, 41467, 41479, 41491, 41507, + 41513, 41519, 41521, 41539, 41543, 41549, 41579, 41593, 41597, 41603, + 41609, 41611, 41617, 41621, 41627, 41641, 41647, 41651, 41659, 41669, + 41681, 41687, 41719, 41729, 41737, 41759, 41761, 41771, 41777, 41801, + 41809, 41813, 41843, 41849, 41851, 41863, 41879, 41887, 41893, 41897, + 41903, 41911, 41927, 41941, 41947, 41953, 41957, 41959, 41969, 41981, + 41983, 41999, 42013, 42017, 42019, 42023, 42043, 42061, 42071, 42073, + 42083, 42089, 42101, 42131, 42139, 42157, 42169, 42179, 42181, 42187, + 42193, 42197, 42209, 42221, 42223, 42227, 42239, 42257, 42281, 42283, + 42293, 42299, 42307, 42323, 42331, 42337, 42349, 42359, 42373, 42379, + 42391, 42397, 42403, 42407, 42409, 42433, 42437, 42443, 42451, 42457, + 42461, 42463, 42467, 42473, 42487, 42491, 42499, 42509, 42533, 42557, + 42569, 42571, 42577, 42589, 42611, 42641, 42643, 42649, 42667, 42677, + 42683, 42689, 42697, 42701, 42703, 42709, 42719, 42727, 42737, 42743, + 42751, 42767, 42773, 42787, 42793, 42797, 42821, 42829, 42839, 42841, + 42853, 42859, 42863, 42899, 42901, 42923, 42929, 42937, 42943, 42953, + 42961, 42967, 42979, 42989, 43003, 43013, 43019, 43037, 43049, 43051, + 43063, 43067, 43093, 43103, 43117, 43133, 43151, 43159, 43177, 43189, + 43201, 43207, 43223, 43237, 43261, 43271, 43283, 43291, 43313, 43319, + 43321, 43331, 43391, 43397, 43399, 43403, 43411, 43427, 43441, 43451, + 43457, 43481, 43487, 43499, 43517, 43541, 43543, 43573, 43577, 43579, + 43591, 43597, 43607, 43609, 43613, 43627, 43633, 43649, 43651, 43661, + 43669, 43691, 43711, 43717, 43721, 43753, 43759, 43777, 43781, 43783, + 43787, 43789, 43793, 43801, 43853, 43867, 43889, 43891, 43913, 43933, + 43943, 43951, 43961, 43963, 43969, 43973, 43987, 43991, 43997, 44017, + 44021, 44027, 44029, 44041, 44053, 44059, 44071, 44087, 44089, 44101, + 44111, 44119, 44123, 44129, 44131, 44159, 44171, 44179, 44189, 44201, + 44203, 44207, 44221, 44249, 44257, 44263, 44267, 44269, 44273, 44279, + 44281, 44293, 44351, 44357, 44371, 44381, 44383, 44389, 44417, 44449, + 44453, 44483, 44491, 44497, 44501, 44507, 44519, 44531, 44533, 44537, + 44543, 44549, 44563, 44579, 44587, 44617, 44621, 44623, 44633, 44641, + 44647, 44651, 44657, 44683, 44687, 44699, 44701, 44711, 44729, 44741, + 44753, 44771, 44773, 44777, 44789, 44797, 44809, 44819, 44839, 44843, + 44851, 44867, 44879, 44887, 44893, 44909, 44917, 44927, 44939, 44953, + 44959, 44963, 44971, 44983, 44987, 45007, 45013, 45053, 45061, 45077, + 45083, 45119, 45121, 45127, 45131, 45137, 45139, 45161, 45179, 45181, + 45191, 45197, 45233, 45247, 45259, 45263, 45281, 45289, 45293, 45307, + 45317, 45319, 45329, 45337, 45341, 45343, 45361, 45377, 45389, 45403, + 45413, 45427, 45433, 45439, 45481, 45491, 45497, 45503, 45523, 45533, + 45541, 45553, 45557, 45569, 45587, 45589, 45599, 45613, 45631, 45641, + 45659, 45667, 45673, 45677, 45691, 45697, 45707, 45737, 45751, 45757, + 45763, 45767, 45779, 45817, 45821, 45823, 45827, 45833, 45841, 45853, + 45863, 45869, 45887, 45893, 45943, 45949, 45953, 45959, 45971, 45979, + 45989, 46021, 46027, 46049, 46051, 46061, 46073, 46091, 46093, 46099, + 46103, 46133, 46141, 46147, 46153, 46171, 46181, 46183, 46187, 46199, + 46219, 46229, 46237, 46261, 46271, 46273, 46279, 46301, 46307, 46309, + 46327, 46337, 46349, 46351, 46381, 46399, 46411, 46439, 46441, 46447, + 46451, 46457, 46471, 46477, 46489, 46499, 46507, 46511, 46523, 46549, + 46559, 46567, 46573, 46589, 46591, 46601, 46619, 46633, 46639, 46643, + 46649, 46663, 46679, 46681, 46687, 46691, 46703, 46723, 46727, 46747, + 46751, 46757, 46769, 46771, 46807, 46811, 46817, 46819, 46829, 46831, + 46853, 46861, 46867, 46877, 46889, 46901, 46919, 46933, 46957, 46993, + 46997, 47017, 47041, 47051, 47057, 47059, 47087, 47093, 47111, 47119, + 47123, 47129, 47137, 47143, 47147, 47149, 47161, 47189, 47207, 47221, + 47237, 47251, 47269, 47279, 47287, 47293, 47297, 47303, 47309, 47317, + 47339, 47351, 47353, 47363, 47381, 47387, 47389, 47407, 47417, 47419, + 47431, 47441, 47459, 47491, 47497, 47501, 47507, 47513, 47521, 47527, + 47533, 47543, 47563, 47569, 47581, 47591, 47599, 47609, 47623, 47629, + 47639, 47653, 47657, 47659, 47681, 47699, 47701, 47711, 47713, 47717, + 47737, 47741, 47743, 47777, 47779, 47791, 47797, 47807, 47809, 47819, + 47837, 47843, 47857, 47869, 47881, 47903, 47911, 47917, 47933, 47939, + 47947, 47951, 47963, 47969, 47977, 47981, 48017, 48023, 48029, 48049, + 48073, 48079, 48091, 48109, 48119, 48121, 48131, 48157, 48163, 48179, + 48187, 48193, 48197, 48221, 48239, 48247, 48259, 48271, 48281, 48299, + 48311, 48313, 48337, 48341, 48353, 48371, 48383, 48397, 48407, 48409, + 48413, 48437, 48449, 48463, 48473, 48479, 48481, 48487, 48491, 48497, + 48523, 48527, 48533, 48539, 48541, 48563, 48571, 48589, 48593, 48611, + 48619, 48623, 48647, 48649, 48661, 48673, 48677, 48679, 48731, 48733, + 48751, 48757, 48761, 48767, 48779, 48781, 48787, 48799, 48809, 48817, + 48821, 48823, 48847, 48857, 48859, 48869, 48871, 48883, 48889, 48907, + 48947, 48953, 48973, 48989, 48991, 49003, 49009, 49019, 49031, 49033, + 49037, 49043, 49057, 49069, 49081, 49103, 49109, 49117, 49121, 49123, + 49139, 49157, 49169, 49171, 49177, 49193, 49199, 49201, 49207, 49211, + 49223, 49253, 49261, 49277, 49279, 49297, 49307, 49331, 49333, 49339, + 49363, 49367, 49369, 49391, 49393, 49409, 49411, 49417, 49429, 49433, + 49451, 49459, 49463, 49477, 49481, 49499, 49523, 49529, 49531, 49537, + 49547, 49549, 49559, 49597, 49603, 49613, 49627, 49633, 49639, 49663, + 49667, 49669, 49681, 49697, 49711, 49727, 49739, 49741, 49747, 49757, + 49783, 49787, 49789, 49801, 49807, 49811, 49823, 49831, 49843, 49853, + 49871, 49877, 49891, 49919, 49921, 49927, 49937, 49939, 49943, 49957, + 49991, 49993, 49999, 50021, 50023, 50033, 50047, 50051, 50053, 50069, + 50077, 50087, 50093, 50101, 50111, 50119, 50123, 50129, 50131, 50147, + 50153, 50159, 50177, 50207, 50221, 50227, 50231, 50261, 50263, 50273, + 50287, 50291, 50311, 50321, 50329, 50333, 50341, 50359, 50363, 50377, + 50383, 50387, 50411, 50417, 50423, 50441, 50459, 50461, 50497, 50503, + 50513, 50527, 50539, 50543, 50549, 50551, 50581, 50587, 50591, 50593, + 50599, 50627, 50647, 50651, 50671, 50683, 50707, 50723, 50741, 50753, + 50767, 50773, 50777, 50789, 50821, 50833, 50839, 50849, 50857, 50867, + 50873, 50891, 50893, 50909, 50923, 50929, 50951, 50957, 50969, 50971, + 50989, 50993, 51001, 51031, 51043, 51047, 51059, 51061, 51071, 51109, + 51131, 51133, 51137, 51151, 51157, 51169, 51193, 51197, 51199, 51203, + 51217, 51229, 51239, 51241, 51257, 51263, 51283, 51287, 51307, 51329, + 51341, 51343, 51347, 51349, 51361, 51383, 51407, 51413, 51419, 51421, + 51427, 51431, 51437, 51439, 51449, 51461, 51473, 51479, 51481, 51487, + 51503, 51511, 51517, 51521, 51539, 51551, 51563, 51577, 51581, 51593, + 51599, 51607, 51613, 51631, 51637, 51647, 51659, 51673, 51679, 51683, + 51691, 51713, 51719, 51721, 51749, 51767, 51769, 51787, 51797, 51803, + 51817, 51827, 51829, 51839, 51853, 51859, 51869, 51871, 51893, 51899, + 51907, 51913, 51929, 51941, 51949, 51971, 51973, 51977, 51991, 52009, + 52021, 52027, 52051, 52057, 52067, 52069, 52081, 52103, 52121, 52127, + 52147, 52153, 52163, 52177, 52181, 52183, 52189, 52201, 52223, 52237, + 52249, 52253, 52259, 52267, 52289, 52291, 52301, 52313, 52321, 52361, + 52363, 52369, 52379, 52387, 52391, 52433, 52453, 52457, 52489, 52501, + 52511, 52517, 52529, 52541, 52543, 52553, 52561, 52567, 52571, 52579, + 52583, 52609, 52627, 52631, 52639, 52667, 52673, 52691, 52697, 52709, + 52711, 52721, 52727, 52733, 52747, 52757, 52769, 52783, 52807, 52813, + 52817, 52837, 52859, 52861, 52879, 52883, 52889, 52901, 52903, 52919, + 52937, 52951, 52957, 52963, 52967, 52973, 52981, 52999, 53003, 53017, + 53047, 53051, 53069, 53077, 53087, 53089, 53093, 53101, 53113, 53117, + 53129, 53147, 53149, 53161, 53171, 53173, 53189, 53197, 53201, 53231, + 53233, 53239, 53267, 53269, 53279, 53281, 53299, 53309, 53323, 53327, + 53353, 53359, 53377, 53381, 53401, 53407, 53411, 53419, 53437, 53441, + 53453, 53479, 53503, 53507, 53527, 53549, 53551, 53569, 53591, 53593, + 53597, 53609, 53611, 53617, 53623, 53629, 53633, 53639, 53653, 53657, + 53681, 53693, 53699, 53717, 53719, 53731, 53759, 53773, 53777, 53783, + 53791, 53813, 53819, 53831, 53849, 53857, 53861, 53881, 53887, 53891, + 53897, 53899, 53917, 53923, 53927, 53939, 53951, 53959, 53987, 53993, + 54001, 54011, 54013, 54037, 54049, 54059, 54083, 54091, 54101, 54121, + 54133, 54139, 54151, 54163, 54167, 54181, 54193, 54217, 54251, 54269, + 54277, 54287, 54293, 54311, 54319, 54323, 54331, 54347, 54361, 54367, + 54371, 54377, 54401, 54403, 54409, 54413, 54419, 54421, 54437, 54443, + 54449, 54469, 54493, 54497, 54499, 54503, 54517, 54521, 54539, 54541, + 54547, 54559, 54563, 54577, 54581, 54583, 54601, 54617, 54623, 54629, + 54631, 54647, 54667, 54673, 54679, 54709, 54713, 54721, 54727, 54751, + 54767, 54773, 54779, 54787, 54799, 54829, 54833, 54851, 54869, 54877, + 54881, 54907, 54917, 54919, 54941, 54949, 54959, 54973, 54979, 54983, + 55001, 55009, 55021, 55049, 55051, 55057, 55061, 55073, 55079, 55103, + 55109, 55117, 55127, 55147, 55163, 55171, 55201, 55207, 55213, 55217, + 55219, 55229, 55243, 55249, 55259, 55291, 55313, 55331, 55333, 55337, + 55339, 55343, 55351, 55373, 55381, 55399, 55411, 55439, 55441, 55457, + 55469, 55487, 55501, 55511, 55529, 55541, 55547, 55579, 55589, 55603, + 55609, 55619, 55621, 55631, 55633, 55639, 55661, 55663, 55667, 55673, + 55681, 55691, 55697, 55711, 55717, 55721, 55733, 55763, 55787, 55793, + 55799, 55807, 55813, 55817, 55819, 55823, 55829, 55837, 55843, 55849, + 55871, 55889, 55897, 55901, 55903, 55921, 55927, 55931, 55933, 55949, + 55967, 55987, 55997, 56003, 56009, 56039, 56041, 56053, 56081, 56087, + 56093, 56099, 56101, 56113, 56123, 56131, 56149, 56167, 56171, 56179, + 56197, 56207, 56209, 56237, 56239, 56249, 56263, 56267, 56269, 56299, + 56311, 56333, 56359, 56369, 56377, 56383, 56393, 56401, 56417, 56431, + 56437, 56443, 56453, 56467, 56473, 56477, 56479, 56489, 56501, 56503, + 56509, 56519, 56527, 56531, 56533, 56543, 56569, 56591, 56597, 56599, + 56611, 56629, 56633, 56659, 56663, 56671, 56681, 56687, 56701, 56711, + 56713, 56731, 56737, 56747, 56767, 56773, 56779, 56783, 56807, 56809, + 56813, 56821, 56827, 56843, 56857, 56873, 56891, 56893, 56897, 56909, + 56911, 56921, 56923, 56929, 56941, 56951, 56957, 56963, 56983, 56989, + 56993, 56999, 57037, 57041, 57047, 57059, 57073, 57077, 57089, 57097, + 57107, 57119, 57131, 57139, 57143, 57149, 57163, 57173, 57179, 57191, + 57193, 57203, 57221, 57223, 57241, 57251, 57259, 57269, 57271, 57283, + 57287, 57301, 57329, 57331, 57347, 57349, 57367, 57373, 57383, 57389, + 57397, 57413, 57427, 57457, 57467, 57487, 57493, 57503, 57527, 57529, + 57557, 57559, 57571, 57587, 57593, 57601, 57637, 57641, 57649, 57653, + 57667, 57679, 57689, 57697, 57709, 57713, 57719, 57727, 57731, 57737, + 57751, 57773, 57781, 57787, 57791, 57793, 57803, 57809, 57829, 57839, + 57847, 57853, 57859, 57881, 57899, 57901, 57917, 57923, 57943, 57947, + 57973, 57977, 57991, 58013, 58027, 58031, 58043, 58049, 58057, 58061, + 58067, 58073, 58099, 58109, 58111, 58129, 58147, 58151, 58153, 58169, + 58171, 58189, 58193, 58199, 58207, 58211, 58217, 58229, 58231, 58237, + 58243, 58271, 58309, 58313, 58321, 58337, 58363, 58367, 58369, 58379, + 58391, 58393, 58403, 58411, 58417, 58427, 58439, 58441, 58451, 58453, + 58477, 58481, 58511, 58537, 58543, 58549, 58567, 58573, 58579, 58601, + 58603, 58613, 58631, 58657, 58661, 58679, 58687, 58693, 58699, 58711, + 58727, 58733, 58741, 58757, 58763, 58771, 58787, 58789, 58831, 58889, + 58897, 58901, 58907, 58909, 58913, 58921, 58937, 58943, 58963, 58967, + 58979, 58991, 58997, 59009, 59011, 59021, 59023, 59029, 59051, 59053, + 59063, 59069, 59077, 59083, 59093, 59107, 59113, 59119, 59123, 59141, + 59149, 59159, 59167, 59183, 59197, 59207, 59209, 59219, 59221, 59233, + 59239, 59243, 59263, 59273, 59281, 59333, 59341, 59351, 59357, 59359, + 59369, 59377, 59387, 59393, 59399, 59407, 59417, 59419, 59441, 59443, + 59447, 59453, 59467, 59471, 59473, 59497, 59509, 59513, 59539, 59557, + 59561, 59567, 59581, 59611, 59617, 59621, 59627, 59629, 59651, 59659, + 59663, 59669, 59671, 59693, 59699, 59707, 59723, 59729, 59743, 59747, + 59753, 59771, 59779, 59791, 59797, 59809, 59833, 59863, 59879, 59887, + 59921, 59929, 59951, 59957, 59971, 59981, 59999, 60013, 60017, 60029, + 60037, 60041, 60077, 60083, 60089, 60091, 60101, 60103, 60107, 60127, + 60133, 60139, 60149, 60161, 60167, 60169, 60209, 60217, 60223, 60251, + 60257, 60259, 60271, 60289, 60293, 60317, 60331, 60337, 60343, 60353, + 60373, 60383, 60397, 60413, 60427, 60443, 60449, 60457, 60493, 60497, + 60509, 60521, 60527, 60539, 60589, 60601, 60607, 60611, 60617, 60623, + 60631, 60637, 60647, 60649, 60659, 60661, 60679, 60689, 60703, 60719, + 60727, 60733, 60737, 60757, 60761, 60763, 60773, 60779, 60793, 60811, + 60821, 60859, 60869, 60887, 60889, 60899, 60901, 60913, 60917, 60919, + 60923, 60937, 60943, 60953, 60961, 61001, 61007, 61027, 61031, 61043, + 61051, 61057, 61091, 61099, 61121, 61129, 61141, 61151, 61153, 61169, + 61211, 61223, 61231, 61253, 61261, 61283, 61291, 61297, 61331, 61333, + 61339, 61343, 61357, 61363, 61379, 61381, 61403, 61409, 61417, 61441, + 61463, 61469, 61471, 61483, 61487, 61493, 61507, 61511, 61519, 61543, + 61547, 61553, 61559, 61561, 61583, 61603, 61609, 61613, 61627, 61631, + 61637, 61643, 61651, 61657, 61667, 61673, 61681, 61687, 61703, 61717, + 61723, 61729, 61751, 61757, 61781, 61813, 61819, 61837, 61843, 61861, + 61871, 61879, 61909, 61927, 61933, 61949, 61961, 61967, 61979, 61981, + 61987, 61991, 62003, 62011, 62017, 62039, 62047, 62053, 62057, 62071, + 62081, 62099, 62119, 62129, 62131, 62137, 62141, 62143, 62171, 62189, + 62191, 62201, 62207, 62213, 62219, 62233, 62273, 62297, 62299, 62303, + 62311, 62323, 62327, 62347, 62351, 62383, 62401, 62417, 62423, 62459, + 62467, 62473, 62477, 62483, 62497, 62501, 62507, 62533, 62539, 62549, + 62563, 62581, 62591, 62597, 62603, 62617, 62627, 62633, 62639, 62653, + 62659, 62683, 62687, 62701, 62723, 62731, 62743, 62753, 62761, 62773, + 62791, 62801, 62819, 62827, 62851, 62861, 62869, 62873, 62897, 62903, + 62921, 62927, 62929, 62939, 62969, 62971, 62981, 62983, 62987, 62989, + 63029, 63031, 63059, 63067, 63073, 63079, 63097, 63103, 63113, 63127, + 63131, 63149, 63179, 63197, 63199, 63211, 63241, 63247, 63277, 63281, + 63299, 63311, 63313, 63317, 63331, 63337, 63347, 63353, 63361, 63367, + 63377, 63389, 63391, 63397, 63409, 63419, 63421, 63439, 63443, 63463, + 63467, 63473, 63487, 63493, 63499, 63521, 63527, 63533, 63541, 63559, + 63577, 63587, 63589, 63599, 63601, 63607, 63611, 63617, 63629, 63647, + 63649, 63659, 63667, 63671, 63689, 63691, 63697, 63703, 63709, 63719, + 63727, 63737, 63743, 63761, 63773, 63781, 63793, 63799, 63803, 63809, + 63823, 63839, 63841, 63853, 63857, 63863, 63901, 63907, 63913, 63929, + 63949, 63977, 63997, 64007, 64013, 64019, 64033, 64037, 64063, 64067, + 64081, 64091, 64109, 64123, 64151, 64153, 64157, 64171, 64187, 64189, + 64217, 64223, 64231, 64237, 64271, 64279, 64283, 64301, 64303, 64319, + 64327, 64333, 64373, 64381, 64399, 64403, 64433, 64439, 64451, 64453, + 64483, 64489, 64499, 64513, 64553, 64567, 64577, 64579, 64591, 64601, + 64609, 64613, 64621, 64627, 64633, 64661, 64663, 64667, 64679, 64693, + 64709, 64717, 64747, 64763, 64781, 64783, 64793, 64811, 64817, 64849, + 64853, 64871, 64877, 64879, 64891, 64901, 64919, 64921, 64927, 64937, + 64951, 64969, 64997, 65003, 65011, 65027, 65029, 65033, 65053, 65063, + 65071, 65089, 65099, 65101, 65111, 65119, 65123, 65129, 65141, 65147, + 65167, 65171, 65173, 65179, 65183, 65203, 65213, 65239, 65257, 65267, + 65269, 65287, 65293, 65309, 65323, 65327, 65353, 65357, 65371, 65381, + 65393, 65407, 65413, 65419, 65423, 65437, 65447, 65449, 65479, 65497, + 65519, 65521, 65537, 65539, 65543, 65551, 65557, 65563, 65579, 65581, + 65587, 65599, 65609, 65617, 65629, 65633, 65647, 65651, 65657, 65677, + 65687, 65699, 65701, 65707, 65713, 65717, 65719, 65729, 65731, 65761, + 65777, 65789, 65809, 65827, 65831, 65837, 65839, 65843, 65851, 65867, + 65881, 65899, 65921, 65927, 65929, 65951, 65957, 65963, 65981, 65983, + 65993, 66029, 66037, 66041, 66047, 66067, 66071, 66083, 66089, 66103, + 66107, 66109, 66137, 66161, 66169, 66173, 66179, 66191, 66221, 66239, + 66271, 66293, 66301, 66337, 66343, 66347, 66359, 66361, 66373, 66377, + 66383, 66403, 66413, 66431, 66449, 66457, 66463, 66467, 66491, 66499, + 66509, 66523, 66529, 66533, 66541, 66553, 66569, 66571, 66587, 66593, + 66601, 66617, 66629, 66643, 66653, 66683, 66697, 66701, 66713, 66721, + 66733, 66739, 66749, 66751, 66763, 66791, 66797, 66809, 66821, 66841, + 66851, 66853, 66863, 66877, 66883, 66889, 66919, 66923, 66931, 66943, + 66947, 66949, 66959, 66973, 66977, 67003, 67021, 67033, 67043, 67049, + 67057, 67061, 67073, 67079, 67103, 67121, 67129, 67139, 67141, 67153, + 67157, 67169, 67181, 67187, 67189, 67211, 67213, 67217, 67219, 67231, + 67247, 67261, 67271, 67273, 67289, 67307, 67339, 67343, 67349, 67369, + 67391, 67399, 67409, 67411, 67421, 67427, 67429, 67433, 67447, 67453, + 67477, 67481, 67489, 67493, 67499, 67511, 67523, 67531, 67537, 67547, + 67559, 67567, 67577, 67579, 67589, 67601, 67607, 67619, 67631, 67651, + 67679, 67699, 67709, 67723, 67733, 67741, 67751, 67757, 67759, 67763, + 67777, 67783, 67789, 67801, 67807, 67819, 67829, 67843, 67853, 67867, + 67883, 67891, 67901, 67927, 67931, 67933, 67939, 67943, 67957, 67961, + 67967, 67979, 67987, 67993, 68023, 68041, 68053, 68059, 68071, 68087, + 68099, 68111, 68113, 68141, 68147, 68161, 68171, 68207, 68209, 68213, + 68219, 68227, 68239, 68261, 68279, 68281, 68311, 68329, 68351, 68371, + 68389, 68399, 68437, 68443, 68447, 68449, 68473, 68477, 68483, 68489, + 68491, 68501, 68507, 68521, 68531, 68539, 68543, 68567, 68581, 68597, + 68611, 68633, 68639, 68659, 68669, 68683, 68687, 68699, 68711, 68713, + 68729, 68737, 68743, 68749, 68767, 68771, 68777, 68791, 68813, 68819, + 68821, 68863, 68879, 68881, 68891, 68897, 68899, 68903, 68909, 68917, + 68927, 68947, 68963, 68993, 69001, 69011, 69019, 69029, 69031, 69061, + 69067, 69073, 69109, 69119, 69127, 69143, 69149, 69151, 69163, 69191, + 69193, 69197, 69203, 69221, 69233, 69239, 69247, 69257, 69259, 69263, + 69313, 69317, 69337, 69341, 69371, 69379, 69383, 69389, 69401, 69403, + 69427, 69431, 69439, 69457, 69463, 69467, 69473, 69481, 69491, 69493, + 69497, 69499, 69539, 69557, 69593, 69623, 69653, 69661, 69677, 69691, + 69697, 69709, 69737, 69739, 69761, 69763, 69767, 69779, 69809, 69821, + 69827, 69829, 69833, 69847, 69857, 69859, 69877, 69899, 69911, 69929, + 69931, 69941, 69959, 69991, 69997, 70001, 70003, 70009, 70019, 70039, + 70051, 70061, 70067, 70079, 70099, 70111, 70117, 70121, 70123, 70139, + 70141, 70157, 70163, 70177, 70181, 70183, 70199, 70201, 70207, 70223, + 70229, 70237, 70241, 70249, 70271, 70289, 70297, 70309, 70313, 70321, + 70327, 70351, 70373, 70379, 70381, 70393, 70423, 70429, 70439, 70451, + 70457, 70459, 70481, 70487, 70489, 70501, 70507, 70529, 70537, 70549, + 70571, 70573, 70583, 70589, 70607, 70619, 70621, 70627, 70639, 70657, + 70663, 70667, 70687, 70709, 70717, 70729, 70753, 70769, 70783, 70793, + 70823, 70841, 70843, 70849, 70853, 70867, 70877, 70879, 70891, 70901, + 70913, 70919, 70921, 70937, 70949, 70951, 70957, 70969, 70979, 70981, + 70991, 70997, 70999, 71011, 71023, 71039, 71059, 71069, 71081, 71089, + 71119, 71129, 71143, 71147, 71153, 71161, 71167, 71171, 71191, 71209, + 71233, 71237, 71249, 71257, 71261, 71263, 71287, 71293, 71317, 71327, + 71329, 71333, 71339, 71341, 71347, 71353, 71359, 71363, 71387, 71389, + 71399, 71411, 71413, 71419, 71429, 71437, 71443, 71453, 71471, 71473, + 71479, 71483, 71503, 71527, 71537, 71549, 71551, 71563, 71569, 71593, + 71597, 71633, 71647, 71663, 71671, 71693, 71699, 71707, 71711, 71713, + 71719, 71741, 71761, 71777, 71789, 71807, 71809, 71821, 71837, 71843, + 71849, 71861, 71867, 71879, 71881, 71887, 71899, 71909, 71917, 71933, + 71941, 71947, 71963, 71971, 71983, 71987, 71993, 71999, 72019, 72031, + 72043, 72047, 72053, 72073, 72077, 72089, 72091, 72101, 72103, 72109, + 72139, 72161, 72167, 72169, 72173, 72211, 72221, 72223, 72227, 72229, + 72251, 72253, 72269, 72271, 72277, 72287, 72307, 72313, 72337, 72341, + 72353, 72367, 72379, 72383, 72421, 72431, 72461, 72467, 72469, 72481, + 72493, 72497, 72503, 72533, 72547, 72551, 72559, 72577, 72613, 72617, + 72623, 72643, 72647, 72649, 72661, 72671, 72673, 72679, 72689, 72701, + 72707, 72719, 72727, 72733, 72739, 72763, 72767, 72797, 72817, 72823, + 72859, 72869, 72871, 72883, 72889, 72893, 72901, 72907, 72911, 72923, + 72931, 72937, 72949, 72953, 72959, 72973, 72977, 72997, 73009, 73013, + 73019, 73037, 73039, 73043, 73061, 73063, 73079, 73091, 73121, 73127, + 73133, 73141, 73181, 73189, 73237, 73243, 73259, 73277, 73291, 73303, + 73309, 73327, 73331, 73351, 73361, 73363, 73369, 73379, 73387, 73417, + 73421, 73433, 73453, 73459, 73471, 73477, 73483, 73517, 73523, 73529, + 73547, 73553, 73561, 73571, 73583, 73589, 73597, 73607, 73609, 73613, + 73637, 73643, 73651, 73673, 73679, 73681, 73693, 73699, 73709, 73721, + 73727, 73751, 73757, 73771, 73783, 73819, 73823, 73847, 73849, 73859, + 73867, 73877, 73883, 73897, 73907, 73939, 73943, 73951, 73961, 73973, + 73999, 74017, 74021, 74027, 74047, 74051, 74071, 74077, 74093, 74099, + 74101, 74131, 74143, 74149, 74159, 74161, 74167, 74177, 74189, 74197, + 74201, 74203, 74209, 74219, 74231, 74257, 74279, 74287, 74293, 74297, + 74311, 74317, 74323, 74353, 74357, 74363, 74377, 74381, 74383, 74411, + 74413, 74419, 74441, 74449, 74453, 74471, 74489, 74507, 74509, 74521, + 74527, 74531, 74551, 74561, 74567, 74573, 74587, 74597, 74609, 74611, + 74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719, 74729, 74731, + 74747, 74759, 74761, 74771, 74779, 74797, 74821, 74827, 74831, 74843, + 74857, 74861, 74869, 74873, 74887, 74891, 74897, 74903, 74923, 74929, + 74933, 74941, 74959, 75011, 75013, 75017, 75029, 75037, 75041, 75079, + 75083, 75109, 75133, 75149, 75161, 75167, 75169, 75181, 75193, 75209, + 75211, 75217, 75223, 75227, 75239, 75253, 75269, 75277, 75289, 75307, + 75323, 75329, 75337, 75347, 75353, 75367, 75377, 75389, 75391, 75401, + 75403, 75407, 75431, 75437, 75479, 75503, 75511, 75521, 75527, 75533, + 75539, 75541, 75553, 75557, 75571, 75577, 75583, 75611, 75617, 75619, + 75629, 75641, 75653, 75659, 75679, 75683, 75689, 75703, 75707, 75709, + 75721, 75731, 75743, 75767, 75773, 75781, 75787, 75793, 75797, 75821, + 75833, 75853, 75869, 75883, 75913, 75931, 75937, 75941, 75967, 75979, + 75983, 75989, 75991, 75997, 76001, 76003, 76031, 76039, 76079, 76081, + 76091, 76099, 76103, 76123, 76129, 76147, 76157, 76159, 76163, 76207, + 76213, 76231, 76243, 76249, 76253, 76259, 76261, 76283, 76289, 76303, + 76333, 76343, 76367, 76369, 76379, 76387, 76403, 76421, 76423, 76441, + 76463, 76471, 76481, 76487, 76493, 76507, 76511, 76519, 76537, 76541, + 76543, 76561, 76579, 76597, 76603, 76607, 76631, 76649, 76651, 76667, + 76673, 76679, 76697, 76717, 76733, 76753, 76757, 76771, 76777, 76781, + 76801, 76819, 76829, 76831, 76837, 76847, 76871, 76873, 76883, 76907, + 76913, 76919, 76943, 76949, 76961, 76963, 76991, 77003, 77017, 77023, + 77029, 77041, 77047, 77069, 77081, 77093, 77101, 77137, 77141, 77153, + 77167, 77171, 77191, 77201, 77213, 77237, 77239, 77243, 77249, 77261, + 77263, 77267, 77269, 77279, 77291, 77317, 77323, 77339, 77347, 77351, + 77359, 77369, 77377, 77383, 77417, 77419, 77431, 77447, 77471, 77477, + 77479, 77489, 77491, 77509, 77513, 77521, 77527, 77543, 77549, 77551, + 77557, 77563, 77569, 77573, 77587, 77591, 77611, 77617, 77621, 77641, + 77647, 77659, 77681, 77687, 77689, 77699, 77711, 77713, 77719, 77723, + 77731, 77743, 77747, 77761, 77773, 77783, 77797, 77801, 77813, 77839, + 77849, 77863, 77867, 77893, 77899, 77929, 77933, 77951, 77969, 77977, + 77983, 77999, 78007, 78017, 78031, 78041, 78049, 78059, 78079, 78101, + 78121, 78137, 78139, 78157, 78163, 78167, 78173, 78179, 78191, 78193, + 78203, 78229, 78233, 78241, 78259, 78277, 78283, 78301, 78307, 78311, + 78317, 78341, 78347, 78367, 78401, 78427, 78437, 78439, 78467, 78479, + 78487, 78497, 78509, 78511, 78517, 78539, 78541, 78553, 78569, 78571, + 78577, 78583, 78593, 78607, 78623, 78643, 78649, 78653, 78691, 78697, + 78707, 78713, 78721, 78737, 78779, 78781, 78787, 78791, 78797, 78803, + 78809, 78823, 78839, 78853, 78857, 78877, 78887, 78889, 78893, 78901, + 78919, 78929, 78941, 78977, 78979, 78989, 79031, 79039, 79043, 79063, + 79087, 79103, 79111, 79133, 79139, 79147, 79151, 79153, 79159, 79181, + 79187, 79193, 79201, 79229, 79231, 79241, 79259, 79273, 79279, 79283, + 79301, 79309, 79319, 79333, 79337, 79349, 79357, 79367, 79379, 79393, + 79397, 79399, 79411, 79423, 79427, 79433, 79451, 79481, 79493, 79531, + 79537, 79549, 79559, 79561, 79579, 79589, 79601, 79609, 79613, 79621, + 79627, 79631, 79633, 79657, 79669, 79687, 79691, 79693, 79697, 79699, + 79757, 79769, 79777, 79801, 79811, 79813, 79817, 79823, 79829, 79841, + 79843, 79847, 79861, 79867, 79873, 79889, 79901, 79903, 79907, 79939, + 79943, 79967, 79973, 79979, 79987, 79997, 79999, 80021, 80039, 80051, + 80071, 80077, 80107, 80111, 80141, 80147, 80149, 80153, 80167, 80173, + 80177, 80191, 80207, 80209, 80221, 80231, 80233, 80239, 80251, 80263, + 80273, 80279, 80287, 80309, 80317, 80329, 80341, 80347, 80363, 80369, + 80387, 80407, 80429, 80447, 80449, 80471, 80473, 80489, 80491, 80513, + 80527, 80537, 80557, 80567, 80599, 80603, 80611, 80621, 80627, 80629, + 80651, 80657, 80669, 80671, 80677, 80681, 80683, 80687, 80701, 80713, + 80737, 80747, 80749, 80761, 80777, 80779, 80783, 80789, 80803, 80809, + 80819, 80831, 80833, 80849, 80863, 80897, 80909, 80911, 80917, 80923, + 80929, 80933, 80953, 80963, 80989, 81001, 81013, 81017, 81019, 81023, + 81031, 81041, 81043, 81047, 81049, 81071, 81077, 81083, 81097, 81101, + 81119, 81131, 81157, 81163, 81173, 81181, 81197, 81199, 81203, 81223, + 81233, 81239, 81281, 81283, 81293, 81299, 81307, 81331, 81343, 81349, + 81353, 81359, 81371, 81373, 81401, 81409, 81421, 81439, 81457, 81463, + 81509, 81517, 81527, 81533, 81547, 81551, 81553, 81559, 81563, 81569, + 81611, 81619, 81629, 81637, 81647, 81649, 81667, 81671, 81677, 81689, + 81701, 81703, 81707, 81727, 81737, 81749, 81761, 81769, 81773, 81799, + 81817, 81839, 81847, 81853, 81869, 81883, 81899, 81901, 81919, 81929, + 81931, 81937, 81943, 81953, 81967, 81971, 81973, 82003, 82007, 82009, + 82013, 82021, 82031, 82037, 82039, 82051, 82067, 82073, 82129, 82139, + 82141, 82153, 82163, 82171, 82183, 82189, 82193, 82207, 82217, 82219, + 82223, 82231, 82237, 82241, 82261, 82267, 82279, 82301, 82307, 82339, + 82349, 82351, 82361, 82373, 82387, 82393, 82421, 82457, 82463, 82469, + 82471, 82483, 82487, 82493, 82499, 82507, 82529, 82531, 82549, 82559, + 82561, 82567, 82571, 82591, 82601, 82609, 82613, 82619, 82633, 82651, + 82657, 82699, 82721, 82723, 82727, 82729, 82757, 82759, 82763, 82781, + 82787, 82793, 82799, 82811, 82813, 82837, 82847, 82883, 82889, 82891, + 82903, 82913, 82939, 82963, 82981, 82997, 83003, 83009, 83023, 83047, + 83059, 83063, 83071, 83077, 83089, 83093, 83101, 83117, 83137, 83177, + 83203, 83207, 83219, 83221, 83227, 83231, 83233, 83243, 83257, 83267, + 83269, 83273, 83299, 83311, 83339, 83341, 83357, 83383, 83389, 83399, + 83401, 83407, 83417, 83423, 83431, 83437, 83443, 83449, 83459, 83471, + 83477, 83497, 83537, 83557, 83561, 83563, 83579, 83591, 83597, 83609, + 83617, 83621, 83639, 83641, 83653, 83663, 83689, 83701, 83717, 83719, + 83737, 83761, 83773, 83777, 83791, 83813, 83833, 83843, 83857, 83869, + 83873, 83891, 83903, 83911, 83921, 83933, 83939, 83969, 83983, 83987, + 84011, 84017, 84047, 84053, 84059, 84061, 84067, 84089, 84121, 84127, + 84131, 84137, 84143, 84163, 84179, 84181, 84191, 84199, 84211, 84221, + 84223, 84229, 84239, 84247, 84263, 84299, 84307, 84313, 84317, 84319, + 84347, 84349, 84377, 84389, 84391, 84401, 84407, 84421, 84431, 84437, + 84443, 84449, 84457, 84463, 84467, 84481, 84499, 84503, 84509, 84521, + 84523, 84533, 84551, 84559, 84589, 84629, 84631, 84649, 84653, 84659, + 84673, 84691, 84697, 84701, 84713, 84719, 84731, 84737, 84751, 84761, + 84787, 84793, 84809, 84811, 84827, 84857, 84859, 84869, 84871, 84913, + 84919, 84947, 84961, 84967, 84977, 84979, 84991, 85009, 85021, 85027, + 85037, 85049, 85061, 85081, 85087, 85091, 85093, 85103, 85109, 85121, + 85133, 85147, 85159, 85193, 85199, 85201, 85213, 85223, 85229, 85237, + 85243, 85247, 85259, 85297, 85303, 85313, 85331, 85333, 85361, 85363, + 85369, 85381, 85411, 85427, 85429, 85439, 85447, 85451, 85453, 85469, + 85487, 85513, 85517, 85523, 85531, 85549, 85571, 85577, 85597, 85601, + 85607, 85619, 85621, 85627, 85639, 85643, 85661, 85667, 85669, 85691, + 85703, 85711, 85717, 85733, 85751, 85781, 85793, 85817, 85819, 85829, + 85831, 85837, 85843, 85847, 85853, 85889, 85903, 85909, 85931, 85933, + 85991, 85999, 86011, 86017, 86027, 86029, 86069, 86077, 86083, 86111, + 86113, 86117, 86131, 86137, 86143, 86161, 86171, 86179, 86183, 86197, + 86201, 86209, 86239, 86243, 86249, 86257, 86263, 86269, 86287, 86291, + 86293, 86297, 86311, 86323, 86341, 86351, 86353, 86357, 86369, 86371, + 86381, 86389, 86399, 86413, 86423, 86441, 86453, 86461, 86467, 86477, + 86491, 86501, 86509, 86531, 86533, 86539, 86561, 86573, 86579, 86587, + 86599, 86627, 86629, 86677, 86689, 86693, 86711, 86719, 86729, 86743, + 86753, 86767, 86771, 86783, 86813, 86837, 86843, 86851, 86857, 86861, + 86869, 86923, 86927, 86929, 86939, 86951, 86959, 86969, 86981, 86993, + 87011, 87013, 87037, 87041, 87049, 87071, 87083, 87103, 87107, 87119, + 87121, 87133, 87149, 87151, 87179, 87181, 87187, 87211, 87221, 87223, + 87251, 87253, 87257, 87277, 87281, 87293, 87299, 87313, 87317, 87323, + 87337, 87359, 87383, 87403, 87407, 87421, 87427, 87433, 87443, 87473, + 87481, 87491, 87509, 87511, 87517, 87523, 87539, 87541, 87547, 87553, + 87557, 87559, 87583, 87587, 87589, 87613, 87623, 87629, 87631, 87641, + 87643, 87649, 87671, 87679, 87683, 87691, 87697, 87701, 87719, 87721, + 87739, 87743, 87751, 87767, 87793, 87797, 87803, 87811, 87833, 87853, + 87869, 87877, 87881, 87887, 87911, 87917, 87931, 87943, 87959, 87961, + 87973, 87977, 87991, 88001, 88003, 88007, 88019, 88037, 88069, 88079, + 88093, 88117, 88129, 88169, 88177, 88211, 88223, 88237, 88241, 88259, + 88261, 88289, 88301, 88321, 88327, 88337, 88339, 88379, 88397, 88411, + 88423, 88427, 88463, 88469, 88471, 88493, 88499, 88513, 88523, 88547, + 88589, 88591, 88607, 88609, 88643, 88651, 88657, 88661, 88663, 88667, + 88681, 88721, 88729, 88741, 88747, 88771, 88789, 88793, 88799, 88801, + 88807, 88811, 88813, 88817, 88819, 88843, 88853, 88861, 88867, 88873, + 88883, 88897, 88903, 88919, 88937, 88951, 88969, 88993, 88997, 89003, + 89009, 89017, 89021, 89041, 89051, 89057, 89069, 89071, 89083, 89087, + 89101, 89107, 89113, 89119, 89123, 89137, 89153, 89189, 89203, 89209, + 89213, 89227, 89231, 89237, 89261, 89269, 89273, 89293, 89303, 89317, + 89329, 89363, 89371, 89381, 89387, 89393, 89399, 89413, 89417, 89431, + 89443, 89449, 89459, 89477, 89491, 89501, 89513, 89519, 89521, 89527, + 89533, 89561, 89563, 89567, 89591, 89597, 89599, 89603, 89611, 89627, + 89633, 89653, 89657, 89659, 89669, 89671, 89681, 89689, 89753, 89759, + 89767, 89779, 89783, 89797, 89809, 89819, 89821, 89833, 89839, 89849, + 89867, 89891, 89897, 89899, 89909, 89917, 89923, 89939, 89959, 89963, + 89977, 89983, 89989, 90001, 90007, 90011, 90017, 90019, 90023, 90031, + 90053, 90059, 90067, 90071, 90073, 90089, 90107, 90121, 90127, 90149, + 90163, 90173, 90187, 90191, 90197, 90199, 90203, 90217, 90227, 90239, + 90247, 90263, 90271, 90281, 90289, 90313, 90353, 90359, 90371, 90373, + 90379, 90397, 90401, 90403, 90407, 90437, 90439, 90469, 90473, 90481, + 90499, 90511, 90523, 90527, 90529, 90533, 90547, 90583, 90599, 90617, + 90619, 90631, 90641, 90647, 90659, 90677, 90679, 90697, 90703, 90709, + 90731, 90749, 90787, 90793, 90803, 90821, 90823, 90833, 90841, 90847, + 90863, 90887, 90901, 90907, 90911, 90917, 90931, 90947, 90971, 90977, + 90989, 90997, 91009, 91019, 91033, 91079, 91081, 91097, 91099, 91121, + 91127, 91129, 91139, 91141, 91151, 91153, 91159, 91163, 91183, 91193, + 91199, 91229, 91237, 91243, 91249, 91253, 91283, 91291, 91297, 91303, + 91309, 91331, 91367, 91369, 91373, 91381, 91387, 91393, 91397, 91411, + 91423, 91433, 91453, 91457, 91459, 91463, 91493, 91499, 91513, 91529, + 91541, 91571, 91573, 91577, 91583, 91591, 91621, 91631, 91639, 91673, + 91691, 91703, 91711, 91733, 91753, 91757, 91771, 91781, 91801, 91807, + 91811, 91813, 91823, 91837, 91841, 91867, 91873, 91909, 91921, 91939, + 91943, 91951, 91957, 91961, 91967, 91969, 91997, 92003, 92009, 92033, + 92041, 92051, 92077, 92083, 92107, 92111, 92119, 92143, 92153, 92173, + 92177, 92179, 92189, 92203, 92219, 92221, 92227, 92233, 92237, 92243, + 92251, 92269, 92297, 92311, 92317, 92333, 92347, 92353, 92357, 92363, + 92369, 92377, 92381, 92383, 92387, 92399, 92401, 92413, 92419, 92431, + 92459, 92461, 92467, 92479, 92489, 92503, 92507, 92551, 92557, 92567, + 92569, 92581, 92593, 92623, 92627, 92639, 92641, 92647, 92657, 92669, + 92671, 92681, 92683, 92693, 92699, 92707, 92717, 92723, 92737, 92753, + 92761, 92767, 92779, 92789, 92791, 92801, 92809, 92821, 92831, 92849, + 92857, 92861, 92863, 92867, 92893, 92899, 92921, 92927, 92941, 92951, + 92957, 92959, 92987, 92993, 93001, 93047, 93053, 93059, 93077, 93083, + 93089, 93097, 93103, 93113, 93131, 93133, 93139, 93151, 93169, 93179, + 93187, 93199, 93229, 93239, 93241, 93251, 93253, 93257, 93263, 93281, + 93283, 93287, 93307, 93319, 93323, 93329, 93337, 93371, 93377, 93383, + 93407, 93419, 93427, 93463, 93479, 93481, 93487, 93491, 93493, 93497, + 93503, 93523, 93529, 93553, 93557, 93559, 93563, 93581, 93601, 93607, + 93629, 93637, 93683, 93701, 93703, 93719, 93739, 93761, 93763, 93787, + 93809, 93811, 93827, 93851, 93871, 93887, 93889, 93893, 93901, 93911, + 93913, 93923, 93937, 93941, 93949, 93967, 93971, 93979, 93983, 93997, + 94007, 94009, 94033, 94049, 94057, 94063, 94079, 94099, 94109, 94111, + 94117, 94121, 94151, 94153, 94169, 94201, 94207, 94219, 94229, 94253, + 94261, 94273, 94291, 94307, 94309, 94321, 94327, 94331, 94343, 94349, + 94351, 94379, 94397, 94399, 94421, 94427, 94433, 94439, 94441, 94447, + 94463, 94477, 94483, 94513, 94529, 94531, 94541, 94543, 94547, 94559, + 94561, 94573, 94583, 94597, 94603, 94613, 94621, 94649, 94651, 94687, + 94693, 94709, 94723, 94727, 94747, 94771, 94777, 94781, 94789, 94793, + 94811, 94819, 94823, 94837, 94841, 94847, 94849, 94873, 94889, 94903, + 94907, 94933, 94949, 94951, 94961, 94993, 94999, 95003, 95009, 95021, + 95027, 95063, 95071, 95083, 95087, 95089, 95093, 95101, 95107, 95111, + 95131, 95143, 95153, 95177, 95189, 95191, 95203, 95213, 95219, 95231, + 95233, 95239, 95257, 95261, 95267, 95273, 95279, 95287, 95311, 95317, + 95327, 95339, 95369, 95383, 95393, 95401, 95413, 95419, 95429, 95441, + 95443, 95461, 95467, 95471, 95479, 95483, 95507, 95527, 95531, 95539, + 95549, 95561, 95569, 95581, 95597, 95603, 95617, 95621, 95629, 95633, + 95651, 95701, 95707, 95713, 95717, 95723, 95731, 95737, 95747, 95773, + 95783, 95789, 95791, 95801, 95803, 95813, 95819, 95857, 95869, 95873, + 95881, 95891, 95911, 95917, 95923, 95929, 95947, 95957, 95959, 95971, + 95987, 95989, 96001, 96013, 96017, 96043, 96053, 96059, 96079, 96097, + 96137, 96149, 96157, 96167, 96179, 96181, 96199, 96211, 96221, 96223, + 96233, 96259, 96263, 96269, 96281, 96289, 96293, 96323, 96329, 96331, + 96337, 96353, 96377, 96401, 96419, 96431, 96443, 96451, 96457, 96461, + 96469, 96479, 96487, 96493, 96497, 96517, 96527, 96553, 96557, 96581, + 96587, 96589, 96601, 96643, 96661, 96667, 96671, 96697, 96703, 96731, + 96737, 96739, 96749, 96757, 96763, 96769, 96779, 96787, 96797, 96799, + 96821, 96823, 96827, 96847, 96851, 96857, 96893, 96907, 96911, 96931, + 96953, 96959, 96973, 96979, 96989, 96997, 97001, 97003, 97007, 97021, + 97039, 97073, 97081, 97103, 97117, 97127, 97151, 97157, 97159, 97169, + 97171, 97177, 97187, 97213, 97231, 97241, 97259, 97283, 97301, 97303, + 97327, 97367, 97369, 97373, 97379, 97381, 97387, 97397, 97423, 97429, + 97441, 97453, 97459, 97463, 97499, 97501, 97511, 97523, 97547, 97549, + 97553, 97561, 97571, 97577, 97579, 97583, 97607, 97609, 97613, 97649, + 97651, 97673, 97687, 97711, 97729, 97771, 97777, 97787, 97789, 97813, + 97829, 97841, 97843, 97847, 97849, 97859, 97861, 97871, 97879, 97883, + 97919, 97927, 97931, 97943, 97961, 97967, 97973, 97987, 98009, 98011, + 98017, 98041, 98047, 98057, 98081, 98101, 98123, 98129, 98143, 98179, + 98207, 98213, 98221, 98227, 98251, 98257, 98269, 98297, 98299, 98317, + 98321, 98323, 98327, 98347, 98369, 98377, 98387, 98389, 98407, 98411, + 98419, 98429, 98443, 98453, 98459, 98467, 98473, 98479, 98491, 98507, + 98519, 98533, 98543, 98561, 98563, 98573, 98597, 98621, 98627, 98639, + 98641, 98663, 98669, 98689, 98711, 98713, 98717, 98729, 98731, 98737, + 98773, 98779, 98801, 98807, 98809, 98837, 98849, 98867, 98869, 98873, + 98887, 98893, 98897, 98899, 98909, 98911, 98927, 98929, 98939, 98947, + 98953, 98963, 98981, 98993, 98999, 99013, 99017, 99023, 99041, 99053, + 99079, 99083, 99089, 99103, 99109, 99119, 99131, 99133, 99137, 99139, + 99149, 99173, 99181, 99191, 99223, 99233, 99241, 99251, 99257, 99259, + 99277, 99289, 99317, 99347, 99349, 99367, 99371, 99377, 99391, 99397, + 99401, 99409, 99431, 99439, 99469, 99487, 99497, 99523, 99527, 99529, + 99551, 99559, 99563, 99571, 99577, 99581, 99607, 99611, 99623, 99643, + 99661, 99667, 99679, 99689, 99707, 99709, 99713, 99719, 99721, 99733, + 99761, 99767, 99787, 99793, 99809, 99817, 99823, 99829, 99833, 99839, + 99859, 99871, 99877, 99881, 99901, 99907, 99923, 99929, 99961, 99971, + 99989, 99991, 100003, 100019, 100043, 100049, 100057, 100069, 100103, 100109, +100129, 100151, 100153, 100169, 100183, 100189, 100193, 100207, 100213, 100237, +100267, 100271, 100279, 100291, 100297, 100313, 100333, 100343, 100357, 100361, +100363, 100379, 100391, 100393, 100403, 100411, 100417, 100447, 100459, 100469, +100483, 100493, 100501, 100511, 100517, 100519, 100523, 100537, 100547, 100549, +100559, 100591, 100609, 100613, 100621, 100649, 100669, 100673, 100693, 100699, +100703, 100733, 100741, 100747, 100769, 100787, 100799, 100801, 100811, 100823, +100829, 100847, 100853, 100907, 100913, 100927, 100931, 100937, 100943, 100957, +100981, 100987, 100999, 101009, 101021, 101027, 101051, 101063, 101081, 101089, +101107, 101111, 101113, 101117, 101119, 101141, 101149, 101159, 101161, 101173, +101183, 101197, 101203, 101207, 101209, 101221, 101267, 101273, 101279, 101281, +101287, 101293, 101323, 101333, 101341, 101347, 101359, 101363, 101377, 101383, +101399, 101411, 101419, 101429, 101449, 101467, 101477, 101483, 101489, 101501, +101503, 101513, 101527, 101531, 101533, 101537, 101561, 101573, 101581, 101599, +101603, 101611, 101627, 101641, 101653, 101663, 101681, 101693, 101701, 101719, +101723, 101737, 101741, 101747, 101749, 101771, 101789, 101797, 101807, 101833, +101837, 101839, 101863, 101869, 101873, 101879, 101891, 101917, 101921, 101929, +101939, 101957, 101963, 101977, 101987, 101999, 102001, 102013, 102019, 102023, +102031, 102043, 102059, 102061, 102071, 102077, 102079, 102101, 102103, 102107, +102121, 102139, 102149, 102161, 102181, 102191, 102197, 102199, 102203, 102217, +102229, 102233, 102241, 102251, 102253, 102259, 102293, 102299, 102301, 102317, +102329, 102337, 102359, 102367, 102397, 102407, 102409, 102433, 102437, 102451, +102461, 102481, 102497, 102499, 102503, 102523, 102533, 102539, 102547, 102551, +102559, 102563, 102587, 102593, 102607, 102611, 102643, 102647, 102653, 102667, +102673, 102677, 102679, 102701, 102761, 102763, 102769, 102793, 102797, 102811, +102829, 102841, 102859, 102871, 102877, 102881, 102911, 102913, 102929, 102931, +102953, 102967, 102983, 103001, 103007, 103043, 103049, 103067, 103069, 103079, +103087, 103091, 103093, 103099, 103123, 103141, 103171, 103177, 103183, 103217, +103231, 103237, 103289, 103291, 103307, 103319, 103333, 103349, 103357, 103387, +103391, 103393, 103399, 103409, 103421, 103423, 103451, 103457, 103471, 103483, +103511, 103529, 103549, 103553, 103561, 103567, 103573, 103577, 103583, 103591, +103613, 103619, 103643, 103651, 103657, 103669, 103681, 103687, 103699, 103703, +103723, 103769, 103787, 103801, 103811, 103813, 103837, 103841, 103843, 103867, +103889, 103903, 103913, 103919, 103951, 103963, 103967, 103969, 103979, 103981, +103991, 103993, 103997, 104003, 104009, 104021, 104033, 104047, 104053, 104059, +104087, 104089, 104107, 104113, 104119, 104123, 104147, 104149, 104161, 104173, +104179, 104183, 104207, 104231, 104233, 104239, 104243, 104281, 104287, 104297, +104309, 104311, 104323, 104327, 104347, 104369, 104381, 104383, 104393, 104399, +104417, 104459, 104471, 104473, 104479, 104491, 104513, 104527, 104537, 104543, +104549, 104551, 104561, 104579, 104593, 104597, 104623, 104639, 104651, 104659, +104677, 104681, 104683, 104693, 104701, 104707, 104711, 104717, 104723, 104729, +) diff --git a/lib/Crypto/Util/py21compat.py b/lib/Crypto/Util/py21compat.py new file mode 100644 index 0000000..624408b --- /dev/null +++ b/lib/Crypto/Util/py21compat.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# +# Util/py21compat.py : Compatibility code for Python 2.1 +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +"""Compatibility code for Python 2.1 + +Currently, this just defines: + - True and False + - object + - isinstance +""" + +__revision__ = "$Id$" +__all__ = [] + +import sys +import __builtin__ + +# 'True' and 'False' aren't defined in Python 2.1. Define them. +try: + True, False +except NameError: + (True, False) = (1, 0) + __all__ += ['True', 'False'] + +# New-style classes were introduced in Python 2.2. Defining "object" in Python +# 2.1 lets us use new-style classes in versions of Python that support them, +# while still maintaining backward compatibility with old-style classes +try: + object +except NameError: + class object: pass + __all__ += ['object'] + +# Starting with Python 2.2, isinstance allows a tuple for the second argument. +# Also, builtins like "tuple", "list", "str", "unicode", "int", and "long" +# became first-class types, rather than functions. We want to support +# constructs like: +# isinstance(x, (int, long)) +# So we hack it for Python 2.1. +try: + isinstance(5, (int, long)) +except TypeError: + __all__ += ['isinstance'] + _builtin_type_map = { + tuple: type(()), + list: type([]), + str: type(""), + unicode: type(u""), + int: type(0), + long: type(0L), + } + def isinstance(obj, t): + if not __builtin__.isinstance(t, type(())): + # t is not a tuple + return __builtin__.isinstance(obj, _builtin_type_map.get(t, t)) + else: + # t is a tuple + for typ in t: + if __builtin__.isinstance(obj, _builtin_type_map.get(typ, typ)): + return True + return False + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/Util/py3compat.py b/lib/Crypto/Util/py3compat.py new file mode 100644 index 0000000..34e5224 --- /dev/null +++ b/lib/Crypto/Util/py3compat.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +# +# Util/py3compat.py : Compatibility code for handling Py3k / Python 2.x +# +# Written in 2010 by Thorsten Behrens +# +# =================================================================== +# 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. +# =================================================================== + +"""Compatibility code for handling string/bytes changes from Python 2.x to Py3k + +In Python 2.x, strings (of type ''str'') contain binary data, including encoded +Unicode text (e.g. UTF-8). The separate type ''unicode'' holds Unicode text. +Unicode literals are specified via the u'...' prefix. Indexing or slicing +either type always produces a string of the same type as the original. +Data read from a file is always of '''str'' type. + +In Python 3.x, strings (type ''str'') may only contain Unicode text. The u'...' +prefix and the ''unicode'' type are now redundant. A new type (called +''bytes'') has to be used for binary data (including any particular +''encoding'' of a string). The b'...' prefix allows one to specify a binary +literal. Indexing or slicing a string produces another string. Slicing a byte +string produces another byte string, but the indexing operation produces an +integer. Data read from a file is of '''str'' type if the file was opened in +text mode, or of ''bytes'' type otherwise. + +Since PyCrypto aims at supporting both Python 2.x and 3.x, the following helper +functions are used to keep the rest of the library as independent as possible +from the actual Python version. + +In general, the code should always deal with binary strings, and use integers +instead of 1-byte character strings. + +b(s) + Take a text string literal (with no prefix or with u'...' prefix) and + make a byte string. +bchr(c) + Take an integer and make a 1-character byte string. +bord(c) + Take the result of indexing on a byte string and make an integer. +tobytes(s) + Take a text string, a byte string, or a sequence of character taken from + a byte string and make a byte string. +""" + +__revision__ = "$Id$" + +import sys + +if sys.version_info[0] == 2: + def b(s): + return s + def bchr(s): + return chr(s) + def bstr(s): + return str(s) + def bord(s): + return ord(s) + if sys.version_info[1] == 1: + def tobytes(s): + try: + return s.encode('latin-1') + except: + return ''.join(s) + else: + def tobytes(s): + if isinstance(s, unicode): + return s.encode("latin-1") + else: + return ''.join(s) +else: + def b(s): + return s.encode("latin-1") # utf-8 would cause some side-effects we don't want + def bchr(s): + return bytes([s]) + def bstr(s): + if isinstance(s,str): + return bytes(s,"latin-1") + else: + return bytes(s) + def bord(s): + return s + def tobytes(s): + if isinstance(s,bytes): + return s + else: + if isinstance(s,str): + return s.encode("latin-1") + else: + return bytes(s) + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/Util/randpool.py b/lib/Crypto/Util/randpool.py new file mode 100644 index 0000000..8b5a0b7 --- /dev/null +++ b/lib/Crypto/Util/randpool.py @@ -0,0 +1,82 @@ +# +# randpool.py : Cryptographically strong random number generation +# +# Part of the Python Cryptography Toolkit +# +# Written by Andrew M. Kuchling, Mark Moraes, and others +# +# =================================================================== +# 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. +# =================================================================== +# + +__revision__ = "$Id$" + +from Crypto.pct_warnings import RandomPool_DeprecationWarning +import Crypto.Random +import warnings + +class RandomPool: + """Deprecated. Use Random.new() instead. + + See http://www.pycrypto.org/randpool-broken + """ + def __init__(self, numbytes = 160, cipher=None, hash=None, file=None): + warnings.warn("This application uses RandomPool, which is BROKEN in older releases. See http://www.pycrypto.org/randpool-broken", + RandomPool_DeprecationWarning) + self.__rng = Crypto.Random.new() + self.bytes = numbytes + self.bits = self.bytes * 8 + self.entropy = self.bits + + def get_bytes(self, N): + return self.__rng.read(N) + + def _updateEntropyEstimate(self, nbits): + self.entropy += nbits + if self.entropy < 0: + self.entropy = 0 + elif self.entropy > self.bits: + self.entropy = self.bits + + def _randomize(self, N=0, devname="/dev/urandom"): + """Dummy _randomize() function""" + self.__rng.flush() + + def randomize(self, N=0): + """Dummy randomize() function""" + self.__rng.flush() + + def stir(self, s=''): + """Dummy stir() function""" + self.__rng.flush() + + def stir_n(self, N=3): + """Dummy stir_n() function""" + self.__rng.flush() + + def add_event(self, s=''): + """Dummy add_event() function""" + self.__rng.flush() + + def getBytes(self, N): + """Dummy getBytes() function""" + return self.get_bytes(N) + + def addEvent(self, event, s=""): + """Dummy addEvent() function""" + return self.add_event() diff --git a/lib/Crypto/Util/strxor.so b/lib/Crypto/Util/strxor.so new file mode 100755 index 0000000000000000000000000000000000000000..1c8fb2fce3c4db15357398766566e5bb8118cc53 GIT binary patch literal 25724 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 N=VeA9!QUBL@c-TKApQUV literal 0 HcmV?d00001 diff --git a/lib/Crypto/Util/winrandom.py b/lib/Crypto/Util/winrandom.py new file mode 100644 index 0000000..0242815 --- /dev/null +++ b/lib/Crypto/Util/winrandom.py @@ -0,0 +1,28 @@ +# +# Util/winrandom.py : Stub for Crypto.Random.OSRNG.winrandom +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +__revision__ = "$Id$" + +from Crypto.Random.OSRNG.winrandom import * + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/Crypto/__init__.py b/lib/Crypto/__init__.py new file mode 100644 index 0000000..cea887c --- /dev/null +++ b/lib/Crypto/__init__.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# +# =================================================================== +# 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. +# =================================================================== + +"""Python Cryptography Toolkit + +A collection of cryptographic modules implementing various algorithms +and protocols. + +Subpackages: + +Crypto.Cipher + Secret-key (AES, DES, ARC4) and public-key encryption (RSA PKCS#1) algorithms +Crypto.Hash + Hashing algorithms (MD5, SHA, HMAC) +Crypto.Protocol + Cryptographic protocols (Chaffing, all-or-nothing transform, key derivation + functions). This package does not contain any network protocols. +Crypto.PublicKey + Public-key encryption and signature algorithms (RSA, DSA) +Crypto.Signature + Public-key signature algorithms (RSA PKCS#1) +Crypto.Util + Various useful modules and functions (long-to-string conversion, random number + generation, number theoretic functions) +""" + +__all__ = ['Cipher', 'Hash', 'Protocol', 'PublicKey', 'Util', 'Signature'] + +__version__ = '2.6' # See also below and setup.py +__revision__ = "$Id$" + +# New software should look at this instead of at __version__ above. +version_info = (2, 6, 0, 'final', 0) # See also above and setup.py + diff --git a/lib/Crypto/pct_warnings.py b/lib/Crypto/pct_warnings.py new file mode 100644 index 0000000..9b4361e --- /dev/null +++ b/lib/Crypto/pct_warnings.py @@ -0,0 +1,60 @@ +# -*- coding: ascii -*- +# +# pct_warnings.py : PyCrypto warnings file +# +# Written in 2008 by Dwayne C. Litzenberger +# +# =================================================================== +# 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. +# =================================================================== + +# +# Base classes. All our warnings inherit from one of these in order to allow +# the user to specifically filter them. +# + +class CryptoWarning(Warning): + """Base class for PyCrypto warnings""" + +class CryptoDeprecationWarning(DeprecationWarning, CryptoWarning): + """Base PyCrypto DeprecationWarning class""" + +class CryptoRuntimeWarning(RuntimeWarning, CryptoWarning): + """Base PyCrypto RuntimeWarning class""" + +# +# Warnings that we might actually use +# + +class RandomPool_DeprecationWarning(CryptoDeprecationWarning): + """Issued when Crypto.Util.randpool.RandomPool is instantiated.""" + +class ClockRewindWarning(CryptoRuntimeWarning): + """Warning for when the system clock moves backwards.""" + +class GetRandomNumber_DeprecationWarning(CryptoDeprecationWarning): + """Issued when Crypto.Util.number.getRandomNumber is invoked.""" + +class PowmInsecureWarning(CryptoRuntimeWarning): + """Warning for when _fastmath is built without mpz_powm_sec""" + +# By default, we want this warning to be shown every time we compensate for +# clock rewinding. +import warnings as _warnings +_warnings.filterwarnings('always', category=ClockRewindWarning, append=1) + +# vim:set ts=4 sw=4 sts=4 expandtab: diff --git a/lib/paramiko/__init__.py b/lib/paramiko/__init__.py new file mode 100644 index 0000000..96b5943 --- /dev/null +++ b/lib/paramiko/__init__.py @@ -0,0 +1,141 @@ +# Copyright (C) 2003-2011 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +I{Paramiko} (a combination of the esperanto words for "paranoid" and "friend") +is a module for python 2.3 or greater that implements the SSH2 protocol for +secure (encrypted and authenticated) connections to remote machines. Unlike +SSL (aka TLS), the SSH2 protocol does not require hierarchical certificates +signed by a powerful central authority. You may know SSH2 as the protocol that +replaced C{telnet} and C{rsh} for secure access to remote shells, but the +protocol also includes the ability to open arbitrary channels to remote +services across an encrypted tunnel. (This is how C{sftp} works, for example.) + +The high-level client API starts with creation of an L{SSHClient} object. +For more direct control, pass a socket (or socket-like object) to a +L{Transport}, and use L{start_server } or +L{start_client } to negoatite +with the remote host as either a server or client. As a client, you are +responsible for authenticating using a password or private key, and checking +the server's host key. I{(Key signature and verification is done by paramiko, +but you will need to provide private keys and check that the content of a +public key matches what you expected to see.)} As a server, you are +responsible for deciding which users, passwords, and keys to allow, and what +kind of channels to allow. + +Once you have finished, either side may request flow-controlled L{Channel}s to +the other side, which are python objects that act like sockets, but send and +receive data over the encrypted session. + +Paramiko is written entirely in python (no C or platform-dependent code) and is +released under the GNU Lesser General Public License (LGPL). + +Website: U{http://www.lag.net/paramiko/} + +@version: 1.7.7.1 (George) +@author: Robey Pointer +@contact: robeypointer@gmail.com +@license: GNU Lesser General Public License (LGPL) +""" + +import sys + +if sys.version_info < (2, 2): + raise RuntimeError('You need python 2.2 for this module.') + + +__author__ = "Robey Pointer " +__date__ = "21 May 2011" +__version__ = "1.7.7.1 (George)" +__version_info__ = (1, 7, 7, 1) +__license__ = "GNU Lesser General Public License (LGPL)" + + +from transport import SecurityOptions, Transport +from client import SSHClient, MissingHostKeyPolicy, AutoAddPolicy, RejectPolicy, WarningPolicy +from auth_handler import AuthHandler +from channel import Channel, ChannelFile +from ssh_exception import SSHException, PasswordRequiredException, \ + BadAuthenticationType, ChannelException, BadHostKeyException, \ + AuthenticationException +from server import ServerInterface, SubsystemHandler, InteractiveQuery +from rsakey import RSAKey +from dsskey import DSSKey +from sftp import SFTPError, BaseSFTP +from sftp_client import SFTP, SFTPClient +from sftp_server import SFTPServer +from sftp_attr import SFTPAttributes +from sftp_handle import SFTPHandle +from sftp_si import SFTPServerInterface +from sftp_file import SFTPFile +from message import Message +from packet import Packetizer +from file import BufferedFile +from agent import Agent, AgentKey +from pkey import PKey +from hostkeys import HostKeys +from config import SSHConfig + +# fix module names for epydoc +for c in locals().values(): + if issubclass(type(c), type) or type(c).__name__ == 'classobj': + # classobj for exceptions :/ + c.__module__ = __name__ +del c + +from common import AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED, \ + OPEN_SUCCEEDED, OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, OPEN_FAILED_CONNECT_FAILED, \ + OPEN_FAILED_UNKNOWN_CHANNEL_TYPE, OPEN_FAILED_RESOURCE_SHORTAGE + +from sftp import SFTP_OK, SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED, SFTP_FAILURE, \ + SFTP_BAD_MESSAGE, SFTP_NO_CONNECTION, SFTP_CONNECTION_LOST, SFTP_OP_UNSUPPORTED + +__all__ = [ 'Transport', + 'SSHClient', + 'MissingHostKeyPolicy', + 'AutoAddPolicy', + 'RejectPolicy', + 'WarningPolicy', + 'SecurityOptions', + 'SubsystemHandler', + 'Channel', + 'PKey', + 'RSAKey', + 'DSSKey', + 'Message', + 'SSHException', + 'AuthenticationException', + 'PasswordRequiredException', + 'BadAuthenticationType', + 'ChannelException', + 'BadHostKeyException', + 'SFTP', + 'SFTPFile', + 'SFTPHandle', + 'SFTPClient', + 'SFTPServer', + 'SFTPError', + 'SFTPAttributes', + 'SFTPServerInterface', + 'ServerInterface', + 'BufferedFile', + 'Agent', + 'AgentKey', + 'HostKeys', + 'SSHConfig', + 'util' ] diff --git a/lib/paramiko/agent.py b/lib/paramiko/agent.py new file mode 100644 index 0000000..3bb9426 --- /dev/null +++ b/lib/paramiko/agent.py @@ -0,0 +1,153 @@ +# Copyright (C) 2003-2007 John Rochester +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +SSH Agent interface for Unix clients. +""" + +import os +import socket +import struct +import sys + +from paramiko.ssh_exception import SSHException +from paramiko.message import Message +from paramiko.pkey import PKey + + +SSH2_AGENTC_REQUEST_IDENTITIES, SSH2_AGENT_IDENTITIES_ANSWER, \ + SSH2_AGENTC_SIGN_REQUEST, SSH2_AGENT_SIGN_RESPONSE = range(11, 15) + + +class Agent: + """ + Client interface for using private keys from an SSH agent running on the + local machine. If an SSH agent is running, this class can be used to + connect to it and retreive L{PKey} objects which can be used when + attempting to authenticate to remote SSH servers. + + Because the SSH agent protocol uses environment variables and unix-domain + sockets, this probably doesn't work on Windows. It does work on most + posix platforms though (Linux and MacOS X, for example). + """ + + def __init__(self): + """ + Open a session with the local machine's SSH agent, if one is running. + If no agent is running, initialization will succeed, but L{get_keys} + will return an empty tuple. + + @raise SSHException: if an SSH agent is found, but speaks an + incompatible protocol + """ + self.conn = None + self.keys = () + if ('SSH_AUTH_SOCK' in os.environ) and (sys.platform != 'win32'): + conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + conn.connect(os.environ['SSH_AUTH_SOCK']) + except: + # probably a dangling env var: the ssh agent is gone + return + self.conn = conn + elif sys.platform == 'win32': + import win_pageant + if win_pageant.can_talk_to_agent(): + self.conn = win_pageant.PageantConnection() + else: + return + else: + # no agent support + return + + ptype, result = self._send_message(chr(SSH2_AGENTC_REQUEST_IDENTITIES)) + if ptype != SSH2_AGENT_IDENTITIES_ANSWER: + raise SSHException('could not get keys from ssh-agent') + keys = [] + for i in range(result.get_int()): + keys.append(AgentKey(self, result.get_string())) + result.get_string() + self.keys = tuple(keys) + + def close(self): + """ + Close the SSH agent connection. + """ + if self.conn is not None: + self.conn.close() + self.conn = None + self.keys = () + + def get_keys(self): + """ + Return the list of keys available through the SSH agent, if any. If + no SSH agent was running (or it couldn't be contacted), an empty list + will be returned. + + @return: a list of keys available on the SSH agent + @rtype: tuple of L{AgentKey} + """ + return self.keys + + def _send_message(self, msg): + msg = str(msg) + self.conn.send(struct.pack('>I', len(msg)) + msg) + l = self._read_all(4) + msg = Message(self._read_all(struct.unpack('>I', l)[0])) + return ord(msg.get_byte()), msg + + def _read_all(self, wanted): + result = self.conn.recv(wanted) + while len(result) < wanted: + if len(result) == 0: + raise SSHException('lost ssh-agent') + extra = self.conn.recv(wanted - len(result)) + if len(extra) == 0: + raise SSHException('lost ssh-agent') + result += extra + return result + + +class AgentKey(PKey): + """ + Private key held in a local SSH agent. This type of key can be used for + authenticating to a remote server (signing). Most other key operations + work as expected. + """ + + def __init__(self, agent, blob): + self.agent = agent + self.blob = blob + self.name = Message(blob).get_string() + + def __str__(self): + return self.blob + + def get_name(self): + return self.name + + def sign_ssh_data(self, rng, data): + msg = Message() + msg.add_byte(chr(SSH2_AGENTC_SIGN_REQUEST)) + msg.add_string(self.blob) + msg.add_string(data) + msg.add_int(0) + ptype, result = self.agent._send_message(msg) + if ptype != SSH2_AGENT_SIGN_RESPONSE: + raise SSHException('key cannot be used for signing') + return result.get_string() diff --git a/lib/paramiko/auth_handler.py b/lib/paramiko/auth_handler.py new file mode 100644 index 0000000..e3bd82d --- /dev/null +++ b/lib/paramiko/auth_handler.py @@ -0,0 +1,426 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +L{AuthHandler} +""" + +import threading +import weakref + +# this helps freezing utils +import encodings.utf_8 + +from paramiko.common import * +from paramiko import util +from paramiko.message import Message +from paramiko.ssh_exception import SSHException, AuthenticationException, \ + BadAuthenticationType, PartialAuthentication +from paramiko.server import InteractiveQuery + + +class AuthHandler (object): + """ + Internal class to handle the mechanics of authentication. + """ + + def __init__(self, transport): + self.transport = weakref.proxy(transport) + self.username = None + self.authenticated = False + self.auth_event = None + self.auth_method = '' + self.password = None + self.private_key = None + self.interactive_handler = None + self.submethods = None + # for server mode: + self.auth_username = None + self.auth_fail_count = 0 + + def is_authenticated(self): + return self.authenticated + + def get_username(self): + if self.transport.server_mode: + return self.auth_username + else: + return self.username + + def auth_none(self, username, event): + self.transport.lock.acquire() + try: + self.auth_event = event + self.auth_method = 'none' + self.username = username + self._request_auth() + finally: + self.transport.lock.release() + + def auth_publickey(self, username, key, event): + self.transport.lock.acquire() + try: + self.auth_event = event + self.auth_method = 'publickey' + self.username = username + self.private_key = key + self._request_auth() + finally: + self.transport.lock.release() + + def auth_password(self, username, password, event): + self.transport.lock.acquire() + try: + self.auth_event = event + self.auth_method = 'password' + self.username = username + self.password = password + self._request_auth() + finally: + self.transport.lock.release() + + def auth_interactive(self, username, handler, event, submethods=''): + """ + response_list = handler(title, instructions, prompt_list) + """ + self.transport.lock.acquire() + try: + self.auth_event = event + self.auth_method = 'keyboard-interactive' + self.username = username + self.interactive_handler = handler + self.submethods = submethods + self._request_auth() + finally: + self.transport.lock.release() + + def abort(self): + if self.auth_event is not None: + self.auth_event.set() + + + ### internals... + + + def _request_auth(self): + m = Message() + m.add_byte(chr(MSG_SERVICE_REQUEST)) + m.add_string('ssh-userauth') + self.transport._send_message(m) + + def _disconnect_service_not_available(self): + m = Message() + m.add_byte(chr(MSG_DISCONNECT)) + m.add_int(DISCONNECT_SERVICE_NOT_AVAILABLE) + m.add_string('Service not available') + m.add_string('en') + self.transport._send_message(m) + self.transport.close() + + def _disconnect_no_more_auth(self): + m = Message() + m.add_byte(chr(MSG_DISCONNECT)) + m.add_int(DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE) + m.add_string('No more auth methods available') + m.add_string('en') + self.transport._send_message(m) + self.transport.close() + + def _get_session_blob(self, key, service, username): + m = Message() + m.add_string(self.transport.session_id) + m.add_byte(chr(MSG_USERAUTH_REQUEST)) + m.add_string(username) + m.add_string(service) + m.add_string('publickey') + m.add_boolean(1) + m.add_string(key.get_name()) + m.add_string(str(key)) + return str(m) + + def wait_for_response(self, event): + while True: + event.wait(0.1) + if not self.transport.is_active(): + e = self.transport.get_exception() + if (e is None) or issubclass(e.__class__, EOFError): + e = AuthenticationException('Authentication failed.') + raise e + if event.isSet(): + break + if not self.is_authenticated(): + e = self.transport.get_exception() + if e is None: + e = AuthenticationException('Authentication failed.') + # this is horrible. python Exception isn't yet descended from + # object, so type(e) won't work. :( + if issubclass(e.__class__, PartialAuthentication): + return e.allowed_types + raise e + return [] + + def _parse_service_request(self, m): + service = m.get_string() + if self.transport.server_mode and (service == 'ssh-userauth'): + # accepted + m = Message() + m.add_byte(chr(MSG_SERVICE_ACCEPT)) + m.add_string(service) + self.transport._send_message(m) + return + # dunno this one + self._disconnect_service_not_available() + + def _parse_service_accept(self, m): + service = m.get_string() + if service == 'ssh-userauth': + self.transport._log(DEBUG, 'userauth is OK') + m = Message() + m.add_byte(chr(MSG_USERAUTH_REQUEST)) + m.add_string(self.username) + m.add_string('ssh-connection') + m.add_string(self.auth_method) + if self.auth_method == 'password': + m.add_boolean(False) + password = self.password + if isinstance(password, unicode): + password = password.encode('UTF-8') + m.add_string(password) + elif self.auth_method == 'publickey': + m.add_boolean(True) + m.add_string(self.private_key.get_name()) + m.add_string(str(self.private_key)) + blob = self._get_session_blob(self.private_key, 'ssh-connection', self.username) + sig = self.private_key.sign_ssh_data(self.transport.rng, blob) + m.add_string(str(sig)) + elif self.auth_method == 'keyboard-interactive': + m.add_string('') + m.add_string(self.submethods) + elif self.auth_method == 'none': + pass + else: + raise SSHException('Unknown auth method "%s"' % self.auth_method) + self.transport._send_message(m) + else: + self.transport._log(DEBUG, 'Service request "%s" accepted (?)' % service) + + def _send_auth_result(self, username, method, result): + # okay, send result + m = Message() + if result == AUTH_SUCCESSFUL: + self.transport._log(INFO, 'Auth granted (%s).' % method) + m.add_byte(chr(MSG_USERAUTH_SUCCESS)) + self.authenticated = True + else: + self.transport._log(INFO, 'Auth rejected (%s).' % method) + m.add_byte(chr(MSG_USERAUTH_FAILURE)) + m.add_string(self.transport.server_object.get_allowed_auths(username)) + if result == AUTH_PARTIALLY_SUCCESSFUL: + m.add_boolean(1) + else: + m.add_boolean(0) + self.auth_fail_count += 1 + self.transport._send_message(m) + if self.auth_fail_count >= 10: + self._disconnect_no_more_auth() + if result == AUTH_SUCCESSFUL: + self.transport._auth_trigger() + + def _interactive_query(self, q): + # make interactive query instead of response + m = Message() + m.add_byte(chr(MSG_USERAUTH_INFO_REQUEST)) + m.add_string(q.name) + m.add_string(q.instructions) + m.add_string('') + m.add_int(len(q.prompts)) + for p in q.prompts: + m.add_string(p[0]) + m.add_boolean(p[1]) + self.transport._send_message(m) + + def _parse_userauth_request(self, m): + if not self.transport.server_mode: + # er, uh... what? + m = Message() + m.add_byte(chr(MSG_USERAUTH_FAILURE)) + m.add_string('none') + m.add_boolean(0) + self.transport._send_message(m) + return + if self.authenticated: + # ignore + return + username = m.get_string() + service = m.get_string() + method = m.get_string() + self.transport._log(DEBUG, 'Auth request (type=%s) service=%s, username=%s' % (method, service, username)) + if service != 'ssh-connection': + self._disconnect_service_not_available() + return + if (self.auth_username is not None) and (self.auth_username != username): + self.transport._log(WARNING, 'Auth rejected because the client attempted to change username in mid-flight') + self._disconnect_no_more_auth() + return + self.auth_username = username + + if method == 'none': + result = self.transport.server_object.check_auth_none(username) + elif method == 'password': + changereq = m.get_boolean() + password = m.get_string() + try: + password = password.decode('UTF-8') + except UnicodeError: + # some clients/servers expect non-utf-8 passwords! + # in this case, just return the raw byte string. + pass + if changereq: + # always treated as failure, since we don't support changing passwords, but collect + # the list of valid auth types from the callback anyway + self.transport._log(DEBUG, 'Auth request to change passwords (rejected)') + newpassword = m.get_string() + try: + newpassword = newpassword.decode('UTF-8', 'replace') + except UnicodeError: + pass + result = AUTH_FAILED + else: + result = self.transport.server_object.check_auth_password(username, password) + elif method == 'publickey': + sig_attached = m.get_boolean() + keytype = m.get_string() + keyblob = m.get_string() + try: + key = self.transport._key_info[keytype](Message(keyblob)) + except SSHException, e: + self.transport._log(INFO, 'Auth rejected: public key: %s' % str(e)) + key = None + except: + self.transport._log(INFO, 'Auth rejected: unsupported or mangled public key') + key = None + if key is None: + self._disconnect_no_more_auth() + return + # first check if this key is okay... if not, we can skip the verify + result = self.transport.server_object.check_auth_publickey(username, key) + if result != AUTH_FAILED: + # key is okay, verify it + if not sig_attached: + # client wants to know if this key is acceptable, before it + # signs anything... send special "ok" message + m = Message() + m.add_byte(chr(MSG_USERAUTH_PK_OK)) + m.add_string(keytype) + m.add_string(keyblob) + self.transport._send_message(m) + return + sig = Message(m.get_string()) + blob = self._get_session_blob(key, service, username) + if not key.verify_ssh_sig(blob, sig): + self.transport._log(INFO, 'Auth rejected: invalid signature') + result = AUTH_FAILED + elif method == 'keyboard-interactive': + lang = m.get_string() + submethods = m.get_string() + result = self.transport.server_object.check_auth_interactive(username, submethods) + if isinstance(result, InteractiveQuery): + # make interactive query instead of response + self._interactive_query(result) + return + else: + result = self.transport.server_object.check_auth_none(username) + # okay, send result + self._send_auth_result(username, method, result) + + def _parse_userauth_success(self, m): + self.transport._log(INFO, 'Authentication (%s) successful!' % self.auth_method) + self.authenticated = True + self.transport._auth_trigger() + if self.auth_event != None: + self.auth_event.set() + + def _parse_userauth_failure(self, m): + authlist = m.get_list() + partial = m.get_boolean() + if partial: + self.transport._log(INFO, 'Authentication continues...') + self.transport._log(DEBUG, 'Methods: ' + str(authlist)) + self.transport.saved_exception = PartialAuthentication(authlist) + elif self.auth_method not in authlist: + self.transport._log(DEBUG, 'Authentication type (%s) not permitted.' % self.auth_method) + self.transport._log(DEBUG, 'Allowed methods: ' + str(authlist)) + self.transport.saved_exception = BadAuthenticationType('Bad authentication type', authlist) + else: + self.transport._log(INFO, 'Authentication (%s) failed.' % self.auth_method) + self.authenticated = False + self.username = None + if self.auth_event != None: + self.auth_event.set() + + def _parse_userauth_banner(self, m): + banner = m.get_string() + lang = m.get_string() + self.transport._log(INFO, 'Auth banner: ' + banner) + # who cares. + + def _parse_userauth_info_request(self, m): + if self.auth_method != 'keyboard-interactive': + raise SSHException('Illegal info request from server') + title = m.get_string() + instructions = m.get_string() + m.get_string() # lang + prompts = m.get_int() + prompt_list = [] + for i in range(prompts): + prompt_list.append((m.get_string(), m.get_boolean())) + response_list = self.interactive_handler(title, instructions, prompt_list) + + m = Message() + m.add_byte(chr(MSG_USERAUTH_INFO_RESPONSE)) + m.add_int(len(response_list)) + for r in response_list: + m.add_string(r) + self.transport._send_message(m) + + def _parse_userauth_info_response(self, m): + if not self.transport.server_mode: + raise SSHException('Illegal info response from server') + n = m.get_int() + responses = [] + for i in range(n): + responses.append(m.get_string()) + result = self.transport.server_object.check_auth_interactive_response(responses) + if isinstance(type(result), InteractiveQuery): + # make interactive query instead of response + self._interactive_query(result) + return + self._send_auth_result(self.auth_username, 'keyboard-interactive', result) + + + _handler_table = { + MSG_SERVICE_REQUEST: _parse_service_request, + MSG_SERVICE_ACCEPT: _parse_service_accept, + MSG_USERAUTH_REQUEST: _parse_userauth_request, + MSG_USERAUTH_SUCCESS: _parse_userauth_success, + MSG_USERAUTH_FAILURE: _parse_userauth_failure, + MSG_USERAUTH_BANNER: _parse_userauth_banner, + MSG_USERAUTH_INFO_REQUEST: _parse_userauth_info_request, + MSG_USERAUTH_INFO_RESPONSE: _parse_userauth_info_response, + } + diff --git a/lib/paramiko/ber.py b/lib/paramiko/ber.py new file mode 100644 index 0000000..19568dd --- /dev/null +++ b/lib/paramiko/ber.py @@ -0,0 +1,129 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + + +import util + + +class BERException (Exception): + pass + + +class BER(object): + """ + Robey's tiny little attempt at a BER decoder. + """ + + def __init__(self, content=''): + self.content = content + self.idx = 0 + + def __str__(self): + return self.content + + def __repr__(self): + return 'BER(\'' + repr(self.content) + '\')' + + def decode(self): + return self.decode_next() + + def decode_next(self): + if self.idx >= len(self.content): + return None + ident = ord(self.content[self.idx]) + self.idx += 1 + if (ident & 31) == 31: + # identifier > 30 + ident = 0 + while self.idx < len(self.content): + t = ord(self.content[self.idx]) + self.idx += 1 + ident = (ident << 7) | (t & 0x7f) + if not (t & 0x80): + break + if self.idx >= len(self.content): + return None + # now fetch length + size = ord(self.content[self.idx]) + self.idx += 1 + if size & 0x80: + # more complimicated... + # FIXME: theoretically should handle indefinite-length (0x80) + t = size & 0x7f + if self.idx + t > len(self.content): + return None + size = util.inflate_long(self.content[self.idx : self.idx + t], True) + self.idx += t + if self.idx + size > len(self.content): + # can't fit + return None + data = self.content[self.idx : self.idx + size] + self.idx += size + # now switch on id + if ident == 0x30: + # sequence + return self.decode_sequence(data) + elif ident == 2: + # int + return util.inflate_long(data) + else: + # 1: boolean (00 false, otherwise true) + raise BERException('Unknown ber encoding type %d (robey is lazy)' % ident) + + def decode_sequence(data): + out = [] + b = BER(data) + while True: + x = b.decode_next() + if x is None: + break + out.append(x) + return out + decode_sequence = staticmethod(decode_sequence) + + def encode_tlv(self, ident, val): + # no need to support ident > 31 here + self.content += chr(ident) + if len(val) > 0x7f: + lenstr = util.deflate_long(len(val)) + self.content += chr(0x80 + len(lenstr)) + lenstr + else: + self.content += chr(len(val)) + self.content += val + + def encode(self, x): + if type(x) is bool: + if x: + self.encode_tlv(1, '\xff') + else: + self.encode_tlv(1, '\x00') + elif (type(x) is int) or (type(x) is long): + self.encode_tlv(2, util.deflate_long(x)) + elif type(x) is str: + self.encode_tlv(4, x) + elif (type(x) is list) or (type(x) is tuple): + self.encode_tlv(0x30, self.encode_sequence(x)) + else: + raise BERException('Unknown type for encoding: %s' % repr(type(x))) + + def encode_sequence(data): + b = BER() + for item in data: + b.encode(item) + return str(b) + encode_sequence = staticmethod(encode_sequence) diff --git a/lib/paramiko/buffered_pipe.py b/lib/paramiko/buffered_pipe.py new file mode 100644 index 0000000..b19d74b --- /dev/null +++ b/lib/paramiko/buffered_pipe.py @@ -0,0 +1,200 @@ +# Copyright (C) 2006-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Attempt to generalize the "feeder" part of a Channel: an object which can be +read from and closed, but is reading from a buffer fed by another thread. The +read operations are blocking and can have a timeout set. +""" + +import array +import threading +import time + + +class PipeTimeout (IOError): + """ + Indicates that a timeout was reached on a read from a L{BufferedPipe}. + """ + pass + + +class BufferedPipe (object): + """ + A buffer that obeys normal read (with timeout) & close semantics for a + file or socket, but is fed data from another thread. This is used by + L{Channel}. + """ + + def __init__(self): + self._lock = threading.Lock() + self._cv = threading.Condition(self._lock) + self._event = None + self._buffer = array.array('B') + self._closed = False + + def set_event(self, event): + """ + Set an event on this buffer. When data is ready to be read (or the + buffer has been closed), the event will be set. When no data is + ready, the event will be cleared. + + @param event: the event to set/clear + @type event: Event + """ + self._event = event + if len(self._buffer) > 0: + event.set() + else: + event.clear() + + def feed(self, data): + """ + Feed new data into this pipe. This method is assumed to be called + from a separate thread, so synchronization is done. + + @param data: the data to add + @type data: str + """ + self._lock.acquire() + try: + if self._event is not None: + self._event.set() + self._buffer.fromstring(data) + self._cv.notifyAll() + finally: + self._lock.release() + + def read_ready(self): + """ + Returns true if data is buffered and ready to be read from this + feeder. A C{False} result does not mean that the feeder has closed; + it means you may need to wait before more data arrives. + + @return: C{True} if a L{read} call would immediately return at least + one byte; C{False} otherwise. + @rtype: bool + """ + self._lock.acquire() + try: + if len(self._buffer) == 0: + return False + return True + finally: + self._lock.release() + + def read(self, nbytes, timeout=None): + """ + Read data from the pipe. The return value is a string representing + the data received. The maximum amount of data to be received at once + is specified by C{nbytes}. If a string of length zero is returned, + the pipe has been closed. + + The optional C{timeout} argument can be a nonnegative float expressing + seconds, or C{None} for no timeout. If a float is given, a + C{PipeTimeout} will be raised if the timeout period value has + elapsed before any data arrives. + + @param nbytes: maximum number of bytes to read + @type nbytes: int + @param timeout: maximum seconds to wait (or C{None}, the default, to + wait forever) + @type timeout: float + @return: data + @rtype: str + + @raise PipeTimeout: if a timeout was specified and no data was ready + before that timeout + """ + out = '' + self._lock.acquire() + try: + if len(self._buffer) == 0: + if self._closed: + return out + # should we block? + if timeout == 0.0: + raise PipeTimeout() + # loop here in case we get woken up but a different thread has + # grabbed everything in the buffer. + while (len(self._buffer) == 0) and not self._closed: + then = time.time() + self._cv.wait(timeout) + if timeout is not None: + timeout -= time.time() - then + if timeout <= 0.0: + raise PipeTimeout() + + # something's in the buffer and we have the lock! + if len(self._buffer) <= nbytes: + out = self._buffer.tostring() + del self._buffer[:] + if (self._event is not None) and not self._closed: + self._event.clear() + else: + out = self._buffer[:nbytes].tostring() + del self._buffer[:nbytes] + finally: + self._lock.release() + + return out + + def empty(self): + """ + Clear out the buffer and return all data that was in it. + + @return: any data that was in the buffer prior to clearing it out + @rtype: str + """ + self._lock.acquire() + try: + out = self._buffer.tostring() + del self._buffer[:] + if (self._event is not None) and not self._closed: + self._event.clear() + return out + finally: + self._lock.release() + + def close(self): + """ + Close this pipe object. Future calls to L{read} after the buffer + has been emptied will return immediately with an empty string. + """ + self._lock.acquire() + try: + self._closed = True + self._cv.notifyAll() + if self._event is not None: + self._event.set() + finally: + self._lock.release() + + def __len__(self): + """ + Return the number of bytes buffered. + + @return: number of bytes bufferes + @rtype: int + """ + self._lock.acquire() + try: + return len(self._buffer) + finally: + self._lock.release() + diff --git a/lib/paramiko/channel.py b/lib/paramiko/channel.py new file mode 100644 index 0000000..6d895fe --- /dev/null +++ b/lib/paramiko/channel.py @@ -0,0 +1,1234 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Abstraction for an SSH2 channel. +""" + +import binascii +import sys +import time +import threading +import socket +import os + +from paramiko.common import * +from paramiko import util +from paramiko.message import Message +from paramiko.ssh_exception import SSHException +from paramiko.file import BufferedFile +from paramiko.buffered_pipe import BufferedPipe, PipeTimeout +from paramiko import pipe + + +# lower bound on the max packet size we'll accept from the remote host +MIN_PACKET_SIZE = 1024 + + +class Channel (object): + """ + A secure tunnel across an SSH L{Transport}. A Channel is meant to behave + like a socket, and has an API that should be indistinguishable from the + python socket API. + + Because SSH2 has a windowing kind of flow control, if you stop reading data + from a Channel and its buffer fills up, the server will be unable to send + you any more data until you read some of it. (This won't affect other + channels on the same transport -- all channels on a single transport are + flow-controlled independently.) Similarly, if the server isn't reading + data you send, calls to L{send} may block, unless you set a timeout. This + is exactly like a normal network socket, so it shouldn't be too surprising. + """ + + def __init__(self, chanid): + """ + Create a new channel. The channel is not associated with any + particular session or L{Transport} until the Transport attaches it. + Normally you would only call this method from the constructor of a + subclass of L{Channel}. + + @param chanid: the ID of this channel, as passed by an existing + L{Transport}. + @type chanid: int + """ + self.chanid = chanid + self.remote_chanid = 0 + self.transport = None + self.active = False + self.eof_received = 0 + self.eof_sent = 0 + self.in_buffer = BufferedPipe() + self.in_stderr_buffer = BufferedPipe() + self.timeout = None + self.closed = False + self.ultra_debug = False + self.lock = threading.Lock() + self.out_buffer_cv = threading.Condition(self.lock) + self.in_window_size = 0 + self.out_window_size = 0 + self.in_max_packet_size = 0 + self.out_max_packet_size = 0 + self.in_window_threshold = 0 + self.in_window_sofar = 0 + self.status_event = threading.Event() + self._name = str(chanid) + self.logger = util.get_logger('paramiko.transport') + self._pipe = None + self.event = threading.Event() + self.event_ready = False + self.combine_stderr = False + self.exit_status = -1 + self.origin_addr = None + + def __del__(self): + try: + self.close() + except: + pass + + def __repr__(self): + """ + Return a string representation of this object, for debugging. + + @rtype: str + """ + out = ' 0: + out += ' in-buffer=%d' % (len(self.in_buffer),) + out += ' -> ' + repr(self.transport) + out += '>' + return out + + def get_pty(self, term='vt100', width=80, height=24): + """ + Request a pseudo-terminal from the server. This is usually used right + after creating a client channel, to ask the server to provide some + basic terminal semantics for a shell invoked with L{invoke_shell}. + It isn't necessary (or desirable) to call this method if you're going + to exectue a single command with L{exec_command}. + + @param term: the terminal type to emulate (for example, C{'vt100'}) + @type term: str + @param width: width (in characters) of the terminal screen + @type width: int + @param height: height (in characters) of the terminal screen + @type height: int + + @raise SSHException: if the request was rejected or the channel was + closed + """ + if self.closed or self.eof_received or self.eof_sent or not self.active: + raise SSHException('Channel is not open') + m = Message() + m.add_byte(chr(MSG_CHANNEL_REQUEST)) + m.add_int(self.remote_chanid) + m.add_string('pty-req') + m.add_boolean(True) + m.add_string(term) + m.add_int(width) + m.add_int(height) + # pixel height, width (usually useless) + m.add_int(0).add_int(0) + m.add_string('') + self._event_pending() + self.transport._send_user_message(m) + self._wait_for_event() + + def invoke_shell(self): + """ + Request an interactive shell session on this channel. If the server + allows it, the channel will then be directly connected to the stdin, + stdout, and stderr of the shell. + + Normally you would call L{get_pty} before this, in which case the + shell will operate through the pty, and the channel will be connected + to the stdin and stdout of the pty. + + When the shell exits, the channel will be closed and can't be reused. + You must open a new channel if you wish to open another shell. + + @raise SSHException: if the request was rejected or the channel was + closed + """ + if self.closed or self.eof_received or self.eof_sent or not self.active: + raise SSHException('Channel is not open') + m = Message() + m.add_byte(chr(MSG_CHANNEL_REQUEST)) + m.add_int(self.remote_chanid) + m.add_string('shell') + m.add_boolean(1) + self._event_pending() + self.transport._send_user_message(m) + self._wait_for_event() + + def exec_command(self, command): + """ + Execute a command on the server. If the server allows it, the channel + will then be directly connected to the stdin, stdout, and stderr of + the command being executed. + + When the command finishes executing, the channel will be closed and + can't be reused. You must open a new channel if you wish to execute + another command. + + @param command: a shell command to execute. + @type command: str + + @raise SSHException: if the request was rejected or the channel was + closed + """ + if self.closed or self.eof_received or self.eof_sent or not self.active: + raise SSHException('Channel is not open') + m = Message() + m.add_byte(chr(MSG_CHANNEL_REQUEST)) + m.add_int(self.remote_chanid) + m.add_string('exec') + m.add_boolean(True) + m.add_string(command) + self._event_pending() + self.transport._send_user_message(m) + self._wait_for_event() + + def invoke_subsystem(self, subsystem): + """ + Request a subsystem on the server (for example, C{sftp}). If the + server allows it, the channel will then be directly connected to the + requested subsystem. + + When the subsystem finishes, the channel will be closed and can't be + reused. + + @param subsystem: name of the subsystem being requested. + @type subsystem: str + + @raise SSHException: if the request was rejected or the channel was + closed + """ + if self.closed or self.eof_received or self.eof_sent or not self.active: + raise SSHException('Channel is not open') + m = Message() + m.add_byte(chr(MSG_CHANNEL_REQUEST)) + m.add_int(self.remote_chanid) + m.add_string('subsystem') + m.add_boolean(True) + m.add_string(subsystem) + self._event_pending() + self.transport._send_user_message(m) + self._wait_for_event() + + def resize_pty(self, width=80, height=24): + """ + Resize the pseudo-terminal. This can be used to change the width and + height of the terminal emulation created in a previous L{get_pty} call. + + @param width: new width (in characters) of the terminal screen + @type width: int + @param height: new height (in characters) of the terminal screen + @type height: int + + @raise SSHException: if the request was rejected or the channel was + closed + """ + if self.closed or self.eof_received or self.eof_sent or not self.active: + raise SSHException('Channel is not open') + m = Message() + m.add_byte(chr(MSG_CHANNEL_REQUEST)) + m.add_int(self.remote_chanid) + m.add_string('window-change') + m.add_boolean(True) + m.add_int(width) + m.add_int(height) + m.add_int(0).add_int(0) + self._event_pending() + self.transport._send_user_message(m) + self._wait_for_event() + + def exit_status_ready(self): + """ + Return true if the remote process has exited and returned an exit + status. You may use this to poll the process status if you don't + want to block in L{recv_exit_status}. Note that the server may not + return an exit status in some cases (like bad servers). + + @return: True if L{recv_exit_status} will return immediately + @rtype: bool + @since: 1.7.3 + """ + return self.closed or self.status_event.isSet() + + def recv_exit_status(self): + """ + Return the exit status from the process on the server. This is + mostly useful for retrieving the reults of an L{exec_command}. + If the command hasn't finished yet, this method will wait until + it does, or until the channel is closed. If no exit status is + provided by the server, -1 is returned. + + @return: the exit code of the process on the server. + @rtype: int + + @since: 1.2 + """ + self.status_event.wait() + assert self.status_event.isSet() + return self.exit_status + + def send_exit_status(self, status): + """ + Send the exit status of an executed command to the client. (This + really only makes sense in server mode.) Many clients expect to + get some sort of status code back from an executed command after + it completes. + + @param status: the exit code of the process + @type status: int + + @since: 1.2 + """ + # in many cases, the channel will not still be open here. + # that's fine. + m = Message() + m.add_byte(chr(MSG_CHANNEL_REQUEST)) + m.add_int(self.remote_chanid) + m.add_string('exit-status') + m.add_boolean(False) + m.add_int(status) + self.transport._send_user_message(m) + + def request_x11(self, screen_number=0, auth_protocol=None, auth_cookie=None, + single_connection=False, handler=None): + """ + Request an x11 session on this channel. If the server allows it, + further x11 requests can be made from the server to the client, + when an x11 application is run in a shell session. + + From RFC4254:: + + It is RECOMMENDED that the 'x11 authentication cookie' that is + sent be a fake, random cookie, and that the cookie be checked and + replaced by the real cookie when a connection request is received. + + If you omit the auth_cookie, a new secure random 128-bit value will be + generated, used, and returned. You will need to use this value to + verify incoming x11 requests and replace them with the actual local + x11 cookie (which requires some knoweldge of the x11 protocol). + + If a handler is passed in, the handler is called from another thread + whenever a new x11 connection arrives. The default handler queues up + incoming x11 connections, which may be retrieved using + L{Transport.accept}. The handler's calling signature is:: + + handler(channel: Channel, (address: str, port: int)) + + @param screen_number: the x11 screen number (0, 10, etc) + @type screen_number: int + @param auth_protocol: the name of the X11 authentication method used; + if none is given, C{"MIT-MAGIC-COOKIE-1"} is used + @type auth_protocol: str + @param auth_cookie: hexadecimal string containing the x11 auth cookie; + if none is given, a secure random 128-bit value is generated + @type auth_cookie: str + @param single_connection: if True, only a single x11 connection will be + forwarded (by default, any number of x11 connections can arrive + over this session) + @type single_connection: bool + @param handler: an optional handler to use for incoming X11 connections + @type handler: function + @return: the auth_cookie used + """ + if self.closed or self.eof_received or self.eof_sent or not self.active: + raise SSHException('Channel is not open') + if auth_protocol is None: + auth_protocol = 'MIT-MAGIC-COOKIE-1' + if auth_cookie is None: + auth_cookie = binascii.hexlify(self.transport.rng.read(16)) + + m = Message() + m.add_byte(chr(MSG_CHANNEL_REQUEST)) + m.add_int(self.remote_chanid) + m.add_string('x11-req') + m.add_boolean(True) + m.add_boolean(single_connection) + m.add_string(auth_protocol) + m.add_string(auth_cookie) + m.add_int(screen_number) + self._event_pending() + self.transport._send_user_message(m) + self._wait_for_event() + self.transport._set_x11_handler(handler) + return auth_cookie + + def get_transport(self): + """ + Return the L{Transport} associated with this channel. + + @return: the L{Transport} that was used to create this channel. + @rtype: L{Transport} + """ + return self.transport + + def set_name(self, name): + """ + Set a name for this channel. Currently it's only used to set the name + of the channel in logfile entries. The name can be fetched with the + L{get_name} method. + + @param name: new channel name + @type name: str + """ + self._name = name + + def get_name(self): + """ + Get the name of this channel that was previously set by L{set_name}. + + @return: the name of this channel. + @rtype: str + """ + return self._name + + def get_id(self): + """ + Return the ID # for this channel. The channel ID is unique across + a L{Transport} and usually a small number. It's also the number + passed to L{ServerInterface.check_channel_request} when determining + whether to accept a channel request in server mode. + + @return: the ID of this channel. + @rtype: int + """ + return self.chanid + + def set_combine_stderr(self, combine): + """ + Set whether stderr should be combined into stdout on this channel. + The default is C{False}, but in some cases it may be convenient to + have both streams combined. + + If this is C{False}, and L{exec_command} is called (or C{invoke_shell} + with no pty), output to stderr will not show up through the L{recv} + and L{recv_ready} calls. You will have to use L{recv_stderr} and + L{recv_stderr_ready} to get stderr output. + + If this is C{True}, data will never show up via L{recv_stderr} or + L{recv_stderr_ready}. + + @param combine: C{True} if stderr output should be combined into + stdout on this channel. + @type combine: bool + @return: previous setting. + @rtype: bool + + @since: 1.1 + """ + data = '' + self.lock.acquire() + try: + old = self.combine_stderr + self.combine_stderr = combine + if combine and not old: + # copy old stderr buffer into primary buffer + data = self.in_stderr_buffer.empty() + finally: + self.lock.release() + if len(data) > 0: + self._feed(data) + return old + + + ### socket API + + + def settimeout(self, timeout): + """ + Set a timeout on blocking read/write operations. The C{timeout} + argument can be a nonnegative float expressing seconds, or C{None}. If + a float is given, subsequent channel read/write operations will raise + a timeout exception if the timeout period value has elapsed before the + operation has completed. Setting a timeout of C{None} disables + timeouts on socket operations. + + C{chan.settimeout(0.0)} is equivalent to C{chan.setblocking(0)}; + C{chan.settimeout(None)} is equivalent to C{chan.setblocking(1)}. + + @param timeout: seconds to wait for a pending read/write operation + before raising C{socket.timeout}, or C{None} for no timeout. + @type timeout: float + """ + self.timeout = timeout + + def gettimeout(self): + """ + Returns the timeout in seconds (as a float) associated with socket + operations, or C{None} if no timeout is set. This reflects the last + call to L{setblocking} or L{settimeout}. + + @return: timeout in seconds, or C{None}. + @rtype: float + """ + return self.timeout + + def setblocking(self, blocking): + """ + Set blocking or non-blocking mode of the channel: if C{blocking} is 0, + the channel is set to non-blocking mode; otherwise it's set to blocking + mode. Initially all channels are in blocking mode. + + In non-blocking mode, if a L{recv} call doesn't find any data, or if a + L{send} call can't immediately dispose of the data, an error exception + is raised. In blocking mode, the calls block until they can proceed. An + EOF condition is considered "immediate data" for L{recv}, so if the + channel is closed in the read direction, it will never block. + + C{chan.setblocking(0)} is equivalent to C{chan.settimeout(0)}; + C{chan.setblocking(1)} is equivalent to C{chan.settimeout(None)}. + + @param blocking: 0 to set non-blocking mode; non-0 to set blocking + mode. + @type blocking: int + """ + if blocking: + self.settimeout(None) + else: + self.settimeout(0.0) + + def getpeername(self): + """ + Return the address of the remote side of this Channel, if possible. + This is just a wrapper around C{'getpeername'} on the Transport, used + to provide enough of a socket-like interface to allow asyncore to work. + (asyncore likes to call C{'getpeername'}.) + + @return: the address if the remote host, if known + @rtype: tuple(str, int) + """ + return self.transport.getpeername() + + def close(self): + """ + Close the channel. All future read/write operations on the channel + will fail. The remote end will receive no more data (after queued data + is flushed). Channels are automatically closed when their L{Transport} + is closed or when they are garbage collected. + """ + self.lock.acquire() + try: + # only close the pipe when the user explicitly closes the channel. + # otherwise they will get unpleasant surprises. (and do it before + # checking self.closed, since the remote host may have already + # closed the connection.) + if self._pipe is not None: + self._pipe.close() + self._pipe = None + + if not self.active or self.closed: + return + msgs = self._close_internal() + finally: + self.lock.release() + for m in msgs: + if m is not None: + self.transport._send_user_message(m) + + def recv_ready(self): + """ + Returns true if data is buffered and ready to be read from this + channel. A C{False} result does not mean that the channel has closed; + it means you may need to wait before more data arrives. + + @return: C{True} if a L{recv} call on this channel would immediately + return at least one byte; C{False} otherwise. + @rtype: boolean + """ + return self.in_buffer.read_ready() + + def recv(self, nbytes): + """ + Receive data from the channel. The return value is a string + representing the data received. The maximum amount of data to be + received at once is specified by C{nbytes}. If a string of length zero + is returned, the channel stream has closed. + + @param nbytes: maximum number of bytes to read. + @type nbytes: int + @return: data. + @rtype: str + + @raise socket.timeout: if no data is ready before the timeout set by + L{settimeout}. + """ + try: + out = self.in_buffer.read(nbytes, self.timeout) + except PipeTimeout, e: + raise socket.timeout() + + ack = self._check_add_window(len(out)) + # no need to hold the channel lock when sending this + if ack > 0: + m = Message() + m.add_byte(chr(MSG_CHANNEL_WINDOW_ADJUST)) + m.add_int(self.remote_chanid) + m.add_int(ack) + self.transport._send_user_message(m) + + return out + + def recv_stderr_ready(self): + """ + Returns true if data is buffered and ready to be read from this + channel's stderr stream. Only channels using L{exec_command} or + L{invoke_shell} without a pty will ever have data on the stderr + stream. + + @return: C{True} if a L{recv_stderr} call on this channel would + immediately return at least one byte; C{False} otherwise. + @rtype: boolean + + @since: 1.1 + """ + return self.in_stderr_buffer.read_ready() + + def recv_stderr(self, nbytes): + """ + Receive data from the channel's stderr stream. Only channels using + L{exec_command} or L{invoke_shell} without a pty will ever have data + on the stderr stream. The return value is a string representing the + data received. The maximum amount of data to be received at once is + specified by C{nbytes}. If a string of length zero is returned, the + channel stream has closed. + + @param nbytes: maximum number of bytes to read. + @type nbytes: int + @return: data. + @rtype: str + + @raise socket.timeout: if no data is ready before the timeout set by + L{settimeout}. + + @since: 1.1 + """ + try: + out = self.in_stderr_buffer.read(nbytes, self.timeout) + except PipeTimeout, e: + raise socket.timeout() + + ack = self._check_add_window(len(out)) + # no need to hold the channel lock when sending this + if ack > 0: + m = Message() + m.add_byte(chr(MSG_CHANNEL_WINDOW_ADJUST)) + m.add_int(self.remote_chanid) + m.add_int(ack) + self.transport._send_user_message(m) + + return out + + def send_ready(self): + """ + Returns true if data can be written to this channel without blocking. + This means the channel is either closed (so any write attempt would + return immediately) or there is at least one byte of space in the + outbound buffer. If there is at least one byte of space in the + outbound buffer, a L{send} call will succeed immediately and return + the number of bytes actually written. + + @return: C{True} if a L{send} call on this channel would immediately + succeed or fail + @rtype: boolean + """ + self.lock.acquire() + try: + if self.closed or self.eof_sent: + return True + return self.out_window_size > 0 + finally: + self.lock.release() + + def send(self, s): + """ + Send data to the channel. Returns the number of bytes sent, or 0 if + the channel stream is closed. Applications are responsible for + checking that all data has been sent: if only some of the data was + transmitted, the application needs to attempt delivery of the remaining + data. + + @param s: data to send + @type s: str + @return: number of bytes actually sent + @rtype: int + + @raise socket.timeout: if no data could be sent before the timeout set + by L{settimeout}. + """ + size = len(s) + self.lock.acquire() + try: + size = self._wait_for_send_window(size) + if size == 0: + # eof or similar + return 0 + m = Message() + m.add_byte(chr(MSG_CHANNEL_DATA)) + m.add_int(self.remote_chanid) + m.add_string(s[:size]) + finally: + self.lock.release() + # Note: We release self.lock before calling _send_user_message. + # Otherwise, we can deadlock during re-keying. + self.transport._send_user_message(m) + return size + + def send_stderr(self, s): + """ + Send data to the channel on the "stderr" stream. This is normally + only used by servers to send output from shell commands -- clients + won't use this. Returns the number of bytes sent, or 0 if the channel + stream is closed. Applications are responsible for checking that all + data has been sent: if only some of the data was transmitted, the + application needs to attempt delivery of the remaining data. + + @param s: data to send. + @type s: str + @return: number of bytes actually sent. + @rtype: int + + @raise socket.timeout: if no data could be sent before the timeout set + by L{settimeout}. + + @since: 1.1 + """ + size = len(s) + self.lock.acquire() + try: + size = self._wait_for_send_window(size) + if size == 0: + # eof or similar + return 0 + m = Message() + m.add_byte(chr(MSG_CHANNEL_EXTENDED_DATA)) + m.add_int(self.remote_chanid) + m.add_int(1) + m.add_string(s[:size]) + finally: + self.lock.release() + # Note: We release self.lock before calling _send_user_message. + # Otherwise, we can deadlock during re-keying. + self.transport._send_user_message(m) + return size + + def sendall(self, s): + """ + Send data to the channel, without allowing partial results. Unlike + L{send}, this method continues to send data from the given string until + either all data has been sent or an error occurs. Nothing is returned. + + @param s: data to send. + @type s: str + + @raise socket.timeout: if sending stalled for longer than the timeout + set by L{settimeout}. + @raise socket.error: if an error occured before the entire string was + sent. + + @note: If the channel is closed while only part of the data hase been + sent, there is no way to determine how much data (if any) was sent. + This is irritating, but identically follows python's API. + """ + while s: + if self.closed: + # this doesn't seem useful, but it is the documented behavior of Socket + raise socket.error('Socket is closed') + sent = self.send(s) + s = s[sent:] + return None + + def sendall_stderr(self, s): + """ + Send data to the channel's "stderr" stream, without allowing partial + results. Unlike L{send_stderr}, this method continues to send data + from the given string until all data has been sent or an error occurs. + Nothing is returned. + + @param s: data to send to the client as "stderr" output. + @type s: str + + @raise socket.timeout: if sending stalled for longer than the timeout + set by L{settimeout}. + @raise socket.error: if an error occured before the entire string was + sent. + + @since: 1.1 + """ + while s: + if self.closed: + raise socket.error('Socket is closed') + sent = self.send_stderr(s) + s = s[sent:] + return None + + def makefile(self, *params): + """ + Return a file-like object associated with this channel. The optional + C{mode} and C{bufsize} arguments are interpreted the same way as by + the built-in C{file()} function in python. + + @return: object which can be used for python file I/O. + @rtype: L{ChannelFile} + """ + return ChannelFile(*([self] + list(params))) + + def makefile_stderr(self, *params): + """ + Return a file-like object associated with this channel's stderr + stream. Only channels using L{exec_command} or L{invoke_shell} + without a pty will ever have data on the stderr stream. + + The optional C{mode} and C{bufsize} arguments are interpreted the + same way as by the built-in C{file()} function in python. For a + client, it only makes sense to open this file for reading. For a + server, it only makes sense to open this file for writing. + + @return: object which can be used for python file I/O. + @rtype: L{ChannelFile} + + @since: 1.1 + """ + return ChannelStderrFile(*([self] + list(params))) + + def fileno(self): + """ + Returns an OS-level file descriptor which can be used for polling, but + but I{not} for reading or writing. This is primaily to allow python's + C{select} module to work. + + The first time C{fileno} is called on a channel, a pipe is created to + simulate real OS-level file descriptor (FD) behavior. Because of this, + two OS-level FDs are created, which will use up FDs faster than normal. + (You won't notice this effect unless you have hundreds of channels + open at the same time.) + + @return: an OS-level file descriptor + @rtype: int + + @warning: This method causes channel reads to be slightly less + efficient. + """ + self.lock.acquire() + try: + if self._pipe is not None: + return self._pipe.fileno() + # create the pipe and feed in any existing data + self._pipe = pipe.make_pipe() + p1, p2 = pipe.make_or_pipe(self._pipe) + self.in_buffer.set_event(p1) + self.in_stderr_buffer.set_event(p2) + return self._pipe.fileno() + finally: + self.lock.release() + + def shutdown(self, how): + """ + Shut down one or both halves of the connection. If C{how} is 0, + further receives are disallowed. If C{how} is 1, further sends + are disallowed. If C{how} is 2, further sends and receives are + disallowed. This closes the stream in one or both directions. + + @param how: 0 (stop receiving), 1 (stop sending), or 2 (stop + receiving and sending). + @type how: int + """ + if (how == 0) or (how == 2): + # feign "read" shutdown + self.eof_received = 1 + if (how == 1) or (how == 2): + self.lock.acquire() + try: + m = self._send_eof() + finally: + self.lock.release() + if m is not None: + self.transport._send_user_message(m) + + def shutdown_read(self): + """ + Shutdown the receiving side of this socket, closing the stream in + the incoming direction. After this call, future reads on this + channel will fail instantly. This is a convenience method, equivalent + to C{shutdown(0)}, for people who don't make it a habit to + memorize unix constants from the 1970s. + + @since: 1.2 + """ + self.shutdown(0) + + def shutdown_write(self): + """ + Shutdown the sending side of this socket, closing the stream in + the outgoing direction. After this call, future writes on this + channel will fail instantly. This is a convenience method, equivalent + to C{shutdown(1)}, for people who don't make it a habit to + memorize unix constants from the 1970s. + + @since: 1.2 + """ + self.shutdown(1) + + + ### calls from Transport + + + def _set_transport(self, transport): + self.transport = transport + self.logger = util.get_logger(self.transport.get_log_channel()) + + def _set_window(self, window_size, max_packet_size): + self.in_window_size = window_size + self.in_max_packet_size = max_packet_size + # threshold of bytes we receive before we bother to send a window update + self.in_window_threshold = window_size // 10 + self.in_window_sofar = 0 + self._log(DEBUG, 'Max packet in: %d bytes' % max_packet_size) + + def _set_remote_channel(self, chanid, window_size, max_packet_size): + self.remote_chanid = chanid + self.out_window_size = window_size + self.out_max_packet_size = max(max_packet_size, MIN_PACKET_SIZE) + self.active = 1 + self._log(DEBUG, 'Max packet out: %d bytes' % max_packet_size) + + def _request_success(self, m): + self._log(DEBUG, 'Sesch channel %d request ok' % self.chanid) + self.event_ready = True + self.event.set() + return + + def _request_failed(self, m): + self.lock.acquire() + try: + msgs = self._close_internal() + finally: + self.lock.release() + for m in msgs: + if m is not None: + self.transport._send_user_message(m) + + def _feed(self, m): + if type(m) is str: + # passed from _feed_extended + s = m + else: + s = m.get_string() + self.in_buffer.feed(s) + + def _feed_extended(self, m): + code = m.get_int() + s = m.get_string() + if code != 1: + self._log(ERROR, 'unknown extended_data type %d; discarding' % code) + return + if self.combine_stderr: + self._feed(s) + else: + self.in_stderr_buffer.feed(s) + + def _window_adjust(self, m): + nbytes = m.get_int() + self.lock.acquire() + try: + if self.ultra_debug: + self._log(DEBUG, 'window up %d' % nbytes) + self.out_window_size += nbytes + self.out_buffer_cv.notifyAll() + finally: + self.lock.release() + + def _handle_request(self, m): + key = m.get_string() + want_reply = m.get_boolean() + server = self.transport.server_object + ok = False + if key == 'exit-status': + self.exit_status = m.get_int() + self.status_event.set() + ok = True + elif key == 'xon-xoff': + # ignore + ok = True + elif key == 'pty-req': + term = m.get_string() + width = m.get_int() + height = m.get_int() + pixelwidth = m.get_int() + pixelheight = m.get_int() + modes = m.get_string() + if server is None: + ok = False + else: + ok = server.check_channel_pty_request(self, term, width, height, pixelwidth, + pixelheight, modes) + elif key == 'shell': + if server is None: + ok = False + else: + ok = server.check_channel_shell_request(self) + elif key == 'exec': + cmd = m.get_string() + if server is None: + ok = False + else: + ok = server.check_channel_exec_request(self, cmd) + elif key == 'subsystem': + name = m.get_string() + if server is None: + ok = False + else: + ok = server.check_channel_subsystem_request(self, name) + elif key == 'window-change': + width = m.get_int() + height = m.get_int() + pixelwidth = m.get_int() + pixelheight = m.get_int() + if server is None: + ok = False + else: + ok = server.check_channel_window_change_request(self, width, height, pixelwidth, + pixelheight) + elif key == 'x11-req': + single_connection = m.get_boolean() + auth_proto = m.get_string() + auth_cookie = m.get_string() + screen_number = m.get_int() + if server is None: + ok = False + else: + ok = server.check_channel_x11_request(self, single_connection, + auth_proto, auth_cookie, screen_number) + else: + self._log(DEBUG, 'Unhandled channel request "%s"' % key) + ok = False + if want_reply: + m = Message() + if ok: + m.add_byte(chr(MSG_CHANNEL_SUCCESS)) + else: + m.add_byte(chr(MSG_CHANNEL_FAILURE)) + m.add_int(self.remote_chanid) + self.transport._send_user_message(m) + + def _handle_eof(self, m): + self.lock.acquire() + try: + if not self.eof_received: + self.eof_received = True + self.in_buffer.close() + self.in_stderr_buffer.close() + if self._pipe is not None: + self._pipe.set_forever() + finally: + self.lock.release() + self._log(DEBUG, 'EOF received (%s)', self._name) + + def _handle_close(self, m): + self.lock.acquire() + try: + msgs = self._close_internal() + self.transport._unlink_channel(self.chanid) + finally: + self.lock.release() + for m in msgs: + if m is not None: + self.transport._send_user_message(m) + + + ### internals... + + + def _log(self, level, msg, *args): + self.logger.log(level, "[chan " + self._name + "] " + msg, *args) + + def _event_pending(self): + self.event.clear() + self.event_ready = False + + def _wait_for_event(self): + self.event.wait() + assert self.event.isSet() + if self.event_ready: + return + e = self.transport.get_exception() + if e is None: + e = SSHException('Channel closed.') + raise e + + def _set_closed(self): + # you are holding the lock. + self.closed = True + self.in_buffer.close() + self.in_stderr_buffer.close() + self.out_buffer_cv.notifyAll() + # Notify any waiters that we are closed + self.event.set() + self.status_event.set() + if self._pipe is not None: + self._pipe.set_forever() + + def _send_eof(self): + # you are holding the lock. + if self.eof_sent: + return None + m = Message() + m.add_byte(chr(MSG_CHANNEL_EOF)) + m.add_int(self.remote_chanid) + self.eof_sent = True + self._log(DEBUG, 'EOF sent (%s)', self._name) + return m + + def _close_internal(self): + # you are holding the lock. + if not self.active or self.closed: + return None, None + m1 = self._send_eof() + m2 = Message() + m2.add_byte(chr(MSG_CHANNEL_CLOSE)) + m2.add_int(self.remote_chanid) + self._set_closed() + # can't unlink from the Transport yet -- the remote side may still + # try to send meta-data (exit-status, etc) + return m1, m2 + + def _unlink(self): + # server connection could die before we become active: still signal the close! + if self.closed: + return + self.lock.acquire() + try: + self._set_closed() + self.transport._unlink_channel(self.chanid) + finally: + self.lock.release() + + def _check_add_window(self, n): + self.lock.acquire() + try: + if self.closed or self.eof_received or not self.active: + return 0 + if self.ultra_debug: + self._log(DEBUG, 'addwindow %d' % n) + self.in_window_sofar += n + if self.in_window_sofar <= self.in_window_threshold: + return 0 + if self.ultra_debug: + self._log(DEBUG, 'addwindow send %d' % self.in_window_sofar) + out = self.in_window_sofar + self.in_window_sofar = 0 + return out + finally: + self.lock.release() + + def _wait_for_send_window(self, size): + """ + (You are already holding the lock.) + Wait for the send window to open up, and allocate up to C{size} bytes + for transmission. If no space opens up before the timeout, a timeout + exception is raised. Returns the number of bytes available to send + (may be less than requested). + """ + # you are already holding the lock + if self.closed or self.eof_sent: + return 0 + if self.out_window_size == 0: + # should we block? + if self.timeout == 0.0: + raise socket.timeout() + # loop here in case we get woken up but a different thread has filled the buffer + timeout = self.timeout + while self.out_window_size == 0: + if self.closed or self.eof_sent: + return 0 + then = time.time() + self.out_buffer_cv.wait(timeout) + if timeout != None: + timeout -= time.time() - then + if timeout <= 0.0: + raise socket.timeout() + # we have some window to squeeze into + if self.closed or self.eof_sent: + return 0 + if self.out_window_size < size: + size = self.out_window_size + if self.out_max_packet_size - 64 < size: + size = self.out_max_packet_size - 64 + self.out_window_size -= size + if self.ultra_debug: + self._log(DEBUG, 'window down to %d' % self.out_window_size) + return size + + +class ChannelFile (BufferedFile): + """ + A file-like wrapper around L{Channel}. A ChannelFile is created by calling + L{Channel.makefile}. + + @bug: To correctly emulate the file object created from a socket's + C{makefile} method, a L{Channel} and its C{ChannelFile} should be able + to be closed or garbage-collected independently. Currently, closing + the C{ChannelFile} does nothing but flush the buffer. + """ + + def __init__(self, channel, mode = 'r', bufsize = -1): + self.channel = channel + BufferedFile.__init__(self) + self._set_mode(mode, bufsize) + + def __repr__(self): + """ + Returns a string representation of this object, for debugging. + + @rtype: str + """ + return '' + + def _read(self, size): + return self.channel.recv(size) + + def _write(self, data): + self.channel.sendall(data) + return len(data) + + +class ChannelStderrFile (ChannelFile): + def __init__(self, channel, mode = 'r', bufsize = -1): + ChannelFile.__init__(self, channel, mode, bufsize) + + def _read(self, size): + return self.channel.recv_stderr(size) + + def _write(self, data): + self.channel.sendall_stderr(data) + return len(data) + + +# vim: set shiftwidth=4 expandtab : diff --git a/lib/paramiko/client.py b/lib/paramiko/client.py new file mode 100644 index 0000000..4a65477 --- /dev/null +++ b/lib/paramiko/client.py @@ -0,0 +1,498 @@ +# Copyright (C) 2006-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +L{SSHClient}. +""" + +from binascii import hexlify +import getpass +import os +import socket +import warnings + +from paramiko.agent import Agent +from paramiko.common import * +from paramiko.dsskey import DSSKey +from paramiko.hostkeys import HostKeys +from paramiko.resource import ResourceManager +from paramiko.rsakey import RSAKey +from paramiko.ssh_exception import SSHException, BadHostKeyException +from paramiko.transport import Transport + + +SSH_PORT = 22 + +class MissingHostKeyPolicy (object): + """ + Interface for defining the policy that L{SSHClient} should use when the + SSH server's hostname is not in either the system host keys or the + application's keys. Pre-made classes implement policies for automatically + adding the key to the application's L{HostKeys} object (L{AutoAddPolicy}), + and for automatically rejecting the key (L{RejectPolicy}). + + This function may be used to ask the user to verify the key, for example. + """ + + def missing_host_key(self, client, hostname, key): + """ + Called when an L{SSHClient} receives a server key for a server that + isn't in either the system or local L{HostKeys} object. To accept + the key, simply return. To reject, raised an exception (which will + be passed to the calling application). + """ + pass + + +class AutoAddPolicy (MissingHostKeyPolicy): + """ + Policy for automatically adding the hostname and new host key to the + local L{HostKeys} object, and saving it. This is used by L{SSHClient}. + """ + + def missing_host_key(self, client, hostname, key): + client._host_keys.add(hostname, key.get_name(), key) + if client._host_keys_filename is not None: + client.save_host_keys(client._host_keys_filename) + client._log(DEBUG, 'Adding %s host key for %s: %s' % + (key.get_name(), hostname, hexlify(key.get_fingerprint()))) + + +class RejectPolicy (MissingHostKeyPolicy): + """ + Policy for automatically rejecting the unknown hostname & key. This is + used by L{SSHClient}. + """ + + def missing_host_key(self, client, hostname, key): + client._log(DEBUG, 'Rejecting %s host key for %s: %s' % + (key.get_name(), hostname, hexlify(key.get_fingerprint()))) + raise SSHException('Unknown server %s' % hostname) + + +class WarningPolicy (MissingHostKeyPolicy): + """ + Policy for logging a python-style warning for an unknown host key, but + accepting it. This is used by L{SSHClient}. + """ + def missing_host_key(self, client, hostname, key): + warnings.warn('Unknown %s host key for %s: %s' % + (key.get_name(), hostname, hexlify(key.get_fingerprint()))) + + +class SSHClient (object): + """ + A high-level representation of a session with an SSH server. This class + wraps L{Transport}, L{Channel}, and L{SFTPClient} to take care of most + aspects of authenticating and opening channels. A typical use case is:: + + client = SSHClient() + client.load_system_host_keys() + client.connect('ssh.example.com') + stdin, stdout, stderr = client.exec_command('ls -l') + + You may pass in explicit overrides for authentication and server host key + checking. The default mechanism is to try to use local key files or an + SSH agent (if one is running). + + @since: 1.6 + """ + + def __init__(self): + """ + Create a new SSHClient. + """ + self._system_host_keys = HostKeys() + self._host_keys = HostKeys() + self._host_keys_filename = None + self._log_channel = None + self._policy = RejectPolicy() + self._transport = None + self._agent = None + + def load_system_host_keys(self, filename=None): + """ + Load host keys from a system (read-only) file. Host keys read with + this method will not be saved back by L{save_host_keys}. + + This method can be called multiple times. Each new set of host keys + will be merged with the existing set (new replacing old if there are + conflicts). + + If C{filename} is left as C{None}, an attempt will be made to read + keys from the user's local "known hosts" file, as used by OpenSSH, + and no exception will be raised if the file can't be read. This is + probably only useful on posix. + + @param filename: the filename to read, or C{None} + @type filename: str + + @raise IOError: if a filename was provided and the file could not be + read + """ + if filename is None: + # try the user's .ssh key file, and mask exceptions + filename = os.path.expanduser('~/.ssh/known_hosts') + try: + self._system_host_keys.load(filename) + except IOError: + pass + return + self._system_host_keys.load(filename) + + def load_host_keys(self, filename): + """ + Load host keys from a local host-key file. Host keys read with this + method will be checked I{after} keys loaded via L{load_system_host_keys}, + but will be saved back by L{save_host_keys} (so they can be modified). + The missing host key policy L{AutoAddPolicy} adds keys to this set and + saves them, when connecting to a previously-unknown server. + + This method can be called multiple times. Each new set of host keys + will be merged with the existing set (new replacing old if there are + conflicts). When automatically saving, the last hostname is used. + + @param filename: the filename to read + @type filename: str + + @raise IOError: if the filename could not be read + """ + self._host_keys_filename = filename + self._host_keys.load(filename) + + def save_host_keys(self, filename): + """ + Save the host keys back to a file. Only the host keys loaded with + L{load_host_keys} (plus any added directly) will be saved -- not any + host keys loaded with L{load_system_host_keys}. + + @param filename: the filename to save to + @type filename: str + + @raise IOError: if the file could not be written + """ + f = open(filename, 'w') + f.write('# SSH host keys collected by paramiko\n') + for hostname, keys in self._host_keys.iteritems(): + for keytype, key in keys.iteritems(): + f.write('%s %s %s\n' % (hostname, keytype, key.get_base64())) + f.close() + + def get_host_keys(self): + """ + Get the local L{HostKeys} object. This can be used to examine the + local host keys or change them. + + @return: the local host keys + @rtype: L{HostKeys} + """ + return self._host_keys + + def set_log_channel(self, name): + """ + Set the channel for logging. The default is C{"paramiko.transport"} + but it can be set to anything you want. + + @param name: new channel name for logging + @type name: str + """ + self._log_channel = name + + def set_missing_host_key_policy(self, policy): + """ + Set the policy to use when connecting to a server that doesn't have a + host key in either the system or local L{HostKeys} objects. The + default policy is to reject all unknown servers (using L{RejectPolicy}). + You may substitute L{AutoAddPolicy} or write your own policy class. + + @param policy: the policy to use when receiving a host key from a + previously-unknown server + @type policy: L{MissingHostKeyPolicy} + """ + self._policy = policy + + def connect(self, hostname, port=SSH_PORT, username=None, password=None, pkey=None, + key_filename=None, timeout=None, allow_agent=True, look_for_keys=True, + compress=False): + """ + Connect to an SSH server and authenticate to it. The server's host key + is checked against the system host keys (see L{load_system_host_keys}) + and any local host keys (L{load_host_keys}). If the server's hostname + is not found in either set of host keys, the missing host key policy + is used (see L{set_missing_host_key_policy}). The default policy is + to reject the key and raise an L{SSHException}. + + Authentication is attempted in the following order of priority: + + - The C{pkey} or C{key_filename} passed in (if any) + - Any key we can find through an SSH agent + - Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/} + - Plain username/password auth, if a password was given + + If a private key requires a password to unlock it, and a password is + passed in, that password will be used to attempt to unlock the key. + + @param hostname: the server to connect to + @type hostname: str + @param port: the server port to connect to + @type port: int + @param username: the username to authenticate as (defaults to the + current local username) + @type username: str + @param password: a password to use for authentication or for unlocking + a private key + @type password: str + @param pkey: an optional private key to use for authentication + @type pkey: L{PKey} + @param key_filename: the filename, or list of filenames, of optional + private key(s) to try for authentication + @type key_filename: str or list(str) + @param timeout: an optional timeout (in seconds) for the TCP connect + @type timeout: float + @param allow_agent: set to False to disable connecting to the SSH agent + @type allow_agent: bool + @param look_for_keys: set to False to disable searching for discoverable + private key files in C{~/.ssh/} + @type look_for_keys: bool + @param compress: set to True to turn on compression + @type compress: bool + + @raise BadHostKeyException: if the server's host key could not be + verified + @raise AuthenticationException: if authentication failed + @raise SSHException: if there was any other error connecting or + establishing an SSH session + @raise socket.error: if a socket error occurred while connecting + """ + for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM): + if socktype == socket.SOCK_STREAM: + af = family + addr = sockaddr + break + else: + # some OS like AIX don't indicate SOCK_STREAM support, so just guess. :( + af, _, _, _, addr = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM) + sock = socket.socket(af, socket.SOCK_STREAM) + if timeout is not None: + try: + sock.settimeout(timeout) + except: + pass + sock.connect(addr) + t = self._transport = Transport(sock) + t.use_compression(compress=compress) + if self._log_channel is not None: + t.set_log_channel(self._log_channel) + t.start_client() + ResourceManager.register(self, t) + + server_key = t.get_remote_server_key() + keytype = server_key.get_name() + + if port == SSH_PORT: + server_hostkey_name = hostname + else: + server_hostkey_name = "[%s]:%d" % (hostname, port) + our_server_key = self._system_host_keys.get(server_hostkey_name, {}).get(keytype, None) + if our_server_key is None: + our_server_key = self._host_keys.get(server_hostkey_name, {}).get(keytype, None) + if our_server_key is None: + # will raise exception if the key is rejected; let that fall out + self._policy.missing_host_key(self, server_hostkey_name, server_key) + # if the callback returns, assume the key is ok + our_server_key = server_key + + if server_key != our_server_key: + raise BadHostKeyException(hostname, server_key, our_server_key) + + if username is None: + username = getpass.getuser() + + if key_filename is None: + key_filenames = [] + elif isinstance(key_filename, (str, unicode)): + key_filenames = [ key_filename ] + else: + key_filenames = key_filename + self._auth(username, password, pkey, key_filenames, allow_agent, look_for_keys) + + def close(self): + """ + Close this SSHClient and its underlying L{Transport}. + """ + if self._transport is None: + return + self._transport.close() + self._transport = None + + if self._agent != None: + self._agent.close() + self._agent = None + + def exec_command(self, command, bufsize=-1): + """ + Execute a command on the SSH server. A new L{Channel} is opened and + the requested command is executed. The command's input and output + streams are returned as python C{file}-like objects representing + stdin, stdout, and stderr. + + @param command: the command to execute + @type command: str + @param bufsize: interpreted the same way as by the built-in C{file()} function in python + @type bufsize: int + @return: the stdin, stdout, and stderr of the executing command + @rtype: tuple(L{ChannelFile}, L{ChannelFile}, L{ChannelFile}) + + @raise SSHException: if the server fails to execute the command + """ + chan = self._transport.open_session() + chan.exec_command(command) + stdin = chan.makefile('wb', bufsize) + stdout = chan.makefile('rb', bufsize) + stderr = chan.makefile_stderr('rb', bufsize) + return stdin, stdout, stderr + + def invoke_shell(self, term='vt100', width=80, height=24): + """ + Start an interactive shell session on the SSH server. A new L{Channel} + is opened and connected to a pseudo-terminal using the requested + terminal type and size. + + @param term: the terminal type to emulate (for example, C{"vt100"}) + @type term: str + @param width: the width (in characters) of the terminal window + @type width: int + @param height: the height (in characters) of the terminal window + @type height: int + @return: a new channel connected to the remote shell + @rtype: L{Channel} + + @raise SSHException: if the server fails to invoke a shell + """ + chan = self._transport.open_session() + chan.get_pty(term, width, height) + chan.invoke_shell() + return chan + + def open_sftp(self): + """ + Open an SFTP session on the SSH server. + + @return: a new SFTP session object + @rtype: L{SFTPClient} + """ + return self._transport.open_sftp_client() + + def get_transport(self): + """ + Return the underlying L{Transport} object for this SSH connection. + This can be used to perform lower-level tasks, like opening specific + kinds of channels. + + @return: the Transport for this connection + @rtype: L{Transport} + """ + return self._transport + + def _auth(self, username, password, pkey, key_filenames, allow_agent, look_for_keys): + """ + Try, in order: + + - The key passed in, if one was passed in. + - Any key we can find through an SSH agent (if allowed). + - Any "id_rsa" or "id_dsa" key discoverable in ~/.ssh/ (if allowed). + - Plain username/password auth, if a password was given. + + (The password might be needed to unlock a private key.) + """ + saved_exception = None + + if pkey is not None: + try: + self._log(DEBUG, 'Trying SSH key %s' % hexlify(pkey.get_fingerprint())) + self._transport.auth_publickey(username, pkey) + return + except SSHException, e: + saved_exception = e + + for key_filename in key_filenames: + for pkey_class in (RSAKey, DSSKey): + try: + key = pkey_class.from_private_key_file(key_filename, password) + self._log(DEBUG, 'Trying key %s from %s' % (hexlify(key.get_fingerprint()), key_filename)) + self._transport.auth_publickey(username, key) + return + except SSHException, e: + saved_exception = e + + if allow_agent: + if self._agent == None: + self._agent = Agent() + + for key in self._agent.get_keys(): + try: + self._log(DEBUG, 'Trying SSH agent key %s' % hexlify(key.get_fingerprint())) + self._transport.auth_publickey(username, key) + return + except SSHException, e: + saved_exception = e + + keyfiles = [] + rsa_key = os.path.expanduser('~/.ssh/id_rsa') + dsa_key = os.path.expanduser('~/.ssh/id_dsa') + if os.path.isfile(rsa_key): + keyfiles.append((RSAKey, rsa_key)) + if os.path.isfile(dsa_key): + keyfiles.append((DSSKey, dsa_key)) + # look in ~/ssh/ for windows users: + rsa_key = os.path.expanduser('~/ssh/id_rsa') + dsa_key = os.path.expanduser('~/ssh/id_dsa') + if os.path.isfile(rsa_key): + keyfiles.append((RSAKey, rsa_key)) + if os.path.isfile(dsa_key): + keyfiles.append((DSSKey, dsa_key)) + + if not look_for_keys: + keyfiles = [] + + for pkey_class, filename in keyfiles: + try: + key = pkey_class.from_private_key_file(filename, password) + self._log(DEBUG, 'Trying discovered key %s in %s' % (hexlify(key.get_fingerprint()), filename)) + self._transport.auth_publickey(username, key) + return + except SSHException, e: + saved_exception = e + except IOError, e: + saved_exception = e + + if password is not None: + try: + self._transport.auth_password(username, password) + return + except SSHException, e: + saved_exception = e + + # if we got an auth-failed exception earlier, re-raise it + if saved_exception is not None: + raise saved_exception + raise SSHException('No authentication methods available') + + def _log(self, level, msg): + self._transport._log(level, msg) + diff --git a/lib/paramiko/common.py b/lib/paramiko/common.py new file mode 100644 index 0000000..3323f0a --- /dev/null +++ b/lib/paramiko/common.py @@ -0,0 +1,126 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Common constants and global variables. +""" + +MSG_DISCONNECT, MSG_IGNORE, MSG_UNIMPLEMENTED, MSG_DEBUG, MSG_SERVICE_REQUEST, \ + MSG_SERVICE_ACCEPT = range(1, 7) +MSG_KEXINIT, MSG_NEWKEYS = range(20, 22) +MSG_USERAUTH_REQUEST, MSG_USERAUTH_FAILURE, MSG_USERAUTH_SUCCESS, \ + MSG_USERAUTH_BANNER = range(50, 54) +MSG_USERAUTH_PK_OK = 60 +MSG_USERAUTH_INFO_REQUEST, MSG_USERAUTH_INFO_RESPONSE = range(60, 62) +MSG_GLOBAL_REQUEST, MSG_REQUEST_SUCCESS, MSG_REQUEST_FAILURE = range(80, 83) +MSG_CHANNEL_OPEN, MSG_CHANNEL_OPEN_SUCCESS, MSG_CHANNEL_OPEN_FAILURE, \ + MSG_CHANNEL_WINDOW_ADJUST, MSG_CHANNEL_DATA, MSG_CHANNEL_EXTENDED_DATA, \ + MSG_CHANNEL_EOF, MSG_CHANNEL_CLOSE, MSG_CHANNEL_REQUEST, \ + MSG_CHANNEL_SUCCESS, MSG_CHANNEL_FAILURE = range(90, 101) + + +# for debugging: +MSG_NAMES = { + MSG_DISCONNECT: 'disconnect', + MSG_IGNORE: 'ignore', + MSG_UNIMPLEMENTED: 'unimplemented', + MSG_DEBUG: 'debug', + MSG_SERVICE_REQUEST: 'service-request', + MSG_SERVICE_ACCEPT: 'service-accept', + MSG_KEXINIT: 'kexinit', + MSG_NEWKEYS: 'newkeys', + 30: 'kex30', + 31: 'kex31', + 32: 'kex32', + 33: 'kex33', + 34: 'kex34', + MSG_USERAUTH_REQUEST: 'userauth-request', + MSG_USERAUTH_FAILURE: 'userauth-failure', + MSG_USERAUTH_SUCCESS: 'userauth-success', + MSG_USERAUTH_BANNER: 'userauth--banner', + MSG_USERAUTH_PK_OK: 'userauth-60(pk-ok/info-request)', + MSG_USERAUTH_INFO_RESPONSE: 'userauth-info-response', + MSG_GLOBAL_REQUEST: 'global-request', + MSG_REQUEST_SUCCESS: 'request-success', + MSG_REQUEST_FAILURE: 'request-failure', + MSG_CHANNEL_OPEN: 'channel-open', + MSG_CHANNEL_OPEN_SUCCESS: 'channel-open-success', + MSG_CHANNEL_OPEN_FAILURE: 'channel-open-failure', + MSG_CHANNEL_WINDOW_ADJUST: 'channel-window-adjust', + MSG_CHANNEL_DATA: 'channel-data', + MSG_CHANNEL_EXTENDED_DATA: 'channel-extended-data', + MSG_CHANNEL_EOF: 'channel-eof', + MSG_CHANNEL_CLOSE: 'channel-close', + MSG_CHANNEL_REQUEST: 'channel-request', + MSG_CHANNEL_SUCCESS: 'channel-success', + MSG_CHANNEL_FAILURE: 'channel-failure' + } + + +# authentication request return codes: +AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED = range(3) + + +# channel request failed reasons: +(OPEN_SUCCEEDED, + OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, + OPEN_FAILED_CONNECT_FAILED, + OPEN_FAILED_UNKNOWN_CHANNEL_TYPE, + OPEN_FAILED_RESOURCE_SHORTAGE) = range(0, 5) + + +CONNECTION_FAILED_CODE = { + 1: 'Administratively prohibited', + 2: 'Connect failed', + 3: 'Unknown channel type', + 4: 'Resource shortage' +} + + +DISCONNECT_SERVICE_NOT_AVAILABLE, DISCONNECT_AUTH_CANCELLED_BY_USER, \ + DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE = 7, 13, 14 + +from Crypto import Random + +# keep a crypto-strong PRNG nearby +rng = Random.new() + +import sys +if sys.version_info < (2, 3): + try: + import logging + except: + import logging22 as logging + import select + PY22 = True + + import socket + if not hasattr(socket, 'timeout'): + class timeout(socket.error): pass + socket.timeout = timeout + del timeout +else: + import logging + PY22 = False + + +DEBUG = logging.DEBUG +INFO = logging.INFO +WARNING = logging.WARNING +ERROR = logging.ERROR +CRITICAL = logging.CRITICAL diff --git a/lib/paramiko/compress.py b/lib/paramiko/compress.py new file mode 100644 index 0000000..40b430f --- /dev/null +++ b/lib/paramiko/compress.py @@ -0,0 +1,39 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Compression implementations for a Transport. +""" + +import zlib + + +class ZlibCompressor (object): + def __init__(self): + self.z = zlib.compressobj(9) + + def __call__(self, data): + return self.z.compress(data) + self.z.flush(zlib.Z_FULL_FLUSH) + + +class ZlibDecompressor (object): + def __init__(self): + self.z = zlib.decompressobj() + + def __call__(self, data): + return self.z.decompress(data) diff --git a/lib/paramiko/config.py b/lib/paramiko/config.py new file mode 100644 index 0000000..2a2cbff --- /dev/null +++ b/lib/paramiko/config.py @@ -0,0 +1,110 @@ +# Copyright (C) 2006-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +L{SSHConfig}. +""" + +import fnmatch + + +class SSHConfig (object): + """ + Representation of config information as stored in the format used by + OpenSSH. Queries can be made via L{lookup}. The format is described in + OpenSSH's C{ssh_config} man page. This class is provided primarily as a + convenience to posix users (since the OpenSSH format is a de-facto + standard on posix) but should work fine on Windows too. + + @since: 1.6 + """ + + def __init__(self): + """ + Create a new OpenSSH config object. + """ + self._config = [ { 'host': '*' } ] + + def parse(self, file_obj): + """ + Read an OpenSSH config from the given file object. + + @param file_obj: a file-like object to read the config file from + @type file_obj: file + """ + configs = [self._config[0]] + for line in file_obj: + line = line.rstrip('\n').lstrip() + if (line == '') or (line[0] == '#'): + continue + if '=' in line: + key, value = line.split('=', 1) + key = key.strip().lower() + else: + # find first whitespace, and split there + i = 0 + while (i < len(line)) and not line[i].isspace(): + i += 1 + if i == len(line): + raise Exception('Unparsable line: %r' % line) + key = line[:i].lower() + value = line[i:].lstrip() + + if key == 'host': + del configs[:] + # the value may be multiple hosts, space-delimited + for host in value.split(): + # do we have a pre-existing host config to append to? + matches = [c for c in self._config if c['host'] == host] + if len(matches) > 0: + configs.append(matches[0]) + else: + config = { 'host': host } + self._config.append(config) + configs.append(config) + else: + for config in configs: + config[key] = value + + def lookup(self, hostname): + """ + Return a dict of config options for a given hostname. + + The host-matching rules of OpenSSH's C{ssh_config} man page are used, + which means that all configuration options from matching host + specifications are merged, with more specific hostmasks taking + precedence. In other words, if C{"Port"} is set under C{"Host *"} + and also C{"Host *.example.com"}, and the lookup is for + C{"ssh.example.com"}, then the port entry for C{"Host *.example.com"} + will win out. + + The keys in the returned dict are all normalized to lowercase (look for + C{"port"}, not C{"Port"}. No other processing is done to the keys or + values. + + @param hostname: the hostname to lookup + @type hostname: str + """ + matches = [x for x in self._config if fnmatch.fnmatch(hostname, x['host'])] + # sort in order of shortest match (usually '*') to longest + matches.sort(lambda x,y: cmp(len(x['host']), len(y['host']))) + ret = {} + for m in matches: + ret.update(m) + del ret['host'] + return ret diff --git a/lib/paramiko/dsskey.py b/lib/paramiko/dsskey.py new file mode 100644 index 0000000..53ca92b --- /dev/null +++ b/lib/paramiko/dsskey.py @@ -0,0 +1,196 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +L{DSSKey} +""" + +from Crypto.PublicKey import DSA +from Crypto.Hash import SHA + +from paramiko.common import * +from paramiko import util +from paramiko.ssh_exception import SSHException +from paramiko.message import Message +from paramiko.ber import BER, BERException +from paramiko.pkey import PKey + + +class DSSKey (PKey): + """ + Representation of a DSS key which can be used to sign an verify SSH2 + data. + """ + + def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None): + self.p = None + self.q = None + self.g = None + self.y = None + self.x = None + if file_obj is not None: + self._from_private_key(file_obj, password) + return + if filename is not None: + self._from_private_key_file(filename, password) + return + if (msg is None) and (data is not None): + msg = Message(data) + if vals is not None: + self.p, self.q, self.g, self.y = vals + else: + if msg is None: + raise SSHException('Key object may not be empty') + if msg.get_string() != 'ssh-dss': + raise SSHException('Invalid key') + self.p = msg.get_mpint() + self.q = msg.get_mpint() + self.g = msg.get_mpint() + self.y = msg.get_mpint() + self.size = util.bit_length(self.p) + + def __str__(self): + m = Message() + m.add_string('ssh-dss') + m.add_mpint(self.p) + m.add_mpint(self.q) + m.add_mpint(self.g) + m.add_mpint(self.y) + return str(m) + + def __hash__(self): + h = hash(self.get_name()) + h = h * 37 + hash(self.p) + h = h * 37 + hash(self.q) + h = h * 37 + hash(self.g) + h = h * 37 + hash(self.y) + # h might be a long by now... + return hash(h) + + def get_name(self): + return 'ssh-dss' + + def get_bits(self): + return self.size + + def can_sign(self): + return self.x is not None + + def sign_ssh_data(self, rng, data): + digest = SHA.new(data).digest() + dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q), long(self.x))) + # generate a suitable k + qsize = len(util.deflate_long(self.q, 0)) + while True: + k = util.inflate_long(rng.read(qsize), 1) + if (k > 2) and (k < self.q): + break + r, s = dss.sign(util.inflate_long(digest, 1), k) + m = Message() + m.add_string('ssh-dss') + # apparently, in rare cases, r or s may be shorter than 20 bytes! + rstr = util.deflate_long(r, 0) + sstr = util.deflate_long(s, 0) + if len(rstr) < 20: + rstr = '\x00' * (20 - len(rstr)) + rstr + if len(sstr) < 20: + sstr = '\x00' * (20 - len(sstr)) + sstr + m.add_string(rstr + sstr) + return m + + def verify_ssh_sig(self, data, msg): + if len(str(msg)) == 40: + # spies.com bug: signature has no header + sig = str(msg) + else: + kind = msg.get_string() + if kind != 'ssh-dss': + return 0 + sig = msg.get_string() + + # pull out (r, s) which are NOT encoded as mpints + sigR = util.inflate_long(sig[:20], 1) + sigS = util.inflate_long(sig[20:], 1) + sigM = util.inflate_long(SHA.new(data).digest(), 1) + + dss = DSA.construct((long(self.y), long(self.g), long(self.p), long(self.q))) + return dss.verify(sigM, (sigR, sigS)) + + def _encode_key(self): + if self.x is None: + raise SSHException('Not enough key information') + keylist = [ 0, self.p, self.q, self.g, self.y, self.x ] + try: + b = BER() + b.encode(keylist) + except BERException: + raise SSHException('Unable to create ber encoding of key') + return str(b) + + def write_private_key_file(self, filename, password=None): + self._write_private_key_file('DSA', filename, self._encode_key(), password) + + def write_private_key(self, file_obj, password=None): + self._write_private_key('DSA', file_obj, self._encode_key(), password) + + def generate(bits=1024, progress_func=None): + """ + Generate a new private DSS key. This factory function can be used to + generate a new host key or authentication key. + + @param bits: number of bits the generated key should be. + @type bits: int + @param progress_func: an optional function to call at key points in + key generation (used by C{pyCrypto.PublicKey}). + @type progress_func: function + @return: new private key + @rtype: L{DSSKey} + """ + dsa = DSA.generate(bits, rng.read, progress_func) + key = DSSKey(vals=(dsa.p, dsa.q, dsa.g, dsa.y)) + key.x = dsa.x + return key + generate = staticmethod(generate) + + + ### internals... + + + def _from_private_key_file(self, filename, password): + data = self._read_private_key_file('DSA', filename, password) + self._decode_key(data) + + def _from_private_key(self, file_obj, password): + data = self._read_private_key('DSA', file_obj, password) + self._decode_key(data) + + def _decode_key(self, data): + # private key file contains: + # DSAPrivateKey = { version = 0, p, q, g, y, x } + try: + keylist = BER(data).decode() + except BERException, x: + raise SSHException('Unable to parse key file: ' + str(x)) + if (type(keylist) is not list) or (len(keylist) < 6) or (keylist[0] != 0): + raise SSHException('not a valid DSA private key file (bad ber encoding)') + self.p = keylist[1] + self.q = keylist[2] + self.g = keylist[3] + self.y = keylist[4] + self.x = keylist[5] + self.size = util.bit_length(self.p) diff --git a/lib/paramiko/file.py b/lib/paramiko/file.py new file mode 100644 index 0000000..d4aec8e --- /dev/null +++ b/lib/paramiko/file.py @@ -0,0 +1,456 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +BufferedFile. +""" + +from cStringIO import StringIO + + +class BufferedFile (object): + """ + Reusable base class to implement python-style file buffering around a + simpler stream. + """ + + _DEFAULT_BUFSIZE = 8192 + + SEEK_SET = 0 + SEEK_CUR = 1 + SEEK_END = 2 + + FLAG_READ = 0x1 + FLAG_WRITE = 0x2 + FLAG_APPEND = 0x4 + FLAG_BINARY = 0x10 + FLAG_BUFFERED = 0x20 + FLAG_LINE_BUFFERED = 0x40 + FLAG_UNIVERSAL_NEWLINE = 0x80 + + def __init__(self): + self.newlines = None + self._flags = 0 + self._bufsize = self._DEFAULT_BUFSIZE + self._wbuffer = StringIO() + self._rbuffer = '' + self._at_trailing_cr = False + self._closed = False + # pos - position within the file, according to the user + # realpos - position according the OS + # (these may be different because we buffer for line reading) + self._pos = self._realpos = 0 + # size only matters for seekable files + self._size = 0 + + def __del__(self): + self.close() + + def __iter__(self): + """ + Returns an iterator that can be used to iterate over the lines in this + file. This iterator happens to return the file itself, since a file is + its own iterator. + + @raise ValueError: if the file is closed. + + @return: an interator. + @rtype: iterator + """ + if self._closed: + raise ValueError('I/O operation on closed file') + return self + + def close(self): + """ + Close the file. Future read and write operations will fail. + """ + self.flush() + self._closed = True + + def flush(self): + """ + Write out any data in the write buffer. This may do nothing if write + buffering is not turned on. + """ + self._write_all(self._wbuffer.getvalue()) + self._wbuffer = StringIO() + return + + def next(self): + """ + Returns the next line from the input, or raises L{StopIteration} when + EOF is hit. Unlike python file objects, it's okay to mix calls to + C{next} and L{readline}. + + @raise StopIteration: when the end of the file is reached. + + @return: a line read from the file. + @rtype: str + """ + line = self.readline() + if not line: + raise StopIteration + return line + + def read(self, size=None): + """ + Read at most C{size} bytes from the file (less if we hit the end of the + file first). If the C{size} argument is negative or omitted, read all + the remaining data in the file. + + @param size: maximum number of bytes to read + @type size: int + @return: data read from the file, or an empty string if EOF was + encountered immediately + @rtype: str + """ + if self._closed: + raise IOError('File is closed') + if not (self._flags & self.FLAG_READ): + raise IOError('File is not open for reading') + if (size is None) or (size < 0): + # go for broke + result = self._rbuffer + self._rbuffer = '' + self._pos += len(result) + while True: + try: + new_data = self._read(self._DEFAULT_BUFSIZE) + except EOFError: + new_data = None + if (new_data is None) or (len(new_data) == 0): + break + result += new_data + self._realpos += len(new_data) + self._pos += len(new_data) + return result + if size <= len(self._rbuffer): + result = self._rbuffer[:size] + self._rbuffer = self._rbuffer[size:] + self._pos += len(result) + return result + while len(self._rbuffer) < size: + read_size = size - len(self._rbuffer) + if self._flags & self.FLAG_BUFFERED: + read_size = max(self._bufsize, read_size) + try: + new_data = self._read(read_size) + except EOFError: + new_data = None + if (new_data is None) or (len(new_data) == 0): + break + self._rbuffer += new_data + self._realpos += len(new_data) + result = self._rbuffer[:size] + self._rbuffer = self._rbuffer[size:] + self._pos += len(result) + return result + + def readline(self, size=None): + """ + Read one entire line from the file. A trailing newline character is + kept in the string (but may be absent when a file ends with an + incomplete line). If the size argument is present and non-negative, it + is a maximum byte count (including the trailing newline) and an + incomplete line may be returned. An empty string is returned only when + EOF is encountered immediately. + + @note: Unlike stdio's C{fgets()}, the returned string contains null + characters (C{'\\0'}) if they occurred in the input. + + @param size: maximum length of returned string. + @type size: int + @return: next line of the file, or an empty string if the end of the + file has been reached. + @rtype: str + """ + # it's almost silly how complex this function is. + if self._closed: + raise IOError('File is closed') + if not (self._flags & self.FLAG_READ): + raise IOError('File not open for reading') + line = self._rbuffer + while True: + if self._at_trailing_cr and (self._flags & self.FLAG_UNIVERSAL_NEWLINE) and (len(line) > 0): + # edge case: the newline may be '\r\n' and we may have read + # only the first '\r' last time. + if line[0] == '\n': + line = line[1:] + self._record_newline('\r\n') + else: + self._record_newline('\r') + self._at_trailing_cr = False + # check size before looking for a linefeed, in case we already have + # enough. + if (size is not None) and (size >= 0): + if len(line) >= size: + # truncate line and return + self._rbuffer = line[size:] + line = line[:size] + self._pos += len(line) + return line + n = size - len(line) + else: + n = self._bufsize + if ('\n' in line) or ((self._flags & self.FLAG_UNIVERSAL_NEWLINE) and ('\r' in line)): + break + try: + new_data = self._read(n) + except EOFError: + new_data = None + if (new_data is None) or (len(new_data) == 0): + self._rbuffer = '' + self._pos += len(line) + return line + line += new_data + self._realpos += len(new_data) + # find the newline + pos = line.find('\n') + if self._flags & self.FLAG_UNIVERSAL_NEWLINE: + rpos = line.find('\r') + if (rpos >= 0) and ((rpos < pos) or (pos < 0)): + pos = rpos + xpos = pos + 1 + if (line[pos] == '\r') and (xpos < len(line)) and (line[xpos] == '\n'): + xpos += 1 + self._rbuffer = line[xpos:] + lf = line[pos:xpos] + line = line[:pos] + '\n' + if (len(self._rbuffer) == 0) and (lf == '\r'): + # we could read the line up to a '\r' and there could still be a + # '\n' following that we read next time. note that and eat it. + self._at_trailing_cr = True + else: + self._record_newline(lf) + self._pos += len(line) + return line + + def readlines(self, sizehint=None): + """ + Read all remaining lines using L{readline} and return them as a list. + If the optional C{sizehint} argument is present, instead of reading up + to EOF, whole lines totalling approximately sizehint bytes (possibly + after rounding up to an internal buffer size) are read. + + @param sizehint: desired maximum number of bytes to read. + @type sizehint: int + @return: list of lines read from the file. + @rtype: list + """ + lines = [] + bytes = 0 + while True: + line = self.readline() + if len(line) == 0: + break + lines.append(line) + bytes += len(line) + if (sizehint is not None) and (bytes >= sizehint): + break + return lines + + def seek(self, offset, whence=0): + """ + Set the file's current position, like stdio's C{fseek}. Not all file + objects support seeking. + + @note: If a file is opened in append mode (C{'a'} or C{'a+'}), any seek + operations will be undone at the next write (as the file position + will move back to the end of the file). + + @param offset: position to move to within the file, relative to + C{whence}. + @type offset: int + @param whence: type of movement: 0 = absolute; 1 = relative to the + current position; 2 = relative to the end of the file. + @type whence: int + + @raise IOError: if the file doesn't support random access. + """ + raise IOError('File does not support seeking.') + + def tell(self): + """ + Return the file's current position. This may not be accurate or + useful if the underlying file doesn't support random access, or was + opened in append mode. + + @return: file position (in bytes). + @rtype: int + """ + return self._pos + + def write(self, data): + """ + Write data to the file. If write buffering is on (C{bufsize} was + specified and non-zero), some or all of the data may not actually be + written yet. (Use L{flush} or L{close} to force buffered data to be + written out.) + + @param data: data to write. + @type data: str + """ + if self._closed: + raise IOError('File is closed') + if not (self._flags & self.FLAG_WRITE): + raise IOError('File not open for writing') + if not (self._flags & self.FLAG_BUFFERED): + self._write_all(data) + return + self._wbuffer.write(data) + if self._flags & self.FLAG_LINE_BUFFERED: + # only scan the new data for linefeed, to avoid wasting time. + last_newline_pos = data.rfind('\n') + if last_newline_pos >= 0: + wbuf = self._wbuffer.getvalue() + last_newline_pos += len(wbuf) - len(data) + self._write_all(wbuf[:last_newline_pos + 1]) + self._wbuffer = StringIO() + self._wbuffer.write(wbuf[last_newline_pos + 1:]) + return + # even if we're line buffering, if the buffer has grown past the + # buffer size, force a flush. + if self._wbuffer.tell() >= self._bufsize: + self.flush() + return + + def writelines(self, sequence): + """ + Write a sequence of strings to the file. The sequence can be any + iterable object producing strings, typically a list of strings. (The + name is intended to match L{readlines}; C{writelines} does not add line + separators.) + + @param sequence: an iterable sequence of strings. + @type sequence: sequence + """ + for line in sequence: + self.write(line) + return + + def xreadlines(self): + """ + Identical to C{iter(f)}. This is a deprecated file interface that + predates python iterator support. + + @return: an iterator. + @rtype: iterator + """ + return self + + + ### overrides... + + + def _read(self, size): + """ + I{(subclass override)} + Read data from the stream. Return C{None} or raise C{EOFError} to + indicate EOF. + """ + raise EOFError() + + def _write(self, data): + """ + I{(subclass override)} + Write data into the stream. + """ + raise IOError('write not implemented') + + def _get_size(self): + """ + I{(subclass override)} + Return the size of the file. This is called from within L{_set_mode} + if the file is opened in append mode, so the file position can be + tracked and L{seek} and L{tell} will work correctly. If the file is + a stream that can't be randomly accessed, you don't need to override + this method, + """ + return 0 + + + ### internals... + + + def _set_mode(self, mode='r', bufsize=-1): + """ + Subclasses call this method to initialize the BufferedFile. + """ + # set bufsize in any event, because it's used for readline(). + self._bufsize = self._DEFAULT_BUFSIZE + if bufsize < 0: + # do no buffering by default, because otherwise writes will get + # buffered in a way that will probably confuse people. + bufsize = 0 + if bufsize == 1: + # apparently, line buffering only affects writes. reads are only + # buffered if you call readline (directly or indirectly: iterating + # over a file will indirectly call readline). + self._flags |= self.FLAG_BUFFERED | self.FLAG_LINE_BUFFERED + elif bufsize > 1: + self._bufsize = bufsize + self._flags |= self.FLAG_BUFFERED + self._flags &= ~self.FLAG_LINE_BUFFERED + elif bufsize == 0: + # unbuffered + self._flags &= ~(self.FLAG_BUFFERED | self.FLAG_LINE_BUFFERED) + + if ('r' in mode) or ('+' in mode): + self._flags |= self.FLAG_READ + if ('w' in mode) or ('+' in mode): + self._flags |= self.FLAG_WRITE + if ('a' in mode): + self._flags |= self.FLAG_WRITE | self.FLAG_APPEND + self._size = self._get_size() + self._pos = self._realpos = self._size + if ('b' in mode): + self._flags |= self.FLAG_BINARY + if ('U' in mode): + self._flags |= self.FLAG_UNIVERSAL_NEWLINE + # built-in file objects have this attribute to store which kinds of + # line terminations they've seen: + # + self.newlines = None + + def _write_all(self, data): + # the underlying stream may be something that does partial writes (like + # a socket). + while len(data) > 0: + count = self._write(data) + data = data[count:] + if self._flags & self.FLAG_APPEND: + self._size += count + self._pos = self._realpos = self._size + else: + self._pos += count + self._realpos += count + return None + + def _record_newline(self, newline): + # silliness about tracking what kinds of newlines we've seen. + # i don't understand why it can be None, a string, or a tuple, instead + # of just always being a tuple, but we'll emulate that behavior anyway. + if not (self._flags & self.FLAG_UNIVERSAL_NEWLINE): + return + if self.newlines is None: + self.newlines = newline + elif (type(self.newlines) is str) and (self.newlines != newline): + self.newlines = (self.newlines, newline) + elif newline not in self.newlines: + self.newlines += (newline,) diff --git a/lib/paramiko/hostkeys.py b/lib/paramiko/hostkeys.py new file mode 100644 index 0000000..70ccf43 --- /dev/null +++ b/lib/paramiko/hostkeys.py @@ -0,0 +1,316 @@ +# Copyright (C) 2006-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +L{HostKeys} +""" + +import base64 +from Crypto.Hash import SHA, HMAC +import UserDict + +from paramiko.common import * +from paramiko.dsskey import DSSKey +from paramiko.rsakey import RSAKey + + +class HostKeyEntry: + """ + Representation of a line in an OpenSSH-style "known hosts" file. + """ + + def __init__(self, hostnames=None, key=None): + self.valid = (hostnames is not None) and (key is not None) + self.hostnames = hostnames + self.key = key + + def from_line(cls, line): + """ + Parses the given line of text to find the names for the host, + the type of key, and the key data. The line is expected to be in the + format used by the openssh known_hosts file. + + Lines are expected to not have leading or trailing whitespace. + We don't bother to check for comments or empty lines. All of + that should be taken care of before sending the line to us. + + @param line: a line from an OpenSSH known_hosts file + @type line: str + """ + fields = line.split(' ') + if len(fields) < 3: + # Bad number of fields + return None + fields = fields[:3] + + names, keytype, key = fields + names = names.split(',') + + # Decide what kind of key we're looking at and create an object + # to hold it accordingly. + if keytype == 'ssh-rsa': + key = RSAKey(data=base64.decodestring(key)) + elif keytype == 'ssh-dss': + key = DSSKey(data=base64.decodestring(key)) + else: + return None + + return cls(names, key) + from_line = classmethod(from_line) + + def to_line(self): + """ + Returns a string in OpenSSH known_hosts file format, or None if + the object is not in a valid state. A trailing newline is + included. + """ + if self.valid: + return '%s %s %s\n' % (','.join(self.hostnames), self.key.get_name(), + self.key.get_base64()) + return None + + def __repr__(self): + return '' % (self.hostnames, self.key) + + +class HostKeys (UserDict.DictMixin): + """ + Representation of an openssh-style "known hosts" file. Host keys can be + read from one or more files, and then individual hosts can be looked up to + verify server keys during SSH negotiation. + + A HostKeys object can be treated like a dict; any dict lookup is equivalent + to calling L{lookup}. + + @since: 1.5.3 + """ + + def __init__(self, filename=None): + """ + Create a new HostKeys object, optionally loading keys from an openssh + style host-key file. + + @param filename: filename to load host keys from, or C{None} + @type filename: str + """ + # emulate a dict of { hostname: { keytype: PKey } } + self._entries = [] + if filename is not None: + self.load(filename) + + def add(self, hostname, keytype, key): + """ + Add a host key entry to the table. Any existing entry for a + C{(hostname, keytype)} pair will be replaced. + + @param hostname: the hostname (or IP) to add + @type hostname: str + @param keytype: key type (C{"ssh-rsa"} or C{"ssh-dss"}) + @type keytype: str + @param key: the key to add + @type key: L{PKey} + """ + for e in self._entries: + if (hostname in e.hostnames) and (e.key.get_name() == keytype): + e.key = key + return + self._entries.append(HostKeyEntry([hostname], key)) + + def load(self, filename): + """ + Read a file of known SSH host keys, in the format used by openssh. + This type of file unfortunately doesn't exist on Windows, but on + posix, it will usually be stored in + C{os.path.expanduser("~/.ssh/known_hosts")}. + + If this method is called multiple times, the host keys are merged, + not cleared. So multiple calls to C{load} will just call L{add}, + replacing any existing entries and adding new ones. + + @param filename: name of the file to read host keys from + @type filename: str + + @raise IOError: if there was an error reading the file + """ + f = open(filename, 'r') + for line in f: + line = line.strip() + if (len(line) == 0) or (line[0] == '#'): + continue + e = HostKeyEntry.from_line(line) + if e is not None: + self._entries.append(e) + f.close() + + def save(self, filename): + """ + Save host keys into a file, in the format used by openssh. The order of + keys in the file will be preserved when possible (if these keys were + loaded from a file originally). The single exception is that combined + lines will be split into individual key lines, which is arguably a bug. + + @param filename: name of the file to write + @type filename: str + + @raise IOError: if there was an error writing the file + + @since: 1.6.1 + """ + f = open(filename, 'w') + for e in self._entries: + line = e.to_line() + if line: + f.write(line) + f.close() + + def lookup(self, hostname): + """ + Find a hostkey entry for a given hostname or IP. If no entry is found, + C{None} is returned. Otherwise a dictionary of keytype to key is + returned. The keytype will be either C{"ssh-rsa"} or C{"ssh-dss"}. + + @param hostname: the hostname (or IP) to lookup + @type hostname: str + @return: keys associated with this host (or C{None}) + @rtype: dict(str, L{PKey}) + """ + class SubDict (UserDict.DictMixin): + def __init__(self, hostname, entries, hostkeys): + self._hostname = hostname + self._entries = entries + self._hostkeys = hostkeys + + def __getitem__(self, key): + for e in self._entries: + if e.key.get_name() == key: + return e.key + raise KeyError(key) + + def __setitem__(self, key, val): + for e in self._entries: + if e.key is None: + continue + if e.key.get_name() == key: + # replace + e.key = val + break + else: + # add a new one + e = HostKeyEntry([hostname], val) + self._entries.append(e) + self._hostkeys._entries.append(e) + + def keys(self): + return [e.key.get_name() for e in self._entries if e.key is not None] + + entries = [] + for e in self._entries: + for h in e.hostnames: + if (h.startswith('|1|') and (self.hash_host(hostname, h) == h)) or (h == hostname): + entries.append(e) + if len(entries) == 0: + return None + return SubDict(hostname, entries, self) + + def check(self, hostname, key): + """ + Return True if the given key is associated with the given hostname + in this dictionary. + + @param hostname: hostname (or IP) of the SSH server + @type hostname: str + @param key: the key to check + @type key: L{PKey} + @return: C{True} if the key is associated with the hostname; C{False} + if not + @rtype: bool + """ + k = self.lookup(hostname) + if k is None: + return False + host_key = k.get(key.get_name(), None) + if host_key is None: + return False + return str(host_key) == str(key) + + def clear(self): + """ + Remove all host keys from the dictionary. + """ + self._entries = [] + + def __getitem__(self, key): + ret = self.lookup(key) + if ret is None: + raise KeyError(key) + return ret + + def __setitem__(self, hostname, entry): + # don't use this please. + if len(entry) == 0: + self._entries.append(HostKeyEntry([hostname], None)) + return + for key_type in entry.keys(): + found = False + for e in self._entries: + if (hostname in e.hostnames) and (e.key.get_name() == key_type): + # replace + e.key = entry[key_type] + found = True + if not found: + self._entries.append(HostKeyEntry([hostname], entry[key_type])) + + def keys(self): + # python 2.4 sets would be nice here. + ret = [] + for e in self._entries: + for h in e.hostnames: + if h not in ret: + ret.append(h) + return ret + + def values(self): + ret = [] + for k in self.keys(): + ret.append(self.lookup(k)) + return ret + + def hash_host(hostname, salt=None): + """ + Return a "hashed" form of the hostname, as used by openssh when storing + hashed hostnames in the known_hosts file. + + @param hostname: the hostname to hash + @type hostname: str + @param salt: optional salt to use when hashing (must be 20 bytes long) + @type salt: str + @return: the hashed hostname + @rtype: str + """ + if salt is None: + salt = rng.read(SHA.digest_size) + else: + if salt.startswith('|1|'): + salt = salt.split('|')[2] + salt = base64.decodestring(salt) + assert len(salt) == SHA.digest_size + hmac = HMAC.HMAC(salt, hostname, SHA).digest() + hostkey = '|1|%s|%s' % (base64.encodestring(salt), base64.encodestring(hmac)) + return hostkey.replace('\n', '') + hash_host = staticmethod(hash_host) + diff --git a/lib/paramiko/kex_gex.py b/lib/paramiko/kex_gex.py new file mode 100644 index 0000000..9c98339 --- /dev/null +++ b/lib/paramiko/kex_gex.py @@ -0,0 +1,243 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Variant on L{KexGroup1 } where the prime "p" and +generator "g" are provided by the server. A bit more work is required on the +client side, and a B{lot} more on the server side. +""" + +from Crypto.Hash import SHA +from Crypto.Util import number + +from paramiko.common import * +from paramiko import util +from paramiko.message import Message +from paramiko.ssh_exception import SSHException + + +_MSG_KEXDH_GEX_REQUEST_OLD, _MSG_KEXDH_GEX_GROUP, _MSG_KEXDH_GEX_INIT, \ + _MSG_KEXDH_GEX_REPLY, _MSG_KEXDH_GEX_REQUEST = range(30, 35) + + +class KexGex (object): + + name = 'diffie-hellman-group-exchange-sha1' + min_bits = 1024 + max_bits = 8192 + preferred_bits = 2048 + + def __init__(self, transport): + self.transport = transport + self.p = None + self.q = None + self.g = None + self.x = None + self.e = None + self.f = None + self.old_style = False + + def start_kex(self, _test_old_style=False): + if self.transport.server_mode: + self.transport._expect_packet(_MSG_KEXDH_GEX_REQUEST, _MSG_KEXDH_GEX_REQUEST_OLD) + return + # request a bit range: we accept (min_bits) to (max_bits), but prefer + # (preferred_bits). according to the spec, we shouldn't pull the + # minimum up above 1024. + m = Message() + if _test_old_style: + # only used for unit tests: we shouldn't ever send this + m.add_byte(chr(_MSG_KEXDH_GEX_REQUEST_OLD)) + m.add_int(self.preferred_bits) + self.old_style = True + else: + m.add_byte(chr(_MSG_KEXDH_GEX_REQUEST)) + m.add_int(self.min_bits) + m.add_int(self.preferred_bits) + m.add_int(self.max_bits) + self.transport._send_message(m) + self.transport._expect_packet(_MSG_KEXDH_GEX_GROUP) + + def parse_next(self, ptype, m): + if ptype == _MSG_KEXDH_GEX_REQUEST: + return self._parse_kexdh_gex_request(m) + elif ptype == _MSG_KEXDH_GEX_GROUP: + return self._parse_kexdh_gex_group(m) + elif ptype == _MSG_KEXDH_GEX_INIT: + return self._parse_kexdh_gex_init(m) + elif ptype == _MSG_KEXDH_GEX_REPLY: + return self._parse_kexdh_gex_reply(m) + elif ptype == _MSG_KEXDH_GEX_REQUEST_OLD: + return self._parse_kexdh_gex_request_old(m) + raise SSHException('KexGex asked to handle packet type %d' % ptype) + + + ### internals... + + + def _generate_x(self): + # generate an "x" (1 < x < (p-1)/2). + q = (self.p - 1) // 2 + qnorm = util.deflate_long(q, 0) + qhbyte = ord(qnorm[0]) + bytes = len(qnorm) + qmask = 0xff + while not (qhbyte & 0x80): + qhbyte <<= 1 + qmask >>= 1 + while True: + x_bytes = self.transport.rng.read(bytes) + x_bytes = chr(ord(x_bytes[0]) & qmask) + x_bytes[1:] + x = util.inflate_long(x_bytes, 1) + if (x > 1) and (x < q): + break + self.x = x + + def _parse_kexdh_gex_request(self, m): + minbits = m.get_int() + preferredbits = m.get_int() + maxbits = m.get_int() + # smoosh the user's preferred size into our own limits + if preferredbits > self.max_bits: + preferredbits = self.max_bits + if preferredbits < self.min_bits: + preferredbits = self.min_bits + # fix min/max if they're inconsistent. technically, we could just pout + # and hang up, but there's no harm in giving them the benefit of the + # doubt and just picking a bitsize for them. + if minbits > preferredbits: + minbits = preferredbits + if maxbits < preferredbits: + maxbits = preferredbits + # now save a copy + self.min_bits = minbits + self.preferred_bits = preferredbits + self.max_bits = maxbits + # generate prime + pack = self.transport._get_modulus_pack() + if pack is None: + raise SSHException('Can\'t do server-side gex with no modulus pack') + self.transport._log(DEBUG, 'Picking p (%d <= %d <= %d bits)' % (minbits, preferredbits, maxbits)) + self.g, self.p = pack.get_modulus(minbits, preferredbits, maxbits) + m = Message() + m.add_byte(chr(_MSG_KEXDH_GEX_GROUP)) + m.add_mpint(self.p) + m.add_mpint(self.g) + self.transport._send_message(m) + self.transport._expect_packet(_MSG_KEXDH_GEX_INIT) + + def _parse_kexdh_gex_request_old(self, m): + # same as above, but without min_bits or max_bits (used by older clients like putty) + self.preferred_bits = m.get_int() + # smoosh the user's preferred size into our own limits + if self.preferred_bits > self.max_bits: + self.preferred_bits = self.max_bits + if self.preferred_bits < self.min_bits: + self.preferred_bits = self.min_bits + # generate prime + pack = self.transport._get_modulus_pack() + if pack is None: + raise SSHException('Can\'t do server-side gex with no modulus pack') + self.transport._log(DEBUG, 'Picking p (~ %d bits)' % (self.preferred_bits,)) + self.g, self.p = pack.get_modulus(self.min_bits, self.preferred_bits, self.max_bits) + m = Message() + m.add_byte(chr(_MSG_KEXDH_GEX_GROUP)) + m.add_mpint(self.p) + m.add_mpint(self.g) + self.transport._send_message(m) + self.transport._expect_packet(_MSG_KEXDH_GEX_INIT) + self.old_style = True + + def _parse_kexdh_gex_group(self, m): + self.p = m.get_mpint() + self.g = m.get_mpint() + # reject if p's bit length < 1024 or > 8192 + bitlen = util.bit_length(self.p) + if (bitlen < 1024) or (bitlen > 8192): + raise SSHException('Server-generated gex p (don\'t ask) is out of range (%d bits)' % bitlen) + self.transport._log(DEBUG, 'Got server p (%d bits)' % bitlen) + self._generate_x() + # now compute e = g^x mod p + self.e = pow(self.g, self.x, self.p) + m = Message() + m.add_byte(chr(_MSG_KEXDH_GEX_INIT)) + m.add_mpint(self.e) + self.transport._send_message(m) + self.transport._expect_packet(_MSG_KEXDH_GEX_REPLY) + + def _parse_kexdh_gex_init(self, m): + self.e = m.get_mpint() + if (self.e < 1) or (self.e > self.p - 1): + raise SSHException('Client kex "e" is out of range') + self._generate_x() + self.f = pow(self.g, self.x, self.p) + K = pow(self.e, self.x, self.p) + key = str(self.transport.get_server_key()) + # okay, build up the hash H of (V_C || V_S || I_C || I_S || K_S || min || n || max || p || g || e || f || K) + hm = Message() + hm.add(self.transport.remote_version, self.transport.local_version, + self.transport.remote_kex_init, self.transport.local_kex_init, + key) + if not self.old_style: + hm.add_int(self.min_bits) + hm.add_int(self.preferred_bits) + if not self.old_style: + hm.add_int(self.max_bits) + hm.add_mpint(self.p) + hm.add_mpint(self.g) + hm.add_mpint(self.e) + hm.add_mpint(self.f) + hm.add_mpint(K) + H = SHA.new(str(hm)).digest() + self.transport._set_K_H(K, H) + # sign it + sig = self.transport.get_server_key().sign_ssh_data(self.transport.rng, H) + # send reply + m = Message() + m.add_byte(chr(_MSG_KEXDH_GEX_REPLY)) + m.add_string(key) + m.add_mpint(self.f) + m.add_string(str(sig)) + self.transport._send_message(m) + self.transport._activate_outbound() + + def _parse_kexdh_gex_reply(self, m): + host_key = m.get_string() + self.f = m.get_mpint() + sig = m.get_string() + if (self.f < 1) or (self.f > self.p - 1): + raise SSHException('Server kex "f" is out of range') + K = pow(self.f, self.x, self.p) + # okay, build up the hash H of (V_C || V_S || I_C || I_S || K_S || min || n || max || p || g || e || f || K) + hm = Message() + hm.add(self.transport.local_version, self.transport.remote_version, + self.transport.local_kex_init, self.transport.remote_kex_init, + host_key) + if not self.old_style: + hm.add_int(self.min_bits) + hm.add_int(self.preferred_bits) + if not self.old_style: + hm.add_int(self.max_bits) + hm.add_mpint(self.p) + hm.add_mpint(self.g) + hm.add_mpint(self.e) + hm.add_mpint(self.f) + hm.add_mpint(K) + self.transport._set_K_H(K, SHA.new(str(hm)).digest()) + self.transport._verify_key(host_key, sig) + self.transport._activate_outbound() diff --git a/lib/paramiko/kex_group1.py b/lib/paramiko/kex_group1.py new file mode 100644 index 0000000..1386cf3 --- /dev/null +++ b/lib/paramiko/kex_group1.py @@ -0,0 +1,135 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Standard SSH key exchange ("kex" if you wanna sound cool). Diffie-Hellman of +1024 bit key halves, using a known "p" prime and "g" generator. +""" + +from Crypto.Hash import SHA + +from paramiko.common import * +from paramiko import util +from paramiko.message import Message +from paramiko.ssh_exception import SSHException + + +_MSG_KEXDH_INIT, _MSG_KEXDH_REPLY = range(30, 32) + +# draft-ietf-secsh-transport-09.txt, page 17 +P = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFFL +G = 2 + + +class KexGroup1(object): + + name = 'diffie-hellman-group1-sha1' + + def __init__(self, transport): + self.transport = transport + self.x = 0L + self.e = 0L + self.f = 0L + + def start_kex(self): + self._generate_x() + if self.transport.server_mode: + # compute f = g^x mod p, but don't send it yet + self.f = pow(G, self.x, P) + self.transport._expect_packet(_MSG_KEXDH_INIT) + return + # compute e = g^x mod p (where g=2), and send it + self.e = pow(G, self.x, P) + m = Message() + m.add_byte(chr(_MSG_KEXDH_INIT)) + m.add_mpint(self.e) + self.transport._send_message(m) + self.transport._expect_packet(_MSG_KEXDH_REPLY) + + def parse_next(self, ptype, m): + if self.transport.server_mode and (ptype == _MSG_KEXDH_INIT): + return self._parse_kexdh_init(m) + elif not self.transport.server_mode and (ptype == _MSG_KEXDH_REPLY): + return self._parse_kexdh_reply(m) + raise SSHException('KexGroup1 asked to handle packet type %d' % ptype) + + + ### internals... + + + def _generate_x(self): + # generate an "x" (1 < x < q), where q is (p-1)/2. + # p is a 128-byte (1024-bit) number, where the first 64 bits are 1. + # therefore q can be approximated as a 2^1023. we drop the subset of + # potential x where the first 63 bits are 1, because some of those will be + # larger than q (but this is a tiny tiny subset of potential x). + while 1: + x_bytes = self.transport.rng.read(128) + x_bytes = chr(ord(x_bytes[0]) & 0x7f) + x_bytes[1:] + if (x_bytes[:8] != '\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF') and \ + (x_bytes[:8] != '\x00\x00\x00\x00\x00\x00\x00\x00'): + break + self.x = util.inflate_long(x_bytes) + + def _parse_kexdh_reply(self, m): + # client mode + host_key = m.get_string() + self.f = m.get_mpint() + if (self.f < 1) or (self.f > P - 1): + raise SSHException('Server kex "f" is out of range') + sig = m.get_string() + K = pow(self.f, self.x, P) + # okay, build up the hash H of (V_C || V_S || I_C || I_S || K_S || e || f || K) + hm = Message() + hm.add(self.transport.local_version, self.transport.remote_version, + self.transport.local_kex_init, self.transport.remote_kex_init) + hm.add_string(host_key) + hm.add_mpint(self.e) + hm.add_mpint(self.f) + hm.add_mpint(K) + self.transport._set_K_H(K, SHA.new(str(hm)).digest()) + self.transport._verify_key(host_key, sig) + self.transport._activate_outbound() + + def _parse_kexdh_init(self, m): + # server mode + self.e = m.get_mpint() + if (self.e < 1) or (self.e > P - 1): + raise SSHException('Client kex "e" is out of range') + K = pow(self.e, self.x, P) + key = str(self.transport.get_server_key()) + # okay, build up the hash H of (V_C || V_S || I_C || I_S || K_S || e || f || K) + hm = Message() + hm.add(self.transport.remote_version, self.transport.local_version, + self.transport.remote_kex_init, self.transport.local_kex_init) + hm.add_string(key) + hm.add_mpint(self.e) + hm.add_mpint(self.f) + hm.add_mpint(K) + H = SHA.new(str(hm)).digest() + self.transport._set_K_H(K, H) + # sign it + sig = self.transport.get_server_key().sign_ssh_data(self.transport.rng, H) + # send reply + m = Message() + m.add_byte(chr(_MSG_KEXDH_REPLY)) + m.add_string(key) + m.add_mpint(self.f) + m.add_string(str(sig)) + self.transport._send_message(m) + self.transport._activate_outbound() diff --git a/lib/paramiko/logging22.py b/lib/paramiko/logging22.py new file mode 100644 index 0000000..ed1d891 --- /dev/null +++ b/lib/paramiko/logging22.py @@ -0,0 +1,66 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Stub out logging on python < 2.3. +""" + + +DEBUG = 10 +INFO = 20 +WARNING = 30 +ERROR = 40 +CRITICAL = 50 + + +def getLogger(name): + return _logger + + +class logger (object): + def __init__(self): + self.handlers = [ ] + self.level = ERROR + + def setLevel(self, level): + self.level = level + + def addHandler(self, h): + self.handlers.append(h) + + def addFilter(self, filter): + pass + + def log(self, level, text): + if level >= self.level: + for h in self.handlers: + h.f.write(text + '\n') + h.f.flush() + +class StreamHandler (object): + def __init__(self, f): + self.f = f + + def setFormatter(self, f): + pass + +class Formatter (object): + def __init__(self, x, y): + pass + +_logger = logger() diff --git a/lib/paramiko/message.py b/lib/paramiko/message.py new file mode 100644 index 0000000..366c43c --- /dev/null +++ b/lib/paramiko/message.py @@ -0,0 +1,301 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Implementation of an SSH2 "message". +""" + +import struct +import cStringIO + +from paramiko import util + + +class Message (object): + """ + An SSH2 I{Message} is a stream of bytes that encodes some combination of + strings, integers, bools, and infinite-precision integers (known in python + as I{long}s). This class builds or breaks down such a byte stream. + + Normally you don't need to deal with anything this low-level, but it's + exposed for people implementing custom extensions, or features that + paramiko doesn't support yet. + """ + + def __init__(self, content=None): + """ + Create a new SSH2 Message. + + @param content: the byte stream to use as the Message content (passed + in only when decomposing a Message). + @type content: string + """ + if content != None: + self.packet = cStringIO.StringIO(content) + else: + self.packet = cStringIO.StringIO() + + def __str__(self): + """ + Return the byte stream content of this Message, as a string. + + @return: the contents of this Message. + @rtype: string + """ + return self.packet.getvalue() + + def __repr__(self): + """ + Returns a string representation of this object, for debugging. + + @rtype: string + """ + return 'paramiko.Message(' + repr(self.packet.getvalue()) + ')' + + def rewind(self): + """ + Rewind the message to the beginning as if no items had been parsed + out of it yet. + """ + self.packet.seek(0) + + def get_remainder(self): + """ + Return the bytes of this Message that haven't already been parsed and + returned. + + @return: a string of the bytes not parsed yet. + @rtype: string + """ + position = self.packet.tell() + remainder = self.packet.read() + self.packet.seek(position) + return remainder + + def get_so_far(self): + """ + Returns the bytes of this Message that have been parsed and returned. + The string passed into a Message's constructor can be regenerated by + concatenating C{get_so_far} and L{get_remainder}. + + @return: a string of the bytes parsed so far. + @rtype: string + """ + position = self.packet.tell() + self.rewind() + return self.packet.read(position) + + def get_bytes(self, n): + """ + Return the next C{n} bytes of the Message, without decomposing into + an int, string, etc. Just the raw bytes are returned. + + @return: a string of the next C{n} bytes of the Message, or a string + of C{n} zero bytes, if there aren't C{n} bytes remaining. + @rtype: string + """ + b = self.packet.read(n) + if len(b) < n: + return b + '\x00' * (n - len(b)) + return b + + def get_byte(self): + """ + Return the next byte of the Message, without decomposing it. This + is equivalent to L{get_bytes(1)}. + + @return: the next byte of the Message, or C{'\000'} if there aren't + any bytes remaining. + @rtype: string + """ + return self.get_bytes(1) + + def get_boolean(self): + """ + Fetch a boolean from the stream. + + @return: C{True} or C{False} (from the Message). + @rtype: bool + """ + b = self.get_bytes(1) + return b != '\x00' + + def get_int(self): + """ + Fetch an int from the stream. + + @return: a 32-bit unsigned integer. + @rtype: int + """ + return struct.unpack('>I', self.get_bytes(4))[0] + + def get_int64(self): + """ + Fetch a 64-bit int from the stream. + + @return: a 64-bit unsigned integer. + @rtype: long + """ + return struct.unpack('>Q', self.get_bytes(8))[0] + + def get_mpint(self): + """ + Fetch a long int (mpint) from the stream. + + @return: an arbitrary-length integer. + @rtype: long + """ + return util.inflate_long(self.get_string()) + + def get_string(self): + """ + Fetch a string from the stream. This could be a byte string and may + contain unprintable characters. (It's not unheard of for a string to + contain another byte-stream Message.) + + @return: a string. + @rtype: string + """ + return self.get_bytes(self.get_int()) + + def get_list(self): + """ + Fetch a list of strings from the stream. These are trivially encoded + as comma-separated values in a string. + + @return: a list of strings. + @rtype: list of strings + """ + return self.get_string().split(',') + + def add_bytes(self, b): + """ + Write bytes to the stream, without any formatting. + + @param b: bytes to add + @type b: str + """ + self.packet.write(b) + return self + + def add_byte(self, b): + """ + Write a single byte to the stream, without any formatting. + + @param b: byte to add + @type b: str + """ + self.packet.write(b) + return self + + def add_boolean(self, b): + """ + Add a boolean value to the stream. + + @param b: boolean value to add + @type b: bool + """ + if b: + self.add_byte('\x01') + else: + self.add_byte('\x00') + return self + + def add_int(self, n): + """ + Add an integer to the stream. + + @param n: integer to add + @type n: int + """ + self.packet.write(struct.pack('>I', n)) + return self + + def add_int64(self, n): + """ + Add a 64-bit int to the stream. + + @param n: long int to add + @type n: long + """ + self.packet.write(struct.pack('>Q', n)) + return self + + def add_mpint(self, z): + """ + Add a long int to the stream, encoded as an infinite-precision + integer. This method only works on positive numbers. + + @param z: long int to add + @type z: long + """ + self.add_string(util.deflate_long(z)) + return self + + def add_string(self, s): + """ + Add a string to the stream. + + @param s: string to add + @type s: str + """ + self.add_int(len(s)) + self.packet.write(s) + return self + + def add_list(self, l): + """ + Add a list of strings to the stream. They are encoded identically to + a single string of values separated by commas. (Yes, really, that's + how SSH2 does it.) + + @param l: list of strings to add + @type l: list(str) + """ + self.add_string(','.join(l)) + return self + + def _add(self, i): + if type(i) is str: + return self.add_string(i) + elif type(i) is int: + return self.add_int(i) + elif type(i) is long: + if i > 0xffffffffL: + return self.add_mpint(i) + else: + return self.add_int(i) + elif type(i) is bool: + return self.add_boolean(i) + elif type(i) is list: + return self.add_list(i) + else: + raise Exception('Unknown type') + + def add(self, *seq): + """ + Add a sequence of items to the stream. The values are encoded based + on their type: str, int, bool, list, or long. + + @param seq: the sequence of items + @type seq: sequence + + @bug: longs are encoded non-deterministically. Don't use this method. + """ + for item in seq: + self._add(item) diff --git a/lib/paramiko/packet.py b/lib/paramiko/packet.py new file mode 100644 index 0000000..391c5d5 --- /dev/null +++ b/lib/paramiko/packet.py @@ -0,0 +1,485 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Packetizer. +""" + +import errno +import select +import socket +import struct +import threading +import time + +from paramiko.common import * +from paramiko import util +from paramiko.ssh_exception import SSHException +from paramiko.message import Message + + +got_r_hmac = False +try: + import r_hmac + got_r_hmac = True +except ImportError: + pass +def compute_hmac(key, message, digest_class): + if got_r_hmac: + return r_hmac.HMAC(key, message, digest_class).digest() + from Crypto.Hash import HMAC + return HMAC.HMAC(key, message, digest_class).digest() + + +class NeedRekeyException (Exception): + pass + + +class Packetizer (object): + """ + Implementation of the base SSH packet protocol. + """ + + # READ the secsh RFC's before raising these values. if anything, + # they should probably be lower. + REKEY_PACKETS = pow(2, 30) + REKEY_BYTES = pow(2, 30) + + def __init__(self, socket): + self.__socket = socket + self.__logger = None + self.__closed = False + self.__dump_packets = False + self.__need_rekey = False + self.__init_count = 0 + self.__remainder = '' + + # used for noticing when to re-key: + self.__sent_bytes = 0 + self.__sent_packets = 0 + self.__received_bytes = 0 + self.__received_packets = 0 + self.__received_packets_overflow = 0 + + # current inbound/outbound ciphering: + self.__block_size_out = 8 + self.__block_size_in = 8 + self.__mac_size_out = 0 + self.__mac_size_in = 0 + self.__block_engine_out = None + self.__block_engine_in = None + self.__mac_engine_out = None + self.__mac_engine_in = None + self.__mac_key_out = '' + self.__mac_key_in = '' + self.__compress_engine_out = None + self.__compress_engine_in = None + self.__sequence_number_out = 0L + self.__sequence_number_in = 0L + + # lock around outbound writes (packet computation) + self.__write_lock = threading.RLock() + + # keepalives: + self.__keepalive_interval = 0 + self.__keepalive_last = time.time() + self.__keepalive_callback = None + + def set_log(self, log): + """ + Set the python log object to use for logging. + """ + self.__logger = log + + def set_outbound_cipher(self, block_engine, block_size, mac_engine, mac_size, mac_key): + """ + Switch outbound data cipher. + """ + self.__block_engine_out = block_engine + self.__block_size_out = block_size + self.__mac_engine_out = mac_engine + self.__mac_size_out = mac_size + self.__mac_key_out = mac_key + self.__sent_bytes = 0 + self.__sent_packets = 0 + # wait until the reset happens in both directions before clearing rekey flag + self.__init_count |= 1 + if self.__init_count == 3: + self.__init_count = 0 + self.__need_rekey = False + + def set_inbound_cipher(self, block_engine, block_size, mac_engine, mac_size, mac_key): + """ + Switch inbound data cipher. + """ + self.__block_engine_in = block_engine + self.__block_size_in = block_size + self.__mac_engine_in = mac_engine + self.__mac_size_in = mac_size + self.__mac_key_in = mac_key + self.__received_bytes = 0 + self.__received_packets = 0 + self.__received_packets_overflow = 0 + # wait until the reset happens in both directions before clearing rekey flag + self.__init_count |= 2 + if self.__init_count == 3: + self.__init_count = 0 + self.__need_rekey = False + + def set_outbound_compressor(self, compressor): + self.__compress_engine_out = compressor + + def set_inbound_compressor(self, compressor): + self.__compress_engine_in = compressor + + def close(self): + self.__closed = True + self.__socket.close() + + def set_hexdump(self, hexdump): + self.__dump_packets = hexdump + + def get_hexdump(self): + return self.__dump_packets + + def get_mac_size_in(self): + return self.__mac_size_in + + def get_mac_size_out(self): + return self.__mac_size_out + + def need_rekey(self): + """ + Returns C{True} if a new set of keys needs to be negotiated. This + will be triggered during a packet read or write, so it should be + checked after every read or write, or at least after every few. + + @return: C{True} if a new set of keys needs to be negotiated + """ + return self.__need_rekey + + def set_keepalive(self, interval, callback): + """ + Turn on/off the callback keepalive. If C{interval} seconds pass with + no data read from or written to the socket, the callback will be + executed and the timer will be reset. + """ + self.__keepalive_interval = interval + self.__keepalive_callback = callback + self.__keepalive_last = time.time() + + def read_all(self, n, check_rekey=False): + """ + Read as close to N bytes as possible, blocking as long as necessary. + + @param n: number of bytes to read + @type n: int + @return: the data read + @rtype: str + @raise EOFError: if the socket was closed before all the bytes could + be read + """ + out = '' + # handle over-reading from reading the banner line + if len(self.__remainder) > 0: + out = self.__remainder[:n] + self.__remainder = self.__remainder[n:] + n -= len(out) + if PY22: + return self._py22_read_all(n, out) + while n > 0: + got_timeout = False + try: + x = self.__socket.recv(n) + if len(x) == 0: + raise EOFError() + out += x + n -= len(x) + except socket.timeout: + got_timeout = True + except socket.error, e: + # on Linux, sometimes instead of socket.timeout, we get + # EAGAIN. this is a bug in recent (> 2.6.9) kernels but + # we need to work around it. + if (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EAGAIN): + got_timeout = True + elif (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EINTR): + # syscall interrupted; try again + pass + elif self.__closed: + raise EOFError() + else: + raise + if got_timeout: + if self.__closed: + raise EOFError() + if check_rekey and (len(out) == 0) and self.__need_rekey: + raise NeedRekeyException() + self._check_keepalive() + return out + + def write_all(self, out): + self.__keepalive_last = time.time() + while len(out) > 0: + got_timeout = False + try: + n = self.__socket.send(out) + except socket.timeout: + got_timeout = True + except socket.error, e: + if (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EAGAIN): + got_timeout = True + elif (type(e.args) is tuple) and (len(e.args) > 0) and (e.args[0] == errno.EINTR): + # syscall interrupted; try again + pass + else: + n = -1 + except Exception: + # could be: (32, 'Broken pipe') + n = -1 + if got_timeout: + n = 0 + if self.__closed: + n = -1 + if n < 0: + raise EOFError() + if n == len(out): + break + out = out[n:] + return + + def readline(self, timeout): + """ + Read a line from the socket. We assume no data is pending after the + line, so it's okay to attempt large reads. + """ + buf = self.__remainder + while not '\n' in buf: + buf += self._read_timeout(timeout) + n = buf.index('\n') + self.__remainder = buf[n+1:] + buf = buf[:n] + if (len(buf) > 0) and (buf[-1] == '\r'): + buf = buf[:-1] + return buf + + def send_message(self, data): + """ + Write a block of data using the current cipher, as an SSH block. + """ + # encrypt this sucka + data = str(data) + cmd = ord(data[0]) + if cmd in MSG_NAMES: + cmd_name = MSG_NAMES[cmd] + else: + cmd_name = '$%x' % cmd + orig_len = len(data) + self.__write_lock.acquire() + try: + if self.__compress_engine_out is not None: + data = self.__compress_engine_out(data) + packet = self._build_packet(data) + if self.__dump_packets: + self._log(DEBUG, 'Write packet <%s>, length %d' % (cmd_name, orig_len)) + self._log(DEBUG, util.format_binary(packet, 'OUT: ')) + if self.__block_engine_out != None: + out = self.__block_engine_out.encrypt(packet) + else: + out = packet + # + mac + if self.__block_engine_out != None: + payload = struct.pack('>I', self.__sequence_number_out) + packet + out += compute_hmac(self.__mac_key_out, payload, self.__mac_engine_out)[:self.__mac_size_out] + self.__sequence_number_out = (self.__sequence_number_out + 1) & 0xffffffffL + self.write_all(out) + + self.__sent_bytes += len(out) + self.__sent_packets += 1 + if ((self.__sent_packets >= self.REKEY_PACKETS) or (self.__sent_bytes >= self.REKEY_BYTES)) \ + and not self.__need_rekey: + # only ask once for rekeying + self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes sent)' % + (self.__sent_packets, self.__sent_bytes)) + self.__received_packets_overflow = 0 + self._trigger_rekey() + finally: + self.__write_lock.release() + + def read_message(self): + """ + Only one thread should ever be in this function (no other locking is + done). + + @raise SSHException: if the packet is mangled + @raise NeedRekeyException: if the transport should rekey + """ + header = self.read_all(self.__block_size_in, check_rekey=True) + if self.__block_engine_in != None: + header = self.__block_engine_in.decrypt(header) + if self.__dump_packets: + self._log(DEBUG, util.format_binary(header, 'IN: ')); + packet_size = struct.unpack('>I', header[:4])[0] + # leftover contains decrypted bytes from the first block (after the length field) + leftover = header[4:] + if (packet_size - len(leftover)) % self.__block_size_in != 0: + raise SSHException('Invalid packet blocking') + buf = self.read_all(packet_size + self.__mac_size_in - len(leftover)) + packet = buf[:packet_size - len(leftover)] + post_packet = buf[packet_size - len(leftover):] + if self.__block_engine_in != None: + packet = self.__block_engine_in.decrypt(packet) + if self.__dump_packets: + self._log(DEBUG, util.format_binary(packet, 'IN: ')); + packet = leftover + packet + + if self.__mac_size_in > 0: + mac = post_packet[:self.__mac_size_in] + mac_payload = struct.pack('>II', self.__sequence_number_in, packet_size) + packet + my_mac = compute_hmac(self.__mac_key_in, mac_payload, self.__mac_engine_in)[:self.__mac_size_in] + if my_mac != mac: + raise SSHException('Mismatched MAC') + padding = ord(packet[0]) + payload = packet[1:packet_size - padding] + + if self.__dump_packets: + self._log(DEBUG, 'Got payload (%d bytes, %d padding)' % (packet_size, padding)) + + if self.__compress_engine_in is not None: + payload = self.__compress_engine_in(payload) + + msg = Message(payload[1:]) + msg.seqno = self.__sequence_number_in + self.__sequence_number_in = (self.__sequence_number_in + 1) & 0xffffffffL + + # check for rekey + self.__received_bytes += packet_size + self.__mac_size_in + 4 + self.__received_packets += 1 + if self.__need_rekey: + # we've asked to rekey -- give them 20 packets to comply before + # dropping the connection + self.__received_packets_overflow += 1 + if self.__received_packets_overflow >= 20: + raise SSHException('Remote transport is ignoring rekey requests') + elif (self.__received_packets >= self.REKEY_PACKETS) or \ + (self.__received_bytes >= self.REKEY_BYTES): + # only ask once for rekeying + self._log(DEBUG, 'Rekeying (hit %d packets, %d bytes received)' % + (self.__received_packets, self.__received_bytes)) + self.__received_packets_overflow = 0 + self._trigger_rekey() + + cmd = ord(payload[0]) + if cmd in MSG_NAMES: + cmd_name = MSG_NAMES[cmd] + else: + cmd_name = '$%x' % cmd + if self.__dump_packets: + self._log(DEBUG, 'Read packet <%s>, length %d' % (cmd_name, len(payload))) + return cmd, msg + + + ########## protected + + + def _log(self, level, msg): + if self.__logger is None: + return + if issubclass(type(msg), list): + for m in msg: + self.__logger.log(level, m) + else: + self.__logger.log(level, msg) + + def _check_keepalive(self): + if (not self.__keepalive_interval) or (not self.__block_engine_out) or \ + self.__need_rekey: + # wait till we're encrypting, and not in the middle of rekeying + return + now = time.time() + if now > self.__keepalive_last + self.__keepalive_interval: + self.__keepalive_callback() + self.__keepalive_last = now + + def _py22_read_all(self, n, out): + while n > 0: + r, w, e = select.select([self.__socket], [], [], 0.1) + if self.__socket not in r: + if self.__closed: + raise EOFError() + self._check_keepalive() + else: + x = self.__socket.recv(n) + if len(x) == 0: + raise EOFError() + out += x + n -= len(x) + return out + + def _py22_read_timeout(self, timeout): + start = time.time() + while True: + r, w, e = select.select([self.__socket], [], [], 0.1) + if self.__socket in r: + x = self.__socket.recv(1) + if len(x) == 0: + raise EOFError() + break + if self.__closed: + raise EOFError() + now = time.time() + if now - start >= timeout: + raise socket.timeout() + return x + + def _read_timeout(self, timeout): + if PY22: + return self._py22_read_timeout(timeout) + start = time.time() + while True: + try: + x = self.__socket.recv(128) + if len(x) == 0: + raise EOFError() + break + except socket.timeout: + pass + if self.__closed: + raise EOFError() + now = time.time() + if now - start >= timeout: + raise socket.timeout() + return x + + def _build_packet(self, payload): + # pad up at least 4 bytes, to nearest block-size (usually 8) + bsize = self.__block_size_out + padding = 3 + bsize - ((len(payload) + 8) % bsize) + packet = struct.pack('>IB', len(payload) + padding + 1, padding) + packet += payload + if self.__block_engine_out is not None: + packet += rng.read(padding) + else: + # cute trick i caught openssh doing: if we're not encrypting, + # don't waste random bytes for the padding + packet += (chr(0) * padding) + return packet + + def _trigger_rekey(self): + # outside code should check for this flag + self.__need_rekey = True diff --git a/lib/paramiko/pipe.py b/lib/paramiko/pipe.py new file mode 100644 index 0000000..37191ef --- /dev/null +++ b/lib/paramiko/pipe.py @@ -0,0 +1,147 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Abstraction of a one-way pipe where the read end can be used in select(). +Normally this is trivial, but Windows makes it nearly impossible. + +The pipe acts like an Event, which can be set or cleared. When set, the pipe +will trigger as readable in select(). +""" + +import sys +import os +import socket + + +def make_pipe (): + if sys.platform[:3] != 'win': + p = PosixPipe() + else: + p = WindowsPipe() + return p + + +class PosixPipe (object): + def __init__ (self): + self._rfd, self._wfd = os.pipe() + self._set = False + self._forever = False + self._closed = False + + def close (self): + os.close(self._rfd) + os.close(self._wfd) + # used for unit tests: + self._closed = True + + def fileno (self): + return self._rfd + + def clear (self): + if not self._set or self._forever: + return + os.read(self._rfd, 1) + self._set = False + + def set (self): + if self._set or self._closed: + return + self._set = True + os.write(self._wfd, '*') + + def set_forever (self): + self._forever = True + self.set() + + +class WindowsPipe (object): + """ + On Windows, only an OS-level "WinSock" may be used in select(), but reads + and writes must be to the actual socket object. + """ + def __init__ (self): + serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + serv.bind(('127.0.0.1', 0)) + serv.listen(1) + + # need to save sockets in _rsock/_wsock so they don't get closed + self._rsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._rsock.connect(('127.0.0.1', serv.getsockname()[1])) + + self._wsock, addr = serv.accept() + serv.close() + self._set = False + self._forever = False + self._closed = False + + def close (self): + self._rsock.close() + self._wsock.close() + # used for unit tests: + self._closed = True + + def fileno (self): + return self._rsock.fileno() + + def clear (self): + if not self._set or self._forever: + return + self._rsock.recv(1) + self._set = False + + def set (self): + if self._set or self._closed: + return + self._set = True + self._wsock.send('*') + + def set_forever (self): + self._forever = True + self.set() + + +class OrPipe (object): + def __init__(self, pipe): + self._set = False + self._partner = None + self._pipe = pipe + + def set(self): + self._set = True + if not self._partner._set: + self._pipe.set() + + def clear(self): + self._set = False + if not self._partner._set: + self._pipe.clear() + + +def make_or_pipe(pipe): + """ + wraps a pipe into two pipe-like objects which are "or"d together to + affect the real pipe. if either returned pipe is set, the wrapped pipe + is set. when both are cleared, the wrapped pipe is cleared. + """ + p1 = OrPipe(pipe) + p2 = OrPipe(pipe) + p1._partner = p2 + p2._partner = p1 + return p1, p2 + diff --git a/lib/paramiko/pkey.py b/lib/paramiko/pkey.py new file mode 100644 index 0000000..3e71222 --- /dev/null +++ b/lib/paramiko/pkey.py @@ -0,0 +1,381 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Common API for all public keys. +""" + +import base64 +from binascii import hexlify, unhexlify +import os + +from Crypto.Hash import MD5 +from Crypto.Cipher import DES3, AES + +from paramiko.common import * +from paramiko import util +from paramiko.message import Message +from paramiko.ssh_exception import SSHException, PasswordRequiredException + + +class PKey (object): + """ + Base class for public keys. + """ + + # known encryption types for private key files: + _CIPHER_TABLE = { + 'AES-128-CBC': { 'cipher': AES, 'keysize': 16, 'blocksize': 16, 'mode': AES.MODE_CBC }, + 'DES-EDE3-CBC': { 'cipher': DES3, 'keysize': 24, 'blocksize': 8, 'mode': DES3.MODE_CBC }, + } + + + def __init__(self, msg=None, data=None): + """ + Create a new instance of this public key type. If C{msg} is given, + the key's public part(s) will be filled in from the message. If + C{data} is given, the key's public part(s) will be filled in from + the string. + + @param msg: an optional SSH L{Message} containing a public key of this + type. + @type msg: L{Message} + @param data: an optional string containing a public key of this type + @type data: str + + @raise SSHException: if a key cannot be created from the C{data} or + C{msg} given, or no key was passed in. + """ + pass + + def __str__(self): + """ + Return a string of an SSH L{Message} made up of the public part(s) of + this key. This string is suitable for passing to L{__init__} to + re-create the key object later. + + @return: string representation of an SSH key message. + @rtype: str + """ + return '' + + def __cmp__(self, other): + """ + Compare this key to another. Returns 0 if this key is equivalent to + the given key, or non-0 if they are different. Only the public parts + of the key are compared, so a public key will compare equal to its + corresponding private key. + + @param other: key to compare to. + @type other: L{PKey} + @return: 0 if the two keys are equivalent, non-0 otherwise. + @rtype: int + """ + hs = hash(self) + ho = hash(other) + if hs != ho: + return cmp(hs, ho) + return cmp(str(self), str(other)) + + def get_name(self): + """ + Return the name of this private key implementation. + + @return: name of this private key type, in SSH terminology (for + example, C{"ssh-rsa"}). + @rtype: str + """ + return '' + + def get_bits(self): + """ + Return the number of significant bits in this key. This is useful + for judging the relative security of a key. + + @return: bits in the key. + @rtype: int + """ + return 0 + + def can_sign(self): + """ + Return C{True} if this key has the private part necessary for signing + data. + + @return: C{True} if this is a private key. + @rtype: bool + """ + return False + + def get_fingerprint(self): + """ + Return an MD5 fingerprint of the public part of this key. Nothing + secret is revealed. + + @return: a 16-byte string (binary) of the MD5 fingerprint, in SSH + format. + @rtype: str + """ + return MD5.new(str(self)).digest() + + def get_base64(self): + """ + Return a base64 string containing the public part of this key. Nothing + secret is revealed. This format is compatible with that used to store + public key files or recognized host keys. + + @return: a base64 string containing the public part of the key. + @rtype: str + """ + return base64.encodestring(str(self)).replace('\n', '') + + def sign_ssh_data(self, rng, data): + """ + Sign a blob of data with this private key, and return a L{Message} + representing an SSH signature message. + + @param rng: a secure random number generator. + @type rng: L{Crypto.Util.rng.RandomPool} + @param data: the data to sign. + @type data: str + @return: an SSH signature message. + @rtype: L{Message} + """ + return '' + + def verify_ssh_sig(self, data, msg): + """ + Given a blob of data, and an SSH message representing a signature of + that data, verify that it was signed with this key. + + @param data: the data that was signed. + @type data: str + @param msg: an SSH signature message + @type msg: L{Message} + @return: C{True} if the signature verifies correctly; C{False} + otherwise. + @rtype: boolean + """ + return False + + def from_private_key_file(cls, filename, password=None): + """ + Create a key object by reading a private key file. If the private + key is encrypted and C{password} is not C{None}, the given password + will be used to decrypt the key (otherwise L{PasswordRequiredException} + is thrown). Through the magic of python, this factory method will + exist in all subclasses of PKey (such as L{RSAKey} or L{DSSKey}), but + is useless on the abstract PKey class. + + @param filename: name of the file to read + @type filename: str + @param password: an optional password to use to decrypt the key file, + if it's encrypted + @type password: str + @return: a new key object based on the given private key + @rtype: L{PKey} + + @raise IOError: if there was an error reading the file + @raise PasswordRequiredException: if the private key file is + encrypted, and C{password} is C{None} + @raise SSHException: if the key file is invalid + """ + key = cls(filename=filename, password=password) + return key + from_private_key_file = classmethod(from_private_key_file) + + def from_private_key(cls, file_obj, password=None): + """ + Create a key object by reading a private key from a file (or file-like) + object. If the private key is encrypted and C{password} is not C{None}, + the given password will be used to decrypt the key (otherwise + L{PasswordRequiredException} is thrown). + + @param file_obj: the file to read from + @type file_obj: file + @param password: an optional password to use to decrypt the key, if it's + encrypted + @type password: str + @return: a new key object based on the given private key + @rtype: L{PKey} + + @raise IOError: if there was an error reading the key + @raise PasswordRequiredException: if the private key file is encrypted, + and C{password} is C{None} + @raise SSHException: if the key file is invalid + """ + key = cls(file_obj=file_obj, password=password) + return key + from_private_key = classmethod(from_private_key) + + def write_private_key_file(self, filename, password=None): + """ + Write private key contents into a file. If the password is not + C{None}, the key is encrypted before writing. + + @param filename: name of the file to write + @type filename: str + @param password: an optional password to use to encrypt the key file + @type password: str + + @raise IOError: if there was an error writing the file + @raise SSHException: if the key is invalid + """ + raise Exception('Not implemented in PKey') + + def write_private_key(self, file_obj, password=None): + """ + Write private key contents into a file (or file-like) object. If the + password is not C{None}, the key is encrypted before writing. + + @param file_obj: the file object to write into + @type file_obj: file + @param password: an optional password to use to encrypt the key + @type password: str + + @raise IOError: if there was an error writing to the file + @raise SSHException: if the key is invalid + """ + raise Exception('Not implemented in PKey') + + def _read_private_key_file(self, tag, filename, password=None): + """ + Read an SSH2-format private key file, looking for a string of the type + C{"BEGIN xxx PRIVATE KEY"} for some C{xxx}, base64-decode the text we + find, and return it as a string. If the private key is encrypted and + C{password} is not C{None}, the given password will be used to decrypt + the key (otherwise L{PasswordRequiredException} is thrown). + + @param tag: C{"RSA"} or C{"DSA"}, the tag used to mark the data block. + @type tag: str + @param filename: name of the file to read. + @type filename: str + @param password: an optional password to use to decrypt the key file, + if it's encrypted. + @type password: str + @return: data blob that makes up the private key. + @rtype: str + + @raise IOError: if there was an error reading the file. + @raise PasswordRequiredException: if the private key file is + encrypted, and C{password} is C{None}. + @raise SSHException: if the key file is invalid. + """ + f = open(filename, 'r') + data = self._read_private_key(tag, f, password) + f.close() + return data + + def _read_private_key(self, tag, f, password=None): + lines = f.readlines() + start = 0 + while (start < len(lines)) and (lines[start].strip() != '-----BEGIN ' + tag + ' PRIVATE KEY-----'): + start += 1 + if start >= len(lines): + raise SSHException('not a valid ' + tag + ' private key file') + # parse any headers first + headers = {} + start += 1 + while start < len(lines): + l = lines[start].split(': ') + if len(l) == 1: + break + headers[l[0].lower()] = l[1].strip() + start += 1 + # find end + end = start + while (lines[end].strip() != '-----END ' + tag + ' PRIVATE KEY-----') and (end < len(lines)): + end += 1 + # if we trudged to the end of the file, just try to cope. + try: + data = base64.decodestring(''.join(lines[start:end])) + except base64.binascii.Error, e: + raise SSHException('base64 decoding error: ' + str(e)) + if 'proc-type' not in headers: + # unencryped: done + return data + # encrypted keyfile: will need a password + if headers['proc-type'] != '4,ENCRYPTED': + raise SSHException('Unknown private key structure "%s"' % headers['proc-type']) + try: + encryption_type, saltstr = headers['dek-info'].split(',') + except: + raise SSHException('Can\'t parse DEK-info in private key file') + if encryption_type not in self._CIPHER_TABLE: + raise SSHException('Unknown private key cipher "%s"' % encryption_type) + # if no password was passed in, raise an exception pointing out that we need one + if password is None: + raise PasswordRequiredException('Private key file is encrypted') + cipher = self._CIPHER_TABLE[encryption_type]['cipher'] + keysize = self._CIPHER_TABLE[encryption_type]['keysize'] + mode = self._CIPHER_TABLE[encryption_type]['mode'] + salt = unhexlify(saltstr) + key = util.generate_key_bytes(MD5, salt, password, keysize) + return cipher.new(key, mode, salt).decrypt(data) + + def _write_private_key_file(self, tag, filename, data, password=None): + """ + Write an SSH2-format private key file in a form that can be read by + paramiko or openssh. If no password is given, the key is written in + a trivially-encoded format (base64) which is completely insecure. If + a password is given, DES-EDE3-CBC is used. + + @param tag: C{"RSA"} or C{"DSA"}, the tag used to mark the data block. + @type tag: str + @param filename: name of the file to write. + @type filename: str + @param data: data blob that makes up the private key. + @type data: str + @param password: an optional password to use to encrypt the file. + @type password: str + + @raise IOError: if there was an error writing the file. + """ + f = open(filename, 'w', 0600) + # grrr... the mode doesn't always take hold + os.chmod(filename, 0600) + self._write_private_key(tag, f, data, password) + f.close() + + def _write_private_key(self, tag, f, data, password=None): + f.write('-----BEGIN %s PRIVATE KEY-----\n' % tag) + if password is not None: + # since we only support one cipher here, use it + cipher_name = self._CIPHER_TABLE.keys()[0] + cipher = self._CIPHER_TABLE[cipher_name]['cipher'] + keysize = self._CIPHER_TABLE[cipher_name]['keysize'] + blocksize = self._CIPHER_TABLE[cipher_name]['blocksize'] + mode = self._CIPHER_TABLE[cipher_name]['mode'] + salt = rng.read(8) + key = util.generate_key_bytes(MD5, salt, password, keysize) + if len(data) % blocksize != 0: + n = blocksize - len(data) % blocksize + #data += rng.read(n) + # that would make more sense ^, but it confuses openssh. + data += '\0' * n + data = cipher.new(key, mode, salt).encrypt(data) + f.write('Proc-Type: 4,ENCRYPTED\n') + f.write('DEK-Info: %s,%s\n' % (cipher_name, hexlify(salt).upper())) + f.write('\n') + s = base64.encodestring(data) + # re-wrap to 64-char lines + s = ''.join(s.split('\n')) + s = '\n'.join([s[i : i+64] for i in range(0, len(s), 64)]) + f.write(s) + f.write('\n') + f.write('-----END %s PRIVATE KEY-----\n' % tag) diff --git a/lib/paramiko/primes.py b/lib/paramiko/primes.py new file mode 100644 index 0000000..9ebfec1 --- /dev/null +++ b/lib/paramiko/primes.py @@ -0,0 +1,151 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Utility functions for dealing with primes. +""" + +from Crypto.Util import number + +from paramiko import util +from paramiko.ssh_exception import SSHException + + +def _generate_prime(bits, rng): + "primtive attempt at prime generation" + hbyte_mask = pow(2, bits % 8) - 1 + while True: + # loop catches the case where we increment n into a higher bit-range + x = rng.read((bits+7) // 8) + if hbyte_mask > 0: + x = chr(ord(x[0]) & hbyte_mask) + x[1:] + n = util.inflate_long(x, 1) + n |= 1 + n |= (1 << (bits - 1)) + while not number.isPrime(n): + n += 2 + if util.bit_length(n) == bits: + break + return n + +def _roll_random(rng, n): + "returns a random # from 0 to N-1" + bits = util.bit_length(n-1) + bytes = (bits + 7) // 8 + hbyte_mask = pow(2, bits % 8) - 1 + + # so here's the plan: + # we fetch as many random bits as we'd need to fit N-1, and if the + # generated number is >= N, we try again. in the worst case (N-1 is a + # power of 2), we have slightly better than 50% odds of getting one that + # fits, so i can't guarantee that this loop will ever finish, but the odds + # of it looping forever should be infinitesimal. + while True: + x = rng.read(bytes) + if hbyte_mask > 0: + x = chr(ord(x[0]) & hbyte_mask) + x[1:] + num = util.inflate_long(x, 1) + if num < n: + break + return num + + +class ModulusPack (object): + """ + convenience object for holding the contents of the /etc/ssh/moduli file, + on systems that have such a file. + """ + + def __init__(self, rpool): + # pack is a hash of: bits -> [ (generator, modulus) ... ] + self.pack = {} + self.discarded = [] + self.rng = rpool + + def _parse_modulus(self, line): + timestamp, mod_type, tests, tries, size, generator, modulus = line.split() + mod_type = int(mod_type) + tests = int(tests) + tries = int(tries) + size = int(size) + generator = int(generator) + modulus = long(modulus, 16) + + # weed out primes that aren't at least: + # type 2 (meets basic structural requirements) + # test 4 (more than just a small-prime sieve) + # tries < 100 if test & 4 (at least 100 tries of miller-rabin) + if (mod_type < 2) or (tests < 4) or ((tests & 4) and (tests < 8) and (tries < 100)): + self.discarded.append((modulus, 'does not meet basic requirements')) + return + if generator == 0: + generator = 2 + + # there's a bug in the ssh "moduli" file (yeah, i know: shock! dismay! + # call cnn!) where it understates the bit lengths of these primes by 1. + # this is okay. + bl = util.bit_length(modulus) + if (bl != size) and (bl != size + 1): + self.discarded.append((modulus, 'incorrectly reported bit length %d' % size)) + return + if bl not in self.pack: + self.pack[bl] = [] + self.pack[bl].append((generator, modulus)) + + def read_file(self, filename): + """ + @raise IOError: passed from any file operations that fail. + """ + self.pack = {} + f = open(filename, 'r') + for line in f: + line = line.strip() + if (len(line) == 0) or (line[0] == '#'): + continue + try: + self._parse_modulus(line) + except: + continue + f.close() + + def get_modulus(self, min, prefer, max): + bitsizes = self.pack.keys() + bitsizes.sort() + if len(bitsizes) == 0: + raise SSHException('no moduli available') + good = -1 + # find nearest bitsize >= preferred + for b in bitsizes: + if (b >= prefer) and (b < max) and ((b < good) or (good == -1)): + good = b + # if that failed, find greatest bitsize >= min + if good == -1: + for b in bitsizes: + if (b >= min) and (b < max) and (b > good): + good = b + if good == -1: + # their entire (min, max) range has no intersection with our range. + # if their range is below ours, pick the smallest. otherwise pick + # the largest. it'll be out of their range requirement either way, + # but we'll be sending them the closest one we have. + good = bitsizes[0] + if min > good: + good = bitsizes[-1] + # now pick a random modulus of this bitsize + n = _roll_random(self.rng, len(self.pack[good])) + return self.pack[good][n] diff --git a/lib/paramiko/resource.py b/lib/paramiko/resource.py new file mode 100644 index 0000000..0d5c82f --- /dev/null +++ b/lib/paramiko/resource.py @@ -0,0 +1,72 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Resource manager. +""" + +import weakref + + +class ResourceManager (object): + """ + A registry of objects and resources that should be closed when those + objects are deleted. + + This is meant to be a safer alternative to python's C{__del__} method, + which can cause reference cycles to never be collected. Objects registered + with the ResourceManager can be collected but still free resources when + they die. + + Resources are registered using L{register}, and when an object is garbage + collected, each registered resource is closed by having its C{close()} + method called. Multiple resources may be registered per object, but a + resource will only be closed once, even if multiple objects register it. + (The last object to register it wins.) + """ + + def __init__(self): + self._table = {} + + def register(self, obj, resource): + """ + Register a resource to be closed with an object is collected. + + When the given C{obj} is garbage-collected by the python interpreter, + the C{resource} will be closed by having its C{close()} method called. + Any exceptions are ignored. + + @param obj: the object to track + @type obj: object + @param resource: the resource to close when the object is collected + @type resource: object + """ + def callback(ref): + try: + resource.close() + except: + pass + del self._table[id(resource)] + + # keep the weakref in a table so it sticks around long enough to get + # its callback called. :) + self._table[id(resource)] = weakref.ref(obj, callback) + + +# singleton +ResourceManager = ResourceManager() diff --git a/lib/paramiko/rsakey.py b/lib/paramiko/rsakey.py new file mode 100644 index 0000000..1e2d8f9 --- /dev/null +++ b/lib/paramiko/rsakey.py @@ -0,0 +1,185 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +L{RSAKey} +""" + +from Crypto.PublicKey import RSA +from Crypto.Hash import SHA, MD5 +from Crypto.Cipher import DES3 + +from paramiko.common import * +from paramiko import util +from paramiko.message import Message +from paramiko.ber import BER, BERException +from paramiko.pkey import PKey +from paramiko.ssh_exception import SSHException + + +class RSAKey (PKey): + """ + Representation of an RSA key which can be used to sign and verify SSH2 + data. + """ + + def __init__(self, msg=None, data=None, filename=None, password=None, vals=None, file_obj=None): + self.n = None + self.e = None + self.d = None + self.p = None + self.q = None + if file_obj is not None: + self._from_private_key(file_obj, password) + return + if filename is not None: + self._from_private_key_file(filename, password) + return + if (msg is None) and (data is not None): + msg = Message(data) + if vals is not None: + self.e, self.n = vals + else: + if msg is None: + raise SSHException('Key object may not be empty') + if msg.get_string() != 'ssh-rsa': + raise SSHException('Invalid key') + self.e = msg.get_mpint() + self.n = msg.get_mpint() + self.size = util.bit_length(self.n) + + def __str__(self): + m = Message() + m.add_string('ssh-rsa') + m.add_mpint(self.e) + m.add_mpint(self.n) + return str(m) + + def __hash__(self): + h = hash(self.get_name()) + h = h * 37 + hash(self.e) + h = h * 37 + hash(self.n) + return hash(h) + + def get_name(self): + return 'ssh-rsa' + + def get_bits(self): + return self.size + + def can_sign(self): + return self.d is not None + + def sign_ssh_data(self, rpool, data): + digest = SHA.new(data).digest() + rsa = RSA.construct((long(self.n), long(self.e), long(self.d))) + sig = util.deflate_long(rsa.sign(self._pkcs1imify(digest), '')[0], 0) + m = Message() + m.add_string('ssh-rsa') + m.add_string(sig) + return m + + def verify_ssh_sig(self, data, msg): + if msg.get_string() != 'ssh-rsa': + return False + sig = util.inflate_long(msg.get_string(), True) + # verify the signature by SHA'ing the data and encrypting it using the + # public key. some wackiness ensues where we "pkcs1imify" the 20-byte + # hash into a string as long as the RSA key. + hash_obj = util.inflate_long(self._pkcs1imify(SHA.new(data).digest()), True) + rsa = RSA.construct((long(self.n), long(self.e))) + return rsa.verify(hash_obj, (sig,)) + + def _encode_key(self): + if (self.p is None) or (self.q is None): + raise SSHException('Not enough key info to write private key file') + keylist = [ 0, self.n, self.e, self.d, self.p, self.q, + self.d % (self.p - 1), self.d % (self.q - 1), + util.mod_inverse(self.q, self.p) ] + try: + b = BER() + b.encode(keylist) + except BERException: + raise SSHException('Unable to create ber encoding of key') + return str(b) + + def write_private_key_file(self, filename, password=None): + self._write_private_key_file('RSA', filename, self._encode_key(), password) + + def write_private_key(self, file_obj, password=None): + self._write_private_key('RSA', file_obj, self._encode_key(), password) + + def generate(bits, progress_func=None): + """ + Generate a new private RSA key. This factory function can be used to + generate a new host key or authentication key. + + @param bits: number of bits the generated key should be. + @type bits: int + @param progress_func: an optional function to call at key points in + key generation (used by C{pyCrypto.PublicKey}). + @type progress_func: function + @return: new private key + @rtype: L{RSAKey} + """ + rsa = RSA.generate(bits, rng.read, progress_func) + key = RSAKey(vals=(rsa.e, rsa.n)) + key.d = rsa.d + key.p = rsa.p + key.q = rsa.q + return key + generate = staticmethod(generate) + + + ### internals... + + + def _pkcs1imify(self, data): + """ + turn a 20-byte SHA1 hash into a blob of data as large as the key's N, + using PKCS1's \"emsa-pkcs1-v1_5\" encoding. totally bizarre. + """ + SHA1_DIGESTINFO = '\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14' + size = len(util.deflate_long(self.n, 0)) + filler = '\xff' * (size - len(SHA1_DIGESTINFO) - len(data) - 3) + return '\x00\x01' + filler + '\x00' + SHA1_DIGESTINFO + data + + def _from_private_key_file(self, filename, password): + data = self._read_private_key_file('RSA', filename, password) + self._decode_key(data) + + def _from_private_key(self, file_obj, password): + data = self._read_private_key('RSA', file_obj, password) + self._decode_key(data) + + def _decode_key(self, data): + # private key file contains: + # RSAPrivateKey = { version = 0, n, e, d, p, q, d mod p-1, d mod q-1, q**-1 mod p } + try: + keylist = BER(data).decode() + except BERException: + raise SSHException('Unable to parse key file') + if (type(keylist) is not list) or (len(keylist) < 4) or (keylist[0] != 0): + raise SSHException('Not a valid RSA private key file (bad ber encoding)') + self.n = keylist[1] + self.e = keylist[2] + self.d = keylist[3] + # not really needed + self.p = keylist[4] + self.q = keylist[5] + self.size = util.bit_length(self.n) diff --git a/lib/paramiko/server.py b/lib/paramiko/server.py new file mode 100644 index 0000000..6424b63 --- /dev/null +++ b/lib/paramiko/server.py @@ -0,0 +1,632 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +L{ServerInterface} is an interface to override for server support. +""" + +import threading +from paramiko.common import * +from paramiko import util + + +class InteractiveQuery (object): + """ + A query (set of prompts) for a user during interactive authentication. + """ + + def __init__(self, name='', instructions='', *prompts): + """ + Create a new interactive query to send to the client. The name and + instructions are optional, but are generally displayed to the end + user. A list of prompts may be included, or they may be added via + the L{add_prompt} method. + + @param name: name of this query + @type name: str + @param instructions: user instructions (usually short) about this query + @type instructions: str + @param prompts: one or more authentication prompts + @type prompts: str + """ + self.name = name + self.instructions = instructions + self.prompts = [] + for x in prompts: + if (type(x) is str) or (type(x) is unicode): + self.add_prompt(x) + else: + self.add_prompt(x[0], x[1]) + + def add_prompt(self, prompt, echo=True): + """ + Add a prompt to this query. The prompt should be a (reasonably short) + string. Multiple prompts can be added to the same query. + + @param prompt: the user prompt + @type prompt: str + @param echo: C{True} (default) if the user's response should be echoed; + C{False} if not (for a password or similar) + @type echo: bool + """ + self.prompts.append((prompt, echo)) + + +class ServerInterface (object): + """ + This class defines an interface for controlling the behavior of paramiko + in server mode. + + Methods on this class are called from paramiko's primary thread, so you + shouldn't do too much work in them. (Certainly nothing that blocks or + sleeps.) + """ + + def check_channel_request(self, kind, chanid): + """ + Determine if a channel request of a given type will be granted, and + return C{OPEN_SUCCEEDED} or an error code. This method is + called in server mode when the client requests a channel, after + authentication is complete. + + If you allow channel requests (and an ssh server that didn't would be + useless), you should also override some of the channel request methods + below, which are used to determine which services will be allowed on + a given channel: + - L{check_channel_pty_request} + - L{check_channel_shell_request} + - L{check_channel_subsystem_request} + - L{check_channel_window_change_request} + - L{check_channel_x11_request} + + The C{chanid} parameter is a small number that uniquely identifies the + channel within a L{Transport}. A L{Channel} object is not created + unless this method returns C{OPEN_SUCCEEDED} -- once a + L{Channel} object is created, you can call L{Channel.get_id} to + retrieve the channel ID. + + The return value should either be C{OPEN_SUCCEEDED} (or + C{0}) to allow the channel request, or one of the following error + codes to reject it: + - C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED} + - C{OPEN_FAILED_CONNECT_FAILED} + - C{OPEN_FAILED_UNKNOWN_CHANNEL_TYPE} + - C{OPEN_FAILED_RESOURCE_SHORTAGE} + + The default implementation always returns + C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}. + + @param kind: the kind of channel the client would like to open + (usually C{"session"}). + @type kind: str + @param chanid: ID of the channel + @type chanid: int + @return: a success or failure code (listed above) + @rtype: int + """ + return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED + + def get_allowed_auths(self, username): + """ + Return a list of authentication methods supported by the server. + This list is sent to clients attempting to authenticate, to inform them + of authentication methods that might be successful. + + The "list" is actually a string of comma-separated names of types of + authentication. Possible values are C{"password"}, C{"publickey"}, + and C{"none"}. + + The default implementation always returns C{"password"}. + + @param username: the username requesting authentication. + @type username: str + @return: a comma-separated list of authentication types + @rtype: str + """ + return 'password' + + def check_auth_none(self, username): + """ + Determine if a client may open channels with no (further) + authentication. + + Return L{AUTH_FAILED} if the client must authenticate, or + L{AUTH_SUCCESSFUL} if it's okay for the client to not + authenticate. + + The default implementation always returns L{AUTH_FAILED}. + + @param username: the username of the client. + @type username: str + @return: L{AUTH_FAILED} if the authentication fails; + L{AUTH_SUCCESSFUL} if it succeeds. + @rtype: int + """ + return AUTH_FAILED + + def check_auth_password(self, username, password): + """ + Determine if a given username and password supplied by the client is + acceptable for use in authentication. + + Return L{AUTH_FAILED} if the password is not accepted, + L{AUTH_SUCCESSFUL} if the password is accepted and completes + the authentication, or L{AUTH_PARTIALLY_SUCCESSFUL} if your + authentication is stateful, and this key is accepted for + authentication, but more authentication is required. (In this latter + case, L{get_allowed_auths} will be called to report to the client what + options it has for continuing the authentication.) + + The default implementation always returns L{AUTH_FAILED}. + + @param username: the username of the authenticating client. + @type username: str + @param password: the password given by the client. + @type password: str + @return: L{AUTH_FAILED} if the authentication fails; + L{AUTH_SUCCESSFUL} if it succeeds; + L{AUTH_PARTIALLY_SUCCESSFUL} if the password auth is + successful, but authentication must continue. + @rtype: int + """ + return AUTH_FAILED + + def check_auth_publickey(self, username, key): + """ + Determine if a given key supplied by the client is acceptable for use + in authentication. You should override this method in server mode to + check the username and key and decide if you would accept a signature + made using this key. + + Return L{AUTH_FAILED} if the key is not accepted, + L{AUTH_SUCCESSFUL} if the key is accepted and completes the + authentication, or L{AUTH_PARTIALLY_SUCCESSFUL} if your + authentication is stateful, and this password is accepted for + authentication, but more authentication is required. (In this latter + case, L{get_allowed_auths} will be called to report to the client what + options it has for continuing the authentication.) + + Note that you don't have to actually verify any key signtature here. + If you're willing to accept the key, paramiko will do the work of + verifying the client's signature. + + The default implementation always returns L{AUTH_FAILED}. + + @param username: the username of the authenticating client + @type username: str + @param key: the key object provided by the client + @type key: L{PKey } + @return: L{AUTH_FAILED} if the client can't authenticate + with this key; L{AUTH_SUCCESSFUL} if it can; + L{AUTH_PARTIALLY_SUCCESSFUL} if it can authenticate with + this key but must continue with authentication + @rtype: int + """ + return AUTH_FAILED + + def check_auth_interactive(self, username, submethods): + """ + Begin an interactive authentication challenge, if supported. You + should override this method in server mode if you want to support the + C{"keyboard-interactive"} auth type, which requires you to send a + series of questions for the client to answer. + + Return L{AUTH_FAILED} if this auth method isn't supported. Otherwise, + you should return an L{InteractiveQuery} object containing the prompts + and instructions for the user. The response will be sent via a call + to L{check_auth_interactive_response}. + + The default implementation always returns L{AUTH_FAILED}. + + @param username: the username of the authenticating client + @type username: str + @param submethods: a comma-separated list of methods preferred by the + client (usually empty) + @type submethods: str + @return: L{AUTH_FAILED} if this auth method isn't supported; otherwise + an object containing queries for the user + @rtype: int or L{InteractiveQuery} + """ + return AUTH_FAILED + + def check_auth_interactive_response(self, responses): + """ + Continue or finish an interactive authentication challenge, if + supported. You should override this method in server mode if you want + to support the C{"keyboard-interactive"} auth type. + + Return L{AUTH_FAILED} if the responses are not accepted, + L{AUTH_SUCCESSFUL} if the responses are accepted and complete + the authentication, or L{AUTH_PARTIALLY_SUCCESSFUL} if your + authentication is stateful, and this set of responses is accepted for + authentication, but more authentication is required. (In this latter + case, L{get_allowed_auths} will be called to report to the client what + options it has for continuing the authentication.) + + If you wish to continue interactive authentication with more questions, + you may return an L{InteractiveQuery} object, which should cause the + client to respond with more answers, calling this method again. This + cycle can continue indefinitely. + + The default implementation always returns L{AUTH_FAILED}. + + @param responses: list of responses from the client + @type responses: list(str) + @return: L{AUTH_FAILED} if the authentication fails; + L{AUTH_SUCCESSFUL} if it succeeds; + L{AUTH_PARTIALLY_SUCCESSFUL} if the interactive auth is + successful, but authentication must continue; otherwise an object + containing queries for the user + @rtype: int or L{InteractiveQuery} + """ + return AUTH_FAILED + + def check_port_forward_request(self, address, port): + """ + Handle a request for port forwarding. The client is asking that + connections to the given address and port be forwarded back across + this ssh connection. An address of C{"0.0.0.0"} indicates a global + address (any address associated with this server) and a port of C{0} + indicates that no specific port is requested (usually the OS will pick + a port). + + The default implementation always returns C{False}, rejecting the + port forwarding request. If the request is accepted, you should return + the port opened for listening. + + @param address: the requested address + @type address: str + @param port: the requested port + @type port: int + @return: the port number that was opened for listening, or C{False} to + reject + @rtype: int + """ + return False + + def cancel_port_forward_request(self, address, port): + """ + The client would like to cancel a previous port-forwarding request. + If the given address and port is being forwarded across this ssh + connection, the port should be closed. + + @param address: the forwarded address + @type address: str + @param port: the forwarded port + @type port: int + """ + pass + + def check_global_request(self, kind, msg): + """ + Handle a global request of the given C{kind}. This method is called + in server mode and client mode, whenever the remote host makes a global + request. If there are any arguments to the request, they will be in + C{msg}. + + There aren't any useful global requests defined, aside from port + forwarding, so usually this type of request is an extension to the + protocol. + + If the request was successful and you would like to return contextual + data to the remote host, return a tuple. Items in the tuple will be + sent back with the successful result. (Note that the items in the + tuple can only be strings, ints, longs, or bools.) + + The default implementation always returns C{False}, indicating that it + does not support any global requests. + + @note: Port forwarding requests are handled separately, in + L{check_port_forward_request}. + + @param kind: the kind of global request being made. + @type kind: str + @param msg: any extra arguments to the request. + @type msg: L{Message} + @return: C{True} or a tuple of data if the request was granted; + C{False} otherwise. + @rtype: bool + """ + return False + + + ### Channel requests + + + def check_channel_pty_request(self, channel, term, width, height, pixelwidth, pixelheight, + modes): + """ + Determine if a pseudo-terminal of the given dimensions (usually + requested for shell access) can be provided on the given channel. + + The default implementation always returns C{False}. + + @param channel: the L{Channel} the pty request arrived on. + @type channel: L{Channel} + @param term: type of terminal requested (for example, C{"vt100"}). + @type term: str + @param width: width of screen in characters. + @type width: int + @param height: height of screen in characters. + @type height: int + @param pixelwidth: width of screen in pixels, if known (may be C{0} if + unknown). + @type pixelwidth: int + @param pixelheight: height of screen in pixels, if known (may be C{0} + if unknown). + @type pixelheight: int + @return: C{True} if the psuedo-terminal has been allocated; C{False} + otherwise. + @rtype: bool + """ + return False + + def check_channel_shell_request(self, channel): + """ + Determine if a shell will be provided to the client on the given + channel. If this method returns C{True}, the channel should be + connected to the stdin/stdout of a shell (or something that acts like + a shell). + + The default implementation always returns C{False}. + + @param channel: the L{Channel} the request arrived on. + @type channel: L{Channel} + @return: C{True} if this channel is now hooked up to a shell; C{False} + if a shell can't or won't be provided. + @rtype: bool + """ + return False + + def check_channel_exec_request(self, channel, command): + """ + Determine if a shell command will be executed for the client. If this + method returns C{True}, the channel should be connected to the stdin, + stdout, and stderr of the shell command. + + The default implementation always returns C{False}. + + @param channel: the L{Channel} the request arrived on. + @type channel: L{Channel} + @param command: the command to execute. + @type command: str + @return: C{True} if this channel is now hooked up to the stdin, + stdout, and stderr of the executing command; C{False} if the + command will not be executed. + @rtype: bool + + @since: 1.1 + """ + return False + + def check_channel_subsystem_request(self, channel, name): + """ + Determine if a requested subsystem will be provided to the client on + the given channel. If this method returns C{True}, all future I/O + through this channel will be assumed to be connected to the requested + subsystem. An example of a subsystem is C{sftp}. + + The default implementation checks for a subsystem handler assigned via + L{Transport.set_subsystem_handler}. + If one has been set, the handler is invoked and this method returns + C{True}. Otherwise it returns C{False}. + + @note: Because the default implementation uses the L{Transport} to + identify valid subsystems, you probably won't need to override this + method. + + @param channel: the L{Channel} the pty request arrived on. + @type channel: L{Channel} + @param name: name of the requested subsystem. + @type name: str + @return: C{True} if this channel is now hooked up to the requested + subsystem; C{False} if that subsystem can't or won't be provided. + @rtype: bool + """ + handler_class, larg, kwarg = channel.get_transport()._get_subsystem_handler(name) + if handler_class is None: + return False + handler = handler_class(channel, name, self, *larg, **kwarg) + handler.start() + return True + + def check_channel_window_change_request(self, channel, width, height, pixelwidth, pixelheight): + """ + Determine if the pseudo-terminal on the given channel can be resized. + This only makes sense if a pty was previously allocated on it. + + The default implementation always returns C{False}. + + @param channel: the L{Channel} the pty request arrived on. + @type channel: L{Channel} + @param width: width of screen in characters. + @type width: int + @param height: height of screen in characters. + @type height: int + @param pixelwidth: width of screen in pixels, if known (may be C{0} if + unknown). + @type pixelwidth: int + @param pixelheight: height of screen in pixels, if known (may be C{0} + if unknown). + @type pixelheight: int + @return: C{True} if the terminal was resized; C{False} if not. + @rtype: bool + """ + return False + + def check_channel_x11_request(self, channel, single_connection, auth_protocol, auth_cookie, screen_number): + """ + Determine if the client will be provided with an X11 session. If this + method returns C{True}, X11 applications should be routed through new + SSH channels, using L{Transport.open_x11_channel}. + + The default implementation always returns C{False}. + + @param channel: the L{Channel} the X11 request arrived on + @type channel: L{Channel} + @param single_connection: C{True} if only a single X11 channel should + be opened + @type single_connection: bool + @param auth_protocol: the protocol used for X11 authentication + @type auth_protocol: str + @param auth_cookie: the cookie used to authenticate to X11 + @type auth_cookie: str + @param screen_number: the number of the X11 screen to connect to + @type screen_number: int + @return: C{True} if the X11 session was opened; C{False} if not + @rtype: bool + """ + return False + + def check_channel_direct_tcpip_request(self, chanid, origin, destination): + """ + Determine if a local port forwarding channel will be granted, and + return C{OPEN_SUCCEEDED} or an error code. This method is + called in server mode when the client requests a channel, after + authentication is complete. + + The C{chanid} parameter is a small number that uniquely identifies the + channel within a L{Transport}. A L{Channel} object is not created + unless this method returns C{OPEN_SUCCEEDED} -- once a + L{Channel} object is created, you can call L{Channel.get_id} to + retrieve the channel ID. + + The origin and destination parameters are (ip_address, port) tuples + that correspond to both ends of the TCP connection in the forwarding + tunnel. + + The return value should either be C{OPEN_SUCCEEDED} (or + C{0}) to allow the channel request, or one of the following error + codes to reject it: + - C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED} + - C{OPEN_FAILED_CONNECT_FAILED} + - C{OPEN_FAILED_UNKNOWN_CHANNEL_TYPE} + - C{OPEN_FAILED_RESOURCE_SHORTAGE} + + The default implementation always returns + C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}. + + @param chanid: ID of the channel + @type chanid: int + @param origin: 2-tuple containing the IP address and port of the + originator (client side) + @type origin: tuple + @param destination: 2-tuple containing the IP address and port of the + destination (server side) + @type destination: tuple + @return: a success or failure code (listed above) + @rtype: int + """ + return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED + + +class SubsystemHandler (threading.Thread): + """ + Handler for a subsytem in server mode. If you create a subclass of this + class and pass it to + L{Transport.set_subsystem_handler}, + an object of this + class will be created for each request for this subsystem. Each new object + will be executed within its own new thread by calling L{start_subsystem}. + When that method completes, the channel is closed. + + For example, if you made a subclass C{MP3Handler} and registered it as the + handler for subsystem C{"mp3"}, then whenever a client has successfully + authenticated and requests subsytem C{"mp3"}, an object of class + C{MP3Handler} will be created, and L{start_subsystem} will be called on + it from a new thread. + """ + def __init__(self, channel, name, server): + """ + Create a new handler for a channel. This is used by L{ServerInterface} + to start up a new handler when a channel requests this subsystem. You + don't need to override this method, but if you do, be sure to pass the + C{channel} and C{name} parameters through to the original C{__init__} + method here. + + @param channel: the channel associated with this subsystem request. + @type channel: L{Channel} + @param name: name of the requested subsystem. + @type name: str + @param server: the server object for the session that started this + subsystem + @type server: L{ServerInterface} + """ + threading.Thread.__init__(self, target=self._run) + self.__channel = channel + self.__transport = channel.get_transport() + self.__name = name + self.__server = server + + def get_server(self): + """ + Return the L{ServerInterface} object associated with this channel and + subsystem. + + @rtype: L{ServerInterface} + """ + return self.__server + + def _run(self): + try: + self.__transport._log(DEBUG, 'Starting handler for subsystem %s' % self.__name) + self.start_subsystem(self.__name, self.__transport, self.__channel) + except Exception, e: + self.__transport._log(ERROR, 'Exception in subsystem handler for "%s": %s' % + (self.__name, str(e))) + self.__transport._log(ERROR, util.tb_strings()) + try: + self.finish_subsystem() + except: + pass + + def start_subsystem(self, name, transport, channel): + """ + Process an ssh subsystem in server mode. This method is called on a + new object (and in a new thread) for each subsystem request. It is + assumed that all subsystem logic will take place here, and when the + subsystem is finished, this method will return. After this method + returns, the channel is closed. + + The combination of C{transport} and C{channel} are unique; this handler + corresponds to exactly one L{Channel} on one L{Transport}. + + @note: It is the responsibility of this method to exit if the + underlying L{Transport} is closed. This can be done by checking + L{Transport.is_active} or noticing an EOF + on the L{Channel}. If this method loops forever without checking + for this case, your python interpreter may refuse to exit because + this thread will still be running. + + @param name: name of the requested subsystem. + @type name: str + @param transport: the server-mode L{Transport}. + @type transport: L{Transport} + @param channel: the channel associated with this subsystem request. + @type channel: L{Channel} + """ + pass + + def finish_subsystem(self): + """ + Perform any cleanup at the end of a subsystem. The default + implementation just closes the channel. + + @since: 1.1 + """ + self.__channel.close() diff --git a/lib/paramiko/sftp.py b/lib/paramiko/sftp.py new file mode 100644 index 0000000..a0b08e0 --- /dev/null +++ b/lib/paramiko/sftp.py @@ -0,0 +1,188 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +import select +import socket +import struct + +from paramiko.common import * +from paramiko import util +from paramiko.channel import Channel +from paramiko.message import Message + + +CMD_INIT, CMD_VERSION, CMD_OPEN, CMD_CLOSE, CMD_READ, CMD_WRITE, CMD_LSTAT, CMD_FSTAT, \ + CMD_SETSTAT, CMD_FSETSTAT, CMD_OPENDIR, CMD_READDIR, CMD_REMOVE, CMD_MKDIR, \ + CMD_RMDIR, CMD_REALPATH, CMD_STAT, CMD_RENAME, CMD_READLINK, CMD_SYMLINK \ + = range(1, 21) +CMD_STATUS, CMD_HANDLE, CMD_DATA, CMD_NAME, CMD_ATTRS = range(101, 106) +CMD_EXTENDED, CMD_EXTENDED_REPLY = range(200, 202) + +SFTP_OK = 0 +SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED, SFTP_FAILURE, SFTP_BAD_MESSAGE, \ + SFTP_NO_CONNECTION, SFTP_CONNECTION_LOST, SFTP_OP_UNSUPPORTED = range(1, 9) + +SFTP_DESC = [ 'Success', + 'End of file', + 'No such file', + 'Permission denied', + 'Failure', + 'Bad message', + 'No connection', + 'Connection lost', + 'Operation unsupported' ] + +SFTP_FLAG_READ = 0x1 +SFTP_FLAG_WRITE = 0x2 +SFTP_FLAG_APPEND = 0x4 +SFTP_FLAG_CREATE = 0x8 +SFTP_FLAG_TRUNC = 0x10 +SFTP_FLAG_EXCL = 0x20 + +_VERSION = 3 + + +# for debugging +CMD_NAMES = { + CMD_INIT: 'init', + CMD_VERSION: 'version', + CMD_OPEN: 'open', + CMD_CLOSE: 'close', + CMD_READ: 'read', + CMD_WRITE: 'write', + CMD_LSTAT: 'lstat', + CMD_FSTAT: 'fstat', + CMD_SETSTAT: 'setstat', + CMD_FSETSTAT: 'fsetstat', + CMD_OPENDIR: 'opendir', + CMD_READDIR: 'readdir', + CMD_REMOVE: 'remove', + CMD_MKDIR: 'mkdir', + CMD_RMDIR: 'rmdir', + CMD_REALPATH: 'realpath', + CMD_STAT: 'stat', + CMD_RENAME: 'rename', + CMD_READLINK: 'readlink', + CMD_SYMLINK: 'symlink', + CMD_STATUS: 'status', + CMD_HANDLE: 'handle', + CMD_DATA: 'data', + CMD_NAME: 'name', + CMD_ATTRS: 'attrs', + CMD_EXTENDED: 'extended', + CMD_EXTENDED_REPLY: 'extended_reply' + } + + +class SFTPError (Exception): + pass + + +class BaseSFTP (object): + def __init__(self): + self.logger = util.get_logger('paramiko.sftp') + self.sock = None + self.ultra_debug = False + + + ### internals... + + + def _send_version(self): + self._send_packet(CMD_INIT, struct.pack('>I', _VERSION)) + t, data = self._read_packet() + if t != CMD_VERSION: + raise SFTPError('Incompatible sftp protocol') + version = struct.unpack('>I', data[:4])[0] + # if version != _VERSION: + # raise SFTPError('Incompatible sftp protocol') + return version + + def _send_server_version(self): + # winscp will freak out if the server sends version info before the + # client finishes sending INIT. + t, data = self._read_packet() + if t != CMD_INIT: + raise SFTPError('Incompatible sftp protocol') + version = struct.unpack('>I', data[:4])[0] + # advertise that we support "check-file" + extension_pairs = [ 'check-file', 'md5,sha1' ] + msg = Message() + msg.add_int(_VERSION) + msg.add(*extension_pairs) + self._send_packet(CMD_VERSION, str(msg)) + return version + + def _log(self, level, msg, *args): + self.logger.log(level, msg, *args) + + def _write_all(self, out): + while len(out) > 0: + n = self.sock.send(out) + if n <= 0: + raise EOFError() + if n == len(out): + return + out = out[n:] + return + + def _read_all(self, n): + out = '' + while n > 0: + if isinstance(self.sock, socket.socket): + # sometimes sftp is used directly over a socket instead of + # through a paramiko channel. in this case, check periodically + # if the socket is closed. (for some reason, recv() won't ever + # return or raise an exception, but calling select on a closed + # socket will.) + while True: + read, write, err = select.select([ self.sock ], [], [], 0.1) + if len(read) > 0: + x = self.sock.recv(n) + break + else: + x = self.sock.recv(n) + + if len(x) == 0: + raise EOFError() + out += x + n -= len(x) + return out + + def _send_packet(self, t, packet): + #self._log(DEBUG2, 'write: %s (len=%d)' % (CMD_NAMES.get(t, '0x%02x' % t), len(packet))) + out = struct.pack('>I', len(packet) + 1) + chr(t) + packet + if self.ultra_debug: + self._log(DEBUG, util.format_binary(out, 'OUT: ')) + self._write_all(out) + + def _read_packet(self): + x = self._read_all(4) + # most sftp servers won't accept packets larger than about 32k, so + # anything with the high byte set (> 16MB) is just garbage. + if x[0] != '\x00': + raise SFTPError('Garbage packet received') + size = struct.unpack('>I', x)[0] + data = self._read_all(size) + if self.ultra_debug: + self._log(DEBUG, util.format_binary(data, 'IN: ')); + if size > 0: + t = ord(data[0]) + #self._log(DEBUG2, 'read: %s (len=%d)' % (CMD_NAMES.get(t), '0x%02x' % t, len(data)-1)) + return t, data[1:] + return 0, '' diff --git a/lib/paramiko/sftp_attr.py b/lib/paramiko/sftp_attr.py new file mode 100644 index 0000000..1f09421 --- /dev/null +++ b/lib/paramiko/sftp_attr.py @@ -0,0 +1,223 @@ +# Copyright (C) 2003-2006 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +import stat +import time +from paramiko.common import * +from paramiko.sftp import * + + +class SFTPAttributes (object): + """ + Representation of the attributes of a file (or proxied file) for SFTP in + client or server mode. It attemps to mirror the object returned by + C{os.stat} as closely as possible, so it may have the following fields, + with the same meanings as those returned by an C{os.stat} object: + - st_size + - st_uid + - st_gid + - st_mode + - st_atime + - st_mtime + + Because SFTP allows flags to have other arbitrary named attributes, these + are stored in a dict named C{attr}. Occasionally, the filename is also + stored, in C{filename}. + """ + + FLAG_SIZE = 1 + FLAG_UIDGID = 2 + FLAG_PERMISSIONS = 4 + FLAG_AMTIME = 8 + FLAG_EXTENDED = 0x80000000L + + def __init__(self): + """ + Create a new (empty) SFTPAttributes object. All fields will be empty. + """ + self._flags = 0 + self.st_size = None + self.st_uid = None + self.st_gid = None + self.st_mode = None + self.st_atime = None + self.st_mtime = None + self.attr = {} + + def from_stat(cls, obj, filename=None): + """ + Create an SFTPAttributes object from an existing C{stat} object (an + object returned by C{os.stat}). + + @param obj: an object returned by C{os.stat} (or equivalent). + @type obj: object + @param filename: the filename associated with this file. + @type filename: str + @return: new L{SFTPAttributes} object with the same attribute fields. + @rtype: L{SFTPAttributes} + """ + attr = cls() + attr.st_size = obj.st_size + attr.st_uid = obj.st_uid + attr.st_gid = obj.st_gid + attr.st_mode = obj.st_mode + attr.st_atime = obj.st_atime + attr.st_mtime = obj.st_mtime + if filename is not None: + attr.filename = filename + return attr + from_stat = classmethod(from_stat) + + def __repr__(self): + return '' % self._debug_str() + + + ### internals... + + + def _from_msg(cls, msg, filename=None, longname=None): + attr = cls() + attr._unpack(msg) + if filename is not None: + attr.filename = filename + if longname is not None: + attr.longname = longname + return attr + _from_msg = classmethod(_from_msg) + + def _unpack(self, msg): + self._flags = msg.get_int() + if self._flags & self.FLAG_SIZE: + self.st_size = msg.get_int64() + if self._flags & self.FLAG_UIDGID: + self.st_uid = msg.get_int() + self.st_gid = msg.get_int() + if self._flags & self.FLAG_PERMISSIONS: + self.st_mode = msg.get_int() + if self._flags & self.FLAG_AMTIME: + self.st_atime = msg.get_int() + self.st_mtime = msg.get_int() + if self._flags & self.FLAG_EXTENDED: + count = msg.get_int() + for i in range(count): + self.attr[msg.get_string()] = msg.get_string() + + def _pack(self, msg): + self._flags = 0 + if self.st_size is not None: + self._flags |= self.FLAG_SIZE + if (self.st_uid is not None) and (self.st_gid is not None): + self._flags |= self.FLAG_UIDGID + if self.st_mode is not None: + self._flags |= self.FLAG_PERMISSIONS + if (self.st_atime is not None) and (self.st_mtime is not None): + self._flags |= self.FLAG_AMTIME + if len(self.attr) > 0: + self._flags |= self.FLAG_EXTENDED + msg.add_int(self._flags) + if self._flags & self.FLAG_SIZE: + msg.add_int64(self.st_size) + if self._flags & self.FLAG_UIDGID: + msg.add_int(self.st_uid) + msg.add_int(self.st_gid) + if self._flags & self.FLAG_PERMISSIONS: + msg.add_int(self.st_mode) + if self._flags & self.FLAG_AMTIME: + # throw away any fractional seconds + msg.add_int(long(self.st_atime)) + msg.add_int(long(self.st_mtime)) + if self._flags & self.FLAG_EXTENDED: + msg.add_int(len(self.attr)) + for key, val in self.attr.iteritems(): + msg.add_string(key) + msg.add_string(val) + return + + def _debug_str(self): + out = '[ ' + if self.st_size is not None: + out += 'size=%d ' % self.st_size + if (self.st_uid is not None) and (self.st_gid is not None): + out += 'uid=%d gid=%d ' % (self.st_uid, self.st_gid) + if self.st_mode is not None: + out += 'mode=' + oct(self.st_mode) + ' ' + if (self.st_atime is not None) and (self.st_mtime is not None): + out += 'atime=%d mtime=%d ' % (self.st_atime, self.st_mtime) + for k, v in self.attr.iteritems(): + out += '"%s"=%r ' % (str(k), v) + out += ']' + return out + + def _rwx(n, suid, sticky=False): + if suid: + suid = 2 + out = '-r'[n >> 2] + '-w'[(n >> 1) & 1] + if sticky: + out += '-xTt'[suid + (n & 1)] + else: + out += '-xSs'[suid + (n & 1)] + return out + _rwx = staticmethod(_rwx) + + def __str__(self): + "create a unix-style long description of the file (like ls -l)" + if self.st_mode is not None: + kind = stat.S_IFMT(self.st_mode) + if kind == stat.S_IFIFO: + ks = 'p' + elif kind == stat.S_IFCHR: + ks = 'c' + elif kind == stat.S_IFDIR: + ks = 'd' + elif kind == stat.S_IFBLK: + ks = 'b' + elif kind == stat.S_IFREG: + ks = '-' + elif kind == stat.S_IFLNK: + ks = 'l' + elif kind == stat.S_IFSOCK: + ks = 's' + else: + ks = '?' + ks += self._rwx((self.st_mode & 0700) >> 6, self.st_mode & stat.S_ISUID) + ks += self._rwx((self.st_mode & 070) >> 3, self.st_mode & stat.S_ISGID) + ks += self._rwx(self.st_mode & 7, self.st_mode & stat.S_ISVTX, True) + else: + ks = '?---------' + # compute display date + if (self.st_mtime is None) or (self.st_mtime == 0xffffffffL): + # shouldn't really happen + datestr = '(unknown date)' + else: + if abs(time.time() - self.st_mtime) > 15552000: + # (15552000 = 6 months) + datestr = time.strftime('%d %b %Y', time.localtime(self.st_mtime)) + else: + datestr = time.strftime('%d %b %H:%M', time.localtime(self.st_mtime)) + filename = getattr(self, 'filename', '?') + + # not all servers support uid/gid + uid = self.st_uid + gid = self.st_gid + if uid is None: + uid = 0 + if gid is None: + gid = 0 + + return '%s 1 %-8d %-8d %8d %-12s %s' % (ks, uid, gid, self.st_size, datestr, filename) + diff --git a/lib/paramiko/sftp_client.py b/lib/paramiko/sftp_client.py new file mode 100644 index 0000000..79a7761 --- /dev/null +++ b/lib/paramiko/sftp_client.py @@ -0,0 +1,733 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Client-mode SFTP support. +""" + +from binascii import hexlify +import errno +import os +import stat +import threading +import time +import weakref + +from paramiko.sftp import * +from paramiko.sftp_attr import SFTPAttributes +from paramiko.ssh_exception import SSHException +from paramiko.sftp_file import SFTPFile + + +def _to_unicode(s): + """ + decode a string as ascii or utf8 if possible (as required by the sftp + protocol). if neither works, just return a byte string because the server + probably doesn't know the filename's encoding. + """ + try: + return s.encode('ascii') + except UnicodeError: + try: + return s.decode('utf-8') + except UnicodeError: + return s + + +class SFTPClient (BaseSFTP): + """ + SFTP client object. C{SFTPClient} is used to open an sftp session across + an open ssh L{Transport} and do remote file operations. + """ + + def __init__(self, sock): + """ + Create an SFTP client from an existing L{Channel}. The channel + should already have requested the C{"sftp"} subsystem. + + An alternate way to create an SFTP client context is by using + L{from_transport}. + + @param sock: an open L{Channel} using the C{"sftp"} subsystem + @type sock: L{Channel} + + @raise SSHException: if there's an exception while negotiating + sftp + """ + BaseSFTP.__init__(self) + self.sock = sock + self.ultra_debug = False + self.request_number = 1 + # lock for request_number + self._lock = threading.Lock() + self._cwd = None + # request # -> SFTPFile + self._expecting = weakref.WeakValueDictionary() + if type(sock) is Channel: + # override default logger + transport = self.sock.get_transport() + self.logger = util.get_logger(transport.get_log_channel() + '.sftp') + self.ultra_debug = transport.get_hexdump() + try: + server_version = self._send_version() + except EOFError, x: + raise SSHException('EOF during negotiation') + self._log(INFO, 'Opened sftp connection (server version %d)' % server_version) + + def from_transport(cls, t): + """ + Create an SFTP client channel from an open L{Transport}. + + @param t: an open L{Transport} which is already authenticated + @type t: L{Transport} + @return: a new L{SFTPClient} object, referring to an sftp session + (channel) across the transport + @rtype: L{SFTPClient} + """ + chan = t.open_session() + if chan is None: + return None + chan.invoke_subsystem('sftp') + return cls(chan) + from_transport = classmethod(from_transport) + + def _log(self, level, msg, *args): + if isinstance(msg, list): + for m in msg: + super(SFTPClient, self)._log(level, "[chan %s] " + m, *([ self.sock.get_name() ] + list(args))) + else: + super(SFTPClient, self)._log(level, "[chan %s] " + msg, *([ self.sock.get_name() ] + list(args))) + + def close(self): + """ + Close the SFTP session and its underlying channel. + + @since: 1.4 + """ + self._log(INFO, 'sftp session closed.') + self.sock.close() + + def get_channel(self): + """ + Return the underlying L{Channel} object for this SFTP session. This + might be useful for doing things like setting a timeout on the channel. + + @return: the SSH channel + @rtype: L{Channel} + + @since: 1.7.1 + """ + return self.sock + + def listdir(self, path='.'): + """ + Return a list containing the names of the entries in the given C{path}. + The list is in arbitrary order. It does not include the special + entries C{'.'} and C{'..'} even if they are present in the folder. + This method is meant to mirror C{os.listdir} as closely as possible. + For a list of full L{SFTPAttributes} objects, see L{listdir_attr}. + + @param path: path to list (defaults to C{'.'}) + @type path: str + @return: list of filenames + @rtype: list of str + """ + return [f.filename for f in self.listdir_attr(path)] + + def listdir_attr(self, path='.'): + """ + Return a list containing L{SFTPAttributes} objects corresponding to + files in the given C{path}. The list is in arbitrary order. It does + not include the special entries C{'.'} and C{'..'} even if they are + present in the folder. + + The returned L{SFTPAttributes} objects will each have an additional + field: C{longname}, which may contain a formatted string of the file's + attributes, in unix format. The content of this string will probably + depend on the SFTP server implementation. + + @param path: path to list (defaults to C{'.'}) + @type path: str + @return: list of attributes + @rtype: list of L{SFTPAttributes} + + @since: 1.2 + """ + path = self._adjust_cwd(path) + self._log(DEBUG, 'listdir(%r)' % path) + t, msg = self._request(CMD_OPENDIR, path) + if t != CMD_HANDLE: + raise SFTPError('Expected handle') + handle = msg.get_string() + filelist = [] + while True: + try: + t, msg = self._request(CMD_READDIR, handle) + except EOFError, e: + # done with handle + break + if t != CMD_NAME: + raise SFTPError('Expected name response') + count = msg.get_int() + for i in range(count): + filename = _to_unicode(msg.get_string()) + longname = _to_unicode(msg.get_string()) + attr = SFTPAttributes._from_msg(msg, filename, longname) + if (filename != '.') and (filename != '..'): + filelist.append(attr) + self._request(CMD_CLOSE, handle) + return filelist + + def open(self, filename, mode='r', bufsize=-1): + """ + Open a file on the remote server. The arguments are the same as for + python's built-in C{file} (aka C{open}). A file-like object is + returned, which closely mimics the behavior of a normal python file + object. + + The mode indicates how the file is to be opened: C{'r'} for reading, + C{'w'} for writing (truncating an existing file), C{'a'} for appending, + C{'r+'} for reading/writing, C{'w+'} for reading/writing (truncating an + existing file), C{'a+'} for reading/appending. The python C{'b'} flag + is ignored, since SSH treats all files as binary. The C{'U'} flag is + supported in a compatible way. + + Since 1.5.2, an C{'x'} flag indicates that the operation should only + succeed if the file was created and did not previously exist. This has + no direct mapping to python's file flags, but is commonly known as the + C{O_EXCL} flag in posix. + + The file will be buffered in standard python style by default, but + can be altered with the C{bufsize} parameter. C{0} turns off + buffering, C{1} uses line buffering, and any number greater than 1 + (C{>1}) uses that specific buffer size. + + @param filename: name of the file to open + @type filename: str + @param mode: mode (python-style) to open in + @type mode: str + @param bufsize: desired buffering (-1 = default buffer size) + @type bufsize: int + @return: a file object representing the open file + @rtype: SFTPFile + + @raise IOError: if the file could not be opened. + """ + filename = self._adjust_cwd(filename) + self._log(DEBUG, 'open(%r, %r)' % (filename, mode)) + imode = 0 + if ('r' in mode) or ('+' in mode): + imode |= SFTP_FLAG_READ + if ('w' in mode) or ('+' in mode) or ('a' in mode): + imode |= SFTP_FLAG_WRITE + if ('w' in mode): + imode |= SFTP_FLAG_CREATE | SFTP_FLAG_TRUNC + if ('a' in mode): + imode |= SFTP_FLAG_CREATE | SFTP_FLAG_APPEND + if ('x' in mode): + imode |= SFTP_FLAG_CREATE | SFTP_FLAG_EXCL + attrblock = SFTPAttributes() + t, msg = self._request(CMD_OPEN, filename, imode, attrblock) + if t != CMD_HANDLE: + raise SFTPError('Expected handle') + handle = msg.get_string() + self._log(DEBUG, 'open(%r, %r) -> %s' % (filename, mode, hexlify(handle))) + return SFTPFile(self, handle, mode, bufsize) + + # python continues to vacillate about "open" vs "file"... + file = open + + def remove(self, path): + """ + Remove the file at the given path. This only works on files; for + removing folders (directories), use L{rmdir}. + + @param path: path (absolute or relative) of the file to remove + @type path: str + + @raise IOError: if the path refers to a folder (directory) + """ + path = self._adjust_cwd(path) + self._log(DEBUG, 'remove(%r)' % path) + self._request(CMD_REMOVE, path) + + unlink = remove + + def rename(self, oldpath, newpath): + """ + Rename a file or folder from C{oldpath} to C{newpath}. + + @param oldpath: existing name of the file or folder + @type oldpath: str + @param newpath: new name for the file or folder + @type newpath: str + + @raise IOError: if C{newpath} is a folder, or something else goes + wrong + """ + oldpath = self._adjust_cwd(oldpath) + newpath = self._adjust_cwd(newpath) + self._log(DEBUG, 'rename(%r, %r)' % (oldpath, newpath)) + self._request(CMD_RENAME, oldpath, newpath) + + def mkdir(self, path, mode=0777): + """ + Create a folder (directory) named C{path} with numeric mode C{mode}. + The default mode is 0777 (octal). On some systems, mode is ignored. + Where it is used, the current umask value is first masked out. + + @param path: name of the folder to create + @type path: str + @param mode: permissions (posix-style) for the newly-created folder + @type mode: int + """ + path = self._adjust_cwd(path) + self._log(DEBUG, 'mkdir(%r, %r)' % (path, mode)) + attr = SFTPAttributes() + attr.st_mode = mode + self._request(CMD_MKDIR, path, attr) + + def rmdir(self, path): + """ + Remove the folder named C{path}. + + @param path: name of the folder to remove + @type path: str + """ + path = self._adjust_cwd(path) + self._log(DEBUG, 'rmdir(%r)' % path) + self._request(CMD_RMDIR, path) + + def stat(self, path): + """ + Retrieve information about a file on the remote system. The return + value is an object whose attributes correspond to the attributes of + python's C{stat} structure as returned by C{os.stat}, except that it + contains fewer fields. An SFTP server may return as much or as little + info as it wants, so the results may vary from server to server. + + Unlike a python C{stat} object, the result may not be accessed as a + tuple. This is mostly due to the author's slack factor. + + The fields supported are: C{st_mode}, C{st_size}, C{st_uid}, C{st_gid}, + C{st_atime}, and C{st_mtime}. + + @param path: the filename to stat + @type path: str + @return: an object containing attributes about the given file + @rtype: SFTPAttributes + """ + path = self._adjust_cwd(path) + self._log(DEBUG, 'stat(%r)' % path) + t, msg = self._request(CMD_STAT, path) + if t != CMD_ATTRS: + raise SFTPError('Expected attributes') + return SFTPAttributes._from_msg(msg) + + def lstat(self, path): + """ + Retrieve information about a file on the remote system, without + following symbolic links (shortcuts). This otherwise behaves exactly + the same as L{stat}. + + @param path: the filename to stat + @type path: str + @return: an object containing attributes about the given file + @rtype: SFTPAttributes + """ + path = self._adjust_cwd(path) + self._log(DEBUG, 'lstat(%r)' % path) + t, msg = self._request(CMD_LSTAT, path) + if t != CMD_ATTRS: + raise SFTPError('Expected attributes') + return SFTPAttributes._from_msg(msg) + + def symlink(self, source, dest): + """ + Create a symbolic link (shortcut) of the C{source} path at + C{destination}. + + @param source: path of the original file + @type source: str + @param dest: path of the newly created symlink + @type dest: str + """ + dest = self._adjust_cwd(dest) + self._log(DEBUG, 'symlink(%r, %r)' % (source, dest)) + if type(source) is unicode: + source = source.encode('utf-8') + self._request(CMD_SYMLINK, source, dest) + + def chmod(self, path, mode): + """ + Change the mode (permissions) of a file. The permissions are + unix-style and identical to those used by python's C{os.chmod} + function. + + @param path: path of the file to change the permissions of + @type path: str + @param mode: new permissions + @type mode: int + """ + path = self._adjust_cwd(path) + self._log(DEBUG, 'chmod(%r, %r)' % (path, mode)) + attr = SFTPAttributes() + attr.st_mode = mode + self._request(CMD_SETSTAT, path, attr) + + def chown(self, path, uid, gid): + """ + Change the owner (C{uid}) and group (C{gid}) of a file. As with + python's C{os.chown} function, you must pass both arguments, so if you + only want to change one, use L{stat} first to retrieve the current + owner and group. + + @param path: path of the file to change the owner and group of + @type path: str + @param uid: new owner's uid + @type uid: int + @param gid: new group id + @type gid: int + """ + path = self._adjust_cwd(path) + self._log(DEBUG, 'chown(%r, %r, %r)' % (path, uid, gid)) + attr = SFTPAttributes() + attr.st_uid, attr.st_gid = uid, gid + self._request(CMD_SETSTAT, path, attr) + + def utime(self, path, times): + """ + Set the access and modified times of the file specified by C{path}. If + C{times} is C{None}, then the file's access and modified times are set + to the current time. Otherwise, C{times} must be a 2-tuple of numbers, + of the form C{(atime, mtime)}, which is used to set the access and + modified times, respectively. This bizarre API is mimicked from python + for the sake of consistency -- I apologize. + + @param path: path of the file to modify + @type path: str + @param times: C{None} or a tuple of (access time, modified time) in + standard internet epoch time (seconds since 01 January 1970 GMT) + @type times: tuple(int) + """ + path = self._adjust_cwd(path) + if times is None: + times = (time.time(), time.time()) + self._log(DEBUG, 'utime(%r, %r)' % (path, times)) + attr = SFTPAttributes() + attr.st_atime, attr.st_mtime = times + self._request(CMD_SETSTAT, path, attr) + + def truncate(self, path, size): + """ + Change the size of the file specified by C{path}. This usually extends + or shrinks the size of the file, just like the C{truncate()} method on + python file objects. + + @param path: path of the file to modify + @type path: str + @param size: the new size of the file + @type size: int or long + """ + path = self._adjust_cwd(path) + self._log(DEBUG, 'truncate(%r, %r)' % (path, size)) + attr = SFTPAttributes() + attr.st_size = size + self._request(CMD_SETSTAT, path, attr) + + def readlink(self, path): + """ + Return the target of a symbolic link (shortcut). You can use + L{symlink} to create these. The result may be either an absolute or + relative pathname. + + @param path: path of the symbolic link file + @type path: str + @return: target path + @rtype: str + """ + path = self._adjust_cwd(path) + self._log(DEBUG, 'readlink(%r)' % path) + t, msg = self._request(CMD_READLINK, path) + if t != CMD_NAME: + raise SFTPError('Expected name response') + count = msg.get_int() + if count == 0: + return None + if count != 1: + raise SFTPError('Readlink returned %d results' % count) + return _to_unicode(msg.get_string()) + + def normalize(self, path): + """ + Return the normalized path (on the server) of a given path. This + can be used to quickly resolve symbolic links or determine what the + server is considering to be the "current folder" (by passing C{'.'} + as C{path}). + + @param path: path to be normalized + @type path: str + @return: normalized form of the given path + @rtype: str + + @raise IOError: if the path can't be resolved on the server + """ + path = self._adjust_cwd(path) + self._log(DEBUG, 'normalize(%r)' % path) + t, msg = self._request(CMD_REALPATH, path) + if t != CMD_NAME: + raise SFTPError('Expected name response') + count = msg.get_int() + if count != 1: + raise SFTPError('Realpath returned %d results' % count) + return _to_unicode(msg.get_string()) + + def chdir(self, path): + """ + Change the "current directory" of this SFTP session. Since SFTP + doesn't really have the concept of a current working directory, this + is emulated by paramiko. Once you use this method to set a working + directory, all operations on this SFTPClient object will be relative + to that path. You can pass in C{None} to stop using a current working + directory. + + @param path: new current working directory + @type path: str + + @raise IOError: if the requested path doesn't exist on the server + + @since: 1.4 + """ + if path is None: + self._cwd = None + return + if not stat.S_ISDIR(self.stat(path).st_mode): + raise SFTPError(errno.ENOTDIR, "%s: %s" % (os.strerror(errno.ENOTDIR), path)) + self._cwd = self.normalize(path).encode('utf-8') + + def getcwd(self): + """ + Return the "current working directory" for this SFTP session, as + emulated by paramiko. If no directory has been set with L{chdir}, + this method will return C{None}. + + @return: the current working directory on the server, or C{None} + @rtype: str + + @since: 1.4 + """ + return self._cwd + + def put(self, localpath, remotepath, callback=None, confirm=True): + """ + Copy a local file (C{localpath}) to the SFTP server as C{remotepath}. + Any exception raised by operations will be passed through. This + method is primarily provided as a convenience. + + The SFTP operations use pipelining for speed. + + @param localpath: the local file to copy + @type localpath: str + @param remotepath: the destination path on the SFTP server + @type remotepath: str + @param callback: optional callback function that accepts the bytes + transferred so far and the total bytes to be transferred + (since 1.7.4) + @type callback: function(int, int) + @param confirm: whether to do a stat() on the file afterwards to + confirm the file size (since 1.7.7) + @type confirm: bool + + @return: an object containing attributes about the given file + (since 1.7.4) + @rtype: SFTPAttributes + + @since: 1.4 + """ + file_size = os.stat(localpath).st_size + fl = file(localpath, 'rb') + try: + fr = self.file(remotepath, 'wb') + fr.set_pipelined(True) + size = 0 + try: + while True: + data = fl.read(32768) + if len(data) == 0: + break + fr.write(data) + size += len(data) + if callback is not None: + callback(size, file_size) + finally: + fr.close() + finally: + fl.close() + if confirm: + s = self.stat(remotepath) + if s.st_size != size: + raise IOError('size mismatch in put! %d != %d' % (s.st_size, size)) + else: + s = SFTPAttributes() + return s + + def get(self, remotepath, localpath, callback=None): + """ + Copy a remote file (C{remotepath}) from the SFTP server to the local + host as C{localpath}. Any exception raised by operations will be + passed through. This method is primarily provided as a convenience. + + @param remotepath: the remote file to copy + @type remotepath: str + @param localpath: the destination path on the local host + @type localpath: str + @param callback: optional callback function that accepts the bytes + transferred so far and the total bytes to be transferred + (since 1.7.4) + @type callback: function(int, int) + + @since: 1.4 + """ + fr = self.file(remotepath, 'rb') + file_size = self.stat(remotepath).st_size + fr.prefetch() + try: + fl = file(localpath, 'wb') + try: + size = 0 + while True: + data = fr.read(32768) + if len(data) == 0: + break + fl.write(data) + size += len(data) + if callback is not None: + callback(size, file_size) + finally: + fl.close() + finally: + fr.close() + s = os.stat(localpath) + if s.st_size != size: + raise IOError('size mismatch in get! %d != %d' % (s.st_size, size)) + + + ### internals... + + + def _request(self, t, *arg): + num = self._async_request(type(None), t, *arg) + return self._read_response(num) + + def _async_request(self, fileobj, t, *arg): + # this method may be called from other threads (prefetch) + self._lock.acquire() + try: + msg = Message() + msg.add_int(self.request_number) + for item in arg: + if type(item) is int: + msg.add_int(item) + elif type(item) is long: + msg.add_int64(item) + elif type(item) is str: + msg.add_string(item) + elif type(item) is SFTPAttributes: + item._pack(msg) + else: + raise Exception('unknown type for %r type %r' % (item, type(item))) + num = self.request_number + self._expecting[num] = fileobj + self._send_packet(t, str(msg)) + self.request_number += 1 + finally: + self._lock.release() + return num + + def _read_response(self, waitfor=None): + while True: + try: + t, data = self._read_packet() + except EOFError, e: + raise SSHException('Server connection dropped: %s' % (str(e),)) + msg = Message(data) + num = msg.get_int() + if num not in self._expecting: + # might be response for a file that was closed before responses came back + self._log(DEBUG, 'Unexpected response #%d' % (num,)) + if waitfor is None: + # just doing a single check + break + continue + fileobj = self._expecting[num] + del self._expecting[num] + if num == waitfor: + # synchronous + if t == CMD_STATUS: + self._convert_status(msg) + return t, msg + if fileobj is not type(None): + fileobj._async_response(t, msg) + if waitfor is None: + # just doing a single check + break + return (None, None) + + def _finish_responses(self, fileobj): + while fileobj in self._expecting.values(): + self._read_response() + fileobj._check_exception() + + def _convert_status(self, msg): + """ + Raises EOFError or IOError on error status; otherwise does nothing. + """ + code = msg.get_int() + text = msg.get_string() + if code == SFTP_OK: + return + elif code == SFTP_EOF: + raise EOFError(text) + elif code == SFTP_NO_SUCH_FILE: + # clever idea from john a. meinel: map the error codes to errno + raise IOError(errno.ENOENT, text) + elif code == SFTP_PERMISSION_DENIED: + raise IOError(errno.EACCES, text) + else: + raise IOError(text) + + def _adjust_cwd(self, path): + """ + Return an adjusted path if we're emulating a "current working + directory" for the server. + """ + if type(path) is unicode: + path = path.encode('utf-8') + if self._cwd is None: + return path + if (len(path) > 0) and (path[0] == '/'): + # absolute path + return path + if self._cwd == '/': + return self._cwd + path + return self._cwd + '/' + path + + +class SFTP (SFTPClient): + "an alias for L{SFTPClient} for backwards compatability" + pass diff --git a/lib/paramiko/sftp_file.py b/lib/paramiko/sftp_file.py new file mode 100644 index 0000000..8c5c7ac --- /dev/null +++ b/lib/paramiko/sftp_file.py @@ -0,0 +1,476 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +L{SFTPFile} +""" + +from binascii import hexlify +import socket +import threading +import time + +from paramiko.common import * +from paramiko.sftp import * +from paramiko.file import BufferedFile +from paramiko.sftp_attr import SFTPAttributes + + +class SFTPFile (BufferedFile): + """ + Proxy object for a file on the remote server, in client mode SFTP. + """ + + # Some sftp servers will choke if you send read/write requests larger than + # this size. + MAX_REQUEST_SIZE = 32768 + + def __init__(self, sftp, handle, mode='r', bufsize=-1): + BufferedFile.__init__(self) + self.sftp = sftp + self.handle = handle + BufferedFile._set_mode(self, mode, bufsize) + self.pipelined = False + self._prefetching = False + self._prefetch_done = False + self._prefetch_data = {} + self._prefetch_reads = [] + self._saved_exception = None + + def __del__(self): + self._close(async=True) + + def close(self): + self._close(async=False) + + def _close(self, async=False): + # We allow double-close without signaling an error, because real + # Python file objects do. However, we must protect against actually + # sending multiple CMD_CLOSE packets, because after we close our + # handle, the same handle may be re-allocated by the server, and we + # may end up mysteriously closing some random other file. (This is + # especially important because we unconditionally call close() from + # __del__.) + if self._closed: + return + self.sftp._log(DEBUG, 'close(%s)' % hexlify(self.handle)) + if self.pipelined: + self.sftp._finish_responses(self) + BufferedFile.close(self) + try: + if async: + # GC'd file handle could be called from an arbitrary thread -- don't wait for a response + self.sftp._async_request(type(None), CMD_CLOSE, self.handle) + else: + self.sftp._request(CMD_CLOSE, self.handle) + except EOFError: + # may have outlived the Transport connection + pass + except (IOError, socket.error): + # may have outlived the Transport connection + pass + + def _data_in_prefetch_requests(self, offset, size): + k = [i for i in self._prefetch_reads if i[0] <= offset] + if len(k) == 0: + return False + k.sort(lambda x, y: cmp(x[0], y[0])) + buf_offset, buf_size = k[-1] + if buf_offset + buf_size <= offset: + # prefetch request ends before this one begins + return False + if buf_offset + buf_size >= offset + size: + # inclusive + return True + # well, we have part of the request. see if another chunk has the rest. + return self._data_in_prefetch_requests(buf_offset + buf_size, offset + size - buf_offset - buf_size) + + def _data_in_prefetch_buffers(self, offset): + """ + if a block of data is present in the prefetch buffers, at the given + offset, return the offset of the relevant prefetch buffer. otherwise, + return None. this guarantees nothing about the number of bytes + collected in the prefetch buffer so far. + """ + k = [i for i in self._prefetch_data.keys() if i <= offset] + if len(k) == 0: + return None + index = max(k) + buf_offset = offset - index + if buf_offset >= len(self._prefetch_data[index]): + # it's not here + return None + return index + + def _read_prefetch(self, size): + """ + read data out of the prefetch buffer, if possible. if the data isn't + in the buffer, return None. otherwise, behaves like a normal read. + """ + # while not closed, and haven't fetched past the current position, and haven't reached EOF... + while True: + offset = self._data_in_prefetch_buffers(self._realpos) + if offset is not None: + break + if self._prefetch_done or self._closed: + break + self.sftp._read_response() + self._check_exception() + if offset is None: + self._prefetching = False + return None + prefetch = self._prefetch_data[offset] + del self._prefetch_data[offset] + + buf_offset = self._realpos - offset + if buf_offset > 0: + self._prefetch_data[offset] = prefetch[:buf_offset] + prefetch = prefetch[buf_offset:] + if size < len(prefetch): + self._prefetch_data[self._realpos + size] = prefetch[size:] + prefetch = prefetch[:size] + return prefetch + + def _read(self, size): + size = min(size, self.MAX_REQUEST_SIZE) + if self._prefetching: + data = self._read_prefetch(size) + if data is not None: + return data + t, msg = self.sftp._request(CMD_READ, self.handle, long(self._realpos), int(size)) + if t != CMD_DATA: + raise SFTPError('Expected data') + return msg.get_string() + + def _write(self, data): + # may write less than requested if it would exceed max packet size + chunk = min(len(data), self.MAX_REQUEST_SIZE) + req = self.sftp._async_request(type(None), CMD_WRITE, self.handle, long(self._realpos), str(data[:chunk])) + if not self.pipelined or self.sftp.sock.recv_ready(): + t, msg = self.sftp._read_response(req) + if t != CMD_STATUS: + raise SFTPError('Expected status') + # convert_status already called + return chunk + + def settimeout(self, timeout): + """ + Set a timeout on read/write operations on the underlying socket or + ssh L{Channel}. + + @see: L{Channel.settimeout} + @param timeout: seconds to wait for a pending read/write operation + before raising C{socket.timeout}, or C{None} for no timeout + @type timeout: float + """ + self.sftp.sock.settimeout(timeout) + + def gettimeout(self): + """ + Returns the timeout in seconds (as a float) associated with the socket + or ssh L{Channel} used for this file. + + @see: L{Channel.gettimeout} + @rtype: float + """ + return self.sftp.sock.gettimeout() + + def setblocking(self, blocking): + """ + Set blocking or non-blocking mode on the underiying socket or ssh + L{Channel}. + + @see: L{Channel.setblocking} + @param blocking: 0 to set non-blocking mode; non-0 to set blocking + mode. + @type blocking: int + """ + self.sftp.sock.setblocking(blocking) + + def seek(self, offset, whence=0): + self.flush() + if whence == self.SEEK_SET: + self._realpos = self._pos = offset + elif whence == self.SEEK_CUR: + self._pos += offset + self._realpos = self._pos + else: + self._realpos = self._pos = self._get_size() + offset + self._rbuffer = '' + + def stat(self): + """ + Retrieve information about this file from the remote system. This is + exactly like L{SFTP.stat}, except that it operates on an already-open + file. + + @return: an object containing attributes about this file. + @rtype: SFTPAttributes + """ + t, msg = self.sftp._request(CMD_FSTAT, self.handle) + if t != CMD_ATTRS: + raise SFTPError('Expected attributes') + return SFTPAttributes._from_msg(msg) + + def chmod(self, mode): + """ + Change the mode (permissions) of this file. The permissions are + unix-style and identical to those used by python's C{os.chmod} + function. + + @param mode: new permissions + @type mode: int + """ + self.sftp._log(DEBUG, 'chmod(%s, %r)' % (hexlify(self.handle), mode)) + attr = SFTPAttributes() + attr.st_mode = mode + self.sftp._request(CMD_FSETSTAT, self.handle, attr) + + def chown(self, uid, gid): + """ + Change the owner (C{uid}) and group (C{gid}) of this file. As with + python's C{os.chown} function, you must pass both arguments, so if you + only want to change one, use L{stat} first to retrieve the current + owner and group. + + @param uid: new owner's uid + @type uid: int + @param gid: new group id + @type gid: int + """ + self.sftp._log(DEBUG, 'chown(%s, %r, %r)' % (hexlify(self.handle), uid, gid)) + attr = SFTPAttributes() + attr.st_uid, attr.st_gid = uid, gid + self.sftp._request(CMD_FSETSTAT, self.handle, attr) + + def utime(self, times): + """ + Set the access and modified times of this file. If + C{times} is C{None}, then the file's access and modified times are set + to the current time. Otherwise, C{times} must be a 2-tuple of numbers, + of the form C{(atime, mtime)}, which is used to set the access and + modified times, respectively. This bizarre API is mimicked from python + for the sake of consistency -- I apologize. + + @param times: C{None} or a tuple of (access time, modified time) in + standard internet epoch time (seconds since 01 January 1970 GMT) + @type times: tuple(int) + """ + if times is None: + times = (time.time(), time.time()) + self.sftp._log(DEBUG, 'utime(%s, %r)' % (hexlify(self.handle), times)) + attr = SFTPAttributes() + attr.st_atime, attr.st_mtime = times + self.sftp._request(CMD_FSETSTAT, self.handle, attr) + + def truncate(self, size): + """ + Change the size of this file. This usually extends + or shrinks the size of the file, just like the C{truncate()} method on + python file objects. + + @param size: the new size of the file + @type size: int or long + """ + self.sftp._log(DEBUG, 'truncate(%s, %r)' % (hexlify(self.handle), size)) + attr = SFTPAttributes() + attr.st_size = size + self.sftp._request(CMD_FSETSTAT, self.handle, attr) + + def check(self, hash_algorithm, offset=0, length=0, block_size=0): + """ + Ask the server for a hash of a section of this file. This can be used + to verify a successful upload or download, or for various rsync-like + operations. + + The file is hashed from C{offset}, for C{length} bytes. If C{length} + is 0, the remainder of the file is hashed. Thus, if both C{offset} + and C{length} are zero, the entire file is hashed. + + Normally, C{block_size} will be 0 (the default), and this method will + return a byte string representing the requested hash (for example, a + string of length 16 for MD5, or 20 for SHA-1). If a non-zero + C{block_size} is given, each chunk of the file (from C{offset} to + C{offset + length}) of C{block_size} bytes is computed as a separate + hash. The hash results are all concatenated and returned as a single + string. + + For example, C{check('sha1', 0, 1024, 512)} will return a string of + length 40. The first 20 bytes will be the SHA-1 of the first 512 bytes + of the file, and the last 20 bytes will be the SHA-1 of the next 512 + bytes. + + @param hash_algorithm: the name of the hash algorithm to use (normally + C{"sha1"} or C{"md5"}) + @type hash_algorithm: str + @param offset: offset into the file to begin hashing (0 means to start + from the beginning) + @type offset: int or long + @param length: number of bytes to hash (0 means continue to the end of + the file) + @type length: int or long + @param block_size: number of bytes to hash per result (must not be less + than 256; 0 means to compute only one hash of the entire segment) + @type block_size: int + @return: string of bytes representing the hash of each block, + concatenated together + @rtype: str + + @note: Many (most?) servers don't support this extension yet. + + @raise IOError: if the server doesn't support the "check-file" + extension, or possibly doesn't support the hash algorithm + requested + + @since: 1.4 + """ + t, msg = self.sftp._request(CMD_EXTENDED, 'check-file', self.handle, + hash_algorithm, long(offset), long(length), block_size) + ext = msg.get_string() + alg = msg.get_string() + data = msg.get_remainder() + return data + + def set_pipelined(self, pipelined=True): + """ + Turn on/off the pipelining of write operations to this file. When + pipelining is on, paramiko won't wait for the server response after + each write operation. Instead, they're collected as they come in. + At the first non-write operation (including L{close}), all remaining + server responses are collected. This means that if there was an error + with one of your later writes, an exception might be thrown from + within L{close} instead of L{write}. + + By default, files are I{not} pipelined. + + @param pipelined: C{True} if pipelining should be turned on for this + file; C{False} otherwise + @type pipelined: bool + + @since: 1.5 + """ + self.pipelined = pipelined + + def prefetch(self): + """ + Pre-fetch the remaining contents of this file in anticipation of + future L{read} calls. If reading the entire file, pre-fetching can + dramatically improve the download speed by avoiding roundtrip latency. + The file's contents are incrementally buffered in a background thread. + + The prefetched data is stored in a buffer until read via the L{read} + method. Once data has been read, it's removed from the buffer. The + data may be read in a random order (using L{seek}); chunks of the + buffer that haven't been read will continue to be buffered. + + @since: 1.5.1 + """ + size = self.stat().st_size + # queue up async reads for the rest of the file + chunks = [] + n = self._realpos + while n < size: + chunk = min(self.MAX_REQUEST_SIZE, size - n) + chunks.append((n, chunk)) + n += chunk + if len(chunks) > 0: + self._start_prefetch(chunks) + + def readv(self, chunks): + """ + Read a set of blocks from the file by (offset, length). This is more + efficient than doing a series of L{seek} and L{read} calls, since the + prefetch machinery is used to retrieve all the requested blocks at + once. + + @param chunks: a list of (offset, length) tuples indicating which + sections of the file to read + @type chunks: list(tuple(long, int)) + @return: a list of blocks read, in the same order as in C{chunks} + @rtype: list(str) + + @since: 1.5.4 + """ + self.sftp._log(DEBUG, 'readv(%s, %r)' % (hexlify(self.handle), chunks)) + + read_chunks = [] + for offset, size in chunks: + # don't fetch data that's already in the prefetch buffer + if self._data_in_prefetch_buffers(offset) or self._data_in_prefetch_requests(offset, size): + continue + + # break up anything larger than the max read size + while size > 0: + chunk_size = min(size, self.MAX_REQUEST_SIZE) + read_chunks.append((offset, chunk_size)) + offset += chunk_size + size -= chunk_size + + self._start_prefetch(read_chunks) + # now we can just devolve to a bunch of read()s :) + for x in chunks: + self.seek(x[0]) + yield self.read(x[1]) + + + ### internals... + + + def _get_size(self): + try: + return self.stat().st_size + except: + return 0 + + def _start_prefetch(self, chunks): + self._prefetching = True + self._prefetch_done = False + self._prefetch_reads.extend(chunks) + + t = threading.Thread(target=self._prefetch_thread, args=(chunks,)) + t.setDaemon(True) + t.start() + + def _prefetch_thread(self, chunks): + # do these read requests in a temporary thread because there may be + # a lot of them, so it may block. + for offset, length in chunks: + self.sftp._async_request(self, CMD_READ, self.handle, long(offset), int(length)) + + def _async_response(self, t, msg): + if t == CMD_STATUS: + # save exception and re-raise it on next file operation + try: + self.sftp._convert_status(msg) + except Exception, x: + self._saved_exception = x + return + if t != CMD_DATA: + raise SFTPError('Expected data') + data = msg.get_string() + offset, length = self._prefetch_reads.pop(0) + self._prefetch_data[offset] = data + if len(self._prefetch_reads) == 0: + self._prefetch_done = True + + def _check_exception(self): + "if there's a saved exception, raise & clear it" + if self._saved_exception is not None: + x = self._saved_exception + self._saved_exception = None + raise x diff --git a/lib/paramiko/sftp_handle.py b/lib/paramiko/sftp_handle.py new file mode 100644 index 0000000..a6cd44a --- /dev/null +++ b/lib/paramiko/sftp_handle.py @@ -0,0 +1,202 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Abstraction of an SFTP file handle (for server mode). +""" + +import os + +from paramiko.common import * +from paramiko.sftp import * + + +class SFTPHandle (object): + """ + Abstract object representing a handle to an open file (or folder) in an + SFTP server implementation. Each handle has a string representation used + by the client to refer to the underlying file. + + Server implementations can (and should) subclass SFTPHandle to implement + features of a file handle, like L{stat} or L{chattr}. + """ + def __init__(self, flags=0): + """ + Create a new file handle representing a local file being served over + SFTP. If C{flags} is passed in, it's used to determine if the file + is open in append mode. + + @param flags: optional flags as passed to L{SFTPServerInterface.open} + @type flags: int + """ + self.__flags = flags + self.__name = None + # only for handles to folders: + self.__files = { } + self.__tell = None + + def close(self): + """ + When a client closes a file, this method is called on the handle. + Normally you would use this method to close the underlying OS level + file object(s). + + The default implementation checks for attributes on C{self} named + C{readfile} and/or C{writefile}, and if either or both are present, + their C{close()} methods are called. This means that if you are + using the default implementations of L{read} and L{write}, this + method's default implementation should be fine also. + """ + readfile = getattr(self, 'readfile', None) + if readfile is not None: + readfile.close() + writefile = getattr(self, 'writefile', None) + if writefile is not None: + writefile.close() + + def read(self, offset, length): + """ + Read up to C{length} bytes from this file, starting at position + C{offset}. The offset may be a python long, since SFTP allows it + to be 64 bits. + + If the end of the file has been reached, this method may return an + empty string to signify EOF, or it may also return L{SFTP_EOF}. + + The default implementation checks for an attribute on C{self} named + C{readfile}, and if present, performs the read operation on the python + file-like object found there. (This is meant as a time saver for the + common case where you are wrapping a python file object.) + + @param offset: position in the file to start reading from. + @type offset: int or long + @param length: number of bytes to attempt to read. + @type length: int + @return: data read from the file, or an SFTP error code. + @rtype: str + """ + readfile = getattr(self, 'readfile', None) + if readfile is None: + return SFTP_OP_UNSUPPORTED + try: + if self.__tell is None: + self.__tell = readfile.tell() + if offset != self.__tell: + readfile.seek(offset) + self.__tell = offset + data = readfile.read(length) + except IOError, e: + self.__tell = None + return SFTPServer.convert_errno(e.errno) + self.__tell += len(data) + return data + + def write(self, offset, data): + """ + Write C{data} into this file at position C{offset}. Extending the + file past its original end is expected. Unlike python's normal + C{write()} methods, this method cannot do a partial write: it must + write all of C{data} or else return an error. + + The default implementation checks for an attribute on C{self} named + C{writefile}, and if present, performs the write operation on the + python file-like object found there. The attribute is named + differently from C{readfile} to make it easy to implement read-only + (or write-only) files, but if both attributes are present, they should + refer to the same file. + + @param offset: position in the file to start reading from. + @type offset: int or long + @param data: data to write into the file. + @type data: str + @return: an SFTP error code like L{SFTP_OK}. + """ + writefile = getattr(self, 'writefile', None) + if writefile is None: + return SFTP_OP_UNSUPPORTED + try: + # in append mode, don't care about seeking + if (self.__flags & os.O_APPEND) == 0: + if self.__tell is None: + self.__tell = writefile.tell() + if offset != self.__tell: + writefile.seek(offset) + self.__tell = offset + writefile.write(data) + writefile.flush() + except IOError, e: + self.__tell = None + return SFTPServer.convert_errno(e.errno) + if self.__tell is not None: + self.__tell += len(data) + return SFTP_OK + + def stat(self): + """ + Return an L{SFTPAttributes} object referring to this open file, or an + error code. This is equivalent to L{SFTPServerInterface.stat}, except + it's called on an open file instead of a path. + + @return: an attributes object for the given file, or an SFTP error + code (like L{SFTP_PERMISSION_DENIED}). + @rtype: L{SFTPAttributes} I{or error code} + """ + return SFTP_OP_UNSUPPORTED + + def chattr(self, attr): + """ + Change the attributes of this file. The C{attr} object will contain + only those fields provided by the client in its request, so you should + check for the presence of fields before using them. + + @param attr: the attributes to change on this file. + @type attr: L{SFTPAttributes} + @return: an error code like L{SFTP_OK}. + @rtype: int + """ + return SFTP_OP_UNSUPPORTED + + + ### internals... + + + def _set_files(self, files): + """ + Used by the SFTP server code to cache a directory listing. (In + the SFTP protocol, listing a directory is a multi-stage process + requiring a temporary handle.) + """ + self.__files = files + + def _get_next_files(self): + """ + Used by the SFTP server code to retreive a cached directory + listing. + """ + fnlist = self.__files[:16] + self.__files = self.__files[16:] + return fnlist + + def _get_name(self): + return self.__name + + def _set_name(self, name): + self.__name = name + + +from paramiko.sftp_server import SFTPServer diff --git a/lib/paramiko/sftp_server.py b/lib/paramiko/sftp_server.py new file mode 100644 index 0000000..7cc6c0c --- /dev/null +++ b/lib/paramiko/sftp_server.py @@ -0,0 +1,444 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Server-mode SFTP support. +""" + +import os +import errno + +from Crypto.Hash import MD5, SHA +from paramiko.common import * +from paramiko.server import SubsystemHandler +from paramiko.sftp import * +from paramiko.sftp_si import * +from paramiko.sftp_attr import * + + +# known hash algorithms for the "check-file" extension +_hash_class = { + 'sha1': SHA, + 'md5': MD5, +} + + +class SFTPServer (BaseSFTP, SubsystemHandler): + """ + Server-side SFTP subsystem support. Since this is a L{SubsystemHandler}, + it can be (and is meant to be) set as the handler for C{"sftp"} requests. + Use L{Transport.set_subsystem_handler} to activate this class. + """ + + def __init__(self, channel, name, server, sftp_si=SFTPServerInterface, *largs, **kwargs): + """ + The constructor for SFTPServer is meant to be called from within the + L{Transport} as a subsystem handler. C{server} and any additional + parameters or keyword parameters are passed from the original call to + L{Transport.set_subsystem_handler}. + + @param channel: channel passed from the L{Transport}. + @type channel: L{Channel} + @param name: name of the requested subsystem. + @type name: str + @param server: the server object associated with this channel and + subsystem + @type server: L{ServerInterface} + @param sftp_si: a subclass of L{SFTPServerInterface} to use for handling + individual requests. + @type sftp_si: class + """ + BaseSFTP.__init__(self) + SubsystemHandler.__init__(self, channel, name, server) + transport = channel.get_transport() + self.logger = util.get_logger(transport.get_log_channel() + '.sftp') + self.ultra_debug = transport.get_hexdump() + self.next_handle = 1 + # map of handle-string to SFTPHandle for files & folders: + self.file_table = { } + self.folder_table = { } + self.server = sftp_si(server, *largs, **kwargs) + + def _log(self, level, msg): + if issubclass(type(msg), list): + for m in msg: + super(SFTPServer, self)._log(level, "[chan " + self.sock.get_name() + "] " + m) + else: + super(SFTPServer, self)._log(level, "[chan " + self.sock.get_name() + "] " + msg) + + def start_subsystem(self, name, transport, channel): + self.sock = channel + self._log(DEBUG, 'Started sftp server on channel %s' % repr(channel)) + self._send_server_version() + self.server.session_started() + while True: + try: + t, data = self._read_packet() + except EOFError: + self._log(DEBUG, 'EOF -- end of session') + return + except Exception, e: + self._log(DEBUG, 'Exception on channel: ' + str(e)) + self._log(DEBUG, util.tb_strings()) + return + msg = Message(data) + request_number = msg.get_int() + try: + self._process(t, request_number, msg) + except Exception, e: + self._log(DEBUG, 'Exception in server processing: ' + str(e)) + self._log(DEBUG, util.tb_strings()) + # send some kind of failure message, at least + try: + self._send_status(request_number, SFTP_FAILURE) + except: + pass + + def finish_subsystem(self): + self.server.session_ended() + super(SFTPServer, self).finish_subsystem() + # close any file handles that were left open (so we can return them to the OS quickly) + for f in self.file_table.itervalues(): + f.close() + for f in self.folder_table.itervalues(): + f.close() + self.file_table = {} + self.folder_table = {} + + def convert_errno(e): + """ + Convert an errno value (as from an C{OSError} or C{IOError}) into a + standard SFTP result code. This is a convenience function for trapping + exceptions in server code and returning an appropriate result. + + @param e: an errno code, as from C{OSError.errno}. + @type e: int + @return: an SFTP error code like L{SFTP_NO_SUCH_FILE}. + @rtype: int + """ + if e == errno.EACCES: + # permission denied + return SFTP_PERMISSION_DENIED + elif (e == errno.ENOENT) or (e == errno.ENOTDIR): + # no such file + return SFTP_NO_SUCH_FILE + else: + return SFTP_FAILURE + convert_errno = staticmethod(convert_errno) + + def set_file_attr(filename, attr): + """ + Change a file's attributes on the local filesystem. The contents of + C{attr} are used to change the permissions, owner, group ownership, + and/or modification & access time of the file, depending on which + attributes are present in C{attr}. + + This is meant to be a handy helper function for translating SFTP file + requests into local file operations. + + @param filename: name of the file to alter (should usually be an + absolute path). + @type filename: str + @param attr: attributes to change. + @type attr: L{SFTPAttributes} + """ + if sys.platform != 'win32': + # mode operations are meaningless on win32 + if attr._flags & attr.FLAG_PERMISSIONS: + os.chmod(filename, attr.st_mode) + if attr._flags & attr.FLAG_UIDGID: + os.chown(filename, attr.st_uid, attr.st_gid) + if attr._flags & attr.FLAG_AMTIME: + os.utime(filename, (attr.st_atime, attr.st_mtime)) + if attr._flags & attr.FLAG_SIZE: + open(filename, 'w+').truncate(attr.st_size) + set_file_attr = staticmethod(set_file_attr) + + + ### internals... + + + def _response(self, request_number, t, *arg): + msg = Message() + msg.add_int(request_number) + for item in arg: + if type(item) is int: + msg.add_int(item) + elif type(item) is long: + msg.add_int64(item) + elif type(item) is str: + msg.add_string(item) + elif type(item) is SFTPAttributes: + item._pack(msg) + else: + raise Exception('unknown type for ' + repr(item) + ' type ' + repr(type(item))) + self._send_packet(t, str(msg)) + + def _send_handle_response(self, request_number, handle, folder=False): + if not issubclass(type(handle), SFTPHandle): + # must be error code + self._send_status(request_number, handle) + return + handle._set_name('hx%d' % self.next_handle) + self.next_handle += 1 + if folder: + self.folder_table[handle._get_name()] = handle + else: + self.file_table[handle._get_name()] = handle + self._response(request_number, CMD_HANDLE, handle._get_name()) + + def _send_status(self, request_number, code, desc=None): + if desc is None: + try: + desc = SFTP_DESC[code] + except IndexError: + desc = 'Unknown' + # some clients expect a "langauge" tag at the end (but don't mind it being blank) + self._response(request_number, CMD_STATUS, code, desc, '') + + def _open_folder(self, request_number, path): + resp = self.server.list_folder(path) + if issubclass(type(resp), list): + # got an actual list of filenames in the folder + folder = SFTPHandle() + folder._set_files(resp) + self._send_handle_response(request_number, folder, True) + return + # must be an error code + self._send_status(request_number, resp) + + def _read_folder(self, request_number, folder): + flist = folder._get_next_files() + if len(flist) == 0: + self._send_status(request_number, SFTP_EOF) + return + msg = Message() + msg.add_int(request_number) + msg.add_int(len(flist)) + for attr in flist: + msg.add_string(attr.filename) + msg.add_string(str(attr)) + attr._pack(msg) + self._send_packet(CMD_NAME, str(msg)) + + def _check_file(self, request_number, msg): + # this extension actually comes from v6 protocol, but since it's an + # extension, i feel like we can reasonably support it backported. + # it's very useful for verifying uploaded files or checking for + # rsync-like differences between local and remote files. + handle = msg.get_string() + alg_list = msg.get_list() + start = msg.get_int64() + length = msg.get_int64() + block_size = msg.get_int() + if handle not in self.file_table: + self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle') + return + f = self.file_table[handle] + for x in alg_list: + if x in _hash_class: + algname = x + alg = _hash_class[x] + break + else: + self._send_status(request_number, SFTP_FAILURE, 'No supported hash types found') + return + if length == 0: + st = f.stat() + if not issubclass(type(st), SFTPAttributes): + self._send_status(request_number, st, 'Unable to stat file') + return + length = st.st_size - start + if block_size == 0: + block_size = length + if block_size < 256: + self._send_status(request_number, SFTP_FAILURE, 'Block size too small') + return + + sum_out = '' + offset = start + while offset < start + length: + blocklen = min(block_size, start + length - offset) + # don't try to read more than about 64KB at a time + chunklen = min(blocklen, 65536) + count = 0 + hash_obj = alg.new() + while count < blocklen: + data = f.read(offset, chunklen) + if not type(data) is str: + self._send_status(request_number, data, 'Unable to hash file') + return + hash_obj.update(data) + count += len(data) + offset += count + sum_out += hash_obj.digest() + + msg = Message() + msg.add_int(request_number) + msg.add_string('check-file') + msg.add_string(algname) + msg.add_bytes(sum_out) + self._send_packet(CMD_EXTENDED_REPLY, str(msg)) + + def _convert_pflags(self, pflags): + "convert SFTP-style open() flags to python's os.open() flags" + if (pflags & SFTP_FLAG_READ) and (pflags & SFTP_FLAG_WRITE): + flags = os.O_RDWR + elif pflags & SFTP_FLAG_WRITE: + flags = os.O_WRONLY + else: + flags = os.O_RDONLY + if pflags & SFTP_FLAG_APPEND: + flags |= os.O_APPEND + if pflags & SFTP_FLAG_CREATE: + flags |= os.O_CREAT + if pflags & SFTP_FLAG_TRUNC: + flags |= os.O_TRUNC + if pflags & SFTP_FLAG_EXCL: + flags |= os.O_EXCL + return flags + + def _process(self, t, request_number, msg): + self._log(DEBUG, 'Request: %s' % CMD_NAMES[t]) + if t == CMD_OPEN: + path = msg.get_string() + flags = self._convert_pflags(msg.get_int()) + attr = SFTPAttributes._from_msg(msg) + self._send_handle_response(request_number, self.server.open(path, flags, attr)) + elif t == CMD_CLOSE: + handle = msg.get_string() + if handle in self.folder_table: + del self.folder_table[handle] + self._send_status(request_number, SFTP_OK) + return + if handle in self.file_table: + self.file_table[handle].close() + del self.file_table[handle] + self._send_status(request_number, SFTP_OK) + return + self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle') + elif t == CMD_READ: + handle = msg.get_string() + offset = msg.get_int64() + length = msg.get_int() + if handle not in self.file_table: + self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle') + return + data = self.file_table[handle].read(offset, length) + if type(data) is str: + if len(data) == 0: + self._send_status(request_number, SFTP_EOF) + else: + self._response(request_number, CMD_DATA, data) + else: + self._send_status(request_number, data) + elif t == CMD_WRITE: + handle = msg.get_string() + offset = msg.get_int64() + data = msg.get_string() + if handle not in self.file_table: + self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle') + return + self._send_status(request_number, self.file_table[handle].write(offset, data)) + elif t == CMD_REMOVE: + path = msg.get_string() + self._send_status(request_number, self.server.remove(path)) + elif t == CMD_RENAME: + oldpath = msg.get_string() + newpath = msg.get_string() + self._send_status(request_number, self.server.rename(oldpath, newpath)) + elif t == CMD_MKDIR: + path = msg.get_string() + attr = SFTPAttributes._from_msg(msg) + self._send_status(request_number, self.server.mkdir(path, attr)) + elif t == CMD_RMDIR: + path = msg.get_string() + self._send_status(request_number, self.server.rmdir(path)) + elif t == CMD_OPENDIR: + path = msg.get_string() + self._open_folder(request_number, path) + return + elif t == CMD_READDIR: + handle = msg.get_string() + if handle not in self.folder_table: + self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle') + return + folder = self.folder_table[handle] + self._read_folder(request_number, folder) + elif t == CMD_STAT: + path = msg.get_string() + resp = self.server.stat(path) + if issubclass(type(resp), SFTPAttributes): + self._response(request_number, CMD_ATTRS, resp) + else: + self._send_status(request_number, resp) + elif t == CMD_LSTAT: + path = msg.get_string() + resp = self.server.lstat(path) + if issubclass(type(resp), SFTPAttributes): + self._response(request_number, CMD_ATTRS, resp) + else: + self._send_status(request_number, resp) + elif t == CMD_FSTAT: + handle = msg.get_string() + if handle not in self.file_table: + self._send_status(request_number, SFTP_BAD_MESSAGE, 'Invalid handle') + return + resp = self.file_table[handle].stat() + if issubclass(type(resp), SFTPAttributes): + self._response(request_number, CMD_ATTRS, resp) + else: + self._send_status(request_number, resp) + elif t == CMD_SETSTAT: + path = msg.get_string() + attr = SFTPAttributes._from_msg(msg) + self._send_status(request_number, self.server.chattr(path, attr)) + elif t == CMD_FSETSTAT: + handle = msg.get_string() + attr = SFTPAttributes._from_msg(msg) + if handle not in self.file_table: + self._response(request_number, SFTP_BAD_MESSAGE, 'Invalid handle') + return + self._send_status(request_number, self.file_table[handle].chattr(attr)) + elif t == CMD_READLINK: + path = msg.get_string() + resp = self.server.readlink(path) + if type(resp) is str: + self._response(request_number, CMD_NAME, 1, resp, '', SFTPAttributes()) + else: + self._send_status(request_number, resp) + elif t == CMD_SYMLINK: + # the sftp 2 draft is incorrect here! path always follows target_path + target_path = msg.get_string() + path = msg.get_string() + self._send_status(request_number, self.server.symlink(target_path, path)) + elif t == CMD_REALPATH: + path = msg.get_string() + rpath = self.server.canonicalize(path) + self._response(request_number, CMD_NAME, 1, rpath, '', SFTPAttributes()) + elif t == CMD_EXTENDED: + tag = msg.get_string() + if tag == 'check-file': + self._check_file(request_number, msg) + else: + self._send_status(request_number, SFTP_OP_UNSUPPORTED) + else: + self._send_status(request_number, SFTP_OP_UNSUPPORTED) + + +from paramiko.sftp_handle import SFTPHandle diff --git a/lib/paramiko/sftp_si.py b/lib/paramiko/sftp_si.py new file mode 100644 index 0000000..401a4e9 --- /dev/null +++ b/lib/paramiko/sftp_si.py @@ -0,0 +1,310 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +L{SFTPServerInterface} is an interface to override for SFTP server support. +""" + +import os + +from paramiko.common import * +from paramiko.sftp import * + + +class SFTPServerInterface (object): + """ + This class defines an interface for controlling the behavior of paramiko + when using the L{SFTPServer} subsystem to provide an SFTP server. + + Methods on this class are called from the SFTP session's thread, so you can + block as long as necessary without affecting other sessions (even other + SFTP sessions). However, raising an exception will usually cause the SFTP + session to abruptly end, so you will usually want to catch exceptions and + return an appropriate error code. + + All paths are in string form instead of unicode because not all SFTP + clients & servers obey the requirement that paths be encoded in UTF-8. + """ + + def __init__ (self, server, *largs, **kwargs): + """ + Create a new SFTPServerInterface object. This method does nothing by + default and is meant to be overridden by subclasses. + + @param server: the server object associated with this channel and + SFTP subsystem + @type server: L{ServerInterface} + """ + super(SFTPServerInterface, self).__init__(*largs, **kwargs) + + def session_started(self): + """ + The SFTP server session has just started. This method is meant to be + overridden to perform any necessary setup before handling callbacks + from SFTP operations. + """ + pass + + def session_ended(self): + """ + The SFTP server session has just ended, either cleanly or via an + exception. This method is meant to be overridden to perform any + necessary cleanup before this C{SFTPServerInterface} object is + destroyed. + """ + pass + + def open(self, path, flags, attr): + """ + Open a file on the server and create a handle for future operations + on that file. On success, a new object subclassed from L{SFTPHandle} + should be returned. This handle will be used for future operations + on the file (read, write, etc). On failure, an error code such as + L{SFTP_PERMISSION_DENIED} should be returned. + + C{flags} contains the requested mode for opening (read-only, + write-append, etc) as a bitset of flags from the C{os} module: + - C{os.O_RDONLY} + - C{os.O_WRONLY} + - C{os.O_RDWR} + - C{os.O_APPEND} + - C{os.O_CREAT} + - C{os.O_TRUNC} + - C{os.O_EXCL} + (One of C{os.O_RDONLY}, C{os.O_WRONLY}, or C{os.O_RDWR} will always + be set.) + + The C{attr} object contains requested attributes of the file if it + has to be created. Some or all attribute fields may be missing if + the client didn't specify them. + + @note: The SFTP protocol defines all files to be in "binary" mode. + There is no equivalent to python's "text" mode. + + @param path: the requested path (relative or absolute) of the file + to be opened. + @type path: str + @param flags: flags or'd together from the C{os} module indicating the + requested mode for opening the file. + @type flags: int + @param attr: requested attributes of the file if it is newly created. + @type attr: L{SFTPAttributes} + @return: a new L{SFTPHandle} I{or error code}. + @rtype L{SFTPHandle} + """ + return SFTP_OP_UNSUPPORTED + + def list_folder(self, path): + """ + Return a list of files within a given folder. The C{path} will use + posix notation (C{"/"} separates folder names) and may be an absolute + or relative path. + + The list of files is expected to be a list of L{SFTPAttributes} + objects, which are similar in structure to the objects returned by + C{os.stat}. In addition, each object should have its C{filename} + field filled in, since this is important to a directory listing and + not normally present in C{os.stat} results. The method + L{SFTPAttributes.from_stat} will usually do what you want. + + In case of an error, you should return one of the C{SFTP_*} error + codes, such as L{SFTP_PERMISSION_DENIED}. + + @param path: the requested path (relative or absolute) to be listed. + @type path: str + @return: a list of the files in the given folder, using + L{SFTPAttributes} objects. + @rtype: list of L{SFTPAttributes} I{or error code} + + @note: You should normalize the given C{path} first (see the + C{os.path} module) and check appropriate permissions before returning + the list of files. Be careful of malicious clients attempting to use + relative paths to escape restricted folders, if you're doing a direct + translation from the SFTP server path to your local filesystem. + """ + return SFTP_OP_UNSUPPORTED + + def stat(self, path): + """ + Return an L{SFTPAttributes} object for a path on the server, or an + error code. If your server supports symbolic links (also known as + "aliases"), you should follow them. (L{lstat} is the corresponding + call that doesn't follow symlinks/aliases.) + + @param path: the requested path (relative or absolute) to fetch + file statistics for. + @type path: str + @return: an attributes object for the given file, or an SFTP error + code (like L{SFTP_PERMISSION_DENIED}). + @rtype: L{SFTPAttributes} I{or error code} + """ + return SFTP_OP_UNSUPPORTED + + def lstat(self, path): + """ + Return an L{SFTPAttributes} object for a path on the server, or an + error code. If your server supports symbolic links (also known as + "aliases"), you should I{not} follow them -- instead, you should + return data on the symlink or alias itself. (L{stat} is the + corresponding call that follows symlinks/aliases.) + + @param path: the requested path (relative or absolute) to fetch + file statistics for. + @type path: str + @return: an attributes object for the given file, or an SFTP error + code (like L{SFTP_PERMISSION_DENIED}). + @rtype: L{SFTPAttributes} I{or error code} + """ + return SFTP_OP_UNSUPPORTED + + def remove(self, path): + """ + Delete a file, if possible. + + @param path: the requested path (relative or absolute) of the file + to delete. + @type path: str + @return: an SFTP error code like L{SFTP_OK}. + @rtype: int + """ + return SFTP_OP_UNSUPPORTED + + def rename(self, oldpath, newpath): + """ + Rename (or move) a file. The SFTP specification implies that this + method can be used to move an existing file into a different folder, + and since there's no other (easy) way to move files via SFTP, it's + probably a good idea to implement "move" in this method too, even for + files that cross disk partition boundaries, if at all possible. + + @note: You should return an error if a file with the same name as + C{newpath} already exists. (The rename operation should be + non-desctructive.) + + @param oldpath: the requested path (relative or absolute) of the + existing file. + @type oldpath: str + @param newpath: the requested new path of the file. + @type newpath: str + @return: an SFTP error code like L{SFTP_OK}. + @rtype: int + """ + return SFTP_OP_UNSUPPORTED + + def mkdir(self, path, attr): + """ + Create a new directory with the given attributes. The C{attr} + object may be considered a "hint" and ignored. + + The C{attr} object will contain only those fields provided by the + client in its request, so you should use C{hasattr} to check for + the presense of fields before using them. In some cases, the C{attr} + object may be completely empty. + + @param path: requested path (relative or absolute) of the new + folder. + @type path: str + @param attr: requested attributes of the new folder. + @type attr: L{SFTPAttributes} + @return: an SFTP error code like L{SFTP_OK}. + @rtype: int + """ + return SFTP_OP_UNSUPPORTED + + def rmdir(self, path): + """ + Remove a directory if it exists. The C{path} should refer to an + existing, empty folder -- otherwise this method should return an + error. + + @param path: requested path (relative or absolute) of the folder + to remove. + @type path: str + @return: an SFTP error code like L{SFTP_OK}. + @rtype: int + """ + return SFTP_OP_UNSUPPORTED + + def chattr(self, path, attr): + """ + Change the attributes of a file. The C{attr} object will contain + only those fields provided by the client in its request, so you + should check for the presence of fields before using them. + + @param path: requested path (relative or absolute) of the file to + change. + @type path: str + @param attr: requested attributes to change on the file. + @type attr: L{SFTPAttributes} + @return: an error code like L{SFTP_OK}. + @rtype: int + """ + return SFTP_OP_UNSUPPORTED + + def canonicalize(self, path): + """ + Return the canonical form of a path on the server. For example, + if the server's home folder is C{/home/foo}, the path + C{"../betty"} would be canonicalized to C{"/home/betty"}. Note + the obvious security issues: if you're serving files only from a + specific folder, you probably don't want this method to reveal path + names outside that folder. + + You may find the python methods in C{os.path} useful, especially + C{os.path.normpath} and C{os.path.realpath}. + + The default implementation returns C{os.path.normpath('/' + path)}. + """ + if os.path.isabs(path): + out = os.path.normpath(path) + else: + out = os.path.normpath('/' + path) + if sys.platform == 'win32': + # on windows, normalize backslashes to sftp/posix format + out = out.replace('\\', '/') + return out + + def readlink(self, path): + """ + Return the target of a symbolic link (or shortcut) on the server. + If the specified path doesn't refer to a symbolic link, an error + should be returned. + + @param path: path (relative or absolute) of the symbolic link. + @type path: str + @return: the target path of the symbolic link, or an error code like + L{SFTP_NO_SUCH_FILE}. + @rtype: str I{or error code} + """ + return SFTP_OP_UNSUPPORTED + + def symlink(self, target_path, path): + """ + Create a symbolic link on the server, as new pathname C{path}, + with C{target_path} as the target of the link. + + @param target_path: path (relative or absolute) of the target for + this new symbolic link. + @type target_path: str + @param path: path (relative or absolute) of the symbolic link to + create. + @type path: str + @return: an error code like C{SFTP_OK}. + @rtype: int + """ + return SFTP_OP_UNSUPPORTED diff --git a/lib/paramiko/ssh_exception.py b/lib/paramiko/ssh_exception.py new file mode 100644 index 0000000..68924d0 --- /dev/null +++ b/lib/paramiko/ssh_exception.py @@ -0,0 +1,115 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Exceptions defined by paramiko. +""" + + +class SSHException (Exception): + """ + Exception raised by failures in SSH2 protocol negotiation or logic errors. + """ + pass + + +class AuthenticationException (SSHException): + """ + Exception raised when authentication failed for some reason. It may be + possible to retry with different credentials. (Other classes specify more + specific reasons.) + + @since: 1.6 + """ + pass + + +class PasswordRequiredException (AuthenticationException): + """ + Exception raised when a password is needed to unlock a private key file. + """ + pass + + +class BadAuthenticationType (AuthenticationException): + """ + Exception raised when an authentication type (like password) is used, but + the server isn't allowing that type. (It may only allow public-key, for + example.) + + @ivar allowed_types: list of allowed authentication types provided by the + server (possible values are: C{"none"}, C{"password"}, and + C{"publickey"}). + @type allowed_types: list + + @since: 1.1 + """ + allowed_types = [] + + def __init__(self, explanation, types): + AuthenticationException.__init__(self, explanation) + self.allowed_types = types + + def __str__(self): + return SSHException.__str__(self) + ' (allowed_types=%r)' % self.allowed_types + + +class PartialAuthentication (AuthenticationException): + """ + An internal exception thrown in the case of partial authentication. + """ + allowed_types = [] + + def __init__(self, types): + AuthenticationException.__init__(self, 'partial authentication') + self.allowed_types = types + + +class ChannelException (SSHException): + """ + Exception raised when an attempt to open a new L{Channel} fails. + + @ivar code: the error code returned by the server + @type code: int + + @since: 1.6 + """ + def __init__(self, code, text): + SSHException.__init__(self, text) + self.code = code + + +class BadHostKeyException (SSHException): + """ + The host key given by the SSH server did not match what we were expecting. + + @ivar hostname: the hostname of the SSH server + @type hostname: str + @ivar key: the host key presented by the server + @type key: L{PKey} + @ivar expected_key: the host key expected + @type expected_key: L{PKey} + + @since: 1.6 + """ + def __init__(self, hostname, got_key, expected_key): + SSHException.__init__(self, 'Host key for server %s does not match!' % hostname) + self.hostname = hostname + self.key = got_key + self.expected_key = expected_key + diff --git a/lib/paramiko/transport.py b/lib/paramiko/transport.py new file mode 100644 index 0000000..30de295 --- /dev/null +++ b/lib/paramiko/transport.py @@ -0,0 +1,2098 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +L{Transport} handles the core SSH2 protocol. +""" + +import os +import socket +import string +import struct +import sys +import threading +import time +import weakref + +from paramiko import util +from paramiko.auth_handler import AuthHandler +from paramiko.channel import Channel +from paramiko.common import * +from paramiko.compress import ZlibCompressor, ZlibDecompressor +from paramiko.dsskey import DSSKey +from paramiko.kex_gex import KexGex +from paramiko.kex_group1 import KexGroup1 +from paramiko.message import Message +from paramiko.packet import Packetizer, NeedRekeyException +from paramiko.primes import ModulusPack +from paramiko.rsakey import RSAKey +from paramiko.server import ServerInterface +from paramiko.sftp_client import SFTPClient +from paramiko.ssh_exception import SSHException, BadAuthenticationType, ChannelException + +from Crypto import Random +from Crypto.Cipher import Blowfish, AES, DES3, ARC4 +from Crypto.Hash import SHA, MD5 +try: + from Crypto.Util import Counter +except ImportError: + from paramiko.util import Counter + + +# for thread cleanup +_active_threads = [] +def _join_lingering_threads(): + for thr in _active_threads: + thr.stop_thread() +import atexit +atexit.register(_join_lingering_threads) + + +class SecurityOptions (object): + """ + Simple object containing the security preferences of an ssh transport. + These are tuples of acceptable ciphers, digests, key types, and key + exchange algorithms, listed in order of preference. + + Changing the contents and/or order of these fields affects the underlying + L{Transport} (but only if you change them before starting the session). + If you try to add an algorithm that paramiko doesn't recognize, + C{ValueError} will be raised. If you try to assign something besides a + tuple to one of the fields, C{TypeError} will be raised. + """ + __slots__ = [ 'ciphers', 'digests', 'key_types', 'kex', 'compression', '_transport' ] + + def __init__(self, transport): + self._transport = transport + + def __repr__(self): + """ + Returns a string representation of this object, for debugging. + + @rtype: str + """ + return '' % repr(self._transport) + + def _get_ciphers(self): + return self._transport._preferred_ciphers + + def _get_digests(self): + return self._transport._preferred_macs + + def _get_key_types(self): + return self._transport._preferred_keys + + def _get_kex(self): + return self._transport._preferred_kex + + def _get_compression(self): + return self._transport._preferred_compression + + def _set(self, name, orig, x): + if type(x) is list: + x = tuple(x) + if type(x) is not tuple: + raise TypeError('expected tuple or list') + possible = getattr(self._transport, orig).keys() + forbidden = filter(lambda n: n not in possible, x) + if len(forbidden) > 0: + raise ValueError('unknown cipher') + setattr(self._transport, name, x) + + def _set_ciphers(self, x): + self._set('_preferred_ciphers', '_cipher_info', x) + + def _set_digests(self, x): + self._set('_preferred_macs', '_mac_info', x) + + def _set_key_types(self, x): + self._set('_preferred_keys', '_key_info', x) + + def _set_kex(self, x): + self._set('_preferred_kex', '_kex_info', x) + + def _set_compression(self, x): + self._set('_preferred_compression', '_compression_info', x) + + ciphers = property(_get_ciphers, _set_ciphers, None, + "Symmetric encryption ciphers") + digests = property(_get_digests, _set_digests, None, + "Digest (one-way hash) algorithms") + key_types = property(_get_key_types, _set_key_types, None, + "Public-key algorithms") + kex = property(_get_kex, _set_kex, None, "Key exchange algorithms") + compression = property(_get_compression, _set_compression, None, + "Compression algorithms") + + +class ChannelMap (object): + def __init__(self): + # (id -> Channel) + self._map = weakref.WeakValueDictionary() + self._lock = threading.Lock() + + def put(self, chanid, chan): + self._lock.acquire() + try: + self._map[chanid] = chan + finally: + self._lock.release() + + def get(self, chanid): + self._lock.acquire() + try: + return self._map.get(chanid, None) + finally: + self._lock.release() + + def delete(self, chanid): + self._lock.acquire() + try: + try: + del self._map[chanid] + except KeyError: + pass + finally: + self._lock.release() + + def values(self): + self._lock.acquire() + try: + return self._map.values() + finally: + self._lock.release() + + def __len__(self): + self._lock.acquire() + try: + return len(self._map) + finally: + self._lock.release() + + +class Transport (threading.Thread): + """ + An SSH Transport attaches to a stream (usually a socket), negotiates an + encrypted session, authenticates, and then creates stream tunnels, called + L{Channel}s, across the session. Multiple channels can be multiplexed + across a single session (and often are, in the case of port forwardings). + """ + + _PROTO_ID = '2.0' + _CLIENT_ID = 'paramiko_1.7.7.1' + + _preferred_ciphers = ( 'aes128-ctr', 'aes256-ctr', 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc', + 'arcfour128', 'arcfour256' ) + _preferred_macs = ( 'hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96' ) + _preferred_keys = ( 'ssh-rsa', 'ssh-dss' ) + _preferred_kex = ( 'diffie-hellman-group1-sha1', 'diffie-hellman-group-exchange-sha1' ) + _preferred_compression = ( 'none', ) + + _cipher_info = { + 'aes128-ctr': { 'class': AES, 'mode': AES.MODE_CTR, 'block-size': 16, 'key-size': 16 }, + 'aes256-ctr': { 'class': AES, 'mode': AES.MODE_CTR, 'block-size': 16, 'key-size': 32 }, + 'blowfish-cbc': { 'class': Blowfish, 'mode': Blowfish.MODE_CBC, 'block-size': 8, 'key-size': 16 }, + 'aes128-cbc': { 'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 16 }, + 'aes256-cbc': { 'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 32 }, + '3des-cbc': { 'class': DES3, 'mode': DES3.MODE_CBC, 'block-size': 8, 'key-size': 24 }, + 'arcfour128': { 'class': ARC4, 'mode': None, 'block-size': 8, 'key-size': 16 }, + 'arcfour256': { 'class': ARC4, 'mode': None, 'block-size': 8, 'key-size': 32 }, + } + + _mac_info = { + 'hmac-sha1': { 'class': SHA, 'size': 20 }, + 'hmac-sha1-96': { 'class': SHA, 'size': 12 }, + 'hmac-md5': { 'class': MD5, 'size': 16 }, + 'hmac-md5-96': { 'class': MD5, 'size': 12 }, + } + + _key_info = { + 'ssh-rsa': RSAKey, + 'ssh-dss': DSSKey, + } + + _kex_info = { + 'diffie-hellman-group1-sha1': KexGroup1, + 'diffie-hellman-group-exchange-sha1': KexGex, + } + + _compression_info = { + # zlib@openssh.com is just zlib, but only turned on after a successful + # authentication. openssh servers may only offer this type because + # they've had troubles with security holes in zlib in the past. + 'zlib@openssh.com': ( ZlibCompressor, ZlibDecompressor ), + 'zlib': ( ZlibCompressor, ZlibDecompressor ), + 'none': ( None, None ), + } + + + _modulus_pack = None + + def __init__(self, sock): + """ + Create a new SSH session over an existing socket, or socket-like + object. This only creates the Transport object; it doesn't begin the + SSH session yet. Use L{connect} or L{start_client} to begin a client + session, or L{start_server} to begin a server session. + + If the object is not actually a socket, it must have the following + methods: + - C{send(str)}: Writes from 1 to C{len(str)} bytes, and + returns an int representing the number of bytes written. Returns + 0 or raises C{EOFError} if the stream has been closed. + - C{recv(int)}: Reads from 1 to C{int} bytes and returns them as a + string. Returns 0 or raises C{EOFError} if the stream has been + closed. + - C{close()}: Closes the socket. + - C{settimeout(n)}: Sets a (float) timeout on I/O operations. + + For ease of use, you may also pass in an address (as a tuple) or a host + string as the C{sock} argument. (A host string is a hostname with an + optional port (separated by C{":"}) which will be converted into a + tuple of C{(hostname, port)}.) A socket will be connected to this + address and used for communication. Exceptions from the C{socket} call + may be thrown in this case. + + @param sock: a socket or socket-like object to create the session over. + @type sock: socket + """ + if isinstance(sock, (str, unicode)): + # convert "host:port" into (host, port) + hl = sock.split(':', 1) + if len(hl) == 1: + sock = (hl[0], 22) + else: + sock = (hl[0], int(hl[1])) + if type(sock) is tuple: + # connect to the given (host, port) + hostname, port = sock + reason = 'No suitable address family' + for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM): + if socktype == socket.SOCK_STREAM: + af = family + addr = sockaddr + sock = socket.socket(af, socket.SOCK_STREAM) + try: + sock.connect((hostname, port)) + except socket.error, e: + reason = str(e) + else: + break + else: + raise SSHException( + 'Unable to connect to %s: %s' % (hostname, reason)) + # okay, normal socket-ish flow here... + threading.Thread.__init__(self) + self.setDaemon(True) + self.rng = rng + self.sock = sock + # Python < 2.3 doesn't have the settimeout method - RogerB + try: + # we set the timeout so we can check self.active periodically to + # see if we should bail. socket.timeout exception is never + # propagated. + self.sock.settimeout(0.1) + except AttributeError: + pass + + # negotiated crypto parameters + self.packetizer = Packetizer(sock) + self.local_version = 'SSH-' + self._PROTO_ID + '-' + self._CLIENT_ID + self.remote_version = '' + self.local_cipher = self.remote_cipher = '' + self.local_kex_init = self.remote_kex_init = None + self.local_mac = self.remote_mac = None + self.local_compression = self.remote_compression = None + self.session_id = None + self.host_key_type = None + self.host_key = None + + # state used during negotiation + self.kex_engine = None + self.H = None + self.K = None + + self.active = False + self.initial_kex_done = False + self.in_kex = False + self.authenticated = False + self._expected_packet = tuple() + self.lock = threading.Lock() # synchronization (always higher level than write_lock) + + # tracking open channels + self._channels = ChannelMap() + self.channel_events = { } # (id -> Event) + self.channels_seen = { } # (id -> True) + self._channel_counter = 1 + self.window_size = 65536 + self.max_packet_size = 34816 + self._x11_handler = None + self._tcp_handler = None + + self.saved_exception = None + self.clear_to_send = threading.Event() + self.clear_to_send_lock = threading.Lock() + self.clear_to_send_timeout = 30.0 + self.log_name = 'paramiko.transport' + self.logger = util.get_logger(self.log_name) + self.packetizer.set_log(self.logger) + self.auth_handler = None + self.global_response = None # response Message from an arbitrary global request + self.completion_event = None # user-defined event callbacks + self.banner_timeout = 15 # how long (seconds) to wait for the SSH banner + + # server mode: + self.server_mode = False + self.server_object = None + self.server_key_dict = { } + self.server_accepts = [ ] + self.server_accept_cv = threading.Condition(self.lock) + self.subsystem_table = { } + + def __repr__(self): + """ + Returns a string representation of this object, for debugging. + + @rtype: str + """ + out = '} or + L{auth_publickey }. + + @note: L{connect} is a simpler method for connecting as a client. + + @note: After calling this method (or L{start_server} or L{connect}), + you should no longer directly read from or write to the original + socket object. + + @param event: an event to trigger when negotiation is complete + (optional) + @type event: threading.Event + + @raise SSHException: if negotiation fails (and no C{event} was passed + in) + """ + self.active = True + if event is not None: + # async, return immediately and let the app poll for completion + self.completion_event = event + self.start() + return + + # synchronous, wait for a result + self.completion_event = event = threading.Event() + self.start() + Random.atfork() + while True: + event.wait(0.1) + if not self.active: + e = self.get_exception() + if e is not None: + raise e + raise SSHException('Negotiation failed.') + if event.isSet(): + break + + def start_server(self, event=None, server=None): + """ + Negotiate a new SSH2 session as a server. This is the first step after + creating a new L{Transport} and setting up your server host key(s). A + separate thread is created for protocol negotiation. + + If an event is passed in, this method returns immediately. When + negotiation is done (successful or not), the given C{Event} will + be triggered. On failure, L{is_active} will return C{False}. + + (Since 1.4) If C{event} is C{None}, this method will not return until + negotation is done. On success, the method returns normally. + Otherwise an SSHException is raised. + + After a successful negotiation, the client will need to authenticate. + Override the methods + L{get_allowed_auths }, + L{check_auth_none }, + L{check_auth_password }, and + L{check_auth_publickey } in the + given C{server} object to control the authentication process. + + After a successful authentication, the client should request to open + a channel. Override + L{check_channel_request } in the + given C{server} object to allow channels to be opened. + + @note: After calling this method (or L{start_client} or L{connect}), + you should no longer directly read from or write to the original + socket object. + + @param event: an event to trigger when negotiation is complete. + @type event: threading.Event + @param server: an object used to perform authentication and create + L{Channel}s. + @type server: L{server.ServerInterface} + + @raise SSHException: if negotiation fails (and no C{event} was passed + in) + """ + if server is None: + server = ServerInterface() + self.server_mode = True + self.server_object = server + self.active = True + if event is not None: + # async, return immediately and let the app poll for completion + self.completion_event = event + self.start() + return + + # synchronous, wait for a result + self.completion_event = event = threading.Event() + self.start() + while True: + event.wait(0.1) + if not self.active: + e = self.get_exception() + if e is not None: + raise e + raise SSHException('Negotiation failed.') + if event.isSet(): + break + + def add_server_key(self, key): + """ + Add a host key to the list of keys used for server mode. When behaving + as a server, the host key is used to sign certain packets during the + SSH2 negotiation, so that the client can trust that we are who we say + we are. Because this is used for signing, the key must contain private + key info, not just the public half. Only one key of each type (RSA or + DSS) is kept. + + @param key: the host key to add, usually an L{RSAKey } or + L{DSSKey }. + @type key: L{PKey } + """ + self.server_key_dict[key.get_name()] = key + + def get_server_key(self): + """ + Return the active host key, in server mode. After negotiating with the + client, this method will return the negotiated host key. If only one + type of host key was set with L{add_server_key}, that's the only key + that will ever be returned. But in cases where you have set more than + one type of host key (for example, an RSA key and a DSS key), the key + type will be negotiated by the client, and this method will return the + key of the type agreed on. If the host key has not been negotiated + yet, C{None} is returned. In client mode, the behavior is undefined. + + @return: host key of the type negotiated by the client, or C{None}. + @rtype: L{PKey } + """ + try: + return self.server_key_dict[self.host_key_type] + except KeyError: + pass + return None + + def load_server_moduli(filename=None): + """ + I{(optional)} + Load a file of prime moduli for use in doing group-exchange key + negotiation in server mode. It's a rather obscure option and can be + safely ignored. + + In server mode, the remote client may request "group-exchange" key + negotiation, which asks the server to send a random prime number that + fits certain criteria. These primes are pretty difficult to compute, + so they can't be generated on demand. But many systems contain a file + of suitable primes (usually named something like C{/etc/ssh/moduli}). + If you call C{load_server_moduli} and it returns C{True}, then this + file of primes has been loaded and we will support "group-exchange" in + server mode. Otherwise server mode will just claim that it doesn't + support that method of key negotiation. + + @param filename: optional path to the moduli file, if you happen to + know that it's not in a standard location. + @type filename: str + @return: True if a moduli file was successfully loaded; False + otherwise. + @rtype: bool + + @note: This has no effect when used in client mode. + """ + Transport._modulus_pack = ModulusPack(rng) + # places to look for the openssh "moduli" file + file_list = [ '/etc/ssh/moduli', '/usr/local/etc/moduli' ] + if filename is not None: + file_list.insert(0, filename) + for fn in file_list: + try: + Transport._modulus_pack.read_file(fn) + return True + except IOError: + pass + # none succeeded + Transport._modulus_pack = None + return False + load_server_moduli = staticmethod(load_server_moduli) + + def close(self): + """ + Close this session, and any open channels that are tied to it. + """ + if not self.active: + return + self.active = False + self.packetizer.close() + self.join() + for chan in self._channels.values(): + chan._unlink() + + def get_remote_server_key(self): + """ + Return the host key of the server (in client mode). + + @note: Previously this call returned a tuple of (key type, key string). + You can get the same effect by calling + L{PKey.get_name } for the key type, and + C{str(key)} for the key string. + + @raise SSHException: if no session is currently active. + + @return: public key of the remote server + @rtype: L{PKey } + """ + if (not self.active) or (not self.initial_kex_done): + raise SSHException('No existing session') + return self.host_key + + def is_active(self): + """ + Return true if this session is active (open). + + @return: True if the session is still active (open); False if the + session is closed + @rtype: bool + """ + return self.active + + def open_session(self): + """ + Request a new channel to the server, of type C{"session"}. This + is just an alias for C{open_channel('session')}. + + @return: a new L{Channel} + @rtype: L{Channel} + + @raise SSHException: if the request is rejected or the session ends + prematurely + """ + return self.open_channel('session') + + def open_x11_channel(self, src_addr=None): + """ + Request a new channel to the client, of type C{"x11"}. This + is just an alias for C{open_channel('x11', src_addr=src_addr)}. + + @param src_addr: the source address of the x11 server (port is the + x11 port, ie. 6010) + @type src_addr: (str, int) + @return: a new L{Channel} + @rtype: L{Channel} + + @raise SSHException: if the request is rejected or the session ends + prematurely + """ + return self.open_channel('x11', src_addr=src_addr) + + def open_forwarded_tcpip_channel(self, (src_addr, src_port), (dest_addr, dest_port)): + """ + Request a new channel back to the client, of type C{"forwarded-tcpip"}. + This is used after a client has requested port forwarding, for sending + incoming connections back to the client. + + @param src_addr: originator's address + @param src_port: originator's port + @param dest_addr: local (server) connected address + @param dest_port: local (server) connected port + """ + return self.open_channel('forwarded-tcpip', (dest_addr, dest_port), (src_addr, src_port)) + + def open_channel(self, kind, dest_addr=None, src_addr=None): + """ + Request a new channel to the server. L{Channel}s are socket-like + objects used for the actual transfer of data across the session. + You may only request a channel after negotiating encryption (using + L{connect} or L{start_client}) and authenticating. + + @param kind: the kind of channel requested (usually C{"session"}, + C{"forwarded-tcpip"}, C{"direct-tcpip"}, or C{"x11"}) + @type kind: str + @param dest_addr: the destination address of this port forwarding, + if C{kind} is C{"forwarded-tcpip"} or C{"direct-tcpip"} (ignored + for other channel types) + @type dest_addr: (str, int) + @param src_addr: the source address of this port forwarding, if + C{kind} is C{"forwarded-tcpip"}, C{"direct-tcpip"}, or C{"x11"} + @type src_addr: (str, int) + @return: a new L{Channel} on success + @rtype: L{Channel} + + @raise SSHException: if the request is rejected or the session ends + prematurely + """ + if not self.active: + raise SSHException('SSH session not active') + self.lock.acquire() + try: + chanid = self._next_channel() + m = Message() + m.add_byte(chr(MSG_CHANNEL_OPEN)) + m.add_string(kind) + m.add_int(chanid) + m.add_int(self.window_size) + m.add_int(self.max_packet_size) + if (kind == 'forwarded-tcpip') or (kind == 'direct-tcpip'): + m.add_string(dest_addr[0]) + m.add_int(dest_addr[1]) + m.add_string(src_addr[0]) + m.add_int(src_addr[1]) + elif kind == 'x11': + m.add_string(src_addr[0]) + m.add_int(src_addr[1]) + chan = Channel(chanid) + self._channels.put(chanid, chan) + self.channel_events[chanid] = event = threading.Event() + self.channels_seen[chanid] = True + chan._set_transport(self) + chan._set_window(self.window_size, self.max_packet_size) + finally: + self.lock.release() + self._send_user_message(m) + while True: + event.wait(0.1); + if not self.active: + e = self.get_exception() + if e is None: + e = SSHException('Unable to open channel.') + raise e + if event.isSet(): + break + chan = self._channels.get(chanid) + if chan is not None: + return chan + e = self.get_exception() + if e is None: + e = SSHException('Unable to open channel.') + raise e + + def request_port_forward(self, address, port, handler=None): + """ + Ask the server to forward TCP connections from a listening port on + the server, across this SSH session. + + If a handler is given, that handler is called from a different thread + whenever a forwarded connection arrives. The handler parameters are:: + + handler(channel, (origin_addr, origin_port), (server_addr, server_port)) + + where C{server_addr} and C{server_port} are the address and port that + the server was listening on. + + If no handler is set, the default behavior is to send new incoming + forwarded connections into the accept queue, to be picked up via + L{accept}. + + @param address: the address to bind when forwarding + @type address: str + @param port: the port to forward, or 0 to ask the server to allocate + any port + @type port: int + @param handler: optional handler for incoming forwarded connections + @type handler: function(Channel, (str, int), (str, int)) + @return: the port # allocated by the server + @rtype: int + + @raise SSHException: if the server refused the TCP forward request + """ + if not self.active: + raise SSHException('SSH session not active') + address = str(address) + port = int(port) + response = self.global_request('tcpip-forward', (address, port), wait=True) + if response is None: + raise SSHException('TCP forwarding request denied') + if port == 0: + port = response.get_int() + if handler is None: + def default_handler(channel, (src_addr, src_port), (dest_addr, dest_port)): + self._queue_incoming_channel(channel) + handler = default_handler + self._tcp_handler = handler + return port + + def cancel_port_forward(self, address, port): + """ + Ask the server to cancel a previous port-forwarding request. No more + connections to the given address & port will be forwarded across this + ssh connection. + + @param address: the address to stop forwarding + @type address: str + @param port: the port to stop forwarding + @type port: int + """ + if not self.active: + return + self._tcp_handler = None + self.global_request('cancel-tcpip-forward', (address, port), wait=True) + + def open_sftp_client(self): + """ + Create an SFTP client channel from an open transport. On success, + an SFTP session will be opened with the remote host, and a new + SFTPClient object will be returned. + + @return: a new L{SFTPClient} object, referring to an sftp session + (channel) across this transport + @rtype: L{SFTPClient} + """ + return SFTPClient.from_transport(self) + + def send_ignore(self, bytes=None): + """ + Send a junk packet across the encrypted link. This is sometimes used + to add "noise" to a connection to confuse would-be attackers. It can + also be used as a keep-alive for long lived connections traversing + firewalls. + + @param bytes: the number of random bytes to send in the payload of the + ignored packet -- defaults to a random number from 10 to 41. + @type bytes: int + """ + m = Message() + m.add_byte(chr(MSG_IGNORE)) + if bytes is None: + bytes = (ord(rng.read(1)) % 32) + 10 + m.add_bytes(rng.read(bytes)) + self._send_user_message(m) + + def renegotiate_keys(self): + """ + Force this session to switch to new keys. Normally this is done + automatically after the session hits a certain number of packets or + bytes sent or received, but this method gives you the option of forcing + new keys whenever you want. Negotiating new keys causes a pause in + traffic both ways as the two sides swap keys and do computations. This + method returns when the session has switched to new keys. + + @raise SSHException: if the key renegotiation failed (which causes the + session to end) + """ + self.completion_event = threading.Event() + self._send_kex_init() + while True: + self.completion_event.wait(0.1) + if not self.active: + e = self.get_exception() + if e is not None: + raise e + raise SSHException('Negotiation failed.') + if self.completion_event.isSet(): + break + return + + def set_keepalive(self, interval): + """ + Turn on/off keepalive packets (default is off). If this is set, after + C{interval} seconds without sending any data over the connection, a + "keepalive" packet will be sent (and ignored by the remote host). This + can be useful to keep connections alive over a NAT, for example. + + @param interval: seconds to wait before sending a keepalive packet (or + 0 to disable keepalives). + @type interval: int + """ + self.packetizer.set_keepalive(interval, + lambda x=weakref.proxy(self): x.global_request('keepalive@lag.net', wait=False)) + + def global_request(self, kind, data=None, wait=True): + """ + Make a global request to the remote host. These are normally + extensions to the SSH2 protocol. + + @param kind: name of the request. + @type kind: str + @param data: an optional tuple containing additional data to attach + to the request. + @type data: tuple + @param wait: C{True} if this method should not return until a response + is received; C{False} otherwise. + @type wait: bool + @return: a L{Message} containing possible additional data if the + request was successful (or an empty L{Message} if C{wait} was + C{False}); C{None} if the request was denied. + @rtype: L{Message} + """ + if wait: + self.completion_event = threading.Event() + m = Message() + m.add_byte(chr(MSG_GLOBAL_REQUEST)) + m.add_string(kind) + m.add_boolean(wait) + if data is not None: + m.add(*data) + self._log(DEBUG, 'Sending global request "%s"' % kind) + self._send_user_message(m) + if not wait: + return None + while True: + self.completion_event.wait(0.1) + if not self.active: + return None + if self.completion_event.isSet(): + break + return self.global_response + + def accept(self, timeout=None): + """ + Return the next channel opened by the client over this transport, in + server mode. If no channel is opened before the given timeout, C{None} + is returned. + + @param timeout: seconds to wait for a channel, or C{None} to wait + forever + @type timeout: int + @return: a new Channel opened by the client + @rtype: L{Channel} + """ + self.lock.acquire() + try: + if len(self.server_accepts) > 0: + chan = self.server_accepts.pop(0) + else: + self.server_accept_cv.wait(timeout) + if len(self.server_accepts) > 0: + chan = self.server_accepts.pop(0) + else: + # timeout + chan = None + finally: + self.lock.release() + return chan + + def connect(self, hostkey=None, username='', password=None, pkey=None): + """ + Negotiate an SSH2 session, and optionally verify the server's host key + and authenticate using a password or private key. This is a shortcut + for L{start_client}, L{get_remote_server_key}, and + L{Transport.auth_password} or L{Transport.auth_publickey}. Use those + methods if you want more control. + + You can use this method immediately after creating a Transport to + negotiate encryption with a server. If it fails, an exception will be + thrown. On success, the method will return cleanly, and an encrypted + session exists. You may immediately call L{open_channel} or + L{open_session} to get a L{Channel} object, which is used for data + transfer. + + @note: If you fail to supply a password or private key, this method may + succeed, but a subsequent L{open_channel} or L{open_session} call may + fail because you haven't authenticated yet. + + @param hostkey: the host key expected from the server, or C{None} if + you don't want to do host key verification. + @type hostkey: L{PKey} + @param username: the username to authenticate as. + @type username: str + @param password: a password to use for authentication, if you want to + use password authentication; otherwise C{None}. + @type password: str + @param pkey: a private key to use for authentication, if you want to + use private key authentication; otherwise C{None}. + @type pkey: L{PKey} + + @raise SSHException: if the SSH2 negotiation fails, the host key + supplied by the server is incorrect, or authentication fails. + """ + if hostkey is not None: + self._preferred_keys = [ hostkey.get_name() ] + + self.start_client() + + # check host key if we were given one + if (hostkey is not None): + key = self.get_remote_server_key() + if (key.get_name() != hostkey.get_name()) or (str(key) != str(hostkey)): + self._log(DEBUG, 'Bad host key from server') + self._log(DEBUG, 'Expected: %s: %s' % (hostkey.get_name(), repr(str(hostkey)))) + self._log(DEBUG, 'Got : %s: %s' % (key.get_name(), repr(str(key)))) + raise SSHException('Bad host key from server') + self._log(DEBUG, 'Host key verified (%s)' % hostkey.get_name()) + + if (pkey is not None) or (password is not None): + if password is not None: + self._log(DEBUG, 'Attempting password auth...') + self.auth_password(username, password) + else: + self._log(DEBUG, 'Attempting public-key auth...') + self.auth_publickey(username, pkey) + + return + + def get_exception(self): + """ + Return any exception that happened during the last server request. + This can be used to fetch more specific error information after using + calls like L{start_client}. The exception (if any) is cleared after + this call. + + @return: an exception, or C{None} if there is no stored exception. + @rtype: Exception + + @since: 1.1 + """ + self.lock.acquire() + try: + e = self.saved_exception + self.saved_exception = None + return e + finally: + self.lock.release() + + def set_subsystem_handler(self, name, handler, *larg, **kwarg): + """ + Set the handler class for a subsystem in server mode. If a request + for this subsystem is made on an open ssh channel later, this handler + will be constructed and called -- see L{SubsystemHandler} for more + detailed documentation. + + Any extra parameters (including keyword arguments) are saved and + passed to the L{SubsystemHandler} constructor later. + + @param name: name of the subsystem. + @type name: str + @param handler: subclass of L{SubsystemHandler} that handles this + subsystem. + @type handler: class + """ + try: + self.lock.acquire() + self.subsystem_table[name] = (handler, larg, kwarg) + finally: + self.lock.release() + + def is_authenticated(self): + """ + Return true if this session is active and authenticated. + + @return: True if the session is still open and has been authenticated + successfully; False if authentication failed and/or the session is + closed. + @rtype: bool + """ + return self.active and (self.auth_handler is not None) and self.auth_handler.is_authenticated() + + def get_username(self): + """ + Return the username this connection is authenticated for. If the + session is not authenticated (or authentication failed), this method + returns C{None}. + + @return: username that was authenticated, or C{None}. + @rtype: string + """ + if not self.active or (self.auth_handler is None): + return None + return self.auth_handler.get_username() + + def auth_none(self, username): + """ + Try to authenticate to the server using no authentication at all. + This will almost always fail. It may be useful for determining the + list of authentication types supported by the server, by catching the + L{BadAuthenticationType} exception raised. + + @param username: the username to authenticate as + @type username: string + @return: list of auth types permissible for the next stage of + authentication (normally empty) + @rtype: list + + @raise BadAuthenticationType: if "none" authentication isn't allowed + by the server for this user + @raise SSHException: if the authentication failed due to a network + error + + @since: 1.5 + """ + if (not self.active) or (not self.initial_kex_done): + raise SSHException('No existing session') + my_event = threading.Event() + self.auth_handler = AuthHandler(self) + self.auth_handler.auth_none(username, my_event) + return self.auth_handler.wait_for_response(my_event) + + def auth_password(self, username, password, event=None, fallback=True): + """ + Authenticate to the server using a password. The username and password + are sent over an encrypted link. + + If an C{event} is passed in, this method will return immediately, and + the event will be triggered once authentication succeeds or fails. On + success, L{is_authenticated} will return C{True}. On failure, you may + use L{get_exception} to get more detailed error information. + + Since 1.1, if no event is passed, this method will block until the + authentication succeeds or fails. On failure, an exception is raised. + Otherwise, the method simply returns. + + Since 1.5, if no event is passed and C{fallback} is C{True} (the + default), if the server doesn't support plain password authentication + but does support so-called "keyboard-interactive" mode, an attempt + will be made to authenticate using this interactive mode. If it fails, + the normal exception will be thrown as if the attempt had never been + made. This is useful for some recent Gentoo and Debian distributions, + which turn off plain password authentication in a misguided belief + that interactive authentication is "more secure". (It's not.) + + If the server requires multi-step authentication (which is very rare), + this method will return a list of auth types permissible for the next + step. Otherwise, in the normal case, an empty list is returned. + + @param username: the username to authenticate as + @type username: str + @param password: the password to authenticate with + @type password: str or unicode + @param event: an event to trigger when the authentication attempt is + complete (whether it was successful or not) + @type event: threading.Event + @param fallback: C{True} if an attempt at an automated "interactive" + password auth should be made if the server doesn't support normal + password auth + @type fallback: bool + @return: list of auth types permissible for the next stage of + authentication (normally empty) + @rtype: list + + @raise BadAuthenticationType: if password authentication isn't + allowed by the server for this user (and no event was passed in) + @raise AuthenticationException: if the authentication failed (and no + event was passed in) + @raise SSHException: if there was a network error + """ + if (not self.active) or (not self.initial_kex_done): + # we should never try to send the password unless we're on a secure link + raise SSHException('No existing session') + if event is None: + my_event = threading.Event() + else: + my_event = event + self.auth_handler = AuthHandler(self) + self.auth_handler.auth_password(username, password, my_event) + if event is not None: + # caller wants to wait for event themselves + return [] + try: + return self.auth_handler.wait_for_response(my_event) + except BadAuthenticationType, x: + # if password auth isn't allowed, but keyboard-interactive *is*, try to fudge it + if not fallback or ('keyboard-interactive' not in x.allowed_types): + raise + try: + def handler(title, instructions, fields): + if len(fields) > 1: + raise SSHException('Fallback authentication failed.') + if len(fields) == 0: + # for some reason, at least on os x, a 2nd request will + # be made with zero fields requested. maybe it's just + # to try to fake out automated scripting of the exact + # type we're doing here. *shrug* :) + return [] + return [ password ] + return self.auth_interactive(username, handler) + except SSHException, ignored: + # attempt failed; just raise the original exception + raise x + return None + + def auth_publickey(self, username, key, event=None): + """ + Authenticate to the server using a private key. The key is used to + sign data from the server, so it must include the private part. + + If an C{event} is passed in, this method will return immediately, and + the event will be triggered once authentication succeeds or fails. On + success, L{is_authenticated} will return C{True}. On failure, you may + use L{get_exception} to get more detailed error information. + + Since 1.1, if no event is passed, this method will block until the + authentication succeeds or fails. On failure, an exception is raised. + Otherwise, the method simply returns. + + If the server requires multi-step authentication (which is very rare), + this method will return a list of auth types permissible for the next + step. Otherwise, in the normal case, an empty list is returned. + + @param username: the username to authenticate as + @type username: string + @param key: the private key to authenticate with + @type key: L{PKey } + @param event: an event to trigger when the authentication attempt is + complete (whether it was successful or not) + @type event: threading.Event + @return: list of auth types permissible for the next stage of + authentication (normally empty) + @rtype: list + + @raise BadAuthenticationType: if public-key authentication isn't + allowed by the server for this user (and no event was passed in) + @raise AuthenticationException: if the authentication failed (and no + event was passed in) + @raise SSHException: if there was a network error + """ + if (not self.active) or (not self.initial_kex_done): + # we should never try to authenticate unless we're on a secure link + raise SSHException('No existing session') + if event is None: + my_event = threading.Event() + else: + my_event = event + self.auth_handler = AuthHandler(self) + self.auth_handler.auth_publickey(username, key, my_event) + if event is not None: + # caller wants to wait for event themselves + return [] + return self.auth_handler.wait_for_response(my_event) + + def auth_interactive(self, username, handler, submethods=''): + """ + Authenticate to the server interactively. A handler is used to answer + arbitrary questions from the server. On many servers, this is just a + dumb wrapper around PAM. + + This method will block until the authentication succeeds or fails, + peroidically calling the handler asynchronously to get answers to + authentication questions. The handler may be called more than once + if the server continues to ask questions. + + The handler is expected to be a callable that will handle calls of the + form: C{handler(title, instructions, prompt_list)}. The C{title} is + meant to be a dialog-window title, and the C{instructions} are user + instructions (both are strings). C{prompt_list} will be a list of + prompts, each prompt being a tuple of C{(str, bool)}. The string is + the prompt and the boolean indicates whether the user text should be + echoed. + + A sample call would thus be: + C{handler('title', 'instructions', [('Password:', False)])}. + + The handler should return a list or tuple of answers to the server's + questions. + + If the server requires multi-step authentication (which is very rare), + this method will return a list of auth types permissible for the next + step. Otherwise, in the normal case, an empty list is returned. + + @param username: the username to authenticate as + @type username: string + @param handler: a handler for responding to server questions + @type handler: callable + @param submethods: a string list of desired submethods (optional) + @type submethods: str + @return: list of auth types permissible for the next stage of + authentication (normally empty). + @rtype: list + + @raise BadAuthenticationType: if public-key authentication isn't + allowed by the server for this user + @raise AuthenticationException: if the authentication failed + @raise SSHException: if there was a network error + + @since: 1.5 + """ + if (not self.active) or (not self.initial_kex_done): + # we should never try to authenticate unless we're on a secure link + raise SSHException('No existing session') + my_event = threading.Event() + self.auth_handler = AuthHandler(self) + self.auth_handler.auth_interactive(username, handler, my_event, submethods) + return self.auth_handler.wait_for_response(my_event) + + def set_log_channel(self, name): + """ + Set the channel for this transport's logging. The default is + C{"paramiko.transport"} but it can be set to anything you want. + (See the C{logging} module for more info.) SSH Channels will log + to a sub-channel of the one specified. + + @param name: new channel name for logging + @type name: str + + @since: 1.1 + """ + self.log_name = name + self.logger = util.get_logger(name) + self.packetizer.set_log(self.logger) + + def get_log_channel(self): + """ + Return the channel name used for this transport's logging. + + @return: channel name. + @rtype: str + + @since: 1.2 + """ + return self.log_name + + def set_hexdump(self, hexdump): + """ + Turn on/off logging a hex dump of protocol traffic at DEBUG level in + the logs. Normally you would want this off (which is the default), + but if you are debugging something, it may be useful. + + @param hexdump: C{True} to log protocol traffix (in hex) to the log; + C{False} otherwise. + @type hexdump: bool + """ + self.packetizer.set_hexdump(hexdump) + + def get_hexdump(self): + """ + Return C{True} if the transport is currently logging hex dumps of + protocol traffic. + + @return: C{True} if hex dumps are being logged + @rtype: bool + + @since: 1.4 + """ + return self.packetizer.get_hexdump() + + def use_compression(self, compress=True): + """ + Turn on/off compression. This will only have an affect before starting + the transport (ie before calling L{connect}, etc). By default, + compression is off since it negatively affects interactive sessions. + + @param compress: C{True} to ask the remote client/server to compress + traffic; C{False} to refuse compression + @type compress: bool + + @since: 1.5.2 + """ + if compress: + self._preferred_compression = ( 'zlib@openssh.com', 'zlib', 'none' ) + else: + self._preferred_compression = ( 'none', ) + + def getpeername(self): + """ + Return the address of the remote side of this Transport, if possible. + This is effectively a wrapper around C{'getpeername'} on the underlying + socket. If the socket-like object has no C{'getpeername'} method, + then C{("unknown", 0)} is returned. + + @return: the address if the remote host, if known + @rtype: tuple(str, int) + """ + gp = getattr(self.sock, 'getpeername', None) + if gp is None: + return ('unknown', 0) + return gp() + + def stop_thread(self): + self.active = False + self.packetizer.close() + + + ### internals... + + + def _log(self, level, msg, *args): + if issubclass(type(msg), list): + for m in msg: + self.logger.log(level, m) + else: + self.logger.log(level, msg, *args) + + def _get_modulus_pack(self): + "used by KexGex to find primes for group exchange" + return self._modulus_pack + + def _next_channel(self): + "you are holding the lock" + chanid = self._channel_counter + while self._channels.get(chanid) is not None: + self._channel_counter = (self._channel_counter + 1) & 0xffffff + chanid = self._channel_counter + self._channel_counter = (self._channel_counter + 1) & 0xffffff + return chanid + + def _unlink_channel(self, chanid): + "used by a Channel to remove itself from the active channel list" + self._channels.delete(chanid) + + def _send_message(self, data): + self.packetizer.send_message(data) + + def _send_user_message(self, data): + """ + send a message, but block if we're in key negotiation. this is used + for user-initiated requests. + """ + start = time.time() + while True: + self.clear_to_send.wait(0.1) + if not self.active: + self._log(DEBUG, 'Dropping user packet because connection is dead.') + return + self.clear_to_send_lock.acquire() + if self.clear_to_send.isSet(): + break + self.clear_to_send_lock.release() + if time.time() > start + self.clear_to_send_timeout: + raise SSHException('Key-exchange timed out waiting for key negotiation') + try: + self._send_message(data) + finally: + self.clear_to_send_lock.release() + + def _set_K_H(self, k, h): + "used by a kex object to set the K (root key) and H (exchange hash)" + self.K = k + self.H = h + if self.session_id == None: + self.session_id = h + + def _expect_packet(self, *ptypes): + "used by a kex object to register the next packet type it expects to see" + self._expected_packet = tuple(ptypes) + + def _verify_key(self, host_key, sig): + key = self._key_info[self.host_key_type](Message(host_key)) + if key is None: + raise SSHException('Unknown host key type') + if not key.verify_ssh_sig(self.H, Message(sig)): + raise SSHException('Signature verification (%s) failed.' % self.host_key_type) + self.host_key = key + + def _compute_key(self, id, nbytes): + "id is 'A' - 'F' for the various keys used by ssh" + m = Message() + m.add_mpint(self.K) + m.add_bytes(self.H) + m.add_byte(id) + m.add_bytes(self.session_id) + out = sofar = SHA.new(str(m)).digest() + while len(out) < nbytes: + m = Message() + m.add_mpint(self.K) + m.add_bytes(self.H) + m.add_bytes(sofar) + digest = SHA.new(str(m)).digest() + out += digest + sofar += digest + return out[:nbytes] + + def _get_cipher(self, name, key, iv): + if name not in self._cipher_info: + raise SSHException('Unknown client cipher ' + name) + if name in ('arcfour128', 'arcfour256'): + # arcfour cipher + cipher = self._cipher_info[name]['class'].new(key) + # as per RFC 4345, the first 1536 bytes of keystream + # generated by the cipher MUST be discarded + cipher.encrypt(" " * 1536) + return cipher + elif name.endswith("-ctr"): + # CTR modes, we need a counter + counter = Counter.new(nbits=self._cipher_info[name]['block-size'] * 8, initial_value=util.inflate_long(iv, True)) + return self._cipher_info[name]['class'].new(key, self._cipher_info[name]['mode'], iv, counter) + else: + return self._cipher_info[name]['class'].new(key, self._cipher_info[name]['mode'], iv) + + def _set_x11_handler(self, handler): + # only called if a channel has turned on x11 forwarding + if handler is None: + # by default, use the same mechanism as accept() + def default_handler(channel, (src_addr, src_port)): + self._queue_incoming_channel(channel) + self._x11_handler = default_handler + else: + self._x11_handler = handler + + def _queue_incoming_channel(self, channel): + self.lock.acquire() + try: + self.server_accepts.append(channel) + self.server_accept_cv.notify() + finally: + self.lock.release() + + def run(self): + # (use the exposed "run" method, because if we specify a thread target + # of a private method, threading.Thread will keep a reference to it + # indefinitely, creating a GC cycle and not letting Transport ever be + # GC'd. it's a bug in Thread.) + + # active=True occurs before the thread is launched, to avoid a race + _active_threads.append(self) + if self.server_mode: + self._log(DEBUG, 'starting thread (server mode): %s' % hex(long(id(self)) & 0xffffffffL)) + else: + self._log(DEBUG, 'starting thread (client mode): %s' % hex(long(id(self)) & 0xffffffffL)) + try: + self.packetizer.write_all(self.local_version + '\r\n') + self._check_banner() + self._send_kex_init() + self._expect_packet(MSG_KEXINIT) + + while self.active: + if self.packetizer.need_rekey() and not self.in_kex: + self._send_kex_init() + try: + ptype, m = self.packetizer.read_message() + except NeedRekeyException: + continue + if ptype == MSG_IGNORE: + continue + elif ptype == MSG_DISCONNECT: + self._parse_disconnect(m) + self.active = False + self.packetizer.close() + break + elif ptype == MSG_DEBUG: + self._parse_debug(m) + continue + if len(self._expected_packet) > 0: + if ptype not in self._expected_packet: + raise SSHException('Expecting packet from %r, got %d' % (self._expected_packet, ptype)) + self._expected_packet = tuple() + if (ptype >= 30) and (ptype <= 39): + self.kex_engine.parse_next(ptype, m) + continue + + if ptype in self._handler_table: + self._handler_table[ptype](self, m) + elif ptype in self._channel_handler_table: + chanid = m.get_int() + chan = self._channels.get(chanid) + if chan is not None: + self._channel_handler_table[ptype](chan, m) + elif chanid in self.channels_seen: + self._log(DEBUG, 'Ignoring message for dead channel %d' % chanid) + else: + self._log(ERROR, 'Channel request for unknown channel %d' % chanid) + self.active = False + self.packetizer.close() + elif (self.auth_handler is not None) and (ptype in self.auth_handler._handler_table): + self.auth_handler._handler_table[ptype](self.auth_handler, m) + else: + self._log(WARNING, 'Oops, unhandled type %d' % ptype) + msg = Message() + msg.add_byte(chr(MSG_UNIMPLEMENTED)) + msg.add_int(m.seqno) + self._send_message(msg) + except SSHException, e: + self._log(ERROR, 'Exception: ' + str(e)) + self._log(ERROR, util.tb_strings()) + self.saved_exception = e + except EOFError, e: + self._log(DEBUG, 'EOF in transport thread') + #self._log(DEBUG, util.tb_strings()) + self.saved_exception = e + except socket.error, e: + if type(e.args) is tuple: + emsg = '%s (%d)' % (e.args[1], e.args[0]) + else: + emsg = e.args + self._log(ERROR, 'Socket exception: ' + emsg) + self.saved_exception = e + except Exception, e: + self._log(ERROR, 'Unknown exception: ' + str(e)) + self._log(ERROR, util.tb_strings()) + self.saved_exception = e + _active_threads.remove(self) + for chan in self._channels.values(): + chan._unlink() + if self.active: + self.active = False + self.packetizer.close() + if self.completion_event != None: + self.completion_event.set() + if self.auth_handler is not None: + self.auth_handler.abort() + for event in self.channel_events.values(): + event.set() + try: + self.lock.acquire() + self.server_accept_cv.notify() + finally: + self.lock.release() + self.sock.close() + + + ### protocol stages + + + def _negotiate_keys(self, m): + # throws SSHException on anything unusual + self.clear_to_send_lock.acquire() + try: + self.clear_to_send.clear() + finally: + self.clear_to_send_lock.release() + if self.local_kex_init == None: + # remote side wants to renegotiate + self._send_kex_init() + self._parse_kex_init(m) + self.kex_engine.start_kex() + + def _check_banner(self): + # this is slow, but we only have to do it once + for i in range(100): + # give them 15 seconds for the first line, then just 2 seconds + # each additional line. (some sites have very high latency.) + if i == 0: + timeout = self.banner_timeout + else: + timeout = 2 + try: + buf = self.packetizer.readline(timeout) + except Exception, x: + raise SSHException('Error reading SSH protocol banner' + str(x)) + if buf[:4] == 'SSH-': + break + self._log(DEBUG, 'Banner: ' + buf) + if buf[:4] != 'SSH-': + raise SSHException('Indecipherable protocol version "' + buf + '"') + # save this server version string for later + self.remote_version = buf + # pull off any attached comment + comment = '' + i = string.find(buf, ' ') + if i >= 0: + comment = buf[i+1:] + buf = buf[:i] + # parse out version string and make sure it matches + segs = buf.split('-', 2) + if len(segs) < 3: + raise SSHException('Invalid SSH banner') + version = segs[1] + client = segs[2] + if version != '1.99' and version != '2.0': + raise SSHException('Incompatible version (%s instead of 2.0)' % (version,)) + self._log(INFO, 'Connected (version %s, client %s)' % (version, client)) + + def _send_kex_init(self): + """ + announce to the other side that we'd like to negotiate keys, and what + kind of key negotiation we support. + """ + self.clear_to_send_lock.acquire() + try: + self.clear_to_send.clear() + finally: + self.clear_to_send_lock.release() + self.in_kex = True + if self.server_mode: + if (self._modulus_pack is None) and ('diffie-hellman-group-exchange-sha1' in self._preferred_kex): + # can't do group-exchange if we don't have a pack of potential primes + pkex = list(self.get_security_options().kex) + pkex.remove('diffie-hellman-group-exchange-sha1') + self.get_security_options().kex = pkex + available_server_keys = filter(self.server_key_dict.keys().__contains__, + self._preferred_keys) + else: + available_server_keys = self._preferred_keys + + m = Message() + m.add_byte(chr(MSG_KEXINIT)) + m.add_bytes(rng.read(16)) + m.add_list(self._preferred_kex) + m.add_list(available_server_keys) + m.add_list(self._preferred_ciphers) + m.add_list(self._preferred_ciphers) + m.add_list(self._preferred_macs) + m.add_list(self._preferred_macs) + m.add_list(self._preferred_compression) + m.add_list(self._preferred_compression) + m.add_string('') + m.add_string('') + m.add_boolean(False) + m.add_int(0) + # save a copy for later (needed to compute a hash) + self.local_kex_init = str(m) + self._send_message(m) + + def _parse_kex_init(self, m): + cookie = m.get_bytes(16) + kex_algo_list = m.get_list() + server_key_algo_list = m.get_list() + client_encrypt_algo_list = m.get_list() + server_encrypt_algo_list = m.get_list() + client_mac_algo_list = m.get_list() + server_mac_algo_list = m.get_list() + client_compress_algo_list = m.get_list() + server_compress_algo_list = m.get_list() + client_lang_list = m.get_list() + server_lang_list = m.get_list() + kex_follows = m.get_boolean() + unused = m.get_int() + + self._log(DEBUG, 'kex algos:' + str(kex_algo_list) + ' server key:' + str(server_key_algo_list) + \ + ' client encrypt:' + str(client_encrypt_algo_list) + \ + ' server encrypt:' + str(server_encrypt_algo_list) + \ + ' client mac:' + str(client_mac_algo_list) + \ + ' server mac:' + str(server_mac_algo_list) + \ + ' client compress:' + str(client_compress_algo_list) + \ + ' server compress:' + str(server_compress_algo_list) + \ + ' client lang:' + str(client_lang_list) + \ + ' server lang:' + str(server_lang_list) + \ + ' kex follows?' + str(kex_follows)) + + # as a server, we pick the first item in the client's list that we support. + # as a client, we pick the first item in our list that the server supports. + if self.server_mode: + agreed_kex = filter(self._preferred_kex.__contains__, kex_algo_list) + else: + agreed_kex = filter(kex_algo_list.__contains__, self._preferred_kex) + if len(agreed_kex) == 0: + raise SSHException('Incompatible ssh peer (no acceptable kex algorithm)') + self.kex_engine = self._kex_info[agreed_kex[0]](self) + + if self.server_mode: + available_server_keys = filter(self.server_key_dict.keys().__contains__, + self._preferred_keys) + agreed_keys = filter(available_server_keys.__contains__, server_key_algo_list) + else: + agreed_keys = filter(server_key_algo_list.__contains__, self._preferred_keys) + if len(agreed_keys) == 0: + raise SSHException('Incompatible ssh peer (no acceptable host key)') + self.host_key_type = agreed_keys[0] + if self.server_mode and (self.get_server_key() is None): + raise SSHException('Incompatible ssh peer (can\'t match requested host key type)') + + if self.server_mode: + agreed_local_ciphers = filter(self._preferred_ciphers.__contains__, + server_encrypt_algo_list) + agreed_remote_ciphers = filter(self._preferred_ciphers.__contains__, + client_encrypt_algo_list) + else: + agreed_local_ciphers = filter(client_encrypt_algo_list.__contains__, + self._preferred_ciphers) + agreed_remote_ciphers = filter(server_encrypt_algo_list.__contains__, + self._preferred_ciphers) + if (len(agreed_local_ciphers) == 0) or (len(agreed_remote_ciphers) == 0): + raise SSHException('Incompatible ssh server (no acceptable ciphers)') + self.local_cipher = agreed_local_ciphers[0] + self.remote_cipher = agreed_remote_ciphers[0] + self._log(DEBUG, 'Ciphers agreed: local=%s, remote=%s' % (self.local_cipher, self.remote_cipher)) + + if self.server_mode: + agreed_remote_macs = filter(self._preferred_macs.__contains__, client_mac_algo_list) + agreed_local_macs = filter(self._preferred_macs.__contains__, server_mac_algo_list) + else: + agreed_local_macs = filter(client_mac_algo_list.__contains__, self._preferred_macs) + agreed_remote_macs = filter(server_mac_algo_list.__contains__, self._preferred_macs) + if (len(agreed_local_macs) == 0) or (len(agreed_remote_macs) == 0): + raise SSHException('Incompatible ssh server (no acceptable macs)') + self.local_mac = agreed_local_macs[0] + self.remote_mac = agreed_remote_macs[0] + + if self.server_mode: + agreed_remote_compression = filter(self._preferred_compression.__contains__, client_compress_algo_list) + agreed_local_compression = filter(self._preferred_compression.__contains__, server_compress_algo_list) + else: + agreed_local_compression = filter(client_compress_algo_list.__contains__, self._preferred_compression) + agreed_remote_compression = filter(server_compress_algo_list.__contains__, self._preferred_compression) + if (len(agreed_local_compression) == 0) or (len(agreed_remote_compression) == 0): + raise SSHException('Incompatible ssh server (no acceptable compression) %r %r %r' % (agreed_local_compression, agreed_remote_compression, self._preferred_compression)) + self.local_compression = agreed_local_compression[0] + self.remote_compression = agreed_remote_compression[0] + + self._log(DEBUG, 'using kex %s; server key type %s; cipher: local %s, remote %s; mac: local %s, remote %s; compression: local %s, remote %s' % + (agreed_kex[0], self.host_key_type, self.local_cipher, self.remote_cipher, self.local_mac, + self.remote_mac, self.local_compression, self.remote_compression)) + + # save for computing hash later... + # now wait! openssh has a bug (and others might too) where there are + # actually some extra bytes (one NUL byte in openssh's case) added to + # the end of the packet but not parsed. turns out we need to throw + # away those bytes because they aren't part of the hash. + self.remote_kex_init = chr(MSG_KEXINIT) + m.get_so_far() + + def _activate_inbound(self): + "switch on newly negotiated encryption parameters for inbound traffic" + block_size = self._cipher_info[self.remote_cipher]['block-size'] + if self.server_mode: + IV_in = self._compute_key('A', block_size) + key_in = self._compute_key('C', self._cipher_info[self.remote_cipher]['key-size']) + else: + IV_in = self._compute_key('B', block_size) + key_in = self._compute_key('D', self._cipher_info[self.remote_cipher]['key-size']) + engine = self._get_cipher(self.remote_cipher, key_in, IV_in) + mac_size = self._mac_info[self.remote_mac]['size'] + mac_engine = self._mac_info[self.remote_mac]['class'] + # initial mac keys are done in the hash's natural size (not the potentially truncated + # transmission size) + if self.server_mode: + mac_key = self._compute_key('E', mac_engine.digest_size) + else: + mac_key = self._compute_key('F', mac_engine.digest_size) + self.packetizer.set_inbound_cipher(engine, block_size, mac_engine, mac_size, mac_key) + compress_in = self._compression_info[self.remote_compression][1] + if (compress_in is not None) and ((self.remote_compression != 'zlib@openssh.com') or self.authenticated): + self._log(DEBUG, 'Switching on inbound compression ...') + self.packetizer.set_inbound_compressor(compress_in()) + + def _activate_outbound(self): + "switch on newly negotiated encryption parameters for outbound traffic" + m = Message() + m.add_byte(chr(MSG_NEWKEYS)) + self._send_message(m) + block_size = self._cipher_info[self.local_cipher]['block-size'] + if self.server_mode: + IV_out = self._compute_key('B', block_size) + key_out = self._compute_key('D', self._cipher_info[self.local_cipher]['key-size']) + else: + IV_out = self._compute_key('A', block_size) + key_out = self._compute_key('C', self._cipher_info[self.local_cipher]['key-size']) + engine = self._get_cipher(self.local_cipher, key_out, IV_out) + mac_size = self._mac_info[self.local_mac]['size'] + mac_engine = self._mac_info[self.local_mac]['class'] + # initial mac keys are done in the hash's natural size (not the potentially truncated + # transmission size) + if self.server_mode: + mac_key = self._compute_key('F', mac_engine.digest_size) + else: + mac_key = self._compute_key('E', mac_engine.digest_size) + self.packetizer.set_outbound_cipher(engine, block_size, mac_engine, mac_size, mac_key) + compress_out = self._compression_info[self.local_compression][0] + if (compress_out is not None) and ((self.local_compression != 'zlib@openssh.com') or self.authenticated): + self._log(DEBUG, 'Switching on outbound compression ...') + self.packetizer.set_outbound_compressor(compress_out()) + if not self.packetizer.need_rekey(): + self.in_kex = False + # we always expect to receive NEWKEYS now + self._expect_packet(MSG_NEWKEYS) + + def _auth_trigger(self): + self.authenticated = True + # delayed initiation of compression + if self.local_compression == 'zlib@openssh.com': + compress_out = self._compression_info[self.local_compression][0] + self._log(DEBUG, 'Switching on outbound compression ...') + self.packetizer.set_outbound_compressor(compress_out()) + if self.remote_compression == 'zlib@openssh.com': + compress_in = self._compression_info[self.remote_compression][1] + self._log(DEBUG, 'Switching on inbound compression ...') + self.packetizer.set_inbound_compressor(compress_in()) + + def _parse_newkeys(self, m): + self._log(DEBUG, 'Switch to new keys ...') + self._activate_inbound() + # can also free a bunch of stuff here + self.local_kex_init = self.remote_kex_init = None + self.K = None + self.kex_engine = None + if self.server_mode and (self.auth_handler is None): + # create auth handler for server mode + self.auth_handler = AuthHandler(self) + if not self.initial_kex_done: + # this was the first key exchange + self.initial_kex_done = True + # send an event? + if self.completion_event != None: + self.completion_event.set() + # it's now okay to send data again (if this was a re-key) + if not self.packetizer.need_rekey(): + self.in_kex = False + self.clear_to_send_lock.acquire() + try: + self.clear_to_send.set() + finally: + self.clear_to_send_lock.release() + return + + def _parse_disconnect(self, m): + code = m.get_int() + desc = m.get_string() + self._log(INFO, 'Disconnect (code %d): %s' % (code, desc)) + + def _parse_global_request(self, m): + kind = m.get_string() + self._log(DEBUG, 'Received global request "%s"' % kind) + want_reply = m.get_boolean() + if not self.server_mode: + self._log(DEBUG, 'Rejecting "%s" global request from server.' % kind) + ok = False + elif kind == 'tcpip-forward': + address = m.get_string() + port = m.get_int() + ok = self.server_object.check_port_forward_request(address, port) + if ok != False: + ok = (ok,) + elif kind == 'cancel-tcpip-forward': + address = m.get_string() + port = m.get_int() + self.server_object.cancel_port_forward_request(address, port) + ok = True + else: + ok = self.server_object.check_global_request(kind, m) + extra = () + if type(ok) is tuple: + extra = ok + ok = True + if want_reply: + msg = Message() + if ok: + msg.add_byte(chr(MSG_REQUEST_SUCCESS)) + msg.add(*extra) + else: + msg.add_byte(chr(MSG_REQUEST_FAILURE)) + self._send_message(msg) + + def _parse_request_success(self, m): + self._log(DEBUG, 'Global request successful.') + self.global_response = m + if self.completion_event is not None: + self.completion_event.set() + + def _parse_request_failure(self, m): + self._log(DEBUG, 'Global request denied.') + self.global_response = None + if self.completion_event is not None: + self.completion_event.set() + + def _parse_channel_open_success(self, m): + chanid = m.get_int() + server_chanid = m.get_int() + server_window_size = m.get_int() + server_max_packet_size = m.get_int() + chan = self._channels.get(chanid) + if chan is None: + self._log(WARNING, 'Success for unrequested channel! [??]') + return + self.lock.acquire() + try: + chan._set_remote_channel(server_chanid, server_window_size, server_max_packet_size) + self._log(INFO, 'Secsh channel %d opened.' % chanid) + if chanid in self.channel_events: + self.channel_events[chanid].set() + del self.channel_events[chanid] + finally: + self.lock.release() + return + + def _parse_channel_open_failure(self, m): + chanid = m.get_int() + reason = m.get_int() + reason_str = m.get_string() + lang = m.get_string() + reason_text = CONNECTION_FAILED_CODE.get(reason, '(unknown code)') + self._log(INFO, 'Secsh channel %d open FAILED: %s: %s' % (chanid, reason_str, reason_text)) + self.lock.acquire() + try: + self.saved_exception = ChannelException(reason, reason_text) + if chanid in self.channel_events: + self._channels.delete(chanid) + if chanid in self.channel_events: + self.channel_events[chanid].set() + del self.channel_events[chanid] + finally: + self.lock.release() + return + + def _parse_channel_open(self, m): + kind = m.get_string() + chanid = m.get_int() + initial_window_size = m.get_int() + max_packet_size = m.get_int() + reject = False + if (kind == 'x11') and (self._x11_handler is not None): + origin_addr = m.get_string() + origin_port = m.get_int() + self._log(DEBUG, 'Incoming x11 connection from %s:%d' % (origin_addr, origin_port)) + self.lock.acquire() + try: + my_chanid = self._next_channel() + finally: + self.lock.release() + elif (kind == 'forwarded-tcpip') and (self._tcp_handler is not None): + server_addr = m.get_string() + server_port = m.get_int() + origin_addr = m.get_string() + origin_port = m.get_int() + self._log(DEBUG, 'Incoming tcp forwarded connection from %s:%d' % (origin_addr, origin_port)) + self.lock.acquire() + try: + my_chanid = self._next_channel() + finally: + self.lock.release() + elif not self.server_mode: + self._log(DEBUG, 'Rejecting "%s" channel request from server.' % kind) + reject = True + reason = OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED + else: + self.lock.acquire() + try: + my_chanid = self._next_channel() + finally: + self.lock.release() + if kind == 'direct-tcpip': + # handle direct-tcpip requests comming from the client + dest_addr = m.get_string() + dest_port = m.get_int() + origin_addr = m.get_string() + origin_port = m.get_int() + reason = self.server_object.check_channel_direct_tcpip_request( + my_chanid, (origin_addr, origin_port), + (dest_addr, dest_port)) + else: + reason = self.server_object.check_channel_request(kind, my_chanid) + if reason != OPEN_SUCCEEDED: + self._log(DEBUG, 'Rejecting "%s" channel request from client.' % kind) + reject = True + if reject: + msg = Message() + msg.add_byte(chr(MSG_CHANNEL_OPEN_FAILURE)) + msg.add_int(chanid) + msg.add_int(reason) + msg.add_string('') + msg.add_string('en') + self._send_message(msg) + return + + chan = Channel(my_chanid) + self.lock.acquire() + try: + self._channels.put(my_chanid, chan) + self.channels_seen[my_chanid] = True + chan._set_transport(self) + chan._set_window(self.window_size, self.max_packet_size) + chan._set_remote_channel(chanid, initial_window_size, max_packet_size) + finally: + self.lock.release() + m = Message() + m.add_byte(chr(MSG_CHANNEL_OPEN_SUCCESS)) + m.add_int(chanid) + m.add_int(my_chanid) + m.add_int(self.window_size) + m.add_int(self.max_packet_size) + self._send_message(m) + self._log(INFO, 'Secsh channel %d (%s) opened.', my_chanid, kind) + if kind == 'x11': + self._x11_handler(chan, (origin_addr, origin_port)) + elif kind == 'forwarded-tcpip': + chan.origin_addr = (origin_addr, origin_port) + self._tcp_handler(chan, (origin_addr, origin_port), (server_addr, server_port)) + else: + self._queue_incoming_channel(chan) + + def _parse_debug(self, m): + always_display = m.get_boolean() + msg = m.get_string() + lang = m.get_string() + self._log(DEBUG, 'Debug msg: ' + util.safe_string(msg)) + + def _get_subsystem_handler(self, name): + try: + self.lock.acquire() + if name not in self.subsystem_table: + return (None, [], {}) + return self.subsystem_table[name] + finally: + self.lock.release() + + _handler_table = { + MSG_NEWKEYS: _parse_newkeys, + MSG_GLOBAL_REQUEST: _parse_global_request, + MSG_REQUEST_SUCCESS: _parse_request_success, + MSG_REQUEST_FAILURE: _parse_request_failure, + MSG_CHANNEL_OPEN_SUCCESS: _parse_channel_open_success, + MSG_CHANNEL_OPEN_FAILURE: _parse_channel_open_failure, + MSG_CHANNEL_OPEN: _parse_channel_open, + MSG_KEXINIT: _negotiate_keys, + } + + _channel_handler_table = { + MSG_CHANNEL_SUCCESS: Channel._request_success, + MSG_CHANNEL_FAILURE: Channel._request_failed, + MSG_CHANNEL_DATA: Channel._feed, + MSG_CHANNEL_EXTENDED_DATA: Channel._feed_extended, + MSG_CHANNEL_WINDOW_ADJUST: Channel._window_adjust, + MSG_CHANNEL_REQUEST: Channel._handle_request, + MSG_CHANNEL_EOF: Channel._handle_eof, + MSG_CHANNEL_CLOSE: Channel._handle_close, + } diff --git a/lib/paramiko/util.py b/lib/paramiko/util.py new file mode 100644 index 0000000..0d6a534 --- /dev/null +++ b/lib/paramiko/util.py @@ -0,0 +1,302 @@ +# Copyright (C) 2003-2007 Robey Pointer +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Useful functions used by the rest of paramiko. +""" + +from __future__ import generators + +import array +from binascii import hexlify, unhexlify +import sys +import struct +import traceback +import threading + +from paramiko.common import * +from paramiko.config import SSHConfig + + +# Change by RogerB - python < 2.3 doesn't have enumerate so we implement it +if sys.version_info < (2,3): + class enumerate: + def __init__ (self, sequence): + self.sequence = sequence + def __iter__ (self): + count = 0 + for item in self.sequence: + yield (count, item) + count += 1 + + +def inflate_long(s, always_positive=False): + "turns a normalized byte string into a long-int (adapted from Crypto.Util.number)" + out = 0L + negative = 0 + if not always_positive and (len(s) > 0) and (ord(s[0]) >= 0x80): + negative = 1 + if len(s) % 4: + filler = '\x00' + if negative: + filler = '\xff' + s = filler * (4 - len(s) % 4) + s + for i in range(0, len(s), 4): + out = (out << 32) + struct.unpack('>I', s[i:i+4])[0] + if negative: + out -= (1L << (8 * len(s))) + return out + +def deflate_long(n, add_sign_padding=True): + "turns a long-int into a normalized byte string (adapted from Crypto.Util.number)" + # after much testing, this algorithm was deemed to be the fastest + s = '' + n = long(n) + while (n != 0) and (n != -1): + s = struct.pack('>I', n & 0xffffffffL) + s + n = n >> 32 + # strip off leading zeros, FFs + for i in enumerate(s): + if (n == 0) and (i[1] != '\000'): + break + if (n == -1) and (i[1] != '\xff'): + break + else: + # degenerate case, n was either 0 or -1 + i = (0,) + if n == 0: + s = '\000' + else: + s = '\xff' + s = s[i[0]:] + if add_sign_padding: + if (n == 0) and (ord(s[0]) >= 0x80): + s = '\x00' + s + if (n == -1) and (ord(s[0]) < 0x80): + s = '\xff' + s + return s + +def format_binary_weird(data): + out = '' + for i in enumerate(data): + out += '%02X' % ord(i[1]) + if i[0] % 2: + out += ' ' + if i[0] % 16 == 15: + out += '\n' + return out + +def format_binary(data, prefix=''): + x = 0 + out = [] + while len(data) > x + 16: + out.append(format_binary_line(data[x:x+16])) + x += 16 + if x < len(data): + out.append(format_binary_line(data[x:])) + return [prefix + x for x in out] + +def format_binary_line(data): + left = ' '.join(['%02X' % ord(c) for c in data]) + right = ''.join([('.%c..' % c)[(ord(c)+63)//95] for c in data]) + return '%-50s %s' % (left, right) + +def hexify(s): + return hexlify(s).upper() + +def unhexify(s): + return unhexlify(s) + +def safe_string(s): + out = '' + for c in s: + if (ord(c) >= 32) and (ord(c) <= 127): + out += c + else: + out += '%%%02X' % ord(c) + return out + +# ''.join([['%%%02X' % ord(c), c][(ord(c) >= 32) and (ord(c) <= 127)] for c in s]) + +def bit_length(n): + norm = deflate_long(n, 0) + hbyte = ord(norm[0]) + if hbyte == 0: + return 1 + bitlen = len(norm) * 8 + while not (hbyte & 0x80): + hbyte <<= 1 + bitlen -= 1 + return bitlen + +def tb_strings(): + return ''.join(traceback.format_exception(*sys.exc_info())).split('\n') + +def generate_key_bytes(hashclass, salt, key, nbytes): + """ + Given a password, passphrase, or other human-source key, scramble it + through a secure hash into some keyworthy bytes. This specific algorithm + is used for encrypting/decrypting private key files. + + @param hashclass: class from L{Crypto.Hash} that can be used as a secure + hashing function (like C{MD5} or C{SHA}). + @type hashclass: L{Crypto.Hash} + @param salt: data to salt the hash with. + @type salt: string + @param key: human-entered password or passphrase. + @type key: string + @param nbytes: number of bytes to generate. + @type nbytes: int + @return: key data + @rtype: string + """ + keydata = '' + digest = '' + if len(salt) > 8: + salt = salt[:8] + while nbytes > 0: + hash_obj = hashclass.new() + if len(digest) > 0: + hash_obj.update(digest) + hash_obj.update(key) + hash_obj.update(salt) + digest = hash_obj.digest() + size = min(nbytes, len(digest)) + keydata += digest[:size] + nbytes -= size + return keydata + +def load_host_keys(filename): + """ + Read a file of known SSH host keys, in the format used by openssh, and + return a compound dict of C{hostname -> keytype ->} L{PKey }. + The hostname may be an IP address or DNS name. The keytype will be either + C{"ssh-rsa"} or C{"ssh-dss"}. + + This type of file unfortunately doesn't exist on Windows, but on posix, + it will usually be stored in C{os.path.expanduser("~/.ssh/known_hosts")}. + + Since 1.5.3, this is just a wrapper around L{HostKeys}. + + @param filename: name of the file to read host keys from + @type filename: str + @return: dict of host keys, indexed by hostname and then keytype + @rtype: dict(hostname, dict(keytype, L{PKey })) + """ + from paramiko.hostkeys import HostKeys + return HostKeys(filename) + +def parse_ssh_config(file_obj): + """ + Provided only as a backward-compatible wrapper around L{SSHConfig}. + """ + config = SSHConfig() + config.parse(file_obj) + return config + +def lookup_ssh_host_config(hostname, config): + """ + Provided only as a backward-compatible wrapper around L{SSHConfig}. + """ + return config.lookup(hostname) + +def mod_inverse(x, m): + # it's crazy how small python can make this function. + u1, u2, u3 = 1, 0, m + v1, v2, v3 = 0, 1, x + + while v3 > 0: + q = u3 // v3 + u1, v1 = v1, u1 - v1 * q + u2, v2 = v2, u2 - v2 * q + u3, v3 = v3, u3 - v3 * q + if u2 < 0: + u2 += m + return u2 + +_g_thread_ids = {} +_g_thread_counter = 0 +_g_thread_lock = threading.Lock() +def get_thread_id(): + global _g_thread_ids, _g_thread_counter, _g_thread_lock + tid = id(threading.currentThread()) + try: + return _g_thread_ids[tid] + except KeyError: + _g_thread_lock.acquire() + try: + _g_thread_counter += 1 + ret = _g_thread_ids[tid] = _g_thread_counter + finally: + _g_thread_lock.release() + return ret + +def log_to_file(filename, level=DEBUG): + "send paramiko logs to a logfile, if they're not already going somewhere" + l = logging.getLogger("paramiko") + if len(l.handlers) > 0: + return + l.setLevel(level) + f = open(filename, 'w') + lh = logging.StreamHandler(f) + lh.setFormatter(logging.Formatter('%(levelname)-.3s [%(asctime)s.%(msecs)03d] thr=%(_threadid)-3d %(name)s: %(message)s', + '%Y%m%d-%H:%M:%S')) + l.addHandler(lh) + +# make only one filter object, so it doesn't get applied more than once +class PFilter (object): + def filter(self, record): + record._threadid = get_thread_id() + return True +_pfilter = PFilter() + +def get_logger(name): + l = logging.getLogger(name) + l.addFilter(_pfilter) + return l + + +class Counter (object): + """Stateful counter for CTR mode crypto""" + def __init__(self, nbits, initial_value=1L, overflow=0L): + self.blocksize = nbits / 8 + self.overflow = overflow + # start with value - 1 so we don't have to store intermediate values when counting + # could the iv be 0? + if initial_value == 0: + self.value = array.array('c', '\xFF' * self.blocksize) + else: + x = deflate_long(initial_value - 1, add_sign_padding=False) + self.value = array.array('c', '\x00' * (self.blocksize - len(x)) + x) + + def __call__(self): + """Increament the counter and return the new value""" + i = self.blocksize - 1 + while i > -1: + c = self.value[i] = chr((ord(self.value[i]) + 1) % 256) + if c != '\x00': + return self.value.tostring() + i -= 1 + # counter reset + x = deflate_long(self.overflow, add_sign_padding=False) + self.value = array.array('c', '\x00' * (self.blocksize - len(x)) + x) + return self.value.tostring() + + def new(cls, nbits, initial_value=1L, overflow=0L): + return cls(nbits, initial_value=initial_value, overflow=overflow) + new = classmethod(new) diff --git a/lib/paramiko/win_pageant.py b/lib/paramiko/win_pageant.py new file mode 100644 index 0000000..787032b --- /dev/null +++ b/lib/paramiko/win_pageant.py @@ -0,0 +1,148 @@ +# Copyright (C) 2005 John Arbash-Meinel +# Modified up by: Todd Whiteman +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +Functions for communicating with Pageant, the basic windows ssh agent program. +""" + +import os +import struct +import tempfile +import mmap +import array + +# if you're on windows, you should have one of these, i guess? +# ctypes is part of standard library since Python 2.5 +_has_win32all = False +_has_ctypes = False +try: + # win32gui is preferred over win32ui to avoid MFC dependencies + import win32gui + _has_win32all = True +except ImportError: + try: + import ctypes + _has_ctypes = True + except ImportError: + pass + + +_AGENT_COPYDATA_ID = 0x804e50ba +_AGENT_MAX_MSGLEN = 8192 +# Note: The WM_COPYDATA value is pulled from win32con, as a workaround +# so we do not have to import this huge library just for this one variable. +win32con_WM_COPYDATA = 74 + + +def _get_pageant_window_object(): + if _has_win32all: + try: + hwnd = win32gui.FindWindow('Pageant', 'Pageant') + return hwnd + except win32gui.error: + pass + elif _has_ctypes: + # Return 0 if there is no Pageant window. + return ctypes.windll.user32.FindWindowA('Pageant', 'Pageant') + return None + + +def can_talk_to_agent(): + """ + Check to see if there is a "Pageant" agent we can talk to. + + This checks both if we have the required libraries (win32all or ctypes) + and if there is a Pageant currently running. + """ + if (_has_win32all or _has_ctypes) and _get_pageant_window_object(): + return True + return False + + +def _query_pageant(msg): + hwnd = _get_pageant_window_object() + if not hwnd: + # Raise a failure to connect exception, pageant isn't running anymore! + return None + + # Write our pageant request string into the file (pageant will read this to determine what to do) + filename = tempfile.mktemp('.pag') + map_filename = os.path.basename(filename) + + f = open(filename, 'w+b') + f.write(msg ) + # Ensure the rest of the file is empty, otherwise pageant will read this + f.write('\0' * (_AGENT_MAX_MSGLEN - len(msg))) + # Create the shared file map that pageant will use to read from + pymap = mmap.mmap(f.fileno(), _AGENT_MAX_MSGLEN, tagname=map_filename, access=mmap.ACCESS_WRITE) + try: + # Create an array buffer containing the mapped filename + char_buffer = array.array("c", map_filename + '\0') + char_buffer_address, char_buffer_size = char_buffer.buffer_info() + # Create a string to use for the SendMessage function call + cds = struct.pack("LLP", _AGENT_COPYDATA_ID, char_buffer_size, char_buffer_address) + + if _has_win32all: + # win32gui.SendMessage should also allow the same pattern as + # ctypes, but let's keep it like this for now... + response = win32gui.SendMessage(hwnd, win32con_WM_COPYDATA, len(cds), cds) + elif _has_ctypes: + _buf = array.array('B', cds) + _addr, _size = _buf.buffer_info() + response = ctypes.windll.user32.SendMessageA(hwnd, win32con_WM_COPYDATA, _size, _addr) + else: + response = 0 + + if response > 0: + datalen = pymap.read(4) + retlen = struct.unpack('>I', datalen)[0] + return datalen + pymap.read(retlen) + return None + finally: + pymap.close() + f.close() + # Remove the file, it was temporary only + os.unlink(filename) + + +class PageantConnection (object): + """ + Mock "connection" to an agent which roughly approximates the behavior of + a unix local-domain socket (as used by Agent). Requests are sent to the + pageant daemon via special Windows magick, and responses are buffered back + for subsequent reads. + """ + + def __init__(self): + self._response = None + + def send(self, data): + self._response = _query_pageant(data) + + def recv(self, n): + if self._response is None: + return '' + ret = self._response[:n] + self._response = self._response[n:] + if self._response == '': + self._response = None + return ret + + def close(self): + pass From 77e169706889bbf4614cf08f20ec3a362a8c892d Mon Sep 17 00:00:00 2001 From: Paul Scarrone Date: Mon, 28 Jan 2013 19:11:36 -0500 Subject: [PATCH 02/12] bug that the join does not add a root / if the request was for a / at one above root --- Mote.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Mote.py b/Mote.py index 64febbf..db50521 100644 --- a/Mote.py +++ b/Mote.py @@ -367,8 +367,12 @@ def show_quick_panel(): def cleanlsposix(self, fullpath, file_list): paths = {} paths['..'] = {} - paths['..']['path'] = '/'.join(self.sftp.getcwd().split('/')[0:-1]) + tpath = '/'.join(self.sftp.getcwd().split('/')[0:-1]) + if tpath == "": + tpath = "/" + paths['..']['path'] = tpath paths['..']['type'] = 'folder' + print repr(paths['..']) for path, attr in file_list.items(): dflag = oct(attr.st_mode) named_path = cleanpath(fullpath, path) @@ -461,4 +465,4 @@ def untilprompt(proc, strinput = None): break return buff -MOTES = main() +MOTES = main() \ No newline at end of file From ae490d12fb819661494761a3c59034de425caed5 Mon Sep 17 00:00:00 2001 From: Paul Scarrone Date: Wed, 30 Jan 2013 00:26:20 -0500 Subject: [PATCH 03/12] Update Readme for Installation on Linux and OSX (This is a first run and needs to have a couple more documented installations so we can be accurate) --- README.md | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 78cf170..6c1734e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ Sublime Text plugin to browse and edit files over sftp/ssh2 # Installation -1. Download this package and save and extract to your packages folder. +## Windows + +1. Download this package, save, and extract to your sublime text packages folder. 2. Download and install PuTTY, preferably the whole package. @@ -24,6 +26,41 @@ Sublime Text plugin to browse and edit files over sftp/ssh2 - Copy `psftp.exe` to `Mote\` +##OSX + +1. Download this package, save, and extract to your sublime text packages folder. + + - From the 'Sublime Text 2' Menu Select Preferences -> Browse Packages... + + - Copy the Mote directoy to this folder + +2. Close and Reopen Sublime Text 2 + +If Connections do not work make sure you have connected to the server once from the command line using: + ssh @ so you can accept the hostkey making it available to the plugin + +##Linux + +1. Download this package, save, and extract to your sublime text packages folder. + + - From the 'Sublime Text 2' Menu Select Preferences -> Browse Packages... + + - Copy the Mote directoy to this folder + +2. Install python-dev so you can compile pycrypto + - Download PyCrypto from https://github.com/dlitz/pycrypto + + - chmod +x setup.py + + - run ./setup.py build + + - copy the Crypto folder from build->lib.- to the Mote Lib Folder overwriting the Crypto directory there + +3. Close and Reopen Sublime Text 2 + +If Connections do not work make sure you have connected to the server once from the command line using: + ssh @ so you can accept the hostkey making it available to the plugin + #Usage ## Add Servers @@ -102,3 +139,9 @@ Then - Browse around. The file list populates as you delve deeper into the file tree. - Click on a file to download to a temp folder and open it - Any saves on that file will automatically upload it. + +#ToDos + +1. Allow for port configuration with paramiko for linux osx +2. Automate build for Crypto on install +3. If hostkey doesnot always exist direct user to ssh to location once before trying to connect From ae3d93e26276759d7a95177b61aa0185ab660227 Mon Sep 17 00:00:00 2001 From: Paul Scarrone Date: Wed, 30 Jan 2013 00:26:39 -0500 Subject: [PATCH 04/12] Removed debugging statements that blow up connection if not in connection 2 format --- Mote.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Mote.py b/Mote.py index db50521..e56812e 100644 --- a/Mote.py +++ b/Mote.py @@ -148,13 +148,6 @@ def __init__(self, server, search_path='', connection_string='', password=None, else: self.hostname = connection_string - #lovely debugging - print "Hostname: " + self.hostname - print "Username: " + self.username - print self.connection_string - - - if ('-pw' not in connection_string) and password: self.connection_string = [r'-pw', password, connection_string] else: From 4bb3b2a9292c7936a280b245de9f814f937783e7 Mon Sep 17 00:00:00 2001 From: Paul Scarrone Date: Wed, 30 Jan 2013 00:27:58 -0500 Subject: [PATCH 05/12] Formatting cleanup of docs --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6c1734e..3ec432f 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,9 @@ Sublime Text plugin to browse and edit files over sftp/ssh2 1. Download this package, save, and extract to your sublime text packages folder. - - From the 'Sublime Text 2' Menu Select Preferences -> Browse Packages... + - From the 'Sublime Text 2' Menu Select Preferences -> Browse Packages... - - Copy the Mote directoy to this folder + - Copy the Mote directoy to this folder 2. Close and Reopen Sublime Text 2 @@ -43,18 +43,19 @@ If Connections do not work make sure you have connected to the server once from 1. Download this package, save, and extract to your sublime text packages folder. - - From the 'Sublime Text 2' Menu Select Preferences -> Browse Packages... + - From the 'Sublime Text 2' Menu Select Preferences -> Browse Packages... - - Copy the Mote directoy to this folder + - Copy the Mote directoy to this folder 2. Install python-dev so you can compile pycrypto - - Download PyCrypto from https://github.com/dlitz/pycrypto + + - Download PyCrypto from https://github.com/dlitz/pycrypto - - chmod +x setup.py + - chmod +x setup.py - - run ./setup.py build + - run ./setup.py build - - copy the Crypto folder from build->lib.- to the Mote Lib Folder overwriting the Crypto directory there + - copy the Crypto folder from build->lib.- to the Mote Lib Folder overwriting the Crypto directory there 3. Close and Reopen Sublime Text 2 From 49672fcd7ce13e58d41f39a5ea852e37031b2ab0 Mon Sep 17 00:00:00 2001 From: Paul Scarrone Date: Wed, 30 Jan 2013 19:59:42 -0500 Subject: [PATCH 06/12] Follows Sym Links now. Works with editing and domain traversal --- Mote.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Mote.py b/Mote.py index e56812e..fe7a800 100644 --- a/Mote.py +++ b/Mote.py @@ -365,12 +365,13 @@ def cleanlsposix(self, fullpath, file_list): tpath = "/" paths['..']['path'] = tpath paths['..']['type'] = 'folder' - print repr(paths['..']) for path, attr in file_list.items(): dflag = oct(attr.st_mode) named_path = cleanpath(fullpath, path) if str(dflag[0:2]) == '04': path_key = named_path + '/..' + elif str(dflag[0:3]) == '012': + path_key = named_path + "->" + self.sftp.readlink(path) else: path_key = named_path + '-' @@ -378,6 +379,15 @@ def cleanlsposix(self, fullpath, file_list): paths[path_key]['path'] = named_path if str(dflag[0:2]) == '04': paths[path_key]['type'] = 'folder' + elif str(dflag[0:3]) == '012': + realpath = self.sftp.readlink(path) + substat = self.sftp.lstat(realpath) + subdflag = oct(substat.st_mode) + paths[path_key]['path'] = realpath + if str(subdflag[0:2]) == '04': + paths[path_key]['type'] = 'folder' + else: + paths[path_key]['type'] = 'file' else: paths[path_key]['type'] = 'file' return paths From 8fce370505186fe41e9edd3749bba1cb0ae47be9 Mon Sep 17 00:00:00 2001 From: Paul Scarrone Date: Wed, 30 Jan 2013 20:01:43 -0500 Subject: [PATCH 07/12] Updated instructions with pointer to symlink branch for testing --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3ec432f..9b8aa26 100644 --- a/README.md +++ b/README.md @@ -146,3 +146,4 @@ Then 1. Allow for port configuration with paramiko for linux osx 2. Automate build for Crypto on install 3. If hostkey doesnot always exist direct user to ssh to location once before trying to connect +4. Add SymLink Support (Done in testing branch) From 7c0b164c66b43a682f26c0ca32ee0e861a0df741 Mon Sep 17 00:00:00 2001 From: Paul Scarrone Date: Fri, 1 Feb 2013 09:56:22 -0500 Subject: [PATCH 08/12] Added back port support and ftp connection through paramikos SSH client method --- Mote.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Mote.py b/Mote.py index e56812e..8ef50d3 100644 --- a/Mote.py +++ b/Mote.py @@ -139,6 +139,11 @@ def __init__(self, server, search_path='', connection_string='', password=None, self.connection_string = connection_string self.os_mode = os.name self.base_dir = '' + if not port == None: + self.port = port + else: + print "Port Not Defined Defaulting to 22" + self.port = 22 #Identify if this is a username@hostname string connection_string_parts = connection_string.split('@') @@ -163,6 +168,7 @@ def __init__(self, server, search_path='', connection_string='', password=None, self.results = {} self.transport = None + self.sshClient = None self.sftp = None self.results_lock = threading.Condition() @@ -197,9 +203,16 @@ def connect(self): hostkey = host_keys[self.hostname][hostkeytype] print 'Using host key of type %s' % hostkeytype try: - self.transport = t = paramiko.Transport((self.hostname, 22)) - t.connect(username=self.username, password=self.password, hostkey=hostkey) - self.sftp = paramiko.SFTPClient.from_transport(t) + self.sshClient = client = paramiko.SSHClient() + client.load_system_host_keys() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + client.connect(self.hostname, self.port, username=self.username, password=self.password) + #keyfile = paramiko.DSSKey.from_private_key_file(private_key) + #self.transport = t = paramiko.Transport((self.hostname, 22)) + #t.connect(username=self.username, password=self.password, hostkey=hostkey) + #self.sftp = paramiko.SFTPClient.from_transport(t) + self.transport = client.get_transport() + self.sftp = client.open_sftp() print "SFTP INIT: "+ str(self.transport.is_active()) self.add_command('cd',self.search_path, True) except Exception: From 9fd86bb48358d9d884048c7111e7dde3adc5db73 Mon Sep 17 00:00:00 2001 From: Paul Scarrone Date: Fri, 1 Feb 2013 12:16:45 -0500 Subject: [PATCH 09/12] Version where ssh is used to create transport and nothing else --- Mote.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Mote.py b/Mote.py index 07a590f..05c3991 100644 --- a/Mote.py +++ b/Mote.py @@ -211,12 +211,14 @@ def connect(self): #self.transport = t = paramiko.Transport((self.hostname, 22)) #t.connect(username=self.username, password=self.password, hostkey=hostkey) #self.sftp = paramiko.SFTPClient.from_transport(t) - self.transport = client.get_transport() - self.sftp = client.open_sftp() + self.transport = t = client.get_transport() + self.sftp = paramiko.SFTPClient.from_transport(t) + #self.sftp = client.open_sftp() print "SFTP INIT: "+ str(self.transport.is_active()) self.add_command('cd',self.search_path, True) - except Exception: - print "transport Failed" + except Exception as e: + sublime.set_timeout(lambda:sublime.status_message('Server Config Error Check that you have valid credentils in servers.json'),0) + print "transport Failed ", e return self From 5fbdb64781321006f0176a2b38d88dec8c1da847 Mon Sep 17 00:00:00 2001 From: Paul Scarrone Date: Fri, 1 Feb 2013 12:23:33 -0500 Subject: [PATCH 10/12] Updated changlog --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b8aa26..621e1d1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +# Changes + + -Feb 01 Started working on sudo permissions for reading and moving to files + -Feb 01 Moved SFTP setup to use parmiko ssh to allow auto adding hostkeys + -Feb 01 Added symlink support + -Jan 31 Updated install instructions and began debugging linux installs + -Jan 23 Added Paramiko for OSX and Linux Support + # Info Sublime Text plugin to browse and edit files over sftp/ssh2 @@ -146,4 +154,3 @@ Then 1. Allow for port configuration with paramiko for linux osx 2. Automate build for Crypto on install 3. If hostkey doesnot always exist direct user to ssh to location once before trying to connect -4. Add SymLink Support (Done in testing branch) From 05b99203b8d2468e26f7834c0a78a9d87d0c916e Mon Sep 17 00:00:00 2001 From: Paul Scarrone Date: Sat, 2 Feb 2013 14:00:24 -0500 Subject: [PATCH 11/12] Added error catch for IOError on Ls for general errors and IO Errors --- Mote.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Mote.py b/Mote.py index 05c3991..0ca29c9 100644 --- a/Mote.py +++ b/Mote.py @@ -329,10 +329,14 @@ def ls(self, search_path = ''): self.results.update(results) else: file_list = {} - file_list = dict(zip(self.sftp.listdir(self.sftp.getcwd()), self.sftp.listdir_attr(self.sftp.getcwd()))) - #results = self.cleanls(fullpath, results) - results = self.cleanlsposix(fullpath, file_list) - self.results = results + try: + file_list = dict(zip(self.sftp.listdir(self.sftp.getcwd()), self.sftp.listdir_attr(self.sftp.getcwd()))) + results = self.cleanlsposix(fullpath, file_list) + self.results = results + except IOError as io: + sublime.set_timeout(lambda:sublime.status_message('IO Error %s' % io),10) + except Exception as e: + sublime.set_timeout(lambda:sublime.status_message('Generic Error processing %s' % fullpath),10) if self.idle_recursive: subfolders = dict((k,v) for k,v in results.items() if v['type'] == 'folder') From 9421e2be5cc72ee0cfd90c9ffb1f157b691b10cf Mon Sep 17 00:00:00 2001 From: Paul Scarrone Date: Sat, 2 Feb 2013 14:16:33 -0500 Subject: [PATCH 12/12] Added Error catching for chdir get and put --- Mote.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Mote.py b/Mote.py index 0ca29c9..da34e44 100644 --- a/Mote.py +++ b/Mote.py @@ -270,9 +270,13 @@ def cd(self, path, show_panel): else: print "changing dir: " + path print self.sftp.getcwd() - self.sftp.chdir(path) - self.search_path = self.sftp.getcwd() - self.add_command('ls',path, show_panel) + try: + self.sftp.chdir(path) + self.search_path = self.sftp.getcwd() + print self.sftp.getcwd() + self.add_command('ls',path, show_panel) + except Exception as e: + sublime.set_timeout(lambda:sublime.status_message('Generic Error processing %s' % path),10) def run(self): @@ -334,7 +338,7 @@ def ls(self, search_path = ''): results = self.cleanlsposix(fullpath, file_list) self.results = results except IOError as io: - sublime.set_timeout(lambda:sublime.status_message('IO Error %s' % io),10) + sublime.set_timeout(lambda:sublime.status_message('IO Error %s when listing -> %s' % (io,fullpath)),10) except Exception as e: sublime.set_timeout(lambda:sublime.status_message('Generic Error processing %s' % fullpath),10) @@ -355,8 +359,10 @@ def download(self, path): if not self.is_os_mode('posix'): self.sftp.send('get "%s" "%s"' % (path,localpath) ) else: - print "preget: "+ path+" "+localpath - self.sftp.get(path, localpath) + try: + self.sftp.get(path, localpath) + except Exception as e: + sublime.set_timeout(lambda:sublime.status_message('Generic Error processing %s' % path),10) sublime.set_timeout(lambda:self.window.open_file(localpath), 0) @@ -368,7 +374,10 @@ def upload(self, path): if not self.is_os_mode('posix'): self.sftp.send('put "%s" "%s"' % (localpath,path) ) else: - self.sftp.put(localpath, '/'+path) + try: + self.sftp.put(localpath, '/'+path) + except Exception as e: + sublime.set_timeout(lambda:sublime.status_message('Generic Error processing %s' % path),10) def showfilepanel(self): self.keys = sorted(self.results.keys())