思路:获取当前的时间戳/60(一分钟内任意时刻的时间戳/60为固定的值,对于C/C++),使用HMAC得到摘要,摘要进行位运算处理得到6位的动态口令
TOTP.hpp
#ifndef __TOTP_H
#define __TOTP_H
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <ctime>
#include <iomanip>
#include <algorithm>
//密钥传入HMAC
#define PUBLIC_KEY "AE448C9CFF39F43B9ED7358852F8CC6BF3D92B2B"
#define CC_SHA1_DIGEST_LENGTH 20
#define CC_SHA256_DIGEST_LENGTH 32
#define CC_SHA512_DIGEST_LENGTH 64
class TOTP
{
public:
enum class Hash {HMACSHA1, HMACSHA256, HMACSHA512};
private:
static const int DIGITS_POWER[];
#ifdef __APPLE__
static void hmac_sha(Hash hash, const void *key, size_t key_len, const void *data, size_t data_len, void *mac_out);
#else
static unsigned char * hmac_sha(Hash hash, const void *key, int key_len, const unsigned char *d, int n, unsigned char *md, unsigned int *md_len);
#endif
static void hexStr2Bytes(std::string &hex,std::vector<unsigned char> &bytes);
public:
//sha1
static std::string generateTOTP(std::string &key, std::string &time, std::string &returnDigits);
//sha256
static std::string generateTOTP256(std::string &key, std::string &time, std::string &returnDigits);
//sha512
static std::string generateTOTP512(std::string &key, std::string &time, std::string &returnDigits);
//使用HMAC接口传入不同的算法(多样性)
static std::string generateTOTP(std::string &key, std::string &time, std::string &returnDigits, Hash hash);
//时间戳转为16进制
static std::string toHexString(long T);
static int ET_CheckPwdz201(const std::string& otp, int otplen=6, long X=60, time_t T0=0, long t=0);
};
#endif
TOTP.cpp
#ifdef __APPLE__
#include <CommonCrypto/CommonHMAC.h>
#else
#include <openssl/evp.h>
#include <openssl/hmac.h>
#endif
#include <vector>
#include "TOTP.h"
const int TOTP::DIGITS_POWER[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
//16进制数转为字节流匹配HMAC接口参数
void TOTP::hexStr2Bytes(std::string &hex,std::vector<unsigned char> &bytes)
{
std::stringstream ss;
unsigned int buffer;
unsigned int offset = 0;
while (offset < hex.length())
{
ss.clear();
ss << std::hex << hex.substr(offset, 2);
ss >> buffer;
bytes.emplace_back(static_cast<unsigned char>(buffer));
offset += 2;
}
}
#ifdef __APPLE__
void TOTP::hmac_sha(Hash hash, const void *key, size_t key_len, const void *data, size_t data_len, void *mac_out)
{
CCHmacAlgorithm algorithm;
switch (hash) {
default:
case Hash::HMACSHA1: algorithm = kCCHmacAlgSHA1; break;
case Hash::HMACSHA256: algorithm = kCCHmacAlgSHA256; break;
case Hash::HMACSHA512: algorithm = kCCHmacAlgSHA512; break;
}
CCHmac(algorithm, key, key_len, data, data_len, mac_out);
}
#else
unsigned char * TOTP::hmac_sha(Hash hash, const void *key, int key_len, const unsigned char *d, int n, unsigned char *md, unsigned int *md_len)
{
const EVP_MD *evp_md;
switch(hash)
{
default:
case Hash::HMACSHA1: evp_md = EVP_sha1(); break;
case Hash::HMACSHA256: evp_md = EVP_sha256(); break;
case Hash::HMACSHA512: evp_md = EVP_sha512(); break;
}
//使用openssl中的HMAC接口生成摘要hmac
return HMAC(evp_md, key, key_len, d, n, md, md_len);
}
#endif
std::string TOTP::generateTOTP(std::string &key, std::string &time, std::string &returnDigits)
{
return generateTOTP(key, time, returnDigits, Hash::HMACSHA1);
}
std::string TOTP::generateTOTP256(std::string &key, std::string &time, std::string &returnDigits)
{
return generateTOTP(key, time, returnDigits, Hash::HMACSHA256);
}
std::string TOTP::generateTOTP512(std::string &key, std::string &time, std::string &returnDigits)
{
return generateTOTP(key, time, returnDigits, Hash::HMACSHA512);
}
//当前时间戳通过openssl中的HMAC求出摘要从而获得(六位的验证码)
std::string TOTP::generateTOTP(std::string &key, std::string &time, std::string &returnDigits, Hash hash)
{
unsigned int codeDigits = atoi(returnDigits.c_str());
std::string result;
while (time.length() < 16)
{
time = "0" + time;
}
std::vector<unsigned char> msg;
std::vector<unsigned char> k;
hexStr2Bytes(time,msg);
hexStr2Bytes(key,k);
unsigned int HASH_LENGTH;
switch (hash) {
default:
case Hash::HMACSHA1: HASH_LENGTH = CC_SHA1_DIGEST_LENGTH; break;
case Hash::HMACSHA256: HASH_LENGTH = CC_SHA256_DIGEST_LENGTH; break;
case Hash::HMACSHA512: HASH_LENGTH = CC_SHA512_DIGEST_LENGTH; break;
}
unsigned char hmac[HASH_LENGTH];
unsigned int hmac_length;
#ifdef __APPLE__
hmac_sha(hash, k.data(), k.size(), msg.data(), msg.size(), hmac);
hmac_length = HASH_LENGTH;
#else
hmac_sha(hash, k.data(), k.size(), msg.data(), msg.size(), hmac, &hmac_length);
#endif
//取摘要hmac最后一个字节的后四位 offset范围0-15 (哪一位无所谓)
int offset = hmac[hmac_length - 1] & 0xf;
//随机取任意几个字节 位运算(随机性使得binary值不确定) 此处位运算可以自己随意设置
int binary =
((hmac[offset] & 0x7f) << 24) |
((hmac[offset + 1] & 0xff) << 16) |
((hmac[offset + 2] & 0xff) << 8) |
(hmac[offset + 3] & 0xff);
int otp = binary % DIGITS_POWER[codeDigits];
result = std::to_string(otp);
//补0满6位 验证码
while (result.length() < codeDigits)
result = "0" + result;
return result;
}
//时间戳转换为16进制存到string
std::string TOTP::toHexString(long T)
{
std::string hex;
std::ostringstream oss;
oss << std::hex << T;
hex = oss.str();
std::transform(hex.begin(), hex.end(), hex.begin(), ::toupper);
return hex;
}
//输入的验证码和计算的作为比较相同则进入我们的客户端否则不能启动
int TOTP::ET_CheckPwdz201(const std::string& otp, int otplen, long X, time_t T0, long t)
{
std::string steps = "";
std::string return_digits = std::to_string(otplen);
time_t ltime = t;
long T;
//时间戳/60(一分钟内的任何时刻的时间戳/60后的值相同,这里取long型)
T = (ltime - T0) / X;
steps = toHexString(T);
std::string publicKey = PUBLIC_KEY;
std::string passwd = TOTP::generateTOTP(publicKey, steps, return_digits, TOTP::Hash::HMACSHA1);
/* 第一次验证*/
if (passwd == otp)
{
return 0;
}
//增加容错 防止口令在使用是为58 59秒而输入时已经下一分钟了(给出前后两秒的容错)
/* 若在两秒的范围内可以认为密码正确 */
time_t i = ltime - 2;
while (i < ltime + 3)
{
if (i == ltime)
{
++i;
continue;
}
T = (i - T0) / X;
steps = toHexString(T);
passwd = TOTP::generateTOTP(publicKey, steps, return_digits, TOTP::Hash::HMACSHA1);
if (passwd == otp)
{
return 0;
}
++i;
}
return -1;
}