用OpenSSL 做HMAC(C++)

参考:http://www.askyb.com/cpp/openssl-hmac-hasing-example-in-cpp/


名词解释:

HMAC: Hash-based Message Authentication Code,即基于Hash的消息鉴别码


(下面的algo_hmac.h, algo_hmac.cpp 可以直接拿来放到自己的工程中)

本文工程在这里下载

algo_hmac.h

#ifndef _ALGO_HMAC_H_
#define _ALGO_HMAC_H_

int HmacEncode(const char * algo,
        const char * key, unsigned int key_length,
        const char * input, unsigned int input_length,
        unsigned char * &output, unsigned int &output_length);

#endif

algo_hmac.cpp

#include "algo_hmac.h"
#include 
#include 
#include 
using namespace std;

int HmacEncode(const char * algo,
                const char * key, unsigned int key_length,
                const char * input, unsigned int input_length,
                unsigned char * &output, unsigned int &output_length) {
        const EVP_MD * engine = NULL;
        if(strcasecmp("sha512", algo) == 0) {
                engine = EVP_sha512();
        }
        else if(strcasecmp("sha256", algo) == 0) {
                engine = EVP_sha256();
        }
        else if(strcasecmp("sha1", algo) == 0) {
                engine = EVP_sha1();
        }
        else if(strcasecmp("md5", algo) == 0) {
                engine = EVP_md5();
        }
        else if(strcasecmp("sha224", algo) == 0) {
                engine = EVP_sha224();
        }
        else if(strcasecmp("sha384", algo) == 0) {
                engine = EVP_sha384();
        }
        else if(strcasecmp("sha", algo) == 0) {
                engine = EVP_sha();
        }
        else if(strcasecmp("md2", algo) == 0) {
                engine = EVP_md2();
        }
        else {
                cout << "Algorithm " << algo << " is not supported by this program!" << endl;
                return -1;
        }

        output = (unsigned char*)malloc(EVP_MAX_MD_SIZE);

        HMAC_CTX ctx;
        HMAC_CTX_init(&ctx);
        HMAC_Init_ex(&ctx, key, strlen(key), engine, NULL);
        HMAC_Update(&ctx, (unsigned char*)input, strlen(input));        // input is OK; &input is WRONG !!!

        HMAC_Final(&ctx, output, &output_length);
        HMAC_CTX_cleanup(&ctx);

        return 0;
}
main.cpp

#include "algo_hmac.h"
#include 
#include 
#include 
#include 
#include 
using namespace std;

int main(int argc, char * argv[])
{
        if(argc < 2) {
                cout << "Please specify a hash algorithm!" << endl;
                return -1;
        }

        char key[] = "012345678";
        string data = "hello world";

        unsigned char * mac = NULL;
        unsigned int mac_length = 0;

        int ret = HmacEncode(argv[1], key, strlen(key), data.c_str(), data.length(), mac, mac_length);

        if(0 == ret) {
                cout << "Algorithm HMAC encode succeeded!" << endl;
        }
        else {
                cout << "Algorithm HMAC encode failed!" << endl;
                return -1;
        }

        cout << "mac length: " << mac_length << endl;
        cout << "mac:";
        for(int i = 0; i < mac_length; i++) {
                printf("%-03x", (unsigned int)mac[i]);
        }
        cout << endl;

        if(mac) {
                free(mac);
                cout << "mac is freed!" << endl;
        }

        return 0;
}
Makefile

LNK_OPT = -g -L/usr/lib64/ -lssl

all:
	g++ -g -c algo_hmac.cpp
	g++ -g main.cpp -o test algo_hmac.o $(LNK_OPT)

clean:
	rm -f *.o
	rm -f test

运行结果

[root@ampcommons02 hmac]# ./test sha1
Algorithm HMAC encode succeeded!
mac length: 20
mac:e1 9e 22 1  22 b3 7b 70 8b fb 95 ac a2 57 79 5  ac ab f0 c0
mac is freed!

[root@ampcommons02 hmac]# ./test sha256
Algorithm HMAC encode succeeded!
mac length: 32
mac:8b 4c 3e 7  ad 88 7a 8a 81 d0 80 ce d7 a3 bb 27 db a6 53 5f 28 fd 5e f1 45 2a 40 a6 e9 66 80 42
mac is freed!

[root@ampcommons02 hmac]# ./test sha512
Algorithm HMAC encode succeeded!
mac length: 64
mac:40 81 e3 29 1e ec 15 4  91 10 e3 b8 d3 af 74 78 20 d1 c5 b5 39 f3 7b d7 72 49 a6 3c aa a6 e5 82 83 1  ae 2b 34 46 b1 ee 1c 45 39 d4 18 a6 4b 44 12 22 9f 9d 7  8e dc c7 8  8b 3b 41 b9 3c e6 e3
mac is freed!

[root@ampcommons02 hmac]# ./test md5
Algorithm HMAC encode succeeded!
mac length: 16
mac:49 d5 2c b  92 54 b8 65 2b ea af fc 8d 7e 4c 21
mac is freed!
各种算法得到的摘要的长度

算法 摘要长度(字节)
MD2 16
MD5 16
SHA 20
SHA1 20
SHA224 28
SHA256 32
SHA384 48
SHA512 64









注意

1)参考的帖子中的hmac_sample1.cpp,第13行没有为digest申请空间,结果在第25行之后,做free(digest)会crash!但是,即使在第13行申请了足够的空间,比如1024字节,在第25行之后free(digest)还是会crash,原因如下:

参考 hmac.c 实现代码, 如果传入的 md 和 md_len 是NULL,则 HMAC 返回的是该函数里面定义的 static 的buffer的地址 m。这样的m,由于是静态分配的,不能在外面 free。

unsigned char *HMAC(const EVP_MD *evp_md, const void *key, int key_len,
    const unsigned char *d, int n, unsigned char *md,
    unsigned int *md_len)
{
	HMAC_CTX c;
	static unsigned char m[EVP_MAX_MD_SIZE];

	if (md == NULL) md=m;
	HMAC_CTX_init(&c);
	HMAC_Init(&c,key,key_len,evp_md);
	HMAC_Update(&c,d,n);
	HMAC_Final(&c,md,md_len);
	HMAC_CTX_cleanup(&c);
	return(md);
}
因此, 建议使用 md, md_len 参数作为传出值

2)参考的帖子中的hmac_sample1.cpp,第23行的 mdString[i*2] 越界了,因为第22行只申请了20字节,应改成 mdString[i]。

3)参考的帖子中的hmac_sample2.cpp,第17行有为result申请空间,在第36行做free(result)操作不会crash。但是根据上面表格所列的各种算法的摘要长度,最大的长度为64,于是申请的空间的大小可以用Openssl中的常量EVP_MAX_MD_SIZE,它的值就是64

4)参考的帖子中的hmac_sample2.cpp,第17行为result申请空间的操作,可以封装到HmacEncode() 函数里面,使用者不用关心究竟要分配多少空间,传进一个指针即可,用完后free 它就可以了

5)HmacEncode 函数的output_length 参数是传出参数,HmacEncode 执行结束后,会传出HMAC的长度

6)发现一个问题:对于main() 中传给HmacEncode() 的参数input,如果它的长度≤56,那么如果其他参数不变的话,每次计算得到的结果是一致的;在其他参数不变的情况下,如果input的长度大于56,则每次计算的结果是不一致的!也就是说,这里给成的代码,对于长度≤56字节的待编码字串,每次执行的结果是一致的,但对于长度大于56字节的待编码字串,不能保证输出结果的一致性!难道说,algo_hmac.cpp中第43行的HMAC_Update() 有可能要执行多次多次,即,把input按56个字节为一段的方式分成多段,多所有的分段依次调用HMAC_Update()?事实证明,原因不在于此。

对于6)的问题,后来发现,如果把const char * input 输入参数去掉,把char data[] = "hello world";写在HmacEncode 里面,就不存在5)的问题。出错的根本原因在于:

HMAC_Update() 函数的第二个参数是const unsigned char *类型的,而不是 const unsigned char ** data 类型的:int HMAC_Update(HMAC_CTX *ctx, const unsigned char *data, int len); 之所以参考的帖子中,以及把char data[] = "hello world";写在HmacEncode 里面,没有出错,是因为,对于数组char data[],用指针形式输出data 和 &data,值是一样的;而对于char * p,用指针形式输出p 和 &p,值是不一样的对于数组char data[],&data仍然可以作为const unsigned char *类型变量传给HMAC_Update() 函数,传入的值不变;而对于 char * p,&p 实际上已经是 char ** 类型的了,并且它值和p 不一样,实际上,这个临时变量p的地址在程序每次运行时都是不一样的,于是,每次实际上传给HMAC_Update() 函数的东西都是变化的,怪不得每次执行的最终结果都不一样了!(上面贴的algo_hmac.cpp中的代码是没有问题的,但请注意第46行的注释!

详情见《C 字符串数组和char*指针在做&操作时的区别》。


【补充】

https://www.openssl.org/docs/crypto/hmac.html 这里介绍的函数

unsigned char *HMAC(const EVP_MD *evp_md, const void *key,
               int key_len, const unsigned char *d, int n,
               unsigned char *md, unsigned int *md_len);
貌似更方便。第一个参数 evp_md可以是 VP_sha1()等。

你可能感兴趣的:(用OpenSSL 做HMAC(C++))