Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

A C++ SHA1 and MD5 Implementation with CryptoAPI

5.00/5 (23 votes)
20 Sep 2012CPOL4 min read 95.8K   8.3K  
This article proposes a C++ implementation for computing hashes (SHA1, MD5, MD4 and MD2) on Windows with the Microsoft CryptoAPI library.

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.

C++
template <ALG_ID algorithm>

class cryptohash_t;

template <ALG_ID algorithm>
class cryptohash_helper_t;

Several typedefs are provided for a simplified use of these template classes.

C++
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:

  • C++
    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.
  • C++
    bool update(unsigned char* const buffer, size_t size)
    Adds more data to the current hash. If the function fails, check the last error.
  • C++
    bool finalize()
    Computes the hash value from the current hash object and destroys the hash object. If the function fails, check the last error.
  • C++
    hash_t digest() const
    Retrieves the computed hash value in a binary form.
  • C++
    std::string hexdigest(bool uppercase = false) const
    Retrieves the computed hash value as a string of hex digits.
  • C++
    errorinfo_t lasterror() const
    Returns the last error.

cryptohash_helper_t has the following public interfaces:

  • C++
    hash_t digesttext(std::string const& text)
    Computes and returns the hash value (in binary format) of a text.
  • C++
    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.
  • C++
    hash_t digestfile(std::string const& filename)
    Computes and returns the hash value (in binary format) of a file.
  • C++
    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.
  • C++
    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

C++
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

C++
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

C++
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

C++
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.

Image 1

Additional Readings

History

  • 20th September, 2012: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)