C++ Crypto: Part 2- HMAC
Avoid using the same type to store the results of plain digests and HMACs
You can't (or at least should never be able to) compare a HMAC to a plain digest. So would be good if the type system could catch that potential mistake. Instead of having a DigestStore&lt;Hash&gt; class that is used both for plain digests and HMACs, I would just have Digest&lt;Hash&gt; and HMAC&lt;Hash&gt; each store their own result directly.
Allow adding data to the HMAC in multiple steps
As mentioned in the review for part 1, it is not uncommon to have to add multiple, discontiguous pieces of data to be added to the HMAC, so have a member function add() that can update the HMAC. This would mean splitting the creation of the HMAC in three parts:

The key material is prepared as part of the constructor
The message is added to the hash, either in one go or using multiple function calls
The final value is calculated

I would structure the class like so:
template&lt;typename Hash&gt;
class HMAC {
    Digest&lt;Hash&gt; outer_digest;
    Digest&lt;Hash&gt; inner_digest;

public:
    HMAC(std::string_view key) {
        // Add key XOR opad to outer_digest
        // Add key XOR ipad to inner_digest
    }

    // Convenience constructor to do a one-shot HMAC creation
    HMAC(std::string_view key, std::string_view message): HMAC(key) {
        add(message);
        finish();
    }

    void add(std::string_view message) {
        // Add message to inner_digest
    }

    void finish() {
        // Finish inner_digest, add it to outer_digest
        // Finish outer_digest
    }

    // Something to get the bits out
    const auto &amp;get() {
        return outer_digest.get();
    }
};

You might also want to add some way to prevent finish() from being called more than once.
Avoid unsafe operations
Your own words:

I just hate reading these badly written C++ (which is why I started this hack) projects that are crappy wrappers around C rather than using good type safety and nice clean C++ interfaces and techniques.

You want good type safety, but I also think you want good safety in general. Creating unsafe functions goes against that goal. If you store the results of a hash in a Digest object, and have a way to get a const reference to the data it stores, then you don't need a hashUnsafe() function.