Introduction
Cryptographic hash functions are widely used for securing communication, storing hash values of passwords in databases, verifying that a message or a file has been transmitted correctly between two entities and others. One of the most widely used algorithms is MD5 (Message Digest Five), which produces a 128-bit hash value, usually expressed as a 32 hex digits number. It is the successor of MD4 and MD2, all of them being developed by Ronald Rivest. Another hash algorithm, also based on MD4, is SHA-1, a cryptographically secure one-way hash algorithm that produces a 160-bit message digest (usually represented as a 40 hex digit number). This article proposes a C++ implementation for computing hashes (SHA1, MD5, MD4 and MD2) with the Microsoft CryptoAPI library. CryptoAPI requires minimum Windowx XP or Windows Server 2003.
Note: Both MD5 and SHA1 have been proved to have weaknesses. MD5 it is not collision resistant and has additional weaknesses, which makes it unsuitable for at least some security applications including SSL certificates. US-CERT has concluded that MD5 "should be considered cryptographically broken and unsuitable for further use". The US National Institute of Standards and Technology, the publisher of SHA1 is also currently looking for a replacement of SHA1.
A Few CryptoAPI Considerations
In order to generate a hash value with CyrptoAPI
, one must follow these steps:
- Initialize a context for cryptography operations
- CryptAcquireContext: acquires a handle to a key container within a particular cryptographic service provider; this handle is used with the subsequent calls to cryptography APIs.
- Create a hash object
- CryptCreateHash: creates a hash object for hashing a stream of data; returns a handle that is used in subsequent calls to hash data or session keys.
- Hash data
- CryptHashData: adds data to a previously created hash object. It can be called multiple times to add more data to the hash object.
- Retrieve the hash value
- CryptGetHashValue: retrieves data from the hash object. To retrieve the hash value, one must first call this method with
HP_HASHSIZE
to determine the size of the hash value, allocate the memory for the hash value and then call it with HP_HASHVAL
to get the hash value.
- Clean up by destroying the
hash
object and releasing the cryptographic context
- CryptDestroyHash: destroys a hash object previously created with
CryptCreateHash
. - CryptReleaseContext: releases the handle of a cryptographic service provider and a key container, previously acquired with a call to
CryptAcquireContext
.
Message Digest Implementation
My implementation consists of a couple of template C++ classes that provide: cryptohash_t
is the class that provides hashing for streams of data, and cryptohash_helper_t
is a helper class that provides simplified API for hashing tests and files. Both these classes are parameterized with the hash algorithm.
template <ALG_ID algorithm>
class cryptohash_t;
template <ALG_ID algorithm>
class cryptohash_helper_t;
Several typedef
s are provided for a simplified use of these template classes.
typedef cryptohash_t<CALG_MD2> md2_t;
typedef cryptohash_t<CALG_MD4> md4_t;
typedef cryptohash_t<CALG_MD5> md5_t;
typedef cryptohash_t<CALG_SHA1> sha1_t;
typedef cryptohash_helper_t<CALG_MD2> md2_helper_t;
typedef cryptohash_helper_t<CALG_MD4> md4_helper_t;
typedef cryptohash_helper_t<CALG_MD5> md5_helper_t;
typedef cryptohash_helper_t<CALG_SHA1> sha1_helper_t;
cryptohash_t
has the following public
interfaces:
-
bool begin()
Initializes the hashing routine, creating a hash object used for computing the hashing value of a stream of data. If the function fails, check the last error. -
bool update(unsigned char* const buffer, size_t size)
Adds more data to the current hash. If the function fails, check the last error. -
bool finalize()
Computes the hash value from the current hash object and destroys the hash object. If the function fails, check the last error. -
hash_t digest() const
Retrieves the computed hash value in a binary form. -
std::string hexdigest(bool uppercase = false) const
Retrieves the computed hash value as a string of hex digits. -
errorinfo_t lasterror() const
Returns the last error.
cryptohash_helper_t
has the following public
interfaces:
-
hash_t digesttext(std::string const& text)
Computes and returns the hash value (in binary format) of a text. -
std::string hexdigesttext(std::string const& text, bool uppercase = false)
Computes and returns the hash value (as a string of hex digits) of a text. -
hash_t digestfile(std::string const& filename)
Computes and returns the hash value (in binary format) of a file. -
std::string hexdigestfile(std::string const& filename, bool uppercase = false)
Computes and returns the hash value (as a string of hex digits) of a file. -
errorinfo_t lasterror() const
Returns the last error for any of the above functions.
Examples
Note: In all the following examples, you can simply use any of the provided hash types (md5_t
, md4_t
, md2_t
, sha1_t
) or the helper types (md5_helper_t
, md4_helper_t
, md2_helper_t
, sha1_helper_t
), without any other change to the sample and get the appropriate results.
Compute SHA-1 for a String
std::string text = "mariusbancila";
std::string digest;
sha1_t hasher;
if(hasher.begin())
{
if(hasher.update((unsigned char*)(text.c_str()), text.length()))
{
if(hasher.finalize())
{
digest = hasher.hexdigest();
}
}
}
if(digest.empty())
std::cout << "error code = " << hasher.lasterror().errorCode
<< ", error message = " << hasher.lasterror().errorMessage
<< std::endl;
else
std::cout << digest << std::endl;
Compute SHA-1 for Several Strings
std::string text1 = "marius";
std::string text2 = "bancila";
std::string digest;
sha1_t hasher;
if(hasher.begin())
{
if(hasher.update((unsigned char*)(text1.c_str()), text1.length()))
{
if(hasher.update((unsigned char*)(text2.c_str()), text2.length()))
{
if(hasher.finalize())
{
digest = hasher.hexdigest();
}
}
}
}
if(digest.empty())
std::cout << "error code = " << hasher.lasterror().errorCode
<< ", error message = " << hasher.lasterror().errorMessage
<< std::endl;
else
std::cout << digest << std::endl;
Compute MD5 for a String with a Helper Class
md5_helper_t hhasher;
std::string digest = hhasher.hexdigesttext("mariusbancila");
if(digest.empty())
std::cout << "error code = " << hhasher.lasterror().errorCode
<< ", error message = " << hhasher.lasterror().errorMessage
<< std::endl;
else
std::cout << digest << std::endl;
Compute MD5 for a File with a Helper Class
md5_helper_t hhasher;
std::string digest = hhasher.hexdigestfile("c:\\temp\\sample.png");
if(digest.empty())
std::cout << "error code = " << hhasher.lasterror().errorCode
<< ", error message = " << hhasher.lasterror().errorMessage
<< std::endl;
else
std::cout << digest << std::endl;
Demo Application
Attached to the project, you can find the sources and binaries of a demo application (written in Visual Studio 2008 SP1 with MFC) that uses these hashing template classes to generate hash values (SHA1, MD5, MD4 and MD2) for a text or a selected file. Using the application is straightforward and you can see a screen shot below.
Additional Readings
History
- 20th September, 2012: Initial version