【Linux】使用openssl库得到文件的MD5值;解决16进制16字节长MD5值转换为32字节字符串

之前写过介绍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;

}

运行结果:完全符合预期

发布了99 篇原创文章 · 获赞 80 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/zDavid_2018/article/details/104983864