diff options
Diffstat (limited to 'src/pycryptopp/test')
-rw-r--r-- | src/pycryptopp/test/__init__.py | 0 | ||||
-rw-r--r-- | src/pycryptopp/test/test_aes.py | 114 | ||||
-rw-r--r-- | src/pycryptopp/test/test_ecdsa.py | 263 | ||||
-rw-r--r-- | src/pycryptopp/test/test_ed25519.py | 148 | ||||
-rw-r--r-- | src/pycryptopp/test/test_ed25519_kat.py | 47 | ||||
-rw-r--r-- | src/pycryptopp/test/test_from_Nikratio.py | 32 | ||||
-rw-r--r-- | src/pycryptopp/test/test_rsa.py | 137 | ||||
-rw-r--r-- | src/pycryptopp/test/test_sha256.py | 181 | ||||
-rw-r--r-- | src/pycryptopp/test/test_xsalsa20.py | 136 |
9 files changed, 1058 insertions, 0 deletions
diff --git a/src/pycryptopp/test/__init__.py b/src/pycryptopp/test/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/pycryptopp/test/__init__.py diff --git a/src/pycryptopp/test/test_aes.py b/src/pycryptopp/test/test_aes.py new file mode 100644 index 0000000..16c3e20 --- /dev/null +++ b/src/pycryptopp/test/test_aes.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python + +import random, re + +import unittest + +from binascii import a2b_hex, b2a_hex + +global VERBOSE +VERBOSE=False + +from pycryptopp.cipher import aes + +from pkg_resources import resource_string, resource_listdir + +from base64 import b32encode +def ab(x): # debuggery + if len(x) >= 3: + return "%s:%s" % (len(x), b32encode(x[-3:]),) + elif len(x) == 2: + return "%s:%s" % (len(x), b32encode(x[-2:]),) + elif len(x) == 1: + return "%s:%s" % (len(x), b32encode(x[-1:]),) + elif len(x) == 0: + return "%s:%s" % (len(x), "--empty--",) + +def randstr(n): + return ''.join(map(chr, map(random.randrange, [0]*n, [256]*n))) + +class AES256(unittest.TestCase): + enc0 = "dc95c078a2408989ad48a21492842087530f8afbc74536b9a963b4f1c4cb738b" + + def test_encrypt_zeroes(self): + cryptor = aes.AES(key="\x00"*32) + ct = cryptor.process("\x00"*32) + self.failUnlessEqual(self.enc0, b2a_hex(ct)) + + def test_init_type_check(self): + self.failUnlessRaises(TypeError, aes.AES, None) + self.failUnlessRaises(aes.Error, aes.AES, "a"*1) # too short + self.failUnlessRaises(aes.Error, aes.AES, "a"*17) # not one of the valid key sizes for AES (16, 24, 32) + + def test_encrypt_zeroes_in_two_parts(self): + cryptor = aes.AES(key="\x00"*32) + ct1 = cryptor.process("\x00"*15) + ct2 = cryptor.process("\x00"*17) + self.failUnlessEqual(self.enc0, b2a_hex(ct1+ct2)) + +class AES128(unittest.TestCase): + enc0 = "66e94bd4ef8a2c3b884cfa59ca342b2e" + + def test_encrypt_zeroes(self): + cryptor = aes.AES(key="\x00"*16) + ct = cryptor.process("\x00"*16) + self.failUnlessEqual(self.enc0, b2a_hex(ct)) + + def test_init_type_check(self): + self.failUnlessRaises(TypeError, aes.AES, None) + self.failUnlessRaises(aes.Error, aes.AES, "a") # too short + + def test_encrypt_zeroes_in_two_parts(self): + cryptor = aes.AES(key="\x00"*16) + ct1 = cryptor.process("\x00"*8) + ct2 = cryptor.process("\x00"*8) + self.failUnlessEqual(self.enc0, b2a_hex(ct1+ct2)) + +def fake_ecb_using_ctr(k, p): + return aes.AES(key=k, iv=p).process('\x00'*16) + +NIST_KAT_VECTS_RE=re.compile("\nCOUNT = ([0-9]+)\nKEY = ([0-9a-f]+)\nPLAINTEXT = ([0-9a-f]+)\nCIPHERTEXT = ([0-9a-f]+)") + +class AES_from_NIST_KAT(unittest.TestCase): + def test_NIST_KAT(self): + for vectname in resource_listdir('pycryptopp', 'testvectors/KAT_AES'): + self._test_KAT_file(resource_string('pycryptopp', '/'.join(['testvectors/KAT_AES', vectname]))) + + def _test_KAT_file(self, vects_str): + for mo in NIST_KAT_VECTS_RE.finditer(vects_str): + key = a2b_hex(mo.group(2)) + plaintext = a2b_hex(mo.group(3)) + ciphertext = a2b_hex(mo.group(4)) + + computedciphertext = fake_ecb_using_ctr(key, plaintext) + self.failUnlessEqual(computedciphertext, ciphertext, "computedciphertext: %s, ciphertext: %s, key: %s, plaintext: %s" % (b2a_hex(computedciphertext), b2a_hex(ciphertext), b2a_hex(key), b2a_hex(plaintext))) + +class AES_from_Niels_Ferguson(unittest.TestCase): + # http://blogs.msdn.com/si_team/archive/2006/05/19/aes-test-vectors.aspx + def _test_from_Niels_AES(self, keysize, result): + E = fake_ecb_using_ctr + b = 16 + k = keysize + S = '\x00' * (k+b) + for i in range(1000): + K = S[-k:] + P = S[-k-b:-k] + S += E(K, E(K, P)) + + self.failUnlessEqual(S[-b:], a2b_hex(result)) + + def test_from_Niels_AES128(self): + return self._test_from_Niels_AES(16, 'bd883f01035e58f42f9d812f2dacbcd8') + + def test_from_Niels_AES256(self): + return self._test_from_Niels_AES(32, 'c84b0f3a2c76dd9871900b07f09bdd3e') + +class PartialIV(unittest.TestCase): + def test_partial(self): + k = "k"*16 + for iv_len in range(0, 16)+range(17,70): # all are wrong, 16 is right + self.failUnlessRaises(aes.Error, + aes.AES, k, iv="i"*iv_len) + +if __name__ == "__main__": + unittest.main() diff --git a/src/pycryptopp/test/test_ecdsa.py b/src/pycryptopp/test/test_ecdsa.py new file mode 100644 index 0000000..d70a000 --- /dev/null +++ b/src/pycryptopp/test/test_ecdsa.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python + +import random +import base64 + +import os +SEED = os.environ.get('REPEATABLE_RANDOMNESS_SEED', None) + +if SEED is None: + # Generate a seed which is fairly short (to ease cut-and-paste, writing it + # down, etc.). Note that Python's random module's seed() function is going + # to take the hash() of this seed, which is a 32-bit value (currently) so + # there is no point in making this seed larger than 32 bits. Make it 30 + # bits, which conveniently fits into six base-32 chars. Include a separator + # because chunking facilitates memory (including working and short-term + # memory) in humans. + chars = "ybndrfg8ejkmcpqxot1uwisza345h769" # Zooko's choice, rationale in "DESIGN" doc in z-base-32 project + SEED = ''.join([random.choice(chars) for x in range(3)] + ['-'] + [random.choice(chars) for x in range(3)]) + +import logging +logging.info("REPEATABLE_RANDOMNESS_SEED: %s\n" % SEED) +logging.info("In order to reproduce this run of the code, set the environment variable \"REPEATABLE_RANDOMNESS_SEED\" to %s before executing.\n" % SEED) +random.seed(SEED) + +def seed_which_refuses(a): + logging.warn("I refuse to reseed to %s -- I already seeded with %s.\n" % (a, SEED,)) + return +random.seed = seed_which_refuses + +from random import randrange + +import unittest + +from pycryptopp.publickey import ecdsa + +def randstr(n, rr=randrange): + return ''.join([chr(rr(0, 256)) for x in xrange(n)]) + +from base64 import b32encode +def ab(x): # debuggery + if len(x) >= 3: + return "%s:%s" % (len(x), b32encode(x[-3:]),) + elif len(x) == 2: + return "%s:%s" % (len(x), b32encode(x[-2:]),) + elif len(x) == 1: + return "%s:%s" % (len(x), b32encode(x[-1:]),) + elif len(x) == 0: + return "%s:%s" % (len(x), "--empty--",) + +def div_ceil(n, d): + """ + The smallest integer k such that k*d >= n. + """ + return (n/d) + (n%d != 0) + +KEYBITS=192 + +# The number of bytes required for a seed to have the same security level as a +# key in this elliptic curve: 2 bits of public key per bit of security. +SEEDBITS=div_ceil(192, 2) +SEEDBYTES=div_ceil(SEEDBITS, 8) + +# The number of bytes required to encode a public key in this elliptic curve. +PUBKEYBYTES=div_ceil(KEYBITS, 8)+1 # 1 byte for the sign of the y component + +# The number of bytes requires to encode a signature in this elliptic curve. +SIGBITS=KEYBITS*2 +SIGBYTES=div_ceil(SIGBITS, 8) + +class Signer(unittest.TestCase): + def test_construct(self): + seed = randstr(SEEDBYTES) + ecdsa.SigningKey(seed) + + def test_sign(self): + seed = randstr(SEEDBYTES) + signer = ecdsa.SigningKey(seed) + sig = signer.sign("message") + self.failUnlessEqual(len(sig), SIGBYTES) + + def test_sign_and_verify(self): + seed = randstr(SEEDBYTES) + signer = ecdsa.SigningKey(seed) + sig = signer.sign("message") + v = signer.get_verifying_key() + self.failUnless(v.verify("message", sig)) + + def test_sign_and_verify_emptymsg(self): + seed = randstr(SEEDBYTES) + signer = ecdsa.SigningKey(seed) + sig = signer.sign("") + v = signer.get_verifying_key() + self.failUnless(v.verify("", sig)) + + def test_construct_from_same_seed_is_reproducible(self): + seed = randstr(SEEDBYTES) + signer1 = ecdsa.SigningKey(seed) + signer2 = ecdsa.SigningKey(seed) + self.failUnlessEqual(signer1.get_verifying_key().serialize(), signer2.get_verifying_key().serialize()) + + # ... and using different seeds constructs a different private key. + seed3 = randstr(SEEDBYTES) + assert seed3 != seed, "Internal error in Python random module's PRNG (or in pycryptopp's hacks to it to facilitate testing) -- got two identical strings from randstr(%s)" % SEEDBYTES + signer3 = ecdsa.SigningKey(seed3) + self.failIfEqual(signer1.get_verifying_key().serialize(), signer3.get_verifying_key().serialize()) + + # Also try the all-zeroes string just because bugs sometimes are + # data-dependent on zero or cause bogus zeroes. + seed4 = '\x00'*SEEDBYTES + assert seed4 != seed, "Internal error in Python random module's PRNG (or in pycryptopp's hacks to it to facilitate testing) -- got the all-zeroes string from randstr(%s)" % SEEDBYTES + signer4 = ecdsa.SigningKey(seed4) + self.failIfEqual(signer4.get_verifying_key().serialize(), signer1.get_verifying_key().serialize()) + + signer5 = ecdsa.SigningKey(seed4) + self.failUnlessEqual(signer5.get_verifying_key().serialize(), signer4.get_verifying_key().serialize()) + + def test_construct_short_seed(self): + try: + ecdsa.SigningKey("\x00\x00\x00") + except ecdsa.Error, le: + self.failUnless("seed is required to be of length " in str(le), le) + else: + self.fail("Should have raised error from seed being too short.") + + def test_construct_bad_arg_type(self): + try: + ecdsa.SigningKey(1) + except TypeError, le: + self.failUnless("must be string" in str(le), le) + else: + self.fail("Should have raised error from seed being of the wrong type.") + +class Verifier(unittest.TestCase): + def test_from_signer_and_serialize_and_deserialize(self): + seed = randstr(SEEDBYTES) + signer = ecdsa.SigningKey(seed) + + verifier = signer.get_verifying_key() + s1 = verifier.serialize() + self.failUnlessEqual(len(s1), PUBKEYBYTES) + ecdsa.VerifyingKey(s1) + s2 = verifier.serialize() + self.failUnlessEqual(s1, s2) + +def flip_one_bit(s): + assert s + i = randrange(0, len(s)) + result = s[:i] + chr(ord(s[i])^(0x01<<randrange(0, 8))) + s[i+1:] + assert result != s, "Internal error -- flip_one_bit() produced the same string as its input: %s == %s" % (result, s) + return result + +def randmsg(): + # Choose a random message size from a range probably large enough to + # exercise any different code paths which depend on the message length. + randmsglen = randrange(1, SIGBYTES*2+2) + return randstr(randmsglen) + +class SignAndVerify(unittest.TestCase): + def _help_test_sign_and_check_good_keys(self, signer, verifier): + msg = randmsg() + + sig = signer.sign(msg) + self.failUnlessEqual(len(sig), SIGBYTES) + self.failUnless(verifier.verify(msg, sig)) + + # Now flip one bit of the signature and make sure that the signature doesn't check. + badsig = flip_one_bit(sig) + self.failIf(verifier.verify(msg, badsig)) + + # Now generate a random signature and make sure that the signature doesn't check. + badsig = randstr(len(sig)) + assert badsig != sig, "Internal error -- randstr() produced the same string twice: %s == %s" % (badsig, sig) + self.failIf(verifier.verify(msg, badsig)) + + # Now flip one bit of the message and make sure that the original signature doesn't check. + badmsg = flip_one_bit(msg) + self.failIf(verifier.verify(badmsg, sig)) + + # Now generate a random message and make sure that the original signature doesn't check. + badmsg = randstr(len(msg)) + assert badmsg != msg, "Internal error -- randstr() produced the same string twice: %s == %s" % (badmsg, msg) + self.failIf(verifier.verify(badmsg, sig)) + + def _help_test_sign_and_check_bad_keys(self, signer, verifier): + """ + Make sure that this signer/verifier pair cannot produce and verify signatures. + """ + msg = randmsg() + + sig = signer.sign(msg) + self.failUnlessEqual(len(sig), SIGBYTES) + self.failIf(verifier.verify(msg, sig)) + + def test(self): + seed = randstr(SEEDBYTES) + signer = ecdsa.SigningKey(seed) + verifier = signer.get_verifying_key() + self._help_test_sign_and_check_good_keys(signer, verifier) + + vstr = verifier.serialize() + self.failUnlessEqual(len(vstr), PUBKEYBYTES) + verifier2 = ecdsa.VerifyingKey(vstr) + self._help_test_sign_and_check_good_keys(signer, verifier2) + + signer2 = ecdsa.SigningKey(seed) + self._help_test_sign_and_check_good_keys(signer2, verifier2) + + verifier3 = signer2.get_verifying_key() + self._help_test_sign_and_check_good_keys(signer, verifier3) + + # Now test various ways that the keys could be corrupted or ill-matched. + + # Flip one bit of the public key. + badvstr = flip_one_bit(vstr) + try: + badverifier = ecdsa.VerifyingKey(badvstr) + except ecdsa.Error: + # Ok, fine, the verifying key was corrupted and Crypto++ detected this fact. + pass + else: + self._help_test_sign_and_check_bad_keys(signer, badverifier) + + # Randomize all bits of the public key. + badvstr = randstr(len(vstr)) + assert badvstr != vstr, "Internal error -- randstr() produced the same string twice: %s == %s" % (badvstr, vstr) + try: + badverifier = ecdsa.VerifyingKey(badvstr) + except ecdsa.Error: + # Ok, fine, the key was corrupted and Crypto++ detected this fact. + pass + else: + self._help_test_sign_and_check_bad_keys(signer, badverifier) + + # Flip one bit of the private key. + badseed = flip_one_bit(seed) + badsigner = ecdsa.SigningKey(badseed) + self._help_test_sign_and_check_bad_keys(badsigner, verifier) + + # Randomize all bits of the private key. + badseed = randstr(len(seed)) + assert badseed != seed, "Internal error -- randstr() produced the same string twice: %s == %s" % (badseed, seed) + badsigner = ecdsa.SigningKey(badseed) + self._help_test_sign_and_check_bad_keys(badsigner, verifier) + +class Compatibility(unittest.TestCase): + def test_compatibility(self): + # Confirm that the KDF used by the SigningKey constructor doesn't + # change without suitable backwards-compability + seed = base64.b32decode('XS27TJRP3JBZKDEFBDKQ====') + signer = ecdsa.SigningKey(seed) + v1 = signer.get_verifying_key() + vs = v1.serialize() + vs32 = base64.b32encode(vs) + self.failUnlessEqual(vs32, "ANPNDWJWHQXYSQMD4L36D7WQEGXA42MS5JRUFIWA") + v2 = ecdsa.VerifyingKey(vs) + #print base64.b32encode(signer.sign("message")) + sig32 = "EA3Y7A4T62J3K6MUPJQN3WJ5S4SS53EGZXOSTQW7EQ7OXEMS6QJLYL63BLHMHZD7KFT37KEPJBAKI===" + sig = base64.b32decode(sig32) + self.failUnless(v1.verify("message", sig)) + self.failUnless(v2.verify("message", sig)) + +if __name__ == "__main__": + unittest.main() diff --git a/src/pycryptopp/test/test_ed25519.py b/src/pycryptopp/test/test_ed25519.py new file mode 100644 index 0000000..6e530ab --- /dev/null +++ b/src/pycryptopp/test/test_ed25519.py @@ -0,0 +1,148 @@ + +import unittest +import time +from binascii import hexlify, unhexlify +from pycryptopp.publickey import ed25519 +from pycryptopp.publickey.ed25519 import _ed25519 as raw + +def flip_bit(s, bit=0, in_byte=-1): + as_bytes = [ord(b) for b in s] + as_bytes[in_byte] = as_bytes[in_byte] ^ (0x01<<bit) + return "".join([chr(b) for b in as_bytes]) + +# the pure-python demonstration code (on my 2010 MacBookPro) takes 5s to +# generate a public key, 9s to sign, 14s to verify + +# the SUPERCOP-ref version we use takes 2ms for keygen, 2ms to sign, and 7ms +# to verify + +class Basic(unittest.TestCase): + timer = None + def log(self, msg): + return + now = time.time() + if self.timer is None: + self.timer = now + else: + elapsed = now - self.timer + self.timer = now + print " (%f elapsed)" % elapsed + print msg + + def test_version(self): + # just make sure it can be retrieved + ver = ed25519.__version__ + self.failUnless(isinstance(ver, type(""))) + + def test_constants(self): + # the secret key we get from raw.keypair() are 64 bytes long, and + # are mostly the output of a sha512 call. The first 32 bytes are the + # private exponent (random, with a few bits stomped). + self.failUnlessEqual(raw.SECRETKEYBYTES, 64) + # the public key is the encoded public point + self.failUnlessEqual(raw.PUBLICKEYBYTES, 32) + self.failUnlessEqual(raw.SIGNATUREKEYBYTES, 64) + + def test_raw(self): + sk_s = "\x00" * 32 # usually urandom(32) + vk_s, skvk_s = raw.publickey(sk_s) + self.failUnlessEqual(len(vk_s), 32) + exp_vks = unhexlify("3b6a27bcceb6a42d62a3a8d02a6f0d73" + "653215771de243a63ac048a18b59da29") + self.failUnlessEqual(vk_s, exp_vks) + self.failUnlessEqual(skvk_s[:32], sk_s) + self.failUnlessEqual(skvk_s[32:], vk_s) + msg = "hello world" + msg_and_sig = raw.sign(msg, skvk_s) + sig = msg_and_sig[:-len(msg)] + self.failUnlessEqual(len(sig), 64) + exp_sig = unhexlify("b0b47780f096ae60bfff8d8e7b19c36b" + "321ae6e69cca972f2ff987ef30f20d29" + "774b53bae404485c4391ddf1b3f37aaa" + "8a9747f984eb0884e8aa533386e73305") + self.failUnlessEqual(sig, exp_sig) + ret = raw.open(sig+msg, vk_s) # don't raise exception + self.failUnlessEqual(ret, msg) + self.failUnlessRaises(raw.BadSignatureError, + raw.open, + sig+msg+".. NOT!", vk_s) + self.failUnlessRaises(raw.BadSignatureError, + raw.open, + sig+flip_bit(msg), vk_s) + self.failUnlessRaises(raw.BadSignatureError, + raw.open, + sig+msg, flip_bit(vk_s)) + self.failUnlessRaises(raw.BadSignatureError, + raw.open, + sig+msg, flip_bit(vk_s, in_byte=2)) + self.failUnlessRaises(raw.BadSignatureError, + raw.open, + flip_bit(sig)+msg, vk_s) + self.failUnlessRaises(raw.BadSignatureError, + raw.open, + flip_bit(sig, in_byte=33)+msg, vk_s) + + + def test_publickey(self): + sk_bytes = unhexlify("4ba96b0b5303328c7405220598a587c4" + "acb06ed9a9601d149f85400195f1ec3d") + sk = ed25519.SigningKey(sk_bytes) + self.failUnlessRaises(ValueError, ed25519.SigningKey, "wrong length") + + vk_bytes = sk.get_verifying_key_bytes() + self.failUnlessEqual(hexlify(vk_bytes), + "a66d161e090652b054740748f059f92a" + "5b731f1c27b05571f6d942e4f8b7b264") + + ed25519.VerifyingKey(vk_bytes) + self.failUnlessRaises(ValueError, ed25519.VerifyingKey, "wrong length") + + def test_OOP(self): + sk_bytes = unhexlify("4ba96b0b5303328c7405220598a587c4" + "acb06ed9a9601d149f85400195f1ec3d") + sk = ed25519.SigningKey(sk_bytes) + + self.failUnlessEqual(hexlify(sk.get_verifying_key_bytes()), + "a66d161e090652b054740748f059f92a" + "5b731f1c27b05571f6d942e4f8b7b264") + vk = ed25519.VerifyingKey(sk.get_verifying_key_bytes()) + + msg = "hello world" + sig = sk.sign(msg) + self.failUnlessEqual(len(sig), 64) + self.failUnlessEqual(hexlify(sig), + "6eaffe94f2972b35158b6aaa9b69c1da" + "97f0896aca29c41b1dd7b32e6c9e2ff6" + "76fc8d8b034709cdcc37d8aeb86bebfb" + "173ace3c319e211ea1d7e8d8884c1808") + self.failUnlessEqual(vk.verify(sig, msg), None) # also, don't throw + self.failUnlessRaises(ed25519.BadSignatureError, + vk.verify, sig, msg+".. NOT!") + + def test_object_identity(self): + sk1_bytes = unhexlify("ef32972ae3f1252a5aa1395347ea008c" + "bd2fed0773a4ea45e2d2d06c8cf8fbd4") + sk2_bytes = unhexlify("3d550c158900b4c2922b6656d2f80572" + "89de4ee65043745179685ae7d29b944d") + sk1a = ed25519.SigningKey(sk1_bytes) + sk1b = ed25519.SigningKey(sk1_bytes) + sk2 = ed25519.SigningKey(sk2_bytes) + self.failUnlessEqual(sk1a, sk1b) + self.failIfEqual(sk1a, sk2) + + vk1_bytes = sk1a.get_verifying_key_bytes() + self.failUnlessEqual(vk1_bytes, sk1b.get_verifying_key_bytes()) + vk2_bytes = sk2.get_verifying_key_bytes() + vk1a = ed25519.VerifyingKey(vk1_bytes) + vk1b = ed25519.VerifyingKey(vk1_bytes) + vk2 = ed25519.VerifyingKey(vk2_bytes) + self.failUnlessEqual(vk1a, vk1b) + self.failIfEqual(vk1a, vk2) + + # exercise compare-against-other-type + self.failIfEqual(sk2, "not a SigningKey") + self.failIfEqual(vk2, "not a VerifyingKey") + + +if __name__ == '__main__': + unittest.main() diff --git a/src/pycryptopp/test/test_ed25519_kat.py b/src/pycryptopp/test/test_ed25519_kat.py new file mode 100644 index 0000000..83e38be --- /dev/null +++ b/src/pycryptopp/test/test_ed25519_kat.py @@ -0,0 +1,47 @@ +import unittest +from pkg_resources import resource_string +from binascii import hexlify, unhexlify +from pycryptopp.publickey import ed25519 + +class KnownAnswerTests(unittest.TestCase): + def test_short(self): + # kat-ed25519.txt comes from "sign.input" on ed25519.cr.yp.to . The + # pure-python ed25519.py in the same distribution uses a very + # different key format than the one used by NaCl. + shortkat = resource_string('pycryptopp', + 'testvectors/kat-ed25519-short.txt') + for i,line in enumerate(shortkat.splitlines()): + x = line.split(":") + A,B,C,D = [unhexlify(i) for i in x[:4]] + # A[:32] is the 32 byte seed (the entropy input to H()) + # A[32:] == B == the public point (pubkey) + # C is the message + # D is 64 bytes of signature (R+S) prepended to the message + + seed = A[:32] + vk_s = B + # the NaCl signature is R+S, which happens to be the same as ours + msg = C + sig = D[:64] + # note that R depends only upon the second half of H(seed). S + # depends upon both the first half (the exponent) and the second + # half + + #if len(msg) % 16 == 1: + # print "msg len = %d" % len(msg), time.time() + + sk = ed25519.SigningKey(seed) + vkbs = sk.get_verifying_key_bytes() + self.failUnlessEqual(vkbs, vk_s) + vk = ed25519.VerifyingKey(vkbs) + vk2 = ed25519.VerifyingKey(vk_s) + self.failUnlessEqual(vk2, vk) # objects should compare equal + newsig = sk.sign(msg) + sig_R,sig_S = sig[:32],sig[32:] + newsig_R,newsig_S = newsig[:32],newsig[32:] + self.failUnlessEqual(hexlify(newsig), hexlify(sig)) # deterministic sigs + self.failUnlessEqual(vk.verify(sig, msg), None) # no exception + + +if __name__ == '__main__': + unittest.main() diff --git a/src/pycryptopp/test/test_from_Nikratio.py b/src/pycryptopp/test/test_from_Nikratio.py new file mode 100644 index 0000000..a991415 --- /dev/null +++ b/src/pycryptopp/test/test_from_Nikratio.py @@ -0,0 +1,32 @@ +import unittest + +# This was reported as triggering a "Use of uninitialised value of +# size 4" under valgrind by Nikratio in pycryptopp-0.5.17 and Crypto++ +# 5.6.0. See http://tahoe-lafs.org/trac/pycryptopp/ticket/67 + +class T(unittest.TestCase): + def test_t(self): + import hmac + import pycryptopp + try: + import hashlib + except ImportError: + # Oh nevermind. + return + import struct + + def encrypt(buf, passphrase, nonce): + + key = hashlib.sha256(passphrase + nonce).digest() + cipher = pycryptopp.cipher.aes.AES(key) + hmac_ = hmac.new(key, digestmod=hashlib.sha256) + + hmac_.update(buf) + buf = cipher.process(buf) + hash_ = cipher.process(hmac_.digest()) + + return ''.join( + (struct.pack('<B', len(nonce)), + nonce, hash_, buf)) + + encrypt('foobar', 'passphrase', 'nonce') diff --git a/src/pycryptopp/test/test_rsa.py b/src/pycryptopp/test/test_rsa.py new file mode 100644 index 0000000..15294bc --- /dev/null +++ b/src/pycryptopp/test/test_rsa.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python + +import random + +import unittest + +global VERBOSE +VERBOSE=False + +from pycryptopp.publickey import rsa + +from base64 import b32encode +def ab(x): # debuggery + if len(x) >= 3: + return "%s:%s" % (len(x), b32encode(x[-3:]),) + elif len(x) == 2: + return "%s:%s" % (len(x), b32encode(x[-2:]),) + elif len(x) == 1: + return "%s:%s" % (len(x), b32encode(x[-1:]),) + elif len(x) == 0: + return "%s:%s" % (len(x), "--empty--",) + +def randstr(n): + return ''.join(map(chr, map(random.randrange, [0]*n, [256]*n))) + +KEYSIZE=522 # 522 bits is far too few for actual security -- it is used only for faster unit tests + +class Signer(unittest.TestCase): + def test_generate_bad_size(self): + try: + rsa.generate(KEYSIZE-1) + except rsa.Error, le: + self.failUnless("size in bits is required to be >=" in str(le), le) + else: + self.fail("Should have raised error from size being too small.") + try: + rsa.generate(sizeinbits=KEYSIZE-1) + except rsa.Error, le: + self.failUnless("size in bits is required to be >=" in str(le), le) + else: + self.fail("Should have raised error from size being too small.") + + def test_generate(self): + rsa.generate(KEYSIZE) + # Hooray! It didn't raise an exception! We win! + rsa.generate(sizeinbits=KEYSIZE) + # Hooray! It didn't raise an exception! We win! + + def test_sign(self): + signer = rsa.generate(KEYSIZE) + result = signer.sign("abc") + self.failUnlessEqual(len(result), ((KEYSIZE+7)/8)) + # TODO: test against RSAInc. test vectors. + + def test_create_from_string_invalid(self): + try: + rsa.create_signing_key_from_string("invalid string") + except rsa.Error, le: + self.failUnless("decode error" in str(le), le) + else: + self.fail("Should have raised error from invalid string") + + try: + rsa.create_verifying_key_from_string("invalid string") + except rsa.Error, le: + self.failUnless("decode error" in str(le), le) + else: + self.fail("Should have raised error from invalid string") + +class SignAndVerify(unittest.TestCase): + def _help_test_sign_and_check(self, signer, verifier, msg): + sig = signer.sign(msg) + self.failUnlessEqual(len(sig), ((KEYSIZE+7)/8)) + self.failUnless(verifier.verify(msg, sig)) + + def test_sign_and_check_a(self): + signer = rsa.generate(KEYSIZE) + verifier = signer.get_verifying_key() + return self._help_test_sign_and_check(signer, verifier, "a") + + def _help_test_sign_and_check_random(self, signer, verifier): + for i in range(3): + l = random.randrange(0, 2**10) + msg = randstr(l) + self._help_test_sign_and_check(signer, verifier, msg) + + def test_sign_and_check_random(self): + signer = rsa.generate(KEYSIZE) + verifier = signer.get_verifying_key() + return self._help_test_sign_and_check_random(signer, verifier) + + def _help_test_sign_and_failcheck(self, signer, verifier, msg): + sig = signer.sign("a") + sig = sig[:-1] + chr(ord(sig[-1])^0x01) + self.failUnless(not verifier.verify(msg, sig)) + + def test_sign_and_failcheck_a(self): + signer = rsa.generate(KEYSIZE) + verifier = signer.get_verifying_key() + return self._help_test_sign_and_failcheck(signer, verifier, "a") + + def _help_test_sign_and_failcheck_random(self, signer, verifier): + for i in range(3): + l = random.randrange(0, 2**10) + msg = randstr(l) + self._help_test_sign_and_failcheck(signer, verifier, msg) + + def test_sign_and_failcheck_random(self): + signer = rsa.generate(KEYSIZE) + verifier = signer.get_verifying_key() + return self._help_test_sign_and_failcheck_random(signer, verifier) + + def test_serialize_and_deserialize_verifying_key_and_test(self): + signer = rsa.generate(KEYSIZE) + verifier = signer.get_verifying_key() + serstr = verifier.serialize() + verifier = None + newverifier = rsa.create_verifying_key_from_string(serstr) + self._help_test_sign_and_check(signer, newverifier, "a") + self._help_test_sign_and_check_random(signer, newverifier) + self._help_test_sign_and_failcheck(signer, newverifier, "a") + self._help_test_sign_and_failcheck_random(signer, newverifier) + + def test_serialize_and_deserialize_signing_key_and_test(self): + signer = rsa.generate(KEYSIZE) + verifier = signer.get_verifying_key() + serstr = signer.serialize() + signer = None + newsigner = rsa.create_signing_key_from_string(serstr) + self._help_test_sign_and_check(newsigner, verifier, "a") + self._help_test_sign_and_check_random(newsigner, verifier) + self._help_test_sign_and_failcheck(newsigner, verifier, "a") + self._help_test_sign_and_failcheck_random(newsigner, verifier) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/pycryptopp/test/test_sha256.py b/src/pycryptopp/test/test_sha256.py new file mode 100644 index 0000000..5e982dc --- /dev/null +++ b/src/pycryptopp/test/test_sha256.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python + +import random, re + +import unittest + +from binascii import b2a_hex, a2b_hex + +global VERBOSE +VERBOSE=False + +from pycryptopp.hash import sha256 + +from pkg_resources import resource_string + +def resource_string_lines(pkgname, resname): + return split_on_newlines(resource_string(pkgname, resname)) + +from base64 import b32encode +def ab(x): # debuggery + if len(x) >= 3: + return "%s:%s" % (len(x), b32encode(x[-3:]),) + elif len(x) == 2: + return "%s:%s" % (len(x), b32encode(x[-2:]),) + elif len(x) == 1: + return "%s:%s" % (len(x), b32encode(x[-1:]),) + elif len(x) == 0: + return "%s:%s" % (len(x), "--empty--",) + +def randstr(n): + return ''.join(map(chr, map(random.randrange, [0]*n, [256]*n))) + +h0 = a2b_hex("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") +h_bd = a2b_hex("68325720aabd7c82f30f554b313d0570c95accbb7dc4b5aae11204c08ffe732b") +h_5fd4 = a2b_hex("7c4fbf484498d21b487b9d61de8914b2eadaf2698712936d47c3ada2558f6788") + +class SHA256(unittest.TestCase): + def test_digest(self): + empty_digest = sha256.SHA256().digest() + self.failUnless(isinstance(empty_digest, str)) + self.failUnlessEqual(len(empty_digest), 32) + self.failUnlessEqual(empty_digest, h0) + + def test_hexdigest(self): + empty_hexdigest = sha256.SHA256().hexdigest() + self.failUnlessEqual(a2b_hex(empty_hexdigest), h0) + test_hexdigest.todo = "Not yet implemented: SHA256.hexdigest()." + + def test_onebyte_1(self): + d = sha256.SHA256("\xbd").digest() + self.failUnlessEqual(d, h_bd) + + def test_onebyte_2(self): + s = sha256.SHA256() + s.update("\xbd") + d = s.digest() + self.failUnlessEqual(d, h_bd) + + def test_update(self): + s = sha256.SHA256("\x5f") + s.update("\xd4") + d = s.digest() + self.failUnlessEqual(d, h_5fd4) + + def test_constructor_type_check(self): + self.failUnlessRaises(TypeError, sha256.SHA256, None) + + def test_update_type_check(self): + h = sha256.SHA256() + self.failUnlessRaises(TypeError, h.update, None) + + def test_digest_twice(self): + h = sha256.SHA256() + d1 = h.digest() + self.failUnless(isinstance(d1, str)) + d2 = h.digest() + self.failUnlessEqual(d1, d2) + + def test_digest_then_update_fail(self): + h = sha256.SHA256() + h.digest() + try: + h.update("oops") + except sha256.Error, le: + self.failUnless("digest() has been called" in str(le), le) + + def test_chunksize(self): + # hashes can be computed on arbitrarily-sized chunks + problems = False + for length in range(2, 140): + s = "a"*length + expected = sha256.SHA256(s).hexdigest() + for a in range(0, length): + h = sha256.SHA256() + h.update(s[:a]) + h.update(s[a:]) + got = h.hexdigest() + if got != expected: + problems = True + print len(s[:a]), len(s[a:]), len(s), got, expected + self.failIf(problems) + + def test_recursive_different_chunksizes(self): + """ + Test that updating a hasher with various sized inputs yields + the expected answer. This is somewhat redundant with + test_chunksize(), but that's okay. This one exercises some + slightly different situations (such as finalizing a hash after + different length inputs.) This one is recursive so that there + is a single fixed result that we expect. + """ + hx = sha256.SHA256() + s = ''.join([ chr(c) for c in range(65) ]) + for i in range(0, 65): + hy = sha256.SHA256(s[:i]).digest() + hx.update(hy) + for i in range(0, 65): + hx.update(chr(0xFE)) + hx.update(s[:64]) + self.failUnlessEqual(hx.hexdigest().lower(), '5191c7841dd4e16aa454d40af924585dffc67157ffdbfd0236acddd07901629d') + + +VECTS_RE=re.compile("\nLen = ([0-9]+)\nMsg = ([0-9a-f]+)\nMD = ([0-9a-f]+)") + +# split_on_newlines() copied from pyutil.strutil +def split_on_newlines(s): + """ + Splits s on all of the three newline sequences: "\r\n", "\r", or "\n". + """ + res = [] + for x in s.split('\r\n'): + for y in x.split('\r'): + res.extend(y.split('\n')) + return res + +class SHSVectors(unittest.TestCase): + """ + All of the SHA-256 test vectors from the NIST SHS, in the files distributed + by NIST. (NIST distributes them in a .zip, but we expect them to be + unpacked and in a subdirectory named 'testvectors'). + """ + def test_short(self): + return self._test_vect(resource_string('pycryptopp', 'testvectors/SHA256ShortMsg.txt')) + + def test_long(self): + return self._test_vect(resource_string('pycryptopp', 'testvectors/SHA256LongMsg.txt')) + + def _test_vect(self, vects_str): + for mo in VECTS_RE.finditer(vects_str): + msglenbits = int(mo.group(1)) + assert msglenbits % 8 == 0 + msglen = msglenbits / 8 + msg = a2b_hex(mo.group(2))[:msglen] # The slice is necessary because NIST seems to think that "00" is a reasonable representation for the zero-length string. + assert len(msg) == msglen, (len(msg), msglen) + md = a2b_hex(mo.group(3)) + + computed_md = sha256.SHA256(msg).digest() + self.failUnlessEqual(computed_md, md) + + def test_monte(self): + inlines = resource_string_lines('pycryptopp', 'testvectors/SHA256Monte.txt') + for line in inlines: + line = line.strip() + if line[:7] == 'Seed = ': + seed = a2b_hex(line[7:]) + break + + j = 0 + for line in inlines: + line = line.strip() + if line[:8] == 'COUNT = ': + assert int(line[8:]) == j + elif line[:5] == 'MD = ': + mds = [] + mds.append(seed);mds.append(seed);mds.append(seed); + for i in range(1000): + m = mds[-3]+mds[-2]+mds[-1] + mds.append(sha256.SHA256(m).digest()) + seed = mds[-1] + self.failUnlessEqual(line[5:], b2a_hex(seed)) + j += 1 diff --git a/src/pycryptopp/test/test_xsalsa20.py b/src/pycryptopp/test/test_xsalsa20.py new file mode 100644 index 0000000..f3ee02a --- /dev/null +++ b/src/pycryptopp/test/test_xsalsa20.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python + +import random, re +import unittest + +from binascii import a2b_hex, b2a_hex +from pkg_resources import resource_string + +from pycryptopp.cipher import xsalsa20 +TEST_XSALSA_RE=re.compile("\nCOUNT=([0-9]+)\nKEY=([0-9a-f]+)\nIV=([0-9a-f]+)\nPLAINTEXT=([0-9a-f]+)\nCIPHERTEXT=([0-9a-f]+)") + +class XSalsa20Test(unittest.TestCase): + + enc0="eea6a7251c1e72916d11c2cb214d3c252539121d8e234e652d651fa4c8cff880309e645a74e9e0a60d8243acd9177ab51a1beb8d5a2f5d700c093c5e5585579625337bd3ab619d615760d8c5b224a85b1d0efe0eb8a7ee163abb0376529fcc09bab506c618e13ce777d82c3ae9d1a6f972d4160287cbfe60bf2130fc0a6ff6049d0a5c8a82f429231f0080" + + def test_zero_XSalsa20(self): + key="1b27556473e985d462cd51197a9a46c76009549eac6474f206c4ee0844f68389" + iv="69696ee955b62b73cd62bda875fc73d68219e0036b7a0b37" + computedcipher=xsalsa20.XSalsa20(a2b_hex(key),a2b_hex(iv)).process('\x00'*139) + self.failUnlessEqual(a2b_hex(self.enc0), computedcipher, "enc0: %s, computedciper: %s" % (self.enc0, b2a_hex(computedcipher))) + + cryptor=xsalsa20.XSalsa20(a2b_hex(key),a2b_hex(iv)) + + computedcipher1=cryptor.process('\x00'*69) + computedcipher2=cryptor.process('\x00'*69) + computedcipher3=cryptor.process('\x00') + computedcipher12=b2a_hex(computedcipher1)+b2a_hex(computedcipher2)+b2a_hex(computedcipher3) + self.failUnlessEqual(self.enc0, computedcipher12) + + + def test_XSalsa(self): + # The test vector is from Crypto++'s TestVectors/salsa.txt, comment + # there is: Source: created by Wei Dai using naclcrypto-20090308 . + # naclcrypto being DJB's crypto library and of course DJB designed + # XSalsa20 + s = resource_string("pycryptopp", "testvectors/xsalsa20.txt") + return self._test_XSalsa(s) + + def _test_XSalsa(self, vects_str): + for mo in TEST_XSALSA_RE.finditer(vects_str): + #count = int(mo.group(1)) + key = a2b_hex(mo.group(2)) + iv = a2b_hex(mo.group(3)) + #plaintext = a2b_hex(mo.group(4)) + #ciphertext= a2b_hex(mo.group(5)) + plaintext = mo.group(4) + ciphertext = mo.group(5) + computedcipher=xsalsa20.XSalsa20(key,iv).process(a2b_hex(plaintext)) + #print "ciphertext", b2a_hex(computedcipher), '\n' + #print "computedtext", ciphertext, '\n' + #print count, ": \n" + self.failUnlessEqual(computedcipher,a2b_hex(ciphertext),"computedcipher: %s, ciphertext: %s" % (b2a_hex(computedcipher), ciphertext)) + + #the random decomposing + plaintext1 = "" + plaintext2 = "" + length = len(plaintext) + rccipher = "" + cryptor = xsalsa20.XSalsa20(key,iv) + if length > 2: + point = random.randint(0,length-3) + if (point%2) !=0: + point -= 1 + plaintext1 += plaintext[:point+2] + plaintext2 += plaintext[point+2:] + rccipher += b2a_hex(cryptor.process(a2b_hex(plaintext1))) + rccipher += b2a_hex(cryptor.process(a2b_hex(plaintext2))) + self.failUnlessEqual(rccipher, ciphertext, "random computed cipher: %s, ciphertext: %s" % (rccipher, ciphertext)) + + #every byte encrypted + cryptor = xsalsa20.XSalsa20(key,iv) + eccipher="" + l = 0 + while l<=(length-2): + eccipher += b2a_hex(cryptor.process(a2b_hex(plaintext[l:l+2]))) + l += 2 + self.failUnlessEqual(eccipher, ciphertext, "every byte computed cipher: %s, ciphertext: %s" % (eccipher, ciphertext)) + + + def test_types_and_lengths(self): + # the key= argument must be a bytestring exactly 32 bytes long + self.failUnlessRaises(TypeError, xsalsa20.XSalsa20, None) + for i in range(70): + key = "a"*i + if i != 32: + self.failUnlessRaises(xsalsa20.Error, xsalsa20.XSalsa20, key) + else: + self.failUnless(xsalsa20.XSalsa20(key)) + + # likewise, iv= (if provided) must be exactly 24 bytes long. Passing + # None is not treated the same as not passing the argument at all. + key = "a"*32 + self.failUnlessRaises(TypeError, xsalsa20.XSalsa20, key, None) + for i in range(70): + iv = "i"*i + if i != 24: + self.failUnlessRaises(xsalsa20.Error, xsalsa20.XSalsa20, key, iv) + else: + self.failUnless(xsalsa20.XSalsa20(key, iv)) + + def test_recursive(self): + # Try to use the same technique as: + # http://blogs.msdn.com/si_team/archive/2006/05/19/aes-test-vectors.aspx + # It's not exactly the same, though, because XSalsa20 is a stream + # cipher, whereas the Ferguson code is exercising a block cipher. But + # we try to do something similar. + + # the XSalsa20 internal function uses a 32-byte block. We want to + # exercise it twice for each key, to guard against + # clobbering-after-key-setup errors. Just doing enc(enc(p)) could let + # XOR errors slip through. So to be safe, use B=64. + B=64 + N=24 + K=32 + s = "\x00"*(B+N+K) + def enc(key, nonce, plaintext): + p = xsalsa20.XSalsa20(key=key, iv=nonce) + return p.process(plaintext) + for i in range(1000): + plaintext = s[-K-N-B:-K-N] + nonce = s[-K-N:-K] + key = s[-K:] + ciphertext = enc(key, nonce, plaintext) + s += ciphertext + s = s[-K-N-B:] + output = b2a_hex(s[-B:]) + # I've compared this output against pynacl -warner + self.failUnlessEqual(output, + "77f8e2792dd4f2d44edf469c3a7ad5f7" + "5cb373fe0c3d9c8ee570dc91e00f1caa" + "25f725c202f3781869a40b8a2c856b55" + "8178b6af9576a15799c445c30aeced66") + + +if __name__ == "__main__": + unittest.main() |