c++17 – Cryptographic hashing class using OpenSSL’s EVP interface

I have written a cryptographic hashing class named Ccksum::DigestHasher that allows the caller to select a hashing algorithm by name via OpenSSL’s EVP interface. This class is the bread and butter of my Ccksum programming project. Here is the code so far (I combined the header and source files for the readability of this post. I also removed all documentation comments, because I believe the code speaks for itself):

#include <iomanip>
#include <memory>
#include <openssl/evp.h>
#include <stdexcept>
#include <sstream>
#include <string>
#include <vector>

namespace Ccksum {
class Hasher {
public:
    explicit Hasher() = default;
    virtual ~Hasher() noexcept = default;
    virtual std::string process(std::istream& input) const = 0;
    virtual std::string name() const = 0;
};

class DigestHasher final : public Hasher {
public:
    explicit DigestHasher(const std::string& name)
        : m_context(EVP_MD_CTX_new(), EVP_MD_CTX_free)
        , m_algorithm(EVP_get_digestbyname(name.c_str()))
        , m_name(name)
    {
        if (m_context == nullptr) {
            throw std::runtime_error("Cannot create hash algorithm context");
        }

        if (m_algorithm == nullptr) {
            std::ostringstream errorMsg;
            errorMsg << name << " is not a valid hash algorithm";
            throw std::runtime_error(errorMsg.str());
        }
    }

    ~DigestHasher() noexcept override = default;

    std::string process(std::istream& input) const override
    {
        if (!input.good()) {
            throw std::runtime_error("Input stream is bad");
        }

        constexpr size_t bufferSize = 32768; // 32K seems good...
        std::vector<char> buffer(bufferSize);
        std::vector<unsigned char> hash(EVP_MD_size(m_algorithm));

        if (EVP_DigestInit_ex(m_context.get(), m_algorithm, nullptr) == 0) {
            throw std::runtime_error("Cannot initialize hash algorithm");
        }

        while (input.good()) {
            input.read(buffer.data(), bufferSize);
            if (EVP_DigestUpdate(m_context.get(), buffer.data(), input.gcount()) == 0) {
                throw std::runtime_error("Cannot update hash context");
            }
        }

       if (EVP_DigestFinal_ex(m_context.get(), hash.data(), nullptr) == 0) {
           throw std::runtime_error("Cannot finalize hash context");
       }

       std::ostringstream formatHash;
       formatHash << std::hex << std::setfill('0');

       for (const auto& iterate : hash) {
           formatHash << std::setw(2) << static_cast<unsigned int>(iterate);
       }

        return formatHash.str();
    }

    std::string name() const override
    {
        return m_name;
    }

private:
    std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> m_context;
    const EVP_MD* m_algorithm;
    const std::string m_name;
};
} // Ccksum namespace

NOTE: The Ccksum::Hasher abstract class exists for mocking purposes. My plan is to mock Ccksum::DigestHasher using Ccksum::Hasher to make it easier to unit test the Ccksum::GenerateMode class I am currently working on.

Question 1: Should I wrap m_algorithm in a std::unique_ptr or leave it the way it is?

Current rationale regarding question 1: I left m_algorithm as a raw const pointer of type EVP_MD, because the example code offered by OpenSSL’s docs does not show freeing its EVP_MD variable, nor did I find any kind of EVP_MD_free() function. A Stack Overflow answer states that there is no need to free EVP_MD at all. As a sanity check, I ran my code through valgrind and no memory leaks were found.