HMAC的维基百科解释是:hash-based message authentication code
,其实就是加了盐的单向散列算法。而HMAC的重点就是如何给要散列的数据加盐。
加盐公式如下:
解释一下上面的符号:
- ⊕ \oplus ⊕表示异或运算;
- m m m表示要散列的数据;
- a ∣ ∣ b a || b a∣∣b表示把 a a a加入 b b b,其实就是用散列算法把 a a a算一下,紧接着把 b b b算一下;
- i p a d ipad ipad是一个常量,值为
0x36
; - o p a d opad opad是一个常量,值为
0x5C
; - H H H表示散列算法,比如MD5,SHA256;
- K K K表示你要加入的盐, K ′ K^{'} K′表示转换过的盐,如果盐的长度大于块长(一般是64个字节,MD5,SHA1,SHA256它们处理的数据块都是这个长度)就用散列算法算一下,结果再作为盐;否者盐就是自己。
下面就是这个算法的具体步骤图解(以散列算法SHA1为例子)了:
这个图已经很清晰地表达了加盐散列的步骤,不是很清楚的可以待会看代码。
那么在C++中如何实现呢,先给出代码:
// hmac.h#define HMAC_SALT_UINT8 64class HMACDigest : public BaseDigest
{
public:enum HMACDigestType{MD5,SHA1,SHA256,};
public:HMACDigest(const void *saltByte, size_t saltLength, HMACDigestType type = SHA256);HMACDigest(const void *input, size_t length, const void *saltByte, size_t saltLength, HMACDigestType type = SHA256);~HMACDigest();virtual const byte *Digest() override;virtual void Reset() override;virtual size_t GetSize() override;protected:virtual void Update(const void *input, size_t length) override;private:HMACDigest(const HMACDigest &) = delete;HMACDigest & operator = (const HMACDigest &) = delete;private:void InitSalt(const void *saltByte, size_t saltLength);void Final();private:HMACDigestType _type;std::unique_ptr<BaseDigest> _digestAlgorithm;byte _salt[HMAC_SALT_UINT8];static uint8_t sIpad;static uint8_t sOpad;static uint32_t sDigestByteLength[3];
};
// hmac.cppuint8_t HMACDigest::sIpad = 0x36;
uint8_t HMACDigest::sOpad = 0x5C;
uint32_t HMACDigest::sDigestByteLength[3] =
{16, 20, 32,
};HMACDigest::HMACDigest(const void * saltByte, size_t saltLength, HMACDigestType type): _type(type)
{if (saltByte == nullptr || !saltLength){throw std::exception("salt is invalid");}_digestAlgorithm.reset(type == MD5 ? static_cast<BaseDigest *>(new Md5Digest): type == SHA1 ? static_cast<BaseDigest *>(new SHA1Digest): static_cast<BaseDigest *>(new SHA256Digest));InitSalt(saltByte, saltLength);
}
HMACDigest::HMACDigest(const void * input, size_t length, const void * saltByte, size_t saltLength, HMACDigestType type): HMACDigest::HMACDigest(saltByte, saltLength, type)
{Update(input, length);
}HMACDigest::~HMACDigest()
{Reset();std::fill(std::begin(_salt), std::end(_salt), 0);
}const HMACDigest::byte * HMACDigest::Digest()
{Final();return _digestAlgorithm->Digest();
}void HMACDigest::Reset()
{byte key[HMAC_SALT_UINT8];_digestAlgorithm->Reset();for (byte *ptr = key, *it = std::begin(_salt); it != std::end(_salt); *ptr++ = *it++ ^ sIpad){}_digestAlgorithm->Update(key, HMAC_SALT_UINT8);
}size_t HMACDigest::GetSize()
{return _digestAlgorithm->GetSize();
}void HMACDigest::Update(const void * input, size_t length)
{_digestAlgorithm->Update(input, length);
}void HMACDigest::InitSalt(const void * saltByte, size_t saltLength)
{std::memset(_salt, 0, sizeof(_salt));if (saltLength < HMAC_SALT_UINT8){std::memcpy(_salt, saltByte, saltLength);}else{_digestAlgorithm->Update(saltByte, saltLength);size_t byteCount = _digestAlgorithm->GetSize() >> 1;const byte *ptr = _digestAlgorithm->Digest();std::memcpy(_salt, ptr, byteCount);_digestAlgorithm->Reset();}Reset();
}void HMACDigest::Final()
{std::unique_ptr<byte[]> buff(new byte[sDigestByteLength[_type]]);std::memcpy(buff.get(), _digestAlgorithm->Digest(), sDigestByteLength[_type]);_digestAlgorithm->Reset();byte key[HMAC_SALT_UINT8];for (byte *ptr = key, *it = std::begin(_salt); it != std::end(_salt); *ptr++ = *it++ ^ sOpad){}_digestAlgorithm->Update(key, sizeof(key));_digestAlgorithm->Update(buff.get(), sDigestByteLength[_type]);
}
// BaseDigest Definitionclass BaseDigest
{
public:typedef uint8_t byte;BaseDigest();virtual ~BaseDigest();virtual const byte* Digest() = 0;virtual void Reset() = 0;virtual void Update(const void *input, size_t length);virtual std::string ToString(bool upCase = false){const byte *pstr = Digest();size_t length = GetSize();return BytesToHexString(pstr, length, upCase);}virtual size_t GetSize() = 0;
protected:virtual void UpdateImpl(const void *input, size_t length) = 0;static std::string BytesToHexString(const byte *input, size_t length, bool upCase);static void ByteReverse(uint32_t * buffer, int byteCount);
};
总结一下这段代码:
- 由于之前编写过SHA1和SHA256,发现这些单向散列算法都有一些共同的特征,于是可以抽象出一个基类
BaseDigest
,定义了单向散列算法所共有的东西; - 由于HMAC只是加盐,而散列算法有很多,于是这里要支持扩展,这也是抽象
BaseDigest
的意义,用多态来支持可变; HMACDigest
的默认加密算法是SHA256;- 代码和步骤可以相互印证,如果看懂了步骤图没看懂代码,可以对着步骤图来看代码,反之有效;
- 以上代码给出了核心部分,有一些函数可能需要自己补充,这里就不再赘述。
参考:
- HMAC - Wikipedia
- SHA1散列算法及其C++实现 - FlushHip的CSDN博客
- Zeus框架
最后,以上的两幅图片均来自于维基百科。