一个典型的物联网系统,往往有上百个,甚至成千上万个联网节点,并且每个节点联网的方式可能不同,比如有Zigbee联网,有430/470M无线方式联网,有GPRS/3G无线模块联网,有RS485/CAN总线方式联网,有以太网TCP/IP方式联网等等。如此种种节点,如果需要进行程序更新,这是一个非常令人头疼的事。
另外,随着物联网各种项目的大量实施,在运行维护过程中,其技术人员的交通住宿成本及人力成本的逐年增加,让设备的远程维护,远程升级功能变得越来越重要了。
但是要实现远程升级,也不是一件容易的事,需要重点解决如下三方面的事情:第一就是安全,如何防止有恶意的人篡改和远程升级相关设备的程序;第二就是远程升级通信信道的问题,比如国内的GPRS和3G模块,由于被分配的IP地址不是公网地址,所以必须反连,也就是说模块要主动请求服务器进行远程升级,而不是常规程序升级的做法,直接访问设备对设备进行升级,另外就是各种通信接口的支持,比如Zigbee,430/470M无线信道,由于这类设备,每次传输的字节数有限,实现远程升级功能也要充分考虑到这一点。第三就是可靠升级,比如升级一半,设备突然掉电,如何确保设备不变砖。
基于.NET Micro Framework系统的物联网智能网关,分别采用如下技术来解决以上所提到的问题。
【安全问题】
.NET Micro Framework系统,在V4.1版本之前,一直提供了两种加解密方法,一种是非对称加密RSA和对称加密XTEA(这部分代码一直没有开源,提供各种ARM版本和X86的连接库),但是这个库其中的RSA有两个致命的问题,一个是其生成的公钥和私钥无法和Windows或其他平台的目前已有的方法提供的一致,互不通用。另外一个问题就是其X86版本和嵌入式版本,其加解密竟然无法实现互解(另外也发现嵌入式版本的RSA加密,无法对特定长度的明文进行加解密)。
4.2版本之后,封装了OpenSSL中的RSA、AES,DES等加密方法,但是对一些运行.NET MicroFramework系统比较小的嵌入式设备来说, 集成一个OpenSSL库有些太大了,大概增加400K字节大小,比一个.NET Micro Framework系统还要大,这是让人无法承受的。
后续重新自行调整和构建了RSA相关代码,算相对完美的解决了这个问题。(关于加解密的详情,后续有专门的文件进行介绍)。
远程升级的思路:
首先对要升级的文件进行SHA1哈希计算,拼装成标准签名文件后,用RSA的私钥对该数据进行加密–- 对升级文件进行签名。
考虑到嵌入式系统RAM空间有限,数据可以分批,分片写入到Flash区域中,等完全写入完毕后,直接在Flash上(非NandFlash,可以直接访问),用预先下载好的公钥进行签名验证。
【信道问题】
其实.NET Micro Framework系统也提供了官方的远程升级方案,甚至为了实现这个功能,新提供一个MicroBooter来配合完成远程升级。但是其通信信道是基于.NET Micro Framework调试口的,这就有一个很大的问题,比如GPRS/3G通信设备无法远程直接访问,Zigbee,430/470M无线模块,每次传输的数据有限,都无法被配置为调试通道。
我们的解决方案是,信道和具体的升级功能功能完全剥离,这样信道的问题,就简化成如何远程获取升级文件和签名的问题。针对这个问题,可实现的方案有很多种,也可以分为主动式和被动式升级。
【可靠升级】
我们采取的方案是,接收到的数据,先更新到一个系统Flash区,下载完毕后,用公钥进行签名验证,验证通过后(一是验证来源是否可靠,另外就是验证升级文件的完整性),才置相关标志位,然后让系统复位,复位后根据相关的标志,把相关数据转移到正常程序区,并加载运行。
从以上步骤中可以看出,如果远程升级过程没有完成,原来的程序还将继续保持,并不会被破坏。
升级流程示意图如下:
下面让我们详细介绍完整的远程升级流程:
创建公钥和私钥
在示例“数据加密解密”中,我们提供了一个和.NET Micro Framework相对应的Windows版本的数据加密解密程序(界面如下图所示)。其用到的加解密函数,都是.NET Framework的标准加密解密库。
考虑到存储空间,设备端(.NET Micro Framework系统)的二进制格式的密钥和上位机的二进制格式略有不同,实际要小一些,比如公钥的指数,一般都固定为0x1,0x0,0x1三个字节的数,所以就省略了。同理私钥也是做了这样的处理。
但是xml格式的都是一样的,底层也提供了一个接口函数,可以直接导入XML格式的密钥。
为了各自的方便,我们为对应的PC端程序(远程升级的发起方)提供的是XML格式的私钥,为设备端(被升级的一方)提供的是二进制格式的公钥。
分别用刚才提到的工具进行导出。
导出私钥的相关代码其实很简单,默认是1024位的Key,代码如下:
RSACryptoServiceProvider mskey = new RSACryptoServiceProvider();
stringxmlKey = mskey.ToXmlString(true);
针对二进制格式的公钥,其实我们仅导出128字节的Modulus的数据:
byte[] key = new byte[128];
Array.Copy(Modulus,0, key, 0, 128);
公钥部署
采用最新版本的YFAccessFlash工具(V3.12.0以上),把导出的二进制格式的公钥部署到设备上(.NET Micro Framework系统)。
公钥提取及程序部署、校验
以下代码完成公钥的提取(需要引用YFSoft.Config库)
byte[] PublicKey = new byte[128];
if (YFSoft.Config.Read("PublicKey",PublicKey, 0, 128) == 0)
{
//…
}
分以下四步分别完成程序的部署和校验(需要引用YFSoft.RemoteUpgrade库)
第一步:清空系统部署区
RemoteUpgrade.Initialize();
第二步:向系统数据区写数据(可以分块写)
RemoteUpgrade.Write(0, peFile, 0, peFile.Length);
第三步:RSA+SHA1校验
bool IsVerifyOK = (RemoteUpgrade.Verify(peFile.Length,Signature, PublicKey)==0);
第四步:置标志位,系统重启
RemoteUpgrade.Finish();
升级文件及签名数据远程传输
以上几步,基本上流程一致,没有多少变化,但是这一步,变数比较大。从物理信道上来说,可以是以太网、Wifi、蓝牙、无线(430M/470M)、Zigbee、RS232/RS485串口通信、GPRS/3G等等。
即使同一个信道,采用的升级策略也不相同,比如可以是主动式,和被动式。所谓的主动式,就是远程可以直接对远程的模块进行升级,是相对即时的一种的方式。而被动式,是远程模块,每隔特定的时间间隔,主动去访问指定的服务器,根据获取的信息,判断是否该升级。比如国内通过GPRS/3G通道,也只能采用这种方式了。
我们提供的两个例子,一个是基于以太网TCP/IP通信,一个是基于串口通信,都是主动式的。不过例子中提供了一个YFSoft.WireProtocol源码类,和特定的信道,还有主动被动都没有关系,只需要实现相关的委托接口就可以完成远程升级文件的接收或发送。
要实现的三个接口分别是:
public delegate int TransmitBytesHandle(byte[] buffer, intoffset, int count);
public delegate int ReceiveBytesHandle(byte[]buffer, int offset, intcount);
public delegate int GetReceiveCountHandle();
第一个是发送数据用的,第二个是接收数据用的,第三个是获取接收缓冲区数据个数的。并且这个YFSoft.WireProtocol.cs文件,桌面.NET Framework和设备.NET Micro Framework完全一样的。
比如如果通信信道是串口,则这三个接口的实现代码如下:
private int TransmitBytesHandle(byte[] buffer, intoffset, int count)
{
port.Write(buffer, offset, count);
return0;
}
private intReceiveBytesHandle(byte[] buffer, int offset, intcount)
{
returnport.Read(buffer, offset, count);
}
private int GetReceiveCountHandle()
{
returnport.BytesToRead;
}
如果是网口,则代码如下:
private int TransmitBytesHandle(byte[] buffer, intoffset, int count)
{
returnsocket.Send(buffer, offset, count, SocketFlags.None);
}
private int ReceiveBytesHandle(byte[]buffer, int offset, intcount)
{
returnsocket.Receive(buffer, offset, count, SocketFlags.None);
}
private int GetReceiveCountHandle()
{
returnsocket.Available;
}
接下来,在设备端主程序中,只需要添加两句代码,就可以实现远程升级的服务端功能。
如果是串口,代码如下:
//远程升级服务(基于串口)
PortServerUpdatetsu = new PortServerUpdate("COM1",115200,true);
tsu.Launch();
如果是网口,代码如下:
//远程升级服务(基于TCP)
TcpServerUpdatetsu = new TcpServerUpdate(10189,true);
tsu.Launch()
PC端的接口实现类似,但是远程数据有所不同,由于串口方式和网口方式基本一样,所以我们仅举以太网通信方式。
具体的升级代码如下:
private void btnUpdate_Click(objectsender, EventArgs e)
{
if(txtFiles.Text != null &&txtFiles.Text.Length > 0)
{
txtInfo.Text = ">>> 提取以下文件的数据...\r\n";
varfiles = Directory.EnumerateFiles(txtFiles.Text,"*.pe");
MemoryStreamms = new MemoryStream();
foreach(string file infiles)
{
txtInfo.Text += ">>> "+file + "\r\n";
FileStreamtfs = new FileStream(file,FileMode.Open, FileAccess.Read);
byte[]buffer = new byte[tfs.Length];
tfs.Read(buffer, 0,buffer.Length);
tfs.Close();
ms.Write(buffer, 0,buffer.Length);
}
txtInfo.Text += ">>> 提取并合成数据成功!\r\n";
//创建签名文件
stringxmlPrivateKey = "<RSAKeyValue><Modulus>vBjTkuBruYPSBE5y5T7hd4KEADy6UuBk1v+Es8BOvggOsfEGvxJNraDTCxTPaNhVkbaCFIavw8amXoIkFzjDw7fV3JVDflsqZ4qg23DOcWz/DvF+12sNcXTsHX7HELJYObZI1lo1kE2fFej1uuRzr7v9DgoFurgg9tGU9gD3dCU=</Modulus><Exponent>AQAB</Exponent><P>4NIKd4E+/0RRT58FZriPTWkfysy8c1Hl7CbeKet1m7KDsatwxNYm6u+oKltmG8UQ73pUfsEbBFpCo/UgL36osQ==</P><Q>1i73MFObZqHBvAhBP+uAbDa33k9yBqePDC0lKL5bqw4AhWzKb3EJd0OWUYf+LxZKnYbmbYNIMolDskcnbPjftQ==</Q><DP>2L7HJoWthY6I0blfDLRcO+ZgpzURbiCECVNDlqiRvySwwIandq174b5ho0xwuc8Yz7hhY76qXFzkqIt3lzKGUQ==</DP><DQ>lkj0F0vC8bu0dZyRNCmpvcSTNYEnMDYoMFIJDdKr/ZVglj5kuNdm3fFlqyWyHBYXGvtJ+jOw2AzqnFBDALqMNQ==</DQ><InverseQ>jQhrgi6tUQ0XpH1QIzLmHEMZn1PukayA+5tBps3SCswnFQC3iSW58N/m2YX2Z37USXQtqG8/HmZTyrUdAzkgMA==</InverseQ><D>hzCUyCjyY/ihdqTnoWqrZFjzBLSg+jX7ZCdsOkFKlvx1i2D/h07hc5x2cq13URTDk6IIJjaTl3NsWdrRk7shv4sXcW5bKg57GPvV6CHRij0Af1xQRLpYsgzeyVjRgKaU+Ea9KZV+mYQ8Ey56krF8MW0/Y4IsVvc1sGWG3HixFEE=</D></RSAKeyValue>";
RSACryptoServiceProviderrsa = new RSACryptoServiceProvider();
rsa.FromXmlString(xmlPrivateKey);
byte[]pe = ms.ToArray();
byte[]sis = rsa.SignData(pe, typeof(System.Security.Cryptography.SHA1));
txtInfo.Text += ">>> 对合成数据进行签名成功!\r\n";
txtInfo.Text += ">>> 正在升级...";
Application.DoEvents();
intret = +tcu.Update(pe, sis);
if(ret == 0)
{
txtInfo.Text += "成功!\r\n";
}
else
{
txtInfo.Text += "失败("+ ret.ToString() + ")!\r\n";
}
}
}
升级演示
1、先运行以太网远程升级示例下的MF程序。这个示例LED灯是慢闪的
2、运行远程升级PC端程序(开发板默认IP为192.168.1.100),连接成功后,选择MFSample目录下的
灯快速闪烁示例,然后远程升级。如果成功,会看到灯快速闪烁。这个时候PC端程序断开连接,重新连接,然后远程部署灯慢速闪烁程序,交换部署,以便更好的观察程序是否远程部署成功。
源码下载
源码链接:http://www.yfiot.com/MFRelease/Sample/RemoteUpgradeSample.rar