消息鉴别码(Message Authentication Code)也叫密码校验和(cryptographic checksum),鉴别函数的一种.
消息鉴别码实现鉴别的原理是,用公开函数和密钥产生一个固定长度的值作为认证标识,用这个标识鉴别消息的完整性.使用一个密钥生成一个固定大小的小数据块,即MAC,并将其加入到消息中,然后传输.接收方利用与发送方共享的密钥进行鉴别认证等.
MAC是与明文信息M一同发送给对方,目的方使用事先协商好的密钥K对明文信息M进行完整性认证,由于密钥K仅通信双方知道,所以也可判定消息是由对方发送的(而不是攻击者)。这是MAC与传统单向函数的区别。
消息鉴别码(MAC)仅仅认证消息M的完整性(不会被篡改)和可靠性(不会是虚假的消息或伪造的消息),并不负责信息M是否被安全传输。之所以要放弃信息的保密性(使用公钥加密私钥签名的对称密码协议可以很好的保证信息M的保密性、完整性和可靠性),是因为在某些场合(政府部门公告、网络管理通知等)并不需要对信息进行加密;或者是有些场合(例如广播信息等)需要长时间传输大量信息。由于MAC函数是单向函数,因此对明文M进行摘要计算的时间远比使用对称算法或公开密钥算法对明文加密的时间要小。
例如:A要给B发信,A将明文M使用Hash算法进行摘要提取,提取结果为Hash(M),之后用A的私钥对摘要进行签名SA[Hash(M)],之后将M(当然M可以使用对称算法加密)和SA[Hash(M)]一同发给B。其中SA[Hash(M)]便可称之为消息鉴别码(MAC)。
下面代码实现了消息鉴别码MAC算法编程。详细见代码注释 。
#include <stdio.h> #include <string.h> #include <openssl/evp.h> #include <openssl/hmac.h> //以十六进制打印无符号提示字符串 void print(const char *promptStr,unsigned char *data,int len) { int i; if(promptStr!=NULL) { printf("======%s[out len=%d]======\n",promptStr,len); } for(i = 0; i < len; i++) printf("%02x", data[i]); if(promptStr!=NULL) { printf("\n===============\n"); } } //以十六进制打印无符号提示字符串 void printDirectly(unsigned char *data,int len) { print(NULL,data,len); } /* Return the actual lenght be read out. return 0 when the file is meet the end. and -1 when error occurs. @param len--the actual lenght be read out. @parma buf--the buffer to hold the content. @parma buflen--the capacity of the buffer */ unsigned int read_file(FILE *f,unsigned char * buf, int buflen) { int actualReadLen=0; if(buflen<=0) { printf("缓冲区长度无效\n"); return -1; } actualReadLen = (int)fread(buf, sizeof(unsigned char), buflen, f); if (actualReadLen > 0) { return actualReadLen; } if ( feof(f) ) { return 0; } return -1; } //需从命令行中输入文件 void main(int argc, char *argv[]) { //这是一个例子,跟实际安全的方法是利用随机机制得到主密钥 char key[]="simple_key"; const char* digest="sha"; const char* srcfile="mac.c";//tobe calculate mac FILE *f=NULL; EVP_MD_CTX mdctx; const EVP_MD *md; unsigned char mac_value[EVP_MAX_MD_SIZE]; int mac_len=0; HMAC_CTX mac_ctx; unsigned char buffer[2048]; int actualReadLen=0; OpenSSL_add_all_digests(); md = EVP_get_digestbyname(digest); if(!md) { printf("不能识别的信息摘要: %s\n", digest); exit(1); } EVP_MD_CTX_init(&mdctx); EVP_DigestInit_ex(&mdctx, md, NULL); if( (f=fopen(srcfile, "rb"))==NULL ) { printf("打开文件%s时失败\n", srcfile); exit(1); } HMAC_Init(&mac_ctx, key, sizeof(key), md); for(;;) { actualReadLen=read_file(f, buffer, sizeof(buffer)); if( actualReadLen==0) break;//finish reading. if( actualReadLen<0) { printf("错误发生在文件 [%s]\n", srcfile); exit(1); } HMAC_Update(&mac_ctx, buffer, actualReadLen); } HMAC_Final(&mac_ctx, mac_value, &mac_len); HMAC_cleanup(&mac_ctx); printf("HMAC(%s,%s)=", srcfile,key); printDirectly(mac_value, mac_len); printf("\n"); printf("\n click any key to continue."); //相当于暂停,便于观察运行结果 getchar(); }