diff --git a/ChangeLog b/ChangeLog index 3e334f0..8a3b96d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +2012-02-03 Version 0.5.1 + - Apply performance patch from + 2011-10-26 Version 0.5 - Add test case and fix for - Add test case and fix for diff --git a/dkim/__init__.py b/dkim/__init__.py index 83f39ac..ba7956b 100644 --- a/dkim/__init__.py +++ b/dkim/__init__.py @@ -450,8 +450,7 @@ class DKIM(object): self.logger.debug("sign headers: %r" % self.signed_headers) try: - sig2 = RSASSA_PKCS1_v1_5_sign( - h, pk['privateExponent'], pk['modulus']) + sig2 = RSASSA_PKCS1_v1_5_sign(h, pk) except DigestTooLargeError: raise ParameterError("digest too large for modulus") sig_value += base64.b64encode(bytes(sig2)) @@ -545,8 +544,7 @@ class DKIM(object): try: self.signature_fields = sig signature = base64.b64decode(re.sub(br"\s+", b"", sig[b'b'])) - return RSASSA_PKCS1_v1_5_verify( - h, signature, pk['publicExponent'], pk['modulus']) + return RSASSA_PKCS1_v1_5_verify(h, signature, pk) except (TypeError,DigestTooLargeError) as e: raise KeyFormatError("digest too large for modulus: %s"%e) diff --git a/dkim/crypto.py b/dkim/crypto.py index 0bc2e22..532eabf 100644 --- a/dkim/crypto.py +++ b/dkim/crypto.py @@ -213,41 +213,60 @@ def int2str(n, length=-1): return r -def perform_rsa(message, exponent, modulus, mlen): - """Perform RSA signing or verification. +def rsa_decrypt(message, pk, mlen): + """Perform RSA decryption/signing @param message: byte string to operate on - @param exponent: public or private key exponent - @param modulus: key modulus + @param pk: private key data @param mlen: desired output length @return: byte string result of the operation """ - return int2str(pow(str2int(message), exponent, modulus), mlen) + c = str2int(message) + + m1 = pow(c, pk['exponent1'], pk['prime1']) + m2 = pow(c, pk['exponent2'], pk['prime2']) + + if m1 < m2: + h = pk['coefficient'] * (m1 + pk['prime1'] - m2) % pk['prime1'] + else: + h = pk['coefficient'] * (m1 - m2) % pk['prime1'] + + return int2str(m2 + h * pk['prime2'], mlen) -def RSASSA_PKCS1_v1_5_sign(hash, private_exponent, modulus): +def rsa_encrypt(message, pk, mlen): + """Perform RSA encryption/verification + + @param message: byte string to operate on + @param pk: public key data + @param mlen: desired output length + @return: byte string result of the operation + """ + m = str2int(message) + return int2str(pow(m, pk['publicExponent'], pk['modulus']), mlen) + + +def RSASSA_PKCS1_v1_5_sign(hash, private_key): """Sign a digest with RFC3447 RSASSA-PKCS1-v1_5. @param hash: hash object to sign - @param private_exponent: private key exponent - @param modulus: key modulus + @param private_key: private key data @return: signed digest byte string """ - modlen = len(int2str(modulus)) + modlen = len(int2str(private_key['modulus'])) encoded_digest = EMSA_PKCS1_v1_5_encode(hash, modlen) - return perform_rsa(encoded_digest, private_exponent, modulus, modlen) + return rsa_decrypt(encoded_digest, private_key, modlen) -def RSASSA_PKCS1_v1_5_verify(hash, signature, public_exponent, modulus): +def RSASSA_PKCS1_v1_5_verify(hash, signature, public_key): """Verify a digest signed with RFC3447 RSASSA-PKCS1-v1_5. @param hash: hash object to check @param signature: signed digest byte string - @param public_exponent: public key exponent - @param modulus: key modulus + @param public_key: public key data @return: True if the signature is valid, False otherwise """ - modlen = len(int2str(modulus)) + modlen = len(int2str(public_key['modulus'])) encoded_digest = EMSA_PKCS1_v1_5_encode(hash, modlen) - signed_digest = perform_rsa(signature, public_exponent, modulus, modlen) + signed_digest = rsa_encrypt(signature, public_key, modlen) return encoded_digest == signed_digest diff --git a/dkim/tests/test_crypto.py b/dkim/tests/test_crypto.py index d63f274..6c55f44 100644 --- a/dkim/tests/test_crypto.py +++ b/dkim/tests/test_crypto.py @@ -27,7 +27,6 @@ from dkim.crypto import ( int2str, parse_pem_private_key, parse_public_key, - perform_rsa, RSASSA_PKCS1_v1_5_sign, RSASSA_PKCS1_v1_5_verify, str2int, @@ -50,6 +49,37 @@ TEST_KEY_PRIVATE_EXPONENT = int( '489900956461640273471526152019568303807247290486052565153701534491987040' '131529720476525111651818771481293273124837542067061293644354088836358900' '29771161475005043329') +TEST_KEY_PRIME1 = int( + '127343333492908149956322715568115237787784712176275919666517073343689103' + '280591709737233188193431204382936008602497360201661766158158969883295914' + '16266272177') +TEST_KEY_PRIME2 = int( + '125793967926229270607412639516115399484604596465353856808629588968254772' + '302339293254103556785310783521521266982500068526354237606773478050287350' + '33316975853') +TEST_KEY_EXPONENT1 = int( + '971401692373919639404678505179789291960987093676634885925231250693661495' + '080125935714710587508461815572290443270923375888685273287584323569222368' + '5450962737') +TEST_KEY_EXPONENT2 = int( + '405135004809332318340885085107137607293826268763328174261828392259785080' + '028911220030572618988900118679333717167345003034279703551607153395397272' + '3014807045') +TEST_KEY_COEFFICIENT = int( + '933140693852464192207530806898449261372116224159220632563973880414444021' + '989007318611849609226428922185905596238131661588470844906391982906126973' + '1282880267') +TEST_PK = { + 'version': 0, + 'modulus': TEST_KEY_MODULUS, + 'publicExponent': TEST_KEY_PUBLIC_EXPONENT, + 'privateExponent': TEST_KEY_PRIVATE_EXPONENT, + 'prime1': TEST_KEY_PRIME1, + 'prime2': TEST_KEY_PRIME2, + 'exponent1': TEST_KEY_EXPONENT1, + 'exponent2': TEST_KEY_EXPONENT2, + 'coefficient': TEST_KEY_COEFFICIENT, +} class TestStrIntConversion(unittest.TestCase): @@ -71,9 +101,7 @@ class TestParseKeys(unittest.TestCase): def test_parse_pem_private_key(self): key = parse_pem_private_key(read_test_data('test.private')) - self.assertEqual(key['modulus'], TEST_KEY_MODULUS) - self.assertEqual(key['publicExponent'], TEST_KEY_PUBLIC_EXPONENT) - self.assertEqual(key['privateExponent'], TEST_KEY_PRIVATE_EXPONENT) + self.assertEqual(key, TEST_PK) def test_parse_public_key(self): data = read_test_data('test.txt') @@ -109,27 +137,6 @@ class TestEMSA_PKCS1_v1_5(unittest.TestCase): EMSA_PKCS1_v1_5_encode, hash, 45) -class TestRSA(unittest.TestCase): - - message = binascii.unhexlify(b'0004fb') - modulus = 186101 - modlen = 3 - public_exponent = 907 - private_exponent = 2851 - - def test_perform(self): - signed = perform_rsa( - self.message, self.private_exponent, self.modulus, self.modlen) - self.assertEqual(binascii.unhexlify(b'01f140'), signed) - - def test_sign_and_verify(self): - signed = perform_rsa( - self.message, self.private_exponent, self.modulus, self.modlen) - unsigned = perform_rsa( - signed, self.public_exponent, self.modulus, self.modlen) - self.assertEqual(self.message, unsigned) - - class TestRSASSA(unittest.TestCase): def setUp(self): @@ -144,19 +151,18 @@ class TestRSASSA(unittest.TestCase): b'39d2d6ab8b215b19be0e69ef490885004a474eb26d747a219693e8c') def test_sign_and_verify(self): - signature = RSASSA_PKCS1_v1_5_sign( - self.hash, TEST_KEY_PRIVATE_EXPONENT, TEST_KEY_MODULUS) + signature = RSASSA_PKCS1_v1_5_sign(self.hash, TEST_PK) self.assertEqual(self.test_signature, signature) self.assertTrue( RSASSA_PKCS1_v1_5_verify( - self.hash, signature, TEST_KEY_PUBLIC_EXPONENT, - TEST_KEY_MODULUS)) + self.hash, signature, TEST_PK)) def test_invalid_signature(self): + invalid_key = TEST_PK.copy() + invalid_key['modulus'] += 1 self.assertFalse( RSASSA_PKCS1_v1_5_verify( - self.hash, self.test_signature, TEST_KEY_PUBLIC_EXPONENT, - TEST_KEY_MODULUS + 1)) + self.hash, self.test_signature, invalid_key)) def test_suite():