前言

最近被安排要在产品上新增一个ota升级功能,一开始也没当回事,以为也就是一个从服务器上下载执行程序到本地,成功后弹出提示框并重启的简单功能。奈何自己还是想得太简单,在开发过程中逐渐发现这个过程还涉及到加解密,升级包的解压缩,以及最后对升级文件的校验比对等操作。把这些功能都加上去也颇费了一些时间,这几天把这阵子的收获做一个记录,也算是对这阵子的工作做一个总结。


由于涉及到的内容有点多,因此就分为几篇博文来一一叙述。今天,就先从文件的加解密开始讲起吧。

一、OTA升级包的加解密

在ota的升级过程中,为了保护产品不受外界恶意攻击,被窃取数据,或者被植入恶意代码,在本地上传升级包前的加密以及客户端下载升级包并解密的操作是必不可少的。
如何实现对升级包的加解密操作呢?
当然自己去写算法对文件进行加解密也是可以的,但是安全性可能不太高,因此就需要去找一下是否有开源的加解密算法库能拿来直接用。百度过后,找到了openssl这个开源库里面有我们想要的东西(具体这个库是做什么用的,这里就不再赘述,百度上有很多相关的资料),话不多说,就决定用这个了。

二、openssl库

1.编译

在使用openssl库里面的加解密算法前,我们需要先编译。

android OTA升级成功开机 ota升级后需要还原吗_openssl


android OTA升级成功开机 ota升级后需要还原吗_openssl_02


进入该目录后依次执行以下两条命令编译出我们代码需要的库文件

./config
./make all

在编译成功后,我们可以看到目录下面多出了一些文件,如下图:

android OTA升级成功开机 ota升级后需要还原吗_嵌入式_03


其中libcrypto.a和libssl.a这两个文件就是我们需要的东西。

2.头文件说明

在编译出.a库文件后,接下来我们就需要先编写测试代码,在本地环境下先测试对应加解密代码的可用性,后续移植也会方便许多。

我们采用的是aes加密算法来加解密,头文件是./crypt/aes/文件夹下的aes.h文件。

android OTA升级成功开机 ota升级后需要还原吗_android OTA升级成功开机_04


以下是这个头文件里面的内容

/* crypto/aes/aes.h -*- mode:C; c-file-style: "eay" -*- */
/* ====================================================================
 * Copyright (c) 1998-2002 The OpenSSL Project.  All rights reserved.
 *
 * 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 above 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 acknowledgment:
 *    "This product includes software developed by the OpenSSL Project
 *    for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
 *
 * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please contact
 *    openssl-core@openssl.org.
 *
 * 5. Products derived from this software may not be called "OpenSSL"
 *    nor may "OpenSSL" appear in their names without prior written
 *    permission of the OpenSSL Project.
 *
 * 6. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the OpenSSL Project
 *    for use in the OpenSSL Toolkit (http://www.openssl.org/)"
 *
 * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
 * EXPRESSED 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 OpenSSL PROJECT OR
 * ITS 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.
 * ====================================================================
 *
 */

#ifndef HEADER_AES_H
#define HEADER_AES_H

#include <openssl/opensslconf.h>

#ifdef OPENSSL_NO_AES
#error AES is disabled.
#endif

#include <stddef.h>

#define AES_ENCRYPT	1
#define AES_DECRYPT	0

/* Because array size can't be a const in C, the following two are macros.
   Both sizes are in bytes. */
#define AES_MAXNR 14
#define AES_BLOCK_SIZE 16

#ifdef  __cplusplus
extern "C" {
#endif

/* This should be a hidden type, but EVP requires that the size be known */
struct aes_key_st {
#ifdef AES_LONG
    unsigned long rd_key[4 *(AES_MAXNR + 1)];
#else
    unsigned int rd_key[4 *(AES_MAXNR + 1)];
#endif
    int rounds;
};
typedef struct aes_key_st AES_KEY;

const char *AES_options(void);

int AES_set_encrypt_key(const unsigned char *userKey, const int bits,
	AES_KEY *key);
int AES_set_decrypt_key(const unsigned char *userKey, const int bits,
	AES_KEY *key);

int private_AES_set_encrypt_key(const unsigned char *userKey, const int bits,
	AES_KEY *key);
int private_AES_set_decrypt_key(const unsigned char *userKey, const int bits,
	AES_KEY *key);

void AES_encrypt(const unsigned char *in, unsigned char *out,
	const AES_KEY *key);
void AES_decrypt(const unsigned char *in, unsigned char *out,
	const AES_KEY *key);

void AES_ecb_encrypt(const unsigned char *in, unsigned char *out,
	const AES_KEY *key, const int enc);
void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
	size_t length, const AES_KEY *key,
	unsigned char *ivec, const int enc);
void AES_cfb128_encrypt(const unsigned char *in, unsigned char *out,
	size_t length, const AES_KEY *key,
	unsigned char *ivec, int *num, const int enc);
void AES_cfb1_encrypt(const unsigned char *in, unsigned char *out,
	size_t length, const AES_KEY *key,
	unsigned char *ivec, int *num, const int enc);
void AES_cfb8_encrypt(const unsigned char *in, unsigned char *out,
	size_t length, const AES_KEY *key,
	unsigned char *ivec, int *num, const int enc);
void AES_ofb128_encrypt(const unsigned char *in, unsigned char *out,
	size_t length, const AES_KEY *key,
	unsigned char *ivec, int *num);
void AES_ctr128_encrypt(const unsigned char *in, unsigned char *out,
	size_t length, const AES_KEY *key,
	unsigned char ivec[AES_BLOCK_SIZE],
	unsigned char ecount_buf[AES_BLOCK_SIZE],
	unsigned int *num);
/* NB: the IV is _two_ blocks long */
void AES_ige_encrypt(const unsigned char *in, unsigned char *out,
		     size_t length, const AES_KEY *key,
		     unsigned char *ivec, const int enc);
/* NB: the IV is _four_ blocks long */
void AES_bi_ige_encrypt(const unsigned char *in, unsigned char *out,
			size_t length, const AES_KEY *key,
			const AES_KEY *key2, const unsigned char *ivec,
			const int enc);

int AES_wrap_key(AES_KEY *key, const unsigned char *iv,
		unsigned char *out,
		const unsigned char *in, unsigned int inlen);
int AES_unwrap_key(AES_KEY *key, const unsigned char *iv,
		unsigned char *out,
		const unsigned char *in, unsigned int inlen);


#ifdef  __cplusplus
}
#endif

#endif /* !HEADER_AES_H */

可以看到,这个库提供的加解密算法,还是挺多的。本来以为应该有对应的demo代码,但是找了很久没找到,因此对于这些函数,只能边查资料边试。

三、测试代码

1.Makefile编写

首先,我们先回到主目录下,创建一个otaPack文件夹,用于存放我们的测试代码

android OTA升级成功开机 ota升级后需要还原吗_android OTA升级成功开机_05


接着先创建一个otaPack.c文件,用于编写我们的测试代码

touch otaPack.c

android OTA升级成功开机 ota升级后需要还原吗_嵌入式_06


接下来我们需要编写Makefile,用编译我们的代码

CC=cc
CFLAGS=-O -I../crypto/aes -I../include

OTAPACK_OBJS = otaPack.o ../libcrypto.a ../libssl.a

.c.o:
     $(CC) -c $(CFLAGS) $*.c

all: otaPack

otaPack:  $(OTAPACK_OBJS)
     $(CC) $(CFLAGS) -o $@ $(OTAPACK_OBJS)

clean:
     /bin/rm -f *.o otaPack

2.AES文件加解密代码

编写完Makefile后,最后就是文件加解密测试代码的编写。
main主函数

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <aes.h>
unsigned char indate[AES_BLOCK_SIZE];
unsigned char outdate[AES_BLOCK_SIZE];
unsigned char decryptdate[AES_BLOCK_SIZE];

static void _encrypt_file(char *file_in, char *file_out, char *passwd);
static void _decrypt_file(char *file_in, char *file_out, char *passwd);

int main(int argc, char *argv[])
{
	if(argc < 5)
	{
		printf("enter parameter error!!!\r\n");
		exit(-1);
	}
	char file_in[64];
	char file_out[64];
	int mode = -1;
	char passwd[16+1];

	if(*(argv[1]+0) == '-')	//加密
	{
		if(*(argv[1]+1) == 'd')
			mode = AES_DECRYPT;
		else if(*(argv[1]+1) == 'e')
			mode = AES_ENCRYPT;
	}
	
	strcpy(file_in, argv[2]);
	strcpy(file_out, argv[3]);
	strcpy(passwd, argv[4]);
	printf("mode : %d. \r\n", mode);
	printf("file_in: %s. \r\n", file_in);
	printf("file_out: %s. \r\n", file_out);
	printf("passwd: %s. \r\n", passwd);

	if(mode == AES_DECRYPT)
	{
		_decrypt_file(file_in, file_out, passwd);	
	}
	else if(mode == AES_ENCRYPT)
	{
		_encrypt_file(file_in, file_out, passwd);
	}

	return 0;
}

文件加密函数

static void _encrypt_file(char *file_in, char *file_out, char *passwd)
{
	FILE *fp_in = fopen(file_in, "r+");
	FILE *fp_out = fopen(file_out, "w+");
	unsigned char iv[AES_BLOCK_SIZE];	

	int postion = 0;
	int bytes_read, bytes_write;
	AES_KEY aes_key;	

	AES_set_encrypt_key(passwd, 128, &aes_key);
	while(1)
	{
		memset(iv, 0, AES_BLOCK_SIZE);
		bytes_read = fread(indate, 1, AES_BLOCK_SIZE, fp_in);
        AES_cfb128_encrypt(indate, outdate, bytes_read, &aes_key, iv, &postion, AES_ENCRYPT);
        bytes_write = fwrite(outdate, 1, bytes_read, fp_out);
		if(bytes_read < AES_BLOCK_SIZE)
			break;
	}
	fclose(fp_in);
	fclose(fp_out);
}

文件解密函数

static void _decrypt_file(char *file_in, char *file_out, char *passwd)
{
    FILE *fp_in = fopen(file_in, "r+");
    FILE *fp_out = fopen(file_out, "w+");
    unsigned char iv[AES_BLOCK_SIZE];
	int postion = 0;
    int bytes_read, bytes_write;
	AES_KEY aes_key;

    AES_set_encrypt_key(passwd, 128, &aes_key);
	while (1) 
	{
		memset(iv, 0, AES_BLOCK_SIZE);
        bytes_read = fread(outdate, 1, AES_BLOCK_SIZE, fp_in);
        AES_cfb128_encrypt(outdate, decryptdate, bytes_read, &aes_key, iv, &postion, AES_DECRYPT);
        bytes_write = fwrite(decryptdate, 1, bytes_read, fp_out);
        if (bytes_read < AES_BLOCK_SIZE)
            break;
    }
    fclose(fp_in);
    fclose(fp_out);
}

测试

代码编写完了,最后我们需要对代码进行测试,看看是否可能正常对文件进行加解密。
我们先在otaPack下make,编译出可执行程序。

make all

android OTA升级成功开机 ota升级后需要还原吗_嵌入式_07


编译正常,接下来就是测试环节。

touch test
echo 123456789test1111 >> test
zip test.zip test

加密

./otaPack -e test.zip encrypt.zip 123456

解密

./otaPack -d encrypt.zip decrypt.zip 123456

到这里,测试后发现经过我们的代码加解密后,最后的decrypt.zip压缩包是无法正常解压缩的。那么问题出在哪里呢?这里我查了很多资料,最后查到说是这里passwd是需要16个字符才能正常加解密的,如果没有满16个字符,内部会做填充。我们项目对密码长度没有要求,因此这一块先不花时间去深究。重新设置16个字符长度的密码进行加密测试。

./otaPack -e test.zip encrypt.zip 1234567890123456
./otaPack -d encrypt.zip decrypt.zip 1234560123456

最后是可以对加解密后的压缩包进行正常的解压缩操作的,因此可能说明我们的代码是没有什么大问题的。最后就是需要将相应的解密代码移植到开发板平台上,这里也不再赘述。
好了,今天就先讲到这里吧。有时间在讲其他部分的内容。