Linux C语言调用OpenSSL: 摘要算法(SHA-2和SHA-3)

该博客介绍了如何在Linux环境下使用C语言调用OpenSSL库进行SHA-2和SHA-3摘要算法。文章详细阐述了不同摘要算法的安全特性,包括MD5、SHA-1、SHA-2和SHA-3,并重点讲解了调用OpenSSL的EVP_DigestInit_ex、EVP_DigestUpdate、EVP_DigestFinal_ex等函数实现哈希运算的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

摘要算法概述

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值