之前写过介绍openssl库的博客,还有利用库函数实现对字符串的加密
这次的任务的利用openssl库得到一个文件的MD5值,项目中要用到下载文件,需要MD5校验。
代码:
参考了网上的一个,后来发现,这个代码问题太多了.....以下是我走的坑。。。。。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/md5.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int get_md5(const char *filename, char *md5_str)
{
int fd;
int ret= 0;
int i = 0;
unsigned char data[1024];
unsigned char md5_value[16];
MD5_CTX md5;
if((fd = open(filename, O_RDONLY)) < 0)
{
perror("open failed");
return -1;
}
MD5_Init(&md5);
for(;;)
{
ret = read(fd,data,1024);
if(ret == -1)
{
perror("read failed");
close(fd);
return -1;
}
MD5_Update(&md5, data, ret);
if(ret == 0 || ret < 1024)
{
break;
}
close(fd);
}
MD5_Final((unsigned char *)&md5_value, &md5);
for(i = 0; i < 16;i++)
{
snprintf(md5_str + i * 2, 2 + 1, "%02x", md5_value[i]);
}
md5_str[(16 * 2)] = '\0';
return 0;
}
int main()
{
char md5_str[32];
get_md5("program.bat",md5_str);
printf("md5 = %s\n",md5_str);
return 0;
}
运行结果:
这个代码很有意思,有一些文件可以得到MD5值,比如:program.bat (36字节大小)
运行该程序,再利用md5sum 的shell命令验证,发现程序运行结果正确。,如图:
但是当我换了文件名,选一个略微大的文件,编译程序正确,运行发现error:读文件错误
发现程序代码有问题,文件很大的时候,读文件都1024位,将data[1024]都堵满了,应该read1023,留最后的一个0作为结束。号修改程序。
再次运行,得到文件:testtime.cpp文件(6239字节大小)
惊奇的发现,文件的md5值竟然不一样!
分析:
我想原因就是那个data[1024],第一次的结果正确,是因为data[1024]可以放得下36Byte的文件,所以MD5结果正确;第二次文件很大,只读了1023字节,一共6000多字节,当然结果不正确。
我应该改进一下这个代码,循环读取循环计算,最终得出正确结果。
修改代码。发现是逻辑问题:源代码中的判断读文件错误!读一次就退出循环break close文件了,醉了,那个人写的代码太垃圾了。
我修改后的:读文件还是要1024读满,因为文件很大,不读满 缓冲区就有问题有”0“,这样算出的MD5也不对。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/md5.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int get_md5(const char *filename, char *md5_str)
{
int fd;
int ret= 0;
int i = 0;
unsigned char data[1024] = {0};
unsigned char md5_value[16];
MD5_CTX md5;
if((fd = open(filename, O_RDONLY)) < 0)
{
perror("open failed");
return -1;
}
MD5_Init(&md5);
for(;;)
{
ret = read(fd,data,1024);
if(ret == -1)
{
perror("read failed");
close(fd);
return -1;
}
MD5_Update(&md5, data, ret);
if(ret == 0 || ret < 1024)
{
close(fd);
break;
}
}
MD5_Final((unsigned char *)&md5_value, &md5);
for(i = 0; i < 16;i++)
{
snprintf(md5_str + i * 2, 2 + 1, "%02x", md5_value[i]);
}
md5_str[(16 * 2)] = '\0';
//printf("md5_value = %s\n",md5_value);
return 0;
}
int main()
{
char md5_str[32];
//testtime.cpp program.bat
get_md5("testtime.cpp",md5_str);
printf("md5_str = %s\n",md5_str);
return 0;
}
运行出正确结果:
再补充几句:看到很多代码,文件的MD5值最后都用uchar md5_value[16]存储,仔细研究了一下。
上述代码中也有MD5_value[16];
先说无符号字符型:uchar :范围是0~255(十进制),十六进制就是0x00~0xff。
通过打印unsigned char md5_value[16]
for(i = 0;i < 16;i++)
{
printf("%x ",md5_value[i]);
}
看结果:48 b1 a8 81 24 a1 b1 f3 28 8 c9 b7 d9 79 ef 20
而char md5_str[32] = 48b1a88124a1b1f32808c9b7d979ef20,
二者一个是字符串,一个是uchar数组,并且uchar 的每个元素按照16进制输出的结果的字符就是MD5的字符串,当然没输出0.
所以我大概明白了,为什么项目中存MD5值的时候用unsigned char [16],这样可以节省内存。也明白了snprintf的函数大概是什么意思,将16进制的数字的字符转换成字符。
也就是:十六进制数字 转 ASCII码字符表:
函数如下:
void ascii2hex(uint8_t *hex, uint8_t *str, uint32_t len)
{
uint8_t value = 0;
len = (len + 1) / 2;
while (len--) {
if ((*str >= '0') && (*str <= '9')) {
value |= (*str - '0') & 0x0F;
} else if ((*str >= 'a') && (*str <= 'f')) {
value |= (*str - 'a' + 10) & 0x0F;
} else if ((*str >= 'A') && (*str <= 'F')) {
value |= (*str - 'A' + 10) & 0x0F;
}
value = (value << 4) & 0xF0;
str++;
if ((*str >= '0') && (*str <= '9')) {
value |= (*str - '0') & 0x0F;
} else if ((*str >= 'a') && (*str <= 'f')) {
value |= (*str - 'a' + 10) & 0x0F;
} else if ((*str >= 'A') && (*str <= 'F')) {
value |= (*str - 'A' + 10) & 0x0F;
}
str++;
*hex++ = value;
value = 0;
}
}
void hex2ascii(uint8_t *hex, uint8_t *str, uint32_t len)
{
uint8_t value = 0;
uint32_t i;
bool lower = true;
if (str != NULL) {
for (i = 0; i < len; i++) {
if (lower == true) {
value = 0;
value |= (*hex & 0xF0) >> 4;
value |= (*hex & 0x0F) << 4;
hex++;
lower = false;
} else {
value = (value >> 4) & 0x0F;
lower = true;
}
if ((value & 0x0F) <= 9) {
*str++ = '0' + (value & 0x0F);
} else {
*str++ = 'a' + (value & 0x0F) - 10;
}
}
}
}
下面是我的测试代码:将16进制的数字 转化为32字节长的字符串:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/md5.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define int8_t char
#define uint8_t unsigned char
#define int32_t int
#define uint32_t unsigned int
#define int16_t short
#define uint16_t unsigned short
void hb_hex2ascii(uint8_t *hex, uint8_t *str, uint32_t len)
{
uint8_t value = 0;
uint32_t i;
int lower = 1;
if (str != NULL) {
for (i = 0; i < len; i++) {
if (lower == 1) {
value = 0;
value |= (*hex & 0xF0) >> 4;
value |= (*hex & 0x0F) << 4;
hex++;
lower = 0;
} else {
value = (value >> 4) & 0x0F;
lower = 1;
}
if ((value & 0x0F) <= 9) {
*str++ = '0' + (value & 0x0F);
} else {
*str++ = 'a' + (value & 0x0F) - 10;
}
}
}
}
int get_md5(const int8_t *filename, int8_t *md5_str,uint8_t *md5_16)
{
int32_t fd;
int32_t ret= 0;
int32_t i = 0;
uint8_t data[1024] = {0};
uint8_t md5_value[16];
MD5_CTX md5;
if((fd = open(filename, O_RDONLY)) < 0)
{
perror("open failed");
return -1;
}
MD5_Init(&md5);
while(1)
{
ret = read(fd,data,1024);
if(ret == -1)
{
perror("read failed");
close(fd);
return -1;
}
MD5_Update(&md5, data, ret);
if(ret == 0 || ret < 1024)
{
close(fd);
break;
}
}
MD5_Final((unsigned char *)&md5_value, &md5);
for(i = 0; i < 16;i++)
{
snprintf(md5_str + i * 2, 2 + 1, "%02x", md5_value[i]);
}
md5_str[(16 * 2)] = '\0';
for(i = 0;i < 16;i++)
{
md5_16[i] = md5_value[i];
}
return 0;
}
#define FILE_NEME "testtime.cpp"
#define TARG_MD5 "48b1a88124a1b1f32808c9b7d979ef20"
int main()
{
char md5_str[32] = {0};
uint8_t md5_16[16];
get_md5("testtime.cpp",md5_str,md5_16);
for(int i = 0;i<16;i++)
{
printf("%x",md5_16[i]);
}
printf("\n");
unsigned char md5_32[33] = {0};
memset(md5_32,0,33);
hb_hex2ascii(md5_16,md5_32,32);
printf("md5_32 = %s\n",md5_32);
return 0;
}
运行结果:完全符合预期