(3条消息) 用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

  1. #ifndef _ALGO_HMAC_H_
  2. #define _ALGO_HMAC_H_
  3. int HmacEncode(const char * algo,
  4. const char * key, unsigned int key_length,
  5. const char * input, unsigned int input_length,
  6. unsigned char * &output, unsigned int &output_length);
  7. #endif

algo_hmac.cpp

  1. #include "algo_hmac.h"
  2. #include <openssl/hmac.h>
  3. #include <string.h>
  4. #include <iostream>
  5. using namespace std;
  6. int HmacEncode(const char * algo,
  7. const char * key, unsigned int key_length,
  8. const char * input, unsigned int input_length,
  9. unsigned char * &output, unsigned int &output_length) {
  10. const EVP_MD * engine = NULL;
  11. if(strcasecmp("sha512", algo) == 0) {
  12. engine = EVP_sha512();
  13. }
  14. else if(strcasecmp("sha256", algo) == 0) {
  15. engine = EVP_sha256();
  16. }
  17. else if(strcasecmp("sha1", algo) == 0) {
  18. engine = EVP_sha1();
  19. }
  20. else if(strcasecmp("md5", algo) == 0) {
  21. engine = EVP_md5();
  22. }
  23. else if(strcasecmp("sha224", algo) == 0) {
  24. engine = EVP_sha224();
  25. }
  26. else if(strcasecmp("sha384", algo) == 0) {
  27. engine = EVP_sha384();
  28. }
  29. else if(strcasecmp("sha", algo) == 0) {
  30. engine = EVP_sha();
  31. }
  32. else if(strcasecmp("md2", algo) == 0) {
  33. engine = EVP_md2();
  34. }
  35. else {
  36. cout << "Algorithm " << algo << " is not supported by this program!" << endl;
  37. return -1;
  38. }
  39. output = (unsigned char*)malloc(EVP_MAX_MD_SIZE);
  40. HMAC_CTX ctx;
  41. HMAC_CTX_init(&ctx);
  42. HMAC_Init_ex(&ctx, key, strlen(key), engine, NULL);
  43. HMAC_Update(&ctx, (unsigned char*)input, strlen(input)); // input is OK; &input is WRONG !!!
  44. HMAC_Final(&ctx, output, &output_length);
  45. HMAC_CTX_cleanup(&ctx);
  46. return 0;
  47. }

main.cpp

  1. #include "algo_hmac.h"
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <iostream>
  5. #include <algorithm>
  6. #include <string>
  7. using namespace std;
  8. int main(int argc, char * argv[])
  9. {
  10. if(argc < 2) {
  11. cout << "Please specify a hash algorithm!" << endl;
  12. return -1;
  13. }
  14. char key[] = "012345678";
  15. string data = "hello world";
  16. unsigned char * mac = NULL;
  17. unsigned int mac_length = 0;
  18. int ret = HmacEncode(argv[1], key, strlen(key), data.c_str(), data.length(), mac, mac_length);
  19. if(0 == ret) {
  20. cout << "Algorithm HMAC encode succeeded!" << endl;
  21. }
  22. else {
  23. cout << "Algorithm HMAC encode failed!" << endl;
  24. return -1;
  25. }
  26. cout << "mac length: " << mac_length << endl;
  27. cout << "mac:";
  28. for(int i = 0; i < mac_length; i++) {
  29. printf("%-03x", (unsigned int)mac[i]);
  30. }
  31. cout << endl;
  32. if(mac) {
  33. free(mac);
  34. cout << "mac is freed!" << endl;
  35. }
  36. return 0;
  37. }

Makefile

LNK_OPT = -g -L/usr/lib64/ -lsslall:g++ -g -c algo_hmac.cppg++ -g main.cpp -o test algo_hmac.o $(LNK_OPT)clean:rm -f *.orm -f test

运行结果

[root@ampcommons02 hmac]# ./test sha1Algorithm HMAC encode succeeded!mac length: 20mac:e1 9e 22 1  22 b3 7b 70 8b fb 95 ac a2 57 79 5  ac ab f0 c0mac is freed![root@ampcommons02 hmac]# ./test sha256Algorithm HMAC encode succeeded!mac length: 32mac: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 42mac is freed![root@ampcommons02 hmac]# ./test sha512Algorithm HMAC encode succeeded!mac length: 64mac: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 e3mac is freed![root@ampcommons02 hmac]# ./test md5Algorithm HMAC encode succeeded!mac length: 16mac:49 d5 2c b  92 54 b8 65 2b ea af fc 8d 7e 4c 21mac 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。

  1. unsigned char *HMAC(const EVP_MD *evp_md, const void *key, int key_len,
  2. const unsigned char *d, int n, unsigned char *md,
  3. unsigned int *md_len)
  4. {
  5. HMAC_CTX c;
  6. static unsigned char m[EVP_MAX_MD_SIZE];
  7. if (md == NULL) md=m;
  8. HMAC_CTX_init(&c);
  9. HMAC_Init(&c,key,key_len,evp_md);
  10. HMAC_Update(&c,d,n);
  11. HMAC_Final(&c,md,md_len);
  12. HMAC_CTX_cleanup(&c);
  13. return(md);
  14. }

因此,建议使用 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()等。

(0)

相关推荐