前言

在前面的两个章节中,我们简单介绍了在OTA升级过程中,如何对文件进行解压缩和加解密的操作。今天,就讲讲这个系列最后的内容,MD5加密。


一、MD5加密是什么?

MD5加密,是一种开源的加密算法。由于其不可逆性,被广泛应用于密码管理,电子签名等领域。这里我们可以看下百度百科对于MD5加密应用于电子签名的介绍。

电子签名
MD5 算法还可以作为一种电子签名的方法来使用,使用 MD5算法就可以为任何文件(不管其大小、格式、数量)产生一个独一无二的“数字指纹”,借助这个“数字指纹”,通过检查文件前后 MD5 值是否发生了改变,就可以知道源文件是否被改动。我们在下载软件的时候经常会发现,软件的下载页面上除了会提供软件的下载地址以外,还会给出一串长长的字符串。这串字符串其实就是该软件的MD5 值,它的作用就在于下载该软件后,对下载得到的文件用专门的软件(如 Windows MD5 check 等)做一次 MD5 校验,以确保我们获得的文件与该站点提供的文件为同一文件。利用 MD5 算法来进行文件校验的方案被大量应用到软件下载站、论坛数据库、系统文件安全等方面 [8] 。

通过上述的介绍,我们了解到我们同样可以在OTA升级功能中利用MD5加密,来保证我们从服务器获取到的升级包同我们上传的升级包文件的一致性,从而预防因为下载文件出错导致产品软件升级失败。


二、使用步骤

我们先简单地说下如何在OTA升级过程中利用md5加密来对升级包文件进行校验。过程说起来并不复杂,就是在本地先遍历计算每一个升级包文件的md5值,并将其文件路径和md5值写入文件中,随同升级包一起上传至服务器。客户端下载升级包后,读取该文件,依次计算对应文件的md5值并作对比,从而实现对所有文件的校验。
这整个过程我们分成两部分来介绍。

1.本地遍历计算md5值

这里我们写一个脚本文件,遍历所在路径下所有文件的md5值,并写入check.md5。

.sh脚本如下:

#!/bin/bash 
function ergodic(){ 
    for file in ` ls -a $1 ` 
    do 
        if [ $file == . ] || [ $file == .. ]
        then 
            continue
        fi
	if [ $file == "md5.sh" ]
		then
			continue
		fi
	if [ -d $1/$file ] 
        then 
            ergodic $1/$file 
        else 
            md5sum $1/$file | sed s#$INIT_PATH/## >> $RECORDFILE
        fi 
    done 
}
#设置输出文件名
RECORDFILE=check.md5
#如果存在先删除,防止重复运行脚本时追加到记录文件
test -e $RECORDFILE && rm $RECORDFILE
#获取当前目录
INIT_PATH=`pwd`
#遍历所有文件
ergodic $INIT_PATH

我们试着执行下该脚本,结果如下:
在这里插入图片描述
可以看到在本地生成了一个check.md5文件,查看该文件
在这里插入图片描述
里面记录了我们升级包所有文件的路径及md5值。

2.客户端遍历计算md5值

在本地,我们可以使用md5sum命令来计算文件的md5值,那么,在客户端,怎么通过代码实现md5sum命令的功能呢。相关的算法函数同样可以在openssl库中找到。
在这里插入图片描述
打开md5.h头文件
代码如下:

/* crypto/md5/md5.h */
/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
 * All rights reserved.
 *
 * This package is an SSL implementation written
 * by Eric Young (eay@cryptsoft.com).
 * The implementation was written so as to conform with Netscapes SSL.
 * 
 * This library is free for commercial and non-commercial use as long as
 * the following conditions are aheared to.  The following conditions
 * apply to all code found in this distribution, be it the RC4, RSA,
 * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
 * included with this distribution is covered by the same copyright terms
 * except that the holder is Tim Hudson (tjh@cryptsoft.com).
 * 
 * Copyright remains Eric Young's, and as such any Copyright notices in
 * the code are not to be removed.
 * If this package is used in a product, Eric Young should be given attribution
 * as the author of the parts of the library used.
 * This can be in the form of a textual message at program startup or
 * in documentation (online or textual) provided with the package.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    "This product includes cryptographic software written by
 *     Eric Young (eay@cryptsoft.com)"
 *    The word 'cryptographic' can be left out if the rouines from the library
 *    being used are not cryptographic related :-).
 * 4. If you include any Windows specific code (or a derivative thereof) from 
 *    the apps directory (application code) you must include an acknowledgement:
 *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
 * 
 * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * 
 * The licence and distribution terms for any publically available version or
 * derivative of this code cannot be changed.  i.e. this code cannot simply be
 * copied and put under another distribution licence
 * [including the GNU Public Licence.]
 */

#ifndef HEADER_MD5_H
#define HEADER_MD5_H

#include <openssl/e_os2.h>
#include <stddef.h>

#ifdef  __cplusplus
extern "C" {
#endif

#ifdef OPENSSL_NO_MD5
#error MD5 is disabled.
#endif

/*
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 * ! MD5_LONG has to be at least 32 bits wide. If it's wider, then !
 * ! MD5_LONG_LOG2 has to be defined along.			   !
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 */

#if defined(__LP32__)
#define MD5_LONG unsigned long
#elif defined(OPENSSL_SYS_CRAY) || defined(__ILP64__)
#define MD5_LONG unsigned long
#define MD5_LONG_LOG2 3
/*
 * _CRAY note. I could declare short, but I have no idea what impact
 * does it have on performance on none-T3E machines. I could declare
 * int, but at least on C90 sizeof(int) can be chosen at compile time.
 * So I've chosen long...
 *					<appro@fy.chalmers.se>
 */
#else
#define MD5_LONG unsigned int
#endif

#define MD5_CBLOCK	64
#define MD5_LBLOCK	(MD5_CBLOCK/4)
#define MD5_DIGEST_LENGTH 16

typedef struct MD5state_st
	{
	MD5_LONG A,B,C,D;
	MD5_LONG Nl,Nh;
	MD5_LONG data[MD5_LBLOCK];
	unsigned int num;
	} MD5_CTX;

#ifdef OPENSSL_FIPS
int private_MD5_Init(MD5_CTX *c);
#endif
int MD5_Init(MD5_CTX *c);
int MD5_Update(MD5_CTX *c, const void *data, size_t len);
int MD5_Final(unsigned char *md, MD5_CTX *c);
unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md);
void MD5_Transform(MD5_CTX *c, const unsigned char *b);
#ifdef  __cplusplus
}
#endif

#endif

这里有相关的接口可以直接调用。至于算法库的引用,这里就不再赘述。最后实现函数如下:

/*
 *	功能:计算指定文件的MD5值
 */

static int _compute_file_md5sum(const char *filename, char *md5_str)
{
	FILE *fp;
	MD5_CTX c;
	unsigned char md[MD5_DIGEST_LENGTH];
	int fd;
	int i;
	static unsigned char buf[4096];

	fp=fopen(filename,"r");
	if (!fp)
	{
		printf("fopen %s error. \r\n", filename);
		return -1;
	}
	printf("MD5(%s)= ",filename);
	fd=fileno(fp);
	MD5_Init(&c);

	for (;;)
	{
		i=read(fd,buf,sizeof(buf));
		if (i <= 0) break;
		MD5_Update(&c,buf,(unsigned long)i);
	}
	MD5_Final(&(md[0]),&c);

	for (i=0; i<MD5_DIGEST_LENGTH; i++)
		printf("%02x",md[i]);
	printf("\n");
	
	strcpy(md5_str, md);
	fclose(fp);
	
	return 0;
}



/*
 *	功能:校验check.md5中所有指定文件的MD5
 *	return
 *	0: success	1:error
 */

static int _check_md5sum_file_list(const char *md5list,const char *rootpath)
{
	FILE *fp;
	char md5_correct[MD5_DIGEST_LENGTH*2+1] = {0};
	char filename[128] = {0};
	char entryname[128] = {0};
	unsigned char md[MD5_DIGEST_LENGTH];
	char md5[MD5_DIGEST_LENGTH*2+1] = {0};
	int i;

	//打开待校验列表文件
	fp = fopen(md5list,"r");
	if (!fp)
	{
		printf("[md5_check] Open %s failed!\n",md5list);
		return 1;
	}
	
    while(fscanf(fp,"%s",md5_correct) != EOF )
	{
	    /* 获取文件名 */
	    fscanf(fp,"%s",entryname);
	    /* 拼接文件名 */
	    snprintf(filename, 128, "%s%s", rootpath, entryname);
		_compute_file_md5sum(filename, md);
		_uc_to_ascii(md5, md, MD5_DIGEST_LENGTH);

		if( 0 != strncmp(md5, md5_correct, MD5_DIGEST_LENGTH*2) )
		{
			printf("file(%s): MD5 Error!!! \r\n", filename);
			return 1;
		}
    }
	return 0;
}

本地测试OK后,将相关代码移植到客户端工程中,调试。至此,所有的工作都已经完成。


总结

到今天为止,这个系列所有的内容都已经介绍完了。从文件的加解密,解压缩,已经最后的md5校验,相关功能都已经一一实现,将这三部分的代码整合,OTA升级功能基本就完善了。虽说只是个简单的小功能,但是在实现的这个过程中还是学到了许多。这个系列就到此结束,后续有新的知识再分享给大家,谢谢!

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐