目录
1 单向散列算法概述
针对计算机所处理的消息,有时候我们也需要用到“指纹”。当需要比较两条消息是否一致时,我们不必直接对比消息本身的内容,只要对比它们的“指纹”就可以了。使用单向散列函数就可以获取消息的“指纹”,通过对比“指纹”,就能够知道两条消息是否一致。
单向散列函数(one-way hash function)有一个输入和一个输出,其中输入称为消息(message),输出称为散列值(hash value)。单向散列函数可以根据消息的内容计算出散列值,而散列值就可以被用来检查消息的完整性。
单向散列函数也称为消息摘要函数(message digest function )、哈希函数或者杂凑函数。
输入单向散列函数的消息也称为原像(pre-image)。
单向散列函数输出的散列值也称为消息摘要(message digest )或者指纹(fingerprint )。
这里的消息不一定是人类能够读懂的文字,也可以是图像文件或者声音文件。单向散列函数不需要知道消息实际代表的含义。无论任何消息,单向散列函数都会将它作为单纯的比特序列来处理,即根据比特序列计算出散列值。
散列值的长度和消息的长度无关。无论消息是1比特,还是100MB,甚至是100GB,单向散列函数都会计算出固定长度的散列值。以SHA-256单向散列函数为例,它所计算出的散列值的长度永远是256比特(32字节)。
2 OpenSSL EVP代码实现
OpenSSL EVP(high-level cryptographic functions)提供了丰富的密码学中的各种函数。OpenSSL中实现了各种对称算法、摘要算法以及签名/验签算法。EVP 函数将这些具体的算法进行了封装。 EVP系列的函数的声明包含在”evp.h”里面,这是一系列封装了OpenSSL加密库里面所有算法的函数。通过这样的统一的封装,使得只需要在初始化参数的时候做很少的改变,就可以使用相同的代码但采用不同的加密算法进行数据的加密和解密。 EVP系列函数主要封装了加密、摘要、编码三大类型的算法,使用算法前需要调OpenSSL_add_all_algorithms函数。
2.1 基本数据结构
EVP_MD与EVP_MD_CTX两个基本结构,摘要函数EVP_Digest*一系列函数都是以这两个结构为基础实现了。文件digest.c是最高层的封装实现,而各个m_*.c文件则是真正实现了各种算法的摘要算法,当然它们其实也是一些封装函数,真正的算法实现在各个算法同名目录里面的文件实现。
2.1.1 EVP_MD结构体
所有的摘要算法都维护着指向下面定义的结构体的一个指针,在此基础上实现了算法的功能。该结构EVP_MD如下:
#include<opessl/evp.h>
typedef struct env_md_st
{
int type; //信息摘要算法的NID标识
int pkey_type;//是信息摘要-签名算法体制的相应NID标识,如NID_shaWithRSAEncryption
int md_size; //是信息摘要算法生成的信息摘要的长度,如SHA算法是SHA_DIGEST_LENGTH,该值是20
unsigned long flags;
int (*init)(EVP_MD_CTX *ctx);
//指向一个特定信息摘要算法的初始化函数,如对于SHA算法,指针指向SHA_Init
int (*update)(EVP_MD_CTX *ctx,const void *data,unsigned long count);
//指向一个真正计算摘要值的函数,例如SHA算法就是指向SHA_Update
int (*final)(EVP_MD_CTX *ctx,unsigned char *md);
//指向一个信息摘要值计算之后要调用的函数,该函数完成最后的一块数据的处理工作。例如SHA算法就是指向SHA_Final.
int (*copy)(EVP_MD_CTX *to,const EVP_MD_CTX *from);
//指向一个可以在两个EVP_MD_CTX结构之间拷贝参数值的函数
int (*cleanup)(EVP_MD_CTX *ctx);
/* FIXME: prototype these some day */
int (*sign)(); //签名
int (*verify)(); //认证
int required_pkey_type[5];
//指向一个用来签名的算法EVP_PKEY的类型,如SHA算法就指向EVP_PKEY_RSA_method
int block_size; //一个用来进行信息摘要的输入块的的长度(单位是字节),如SHA算法就是SHA_CBLOCK
int ctx_size; //是CTX结构的长度,在SHA算法里面应该就是sizeof(EVP_MD*)+sizeof(SHA_CTX)
/* control function */
int (*md_ctrl) (EVP_MD_CTX *ctx, int cmd, int p1, void *p2);
} EVP_MD;
2.1.2 EVP_MD_CTX结构体
在调用函数的时候,一般来说需要传入上面说的type的参数和下面所定义的一个CTX结构,用EVP_MD来初始化EVP_MD_CTX的digest成员,该结构EVP_MD_CTX定义如下:
typedef struct env_md_ctx_st
{
const EVP_MD *digest; //digest——指向上面介绍的EVP_MD结构的指针
ENGINE *engine; //如果算法由ENGINE提供,该指针指向该ENGINE
unsigned long flags; //
void *md_data; //信息摘要数据
/* Public key context for sign/verify */
EVP_PKEY_CTX *pctx;
/* Update function: usually copied from EVP_MD */
int (*update) (EVP_MD_CTX *ctx, const void *data, size_t count);
}EVP_MD_CTX ;
2.2 相关函数
/********************************************************************************
功 能:获取摘要结构算法的NID
参 数:name - [in] 摘要算法名称
返 回: 摘要结构算法的NID
备 注:EVP_get_digestbyname(sha256返回NID_sha1()。如果算法不存在,返回null。
********************************************************************************/
const EVP_MD *EVP_get_digestbyname(const char *name);
/********************************************************************************
功 能:摘要计算
参 数:data - [in] 需要进行摘要计算的的串
count - [in] 进行摘要计算的串的长度
md - [out]计算出的摘要值
size - [out]计算出的摘要值的长度
EVP_MD - [in]计算摘要算法的NID(类似:EVP_sha1())
impl - [in]一般为NULL,那么就会使用缺省实现的信息摘要函数
返 回: 1-成功,0-失败
********************************************************************************/
int EVP_Digest(const void *data, size_t count,
unsigned char *md, unsigned int *size, const EVP_MD *type,
ENGINE *impl);
3 演示Demo
3.1 开发环境
-
OpenSSL 1.0.2l
-
Visual Studio 2015
-
Windows 10 Pro x64
3.2 功能介绍
演示程序主界面如下图所示,包括摘要算法设置,输入/输出数据格式设置以及摘要计算等功能。
支持MD5、SHA1、SHA224、SHA256、SHA384、SHA512等摘要算法。
支持String(文本)、Hex(十六进制)、Base64等多种数据格式。
3.3 下载地址
开发环境:
-
Windows 10 pro x64
-
Visual Studio 2015
-
OpenSSL 1.0.2l
下载地址: 单向散列算法的OpenSSL代码实现Demo