From 22338425226b926b97b2e821e4d7d2ea9b2f1b88 Mon Sep 17 00:00:00 2001 From: Victor Wagner Date: Thu, 5 Jun 2014 14:18:33 +0400 Subject: [PATCH] digest module covered by tests --- ctypescrypto/__init__.py | 2 +- ctypescrypto/digest.py | 127 ++++++++++++++++++++++----------------- tests/testdigest.py | 98 ++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 57 deletions(-) create mode 100644 tests/testdigest.py diff --git a/ctypescrypto/__init__.py b/ctypescrypto/__init__.py index b6d00ca..d4dcf7c 100644 --- a/ctypescrypto/__init__.py +++ b/ctypescrypto/__init__.py @@ -6,4 +6,4 @@ from ctypes import CDLL libcrypto = CDLL("libcrypto.so.1.0.0") -libcrypto.OPENSSL_config(None) +libcrypto.OPENSSL_add_all_algorithms_conf() diff --git a/ctypescrypto/digest.py b/ctypescrypto/digest.py index 4b0eeb5..4e33d92 100644 --- a/ctypescrypto/digest.py +++ b/ctypescrypto/digest.py @@ -1,17 +1,32 @@ """ Implmenets interface to OpenSSL EVP_Digest* functions. - Interface made as close to hashlib as possible + Interface made as close to hashlib as possible. + + This module is really an excess effort. Hashlib allows access to + mostly same functionality except oids and nids of hashing + algortithms (which might be needed for private key operations). + + hashlib even allows to use engine-provided digests if it is build + with dinamically linked libcrypto - so use + ctypescrypto.engine.set_default("gost",xFFFF) and md_gost94 + algorithm would be available both to this module and hashlib. + """ -from ctypes import c_int, c_char_p, c_void_p, POINTER, c_long_long +from ctypes import c_int, c_char_p, c_void_p, POINTER, c_long,c_longlong, create_string_buffer,byref from ctypescrypto import libcrypto from ctypescrypto.exception import LibCryptoError +from ctypescrypto.oid import Oid DIGEST_ALGORITHMS = ("MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512") class DigestError(LibCryptoError): - pass + pass def new(algname): + """ + Behaves just like hashlib.new. Creates digest object by + algorithm name + """ md=DigestType(algname) return Digest(md) @@ -22,78 +37,77 @@ class DigestType: digest algorithm """ - def __init__(self, digest_name): + def __init__(self, digest_name): """ Finds digest by its name """ - self.digest_name = digest_name - self.digest = libcrypto.EVP_get_digestbyname(self.digest_name) - if self.digest == 0: - raise DigestError, "Unknown digest: %s" % self.digest_name + self.digest_name = digest_name + self.digest = libcrypto.EVP_get_digestbyname(self.digest_name) + if self.digest is None: + raise DigestError, "Unknown digest: %s" % self.digest_name - def __del__(self): - pass + def __del__(self): + pass def digest_size(self): return libcrypto.EVP_MD_size(self.digest) def block_size(self): return libcrypto.EVP_MD_block_size(self.digest) def oid(self): - pass - #FIXME TBD - # return Oid(nid=libcrypto.EVP_MD_type(self.digest) + return Oid(libcrypto.EVP_MD_type(self.digest)) + class Digest: """ Represents EVP_MD_CTX object which actually used to calculate digests. """ - def __init__(self, digest_type): + def __init__(self,digest_type): """ Initializes digest using given type. """ - self._clean_ctx() - self.ctx = libcrypto.EVP_MD_CTX_create() - if self.ctx == 0: - raise DigestError, "Unable to create digest context" - result = libcrypto.EVP_DigestInit_ex(self.ctx, digest_type.digest, None) - if result == 0: - self._clean_ctx() - raise DigestError, "Unable to initialize digest" - self.digest_type = digest_type + self._clean_ctx() + self.ctx = libcrypto.EVP_MD_CTX_create() + if self.ctx == 0: + raise DigestError, "Unable to create digest context" + result = libcrypto.EVP_DigestInit_ex(self.ctx, digest_type.digest, None) + if result == 0: + self._clean_ctx() + raise DigestError, "Unable to initialize digest" + self.digest_type = digest_type self.digest_size = self.digest_type.digest_size() self.block_size = self.digest_type.block_size() - def __del__(self): - self._clean_ctx() + def __del__(self): + self._clean_ctx() - def update(self, data): + def update(self, data): """ Hashes given byte string as data """ - if self.digest_finalized: - raise DigestError, "No updates allowed" - if type(data) != type(""): - raise TypeError, "A string is expected" - result = libcrypto.EVP_DigestUpdate(self.ctx, c_char_p(data), len(data)) - if result != 1: - raise DigestError, "Unable to update digest" - - def digest(self, data=None): + if self.digest_finalized: + raise DigestError, "No updates allowed" + if type(data) != type(""): + raise TypeError, "A string is expected" + result = libcrypto.EVP_DigestUpdate(self.ctx, c_char_p(data), len(data)) + if result != 1: + raise DigestError, "Unable to update digest" + + def digest(self, data=None): """ Finalizes digest operation and return digest value - Optionally hashes data before finalizing + Optionally hashes more data before finalizing """ - if self.digest_finalized: + if self.digest_finalized: return self.digest_out.raw[:self.digest_size] - if data is not None: - self.update(data) - self.digest_out = create_string_buffer(256) - length = c_long(0) - result = libcrypto.EVP_DigestFinal_ex(self.ctx, self.digest_out, byref(length)) - if result != 1 : - raise DigestError, "Unable to finalize digest" - self.digest_finalized = True - return self.digest_out.raw[:self.digest_size] + if data is not None: + self.update(data) + self.digest_out = create_string_buffer(256) + length = c_long(0) + result = libcrypto.EVP_DigestFinal_ex(self.ctx, self.digest_out, byref(length)) + if result != 1 : + raise DigestError, "Unable to finalize digest" + self.digest_finalized = True + return self.digest_out.raw[:self.digest_size] def copy(self): """ Creates copy of the digest CTX to allow to compute digest @@ -103,15 +117,15 @@ class Digest: libcrypto.EVP_MD_CTX_copy(new_digest.ctx,self.ctx) return new_digest - def _clean_ctx(self): - try: - if self.ctx is not None: - libcrypto.EVP_MD_CTX_destroy(self.ctx) - del(self.ctx) - except AttributeError: - pass - self.digest_out = None - self.digest_finalized = False + def _clean_ctx(self): + try: + if self.ctx is not None: + libcrypto.EVP_MD_CTX_destroy(self.ctx) + del(self.ctx) + except AttributeError: + pass + self.digest_out = None + self.digest_finalized = False def hexdigest(self,data=None): """ @@ -119,11 +133,12 @@ class Digest: with hashlib """ from base64 import b16encode - return b16encode(self.digest(data) + return b16encode(self.digest(data)) # Declare function result and argument types libcrypto.EVP_get_digestbyname.restype = c_void_p +libcrypto.EVP_get_digestbyname.argtypes = (c_char_p,) libcrypto.EVP_MD_CTX_create.restype = c_void_p libcrypto.EVP_DigestInit_ex.argtypes = (c_void_p,c_void_p,c_void_p) libcrypto.EVP_DigestUpdate.argtypes = (c_void_p,c_char_p,c_longlong) diff --git a/tests/testdigest.py b/tests/testdigest.py new file mode 100644 index 0000000..4c4b4ed --- /dev/null +++ b/tests/testdigest.py @@ -0,0 +1,98 @@ +from ctypescrypto.oid import Oid +from ctypescrypto import digest +from base64 import b16decode,b16encode +import unittest + +class TestDigestType(unittest.TestCase): + def test_md4(self): + d=digest.DigestType("md4") + self.assertEqual(d.digest_size(),16) + self.assertEqual(d.block_size(),64) + self.assertEqual(d.oid(),Oid("md4")) + def test_md5(self): + d=digest.DigestType("md5") + self.assertEqual(d.digest_size(),16) + self.assertEqual(d.block_size(),64) + self.assertEqual(d.oid(),Oid("md5")) + def test_sha1(self): + d=digest.DigestType("sha1") + self.assertEqual(d.digest_size(),20) + self.assertEqual(d.block_size(),64) + self.assertEqual(d.oid(),Oid("sha1")) + def test_sha256(self): + d=digest.DigestType("sha256") + self.assertEqual(d.digest_size(),32) + self.assertEqual(d.block_size(),64) + self.assertEqual(d.oid(),Oid("sha256")) + def test_sha384(self): + d=digest.DigestType("sha384") + self.assertEqual(d.digest_size(),48) + self.assertEqual(d.block_size(),128) + self.assertEqual(d.oid(),Oid("sha384")) + def test_sha512(self): + d=digest.DigestType("sha512") + self.assertEqual(d.digest_size(),64) + self.assertEqual(d.block_size(),128) + self.assertEqual(d.oid(),Oid("sha512")) + + +class TestIface(unittest.TestCase): + """ Test all methods with one algorithms """ + def test_cons(self): + md=digest.DigestType("sha1") + dgst=digest.Digest(md) + dgst.update("A quick brown fox jumps over the lazy dog.") + self.assertEqual(dgst.digest_size,20) + self.assertEqual(dgst.hexdigest(),"00CFFE7312BF9CA73584F24BDF7DF1D028340397") + def test_bindigest(self): + dgst=digest.new("sha1") + dgst.update("A quick brown fox jumps over the lazy dog.") + self.assertEqual(dgst.digest_size,20) + self.assertEqual(dgst.digest(),b16decode("00CFFE7312BF9CA73584F24BDF7DF1D028340397",True)) + def test_duplicatedigest(self): + dgst=digest.new("sha1") + dgst.update("A quick brown fox jumps over the lazy dog.") + v1=dgst.digest() + v2=dgst.digest() + self.assertEqual(v1,v2) + def test_copy(self): + dgst=digest.new("sha1") + dgst.update("A quick brown fox jumps over ") + d2=dgst.copy() + dgst.update("the lazy dog.") + value1=dgst.hexdigest() + d2.update("the fat pig.") + value2=d2.hexdigest() + self.assertEqual(value1,"00CFFE7312BF9CA73584F24BDF7DF1D028340397") + self.assertEqual(value2,"5328F33739BEC2A15B6A30F17D3BC13CC11A7C78") +class TestAlgo(unittest.TestCase): + """ Test all statdard algorithms """ + def test_md5(self): + d=digest.new("md5") + self.assertEqual(d.digest_size,16) + d.update("A quick brown fox jumps over the lazy dog.") + self.assertEqual(d.hexdigest(),"DF756A3769FCAB0A261880957590C768") + + def test_md4(self): + d=digest.new("md4") + d.update("A quick brown fox jumps over the lazy dog.") + self.assertEqual(d.digest_size,16) + self.assertEqual(d.hexdigest(),"FAAED595A3E38BBF0D9B4B98021D200F") + def test_sha256(self): + d=digest.new("sha256") + d.update("A quick brown fox jumps over the lazy dog.") + self.assertEqual(d.digest_size,32) + self.assertEqual(d.hexdigest(),"FFCA2587CFD4846E4CB975B503C9EB940F94566AA394E8BD571458B9DA5097D5") + def test_sha384(self): + d=digest.new("sha384") + d.update("A quick brown fox jumps over the lazy dog.") + self.assertEqual(d.digest_size,48) + self.assertEqual(d.hexdigest(),"C7D71B1BA81D0DD028E79C7E75CF2F83169C14BA732CA5A2AD731151584E9DE843C1A314077D62B96B03367F72E126D8") + def test_sha512(self): + d=digest.new("sha512") + self.assertEqual(d.digest_size,64) + d.update("A quick brown fox jumps over the lazy dog.") + self.assertEqual(d.hexdigest(),"3045575CF3B873DD656F5F3426E04A4ACD11950BB2538772EE14867002B408E21FF18EF7F7B2CAB484A3C1C0BE3F8ACC4AED536A427353C7748DC365FC1A8646") + +if __name__ == "__main__": + unittest.main() -- 2.39.2