Linux C语言调用OpenSSL: 摘要算法(SHA-2和SHA-3)
摘要算法概述
OpenSSL库支持多种摘要算法,也称为哈希算法或散列算法。常见的摘要算法主要有MD5、SHA-1、SHA-2 系列 (SHA-224, SHA-256, SHA-384, SHA-512)、SHA-3 系列 (SHA3-224, SHA3-256, SHA3-384, SHA3-512)、RIPEMD-160、WHIRLPOOL。
MD5(Message-Digest Algorithm 5):
特点:MD5算法生成的散列值是128位(16字节)的。它的设计简单且易于实现,因此在早期被广泛使用。
安全性:然而,随着计算机技术的发展,MD5算法已被证明存在安全漏洞,如碰撞攻击(collision attacks)。因此,现在不再推荐将MD5用于需要高安全性的场景,如密码存储或数字签名。
SHA-1(Secure Hash Algorithm 1):
特点:SHA-1算法生成的散列值是160位(20字节)的。与MD5相比,SHA-1在安全性上有所提高,但在过去的几年中也发现了针对其的碰撞攻击。
安全性:虽然SHA-1在一段时间内被广泛使用,但由于其安全性问题,现在已被视为不安全,并逐渐被SHA-2和SHA-3等更安全的算法所取代。
SHA-2(Secure Hash Algorithm 2):
特点:SHA-2是一个哈希函数系列,包括SHA-224、SHA-256、SHA-384和SHA-512等不同的变体。这些算法生成的散列值长度分别为224位、256位、384位和512位。SHA-2在设计上考虑了安全性,并且至今尚未发现针对其的实用碰撞攻击。
安全性:SHA-2被广泛认为是安全的,并被许多标准和协议(如TLS/SSL、SSH等)采用为默认的哈希算法。
SHA-3(Secure Hash Algorithm 3):
特点:SHA-3是NIST(美国国家标准与技术研究院)在2015年发布的最新的哈希函数标准。它提供了一个哈希函数系列,包括SHA3-224、SHA3-256、SHA3-384和SHA3-512等不同的变体。SHA-3的设计基于海绵函数(sponge function)的概念,旨在提供更高的安全性和灵活性。
安全性:SHA-3被设计为在安全性上超越SHA-2,并且尚未发现针对其的实用碰撞攻击。因此,SHA-3被视为当前最安全的哈希算法之一。
调用函数介绍
本文只介绍SHA-2和SHA-3算法的调用。对于该摘要算法,主要会使用到下列函数:
EVP_MD_CTX *EVP_MD_CTX_new(void);
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);
void EVP_MD_CTX_free(EVP_MD_CTX *ctx);
调用流程
EVP_MD_CTX_new
用于创建一个新的 EVP_MD_CTX 上下文,该上下文用于进行消息摘要(哈希)运算。VP_MD_CTX_new 函数没有直接的参数,因为它不需要任何输入来初始化一个新的上下文。
#include <openssl/evp.h>
EVP_MD_CTX *EVP_MD_CTX_new(void);
返回值:
如果成功,该函数将返回一个指向新分配的 EVP_MD_CTX 结构的指针。
如果内存分配失败或发生其他错误,该函数将返回NULL。
注意:
使用 EVP_MD_CTX_new 创建的上下文应该在使用完毕后通过 EVP_MD_CTX_free 函数释放,以避免内存泄漏。
EVP_DigestInit_ex
用于初始化一个EVP_MD_CTX上下文,以便进行消息摘要(哈希)运算。
#include <openssl/evp.h>
int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl);
参数:
EVP_MD_CTX *ctx:
这是一个指向EVP_MD_CTX结构的指针,表示要进行哈希运算的上下文。该结构包含了哈希运算的状态信息。
const EVP_MD *type:
这是一个指向EVP_MD结构的指针,表示要使用的哈希算法类型。OpenSSL库提供了多种哈希算法,如MD5、SHA1、SHA256等,每种算法都有对应的EVP_MD结构。你可以通过调用如EVP_sha256()这样的函数来获取特定算法的EVP_MD指针。
ENGINE *impl:
这是一个指向ENGINE结构的指针,表示用于实现哈希算法的引擎。在大多数情况下,你可以将此参数设置为NULL,以使用OpenSSL库内置的默认实现。然而,如果你正在使用特定的硬件加速器或自定义的哈希算法实现,你可以通过提供一个非NULL的ENGINE指针来指定它。
返回值:
如果函数成功,它将返回1。如果发生错误,它将返回0。使用OpenSSL的错误处理函数(如ERR_get_error)来获取更详细的错误信息。
注意:
在初始化EVP_MD_CTX上下文之后,你可以使用EVP_DigestUpdate函数添加要哈希的数据,并使用EVP_DigestFinal_ex函数来获取最终的哈希值。完成哈希运算后,你应该使用EVP_MD_CTX_free函数来释放EVP_MD_CTX上下文以避免内存泄漏。
EVP_DigestUpdate
用于在哈希计算过程中添加更多的数据。这个函数可以将数据分成多个块,并逐个块地更新哈希状态,而不是一次性提供所有数据。
#include <openssl/evp.h>
int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *d, size_t cnt);
参数:
EVP_MD_CTX *ctx:
这是一个指向 EVP_MD_CTX 结构的指针,该结构包含了哈希操作的当前状态。通常,首先使用 EVP_DigestInit_ex 函数初始化这个上下文,并指定要使用的哈希算法(如 SHA256、MD5 等)。
const void *d:
这是一个指向要添加到哈希中的数据的指针。这个数据可以是任何类型(例如,字节数组、字符串等),但由于它被视为 void * 类型的指针,因此你需要确保在调用函数时正确地传递它。
size_t cnt:
这是一个整数,表示 d 指针指向的数据的长度(以字节为单位)。这个值确保 EVP_DigestUpdate 知道要处理多少数据。
返回值:
如果函数成功,它将返回 1。如果发生错误,它将返回 0。
注意:
EVP_DigestUpdate 只是将数据添加到哈希上下文中。要获取最终的哈希值,你需要使用 EVP_DigestFinal_ex 函数。
从 OpenSSL 1.1.0 开始,EVP_MD_CTX_init 和 EVP_MD_CTX_cleanup 已被 EVP_MD_CTX_new 和 EVP_MD_CTX_free 取代。
EVP_DigestFinal_ex
用于完成哈希运算并获取最终的摘要值。
#include <openssl/evp.h>
int EVP_DigestFinal_ex(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s);
参数:
EVP_MD_CTX *ctx:
这是一个指向EVP_MD_CTX结构的指针,它包含了哈希运算的上下文,即你在调用EVP_DigestInit_ex或相关函数时初始化的那个上下文。
unsigned char *md:
这是一个指向缓冲区的指针,用于存储最终的哈希摘要值。这个缓冲区的大小应该足够大,以容纳所选哈希算法产生的摘要。你可以通过查询EVP_MD_size函数来获取特定哈希算法所需的摘要大小。
unsigned int *s:
这是一个指向unsigned int变量的指针,用于接收哈希摘要的长度(以字节为单位)。在调用EVP_DigestFinal_ex后,这个变量将被设置为实际生成的摘要的长度。这个参数是可选的,如果你不关心摘要的确切长度,可以将其设置为NULL。
返回值:
如果函数成功,它将返回1。如果发生错误,它将返回0。可以使用OpenSSL的错误处理函数(如ERR_get_error)来获取更详细的错误信息。
注意:
在调用EVP_DigestFinal_ex之后,不能再次使用相同的EVP_MD_CTX上下文进行哈希运算,除非再次调用EVP_DigestInit_ex或相关函数来重新初始化它。此外,完成哈希运算后,应该使用EVP_MD_CTX_free函数来释放EVP_MD_CTX上下文以避免内存泄漏。
EVP_MD_CTX_free
用于释放之前通过EVP_MD_CTX_new或类似的函数分配的EVP_MD_CTX结构所占用的内存。
#include <openssl/evp.h>
void EVP_MD_CTX_free(EVP_MD_CTX *ctx);
参数:
EVP_MD_CTX *ctx:
这是一个指向EVP_MD_CTX结构的指针,该结构包含了你之前用于哈希运算的上下文信息。这个上下文通常是通过EVP_MD_CTX_new或类似的函数创建的,现在你想要释放它所占用的内存。
注意:
如果你使用的是OpenSSL 1.1.0或更高版本,建议使用EVP_MD_CTX_new和EVP_MD_CTX_free来分配和释放EVP_MD_CTX结构,而不是使用已弃用的EVP_MD_CTX_init和EVP_MD_CTX_cleanup函数。
在使用OpenSSL进行哈希运算时,总是确保在不再需要EVP_MD_CTX上下文时释放它,以避免内存泄漏。
示例
代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/rsa.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#define WK_OK 0
#define WK_ERR (-1)
#define HASH_PLAIN_LEN 1024
//打印
void my_print(char* str, unsigned char* data, unsigned int len)
{
if(NULL != str)
{
printf("%s\n", str);
}
for(int i=0; i<len; i++)
{
printf("0x%02x, ", data[i]);
if(0 == (i+1)%16)
{
printf("\n");
}
}
printf("\n");
}
/**hash算法测试
入参:
handle:摘要算法选择
src:摘要原文
srclen:摘要原文长度
出参:
dst:摘要值
dstlen:摘要值长度
*/
int test_hash(const EVP_MD* handle, unsigned char* src, unsigned int srclen, unsigned char* dst, unsigned int* dstlen)
{
int ret = 0;
char err[256];
EVP_MD_CTX *mdctx = NULL;
const EVP_MD *md = handle;//摘要算法
unsigned char md_value[EVP_MAX_MD_SIZE] = {0};//摘要结果
unsigned int md_len = 0;//摘要结果长度
//申请摘要上下文空间
mdctx = EVP_MD_CTX_new();
if(NULL == mdctx)
{
goto err_handle;
}
//初始化摘要操作
ret = EVP_DigestInit_ex(mdctx, md, NULL);
if(1 != ret)
{
goto err_handle;
}
//更新摘要
ret = EVP_DigestUpdate(mdctx, src, srclen);
if(1 != ret)
{
goto err_handle;
}
//获取摘要结果
ret = EVP_DigestFinal_ex(mdctx, md_value, &md_len);
if(1 != ret)
{
goto err_handle;
}
memcpy(dst, md_value, md_len);
*dstlen = md_len;
//清理上下文
EVP_MD_CTX_free(mdctx);
return WK_OK;
err_handle:
ERR_error_string(ERR_get_error(), err);
printf("%s\n", err);
//释放空间
if(NULL != mdctx)
{
EVP_MD_CTX_free(mdctx);
mdctx = NULL;
}
return WK_ERR;
}
int main()
{
int ret = 0;
int choice = 0;
unsigned char src[HASH_PLAIN_LEN] = {0};//hash原文
unsigned int srclen = HASH_PLAIN_LEN;
unsigned char md_result[EVP_MAX_MD_SIZE] = {0};//hash结果
unsigned int md_result_len = 0;
for(int i=0; i<HASH_PLAIN_LEN; i++)
{
src[i] = i%100;
}
while(1)
{
printf("\n调用的openssl库版本: num [%lx], text [%s]\r\n", OpenSSL_version_num(), OpenSSL_version(OPENSSL_VERSION));
printf("选择测试项: \n");
printf("0: SHA-256 1: SHA-384 2: SHA-512 \r\n");
printf("3: SHA3-224 4: SHA3-256 5: SHA3-384 6:SHA3-512 \r\n");
scanf("%d", &choice);
switch (choice)
{
case 0:
//SHA2-256
ret = test_hash(EVP_sha256(), src, srclen, md_result, &md_result_len);
break;
case 1:
//SHA2-384
ret = test_hash(EVP_sha384(), src, srclen, md_result, &md_result_len);
break;
case 2:
//SHA2-512
ret = test_hash(EVP_sha512(), src, srclen, md_result, &md_result_len);
break;
case 3:
//SHA3-256
ret = test_hash(EVP_sha3_224(), src, srclen, md_result, &md_result_len);
break;
case 4:
//SHA3-256
ret = test_hash(EVP_sha3_256(), src, srclen, md_result, &md_result_len);
break;
case 5:
//SHA3-384
ret = test_hash(EVP_sha3_384(), src, srclen, md_result, &md_result_len);
break;
case 6:
//SHA3-512
ret = test_hash(EVP_sha3_512(), src, srclen, md_result, &md_result_len);
break;
default:
break;
}
if(WK_OK != ret)
{
printf("err: test_hash\n");
}
else
{
printf("succ: test_hash, md_result_len [%d]\n", md_result_len);
my_print(NULL, md_result, md_result_len);
}
}
return WK_OK;
}
makefile
SRC := $(wildcard ./*.c)
#paramter
CC := gcc
target := app_openssl
#头文件和库路径(修改成安装openssl的路径)
DIR_LIB := -L /xxxxxxxxxxxxxx/openssl/lib64
DIR_INCLUDE := -I /xxxxxxxxxxxxxx/openssl/include/
$(target):$(SRC)
$(CC) $(SRC) $(DIR_INCLUDE) $(DIR_LIB) -lssl -lcrypto -o $@
clean:
rm -rf $(target)
执行结果
参考链接:https://www.openssl.org/source/old/index.html