简介
国产SM3是中国国家密码管理局2010年公布的中国商用密码杂凑算法标准。SM3算法适用于商用密码应用中的数字签名和验证,是在SHA-256基础上改进实现的一种算法。SM3算法采用Merkle-Damgard结构,消息分组长度为512位,摘要值长度为256位。
EVP是OpenSSL提供的一组高级接口,使用EVP可以通过一致简单的接口调用不同的算法。
单次哈希
单次哈希就是只计算一次哈希,适用于数据较短的情况,可以一次完成计算。
OpenSSL提供了接口来直接计算一段数据的哈希值:
int EVP_Digest(const void *data, size_t count, unsigned char *md, unsigned int *size,
const EVP_MD *type, ENGINE *impl);
参数都比较好理解,data
和 count
为输入数据和数据长度,md
和 size
分别哈希值和哈希值长度,type
为使用的哈希算法,impl
是要使用的计算引擎,一般设为 NULL
使用默认的引擎。
一个简单的例子,计算"hello world"的哈希值:
// sm3_str.c
#include <openssl/evp.h>
#include <stdio.h>
#include <string.h>
static unsigned int HASH_RESULT_LEN = 32;
/**
* @brief 计算一段数据的哈希值
*
* @param str 数据
* @param len 数据长度
* @param hash_result 哈希值
* @return unsigned int 哈希值长度
*/
unsigned int hash_str(const char* str, const size_t len,
unsigned char* hash_result)
{
unsigned int ret;
const EVP_MD* alg = EVP_sm3();
EVP_Digest(str, len, hash_result, &ret, alg, NULL);
return ret;
}
int main(int argc, char const* argv[])
{
char* str = "hello world";
// HASH_RESULT_LEN = EVP_MD_size(EVP_sm3());
unsigned char hash_result[HASH_RESULT_LEN];
unsigned int retlen = hash_str(str, strlen(str), hash_result);
printf("hash '%s', return len=%d\nhash val=", str, retlen);
for (int i = 0; i < HASH_RESULT_LEN; i++) {
printf("%02x", hash_result[i]);
}
printf("\n");
return 0;
}
我们在代码第20行调用 EVP_sm3()
函数来使用SM3算法计算哈希,如果想使用其他的哈希算法,可以调用其他类似的函数,如:EVP_md5()
, EVP_sha256()
等。第28行的 EVP_MD_size
用来计算相应哈希算法的哈希值长度,如果不知道一个算法返回的哈希值长度的话可以先用次函数得到。
编译执行:
$ gcc -Wall sm3_str.c -lcrypto -std=c11 -o sm3_str
$ ./sm3_str
hash 'hello world', return len=32
hash val=44F0061E69FA6FDFC290C494654A05DC0C053DA7E5C52B84EF93A9D67D3FFF88
可以使用OpenSSL提供的命令行工具对结果进行验证:
$ echo -n "hello world" | openssl sm3
(stdin)= 44f0061e69fa6fdfc290c494654a05dc0c053da7e5c52b84ef93a9d67d3fff88
多次哈希
有时可能数据较大或其他原因不能将所有的数据放在读入内存,可以多次读取多次计算得到数据的数据值,OpenSSL提供了一组实现这种功能的函数:
int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl);
int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *d, size_t cnt);
int EVP_DigestFinal_ex(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s);
一个简单的例子:
// sm3_file.c
#include <openssl/evp.h>
#include <stdio.h>
#include <string.h>
// 哈希值长度(字节)
static const unsigned int HASH_RESULT_LEN = 32;
static const size_t BUFSIZE = 8;
/**
* @brief 计算文件中的数据哈希值
*
* @param fp 文件结构体
* @param hash_result 文件哈希值
*/
void hash_file(FILE* fp, unsigned char* hash_result)
{
const EVP_MD* md = EVP_sm3();
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
char buf[BUFSIZE];
size_t nread = 0;
EVP_DigestInit_ex(ctx, md, NULL);
while ((nread = fread(buf, 1, BUFSIZE, fp)) > 0) {
EVP_DigestUpdate(ctx, buf, nread);
}
EVP_DigestFinal_ex(ctx, hash_result, NULL);
EVP_MD_CTX_free(ctx);
return;
}
int main(int argc, char const* argv[])
{
if (argc != 2) {
printf("usage: %s <file name>\n", argv[0]);
return 1;
}
FILE* fp = fopen(argv[1], "r");
if (!fp) {
printf("%s open failed.\n", argv[1]);
return 1;
}
unsigned char hash_result[HASH_RESULT_LEN];
hash_file(fp, hash_result);
printf("SM3(%s)= ", argv[1]);
for (int i = 0; i < HASH_RESULT_LEN; i++) {
printf("%02x", hash_result[i]);
}
printf("\n");
fclose(fp);
return 0;
}
我们在第8行定义了每次读取8个字节,避免fread函数一次读取完文件的所有数据,达不到多次计算哈希的效果。
编译运行:
$ gcc -g -Wall sm3_file.c -lcrypto -std=c11 -o sm3_file
$ ./sm3_file sm3_file.c
SM3(sm3_file.c)= 124834c2c678680ef6ead002232f7d5acfb55778446c39719aca8821a68a7fdf
和OpenSSL命令行的结果进行对比:
$ cat sm3_file.c | openssl sm3
(stdin)= 124834c2c678680ef6ead002232f7d5acfb55778446c39719aca8821a68a7fdf
其他
相关代码位于gitee仓库,还包括其他SM2,SM3, SM4,数字信封,RSA,MD5,SHA等代码。