1.简介
在实际使用过程中,在遇到一些重要场合,需要加密存储或者传输一些数据到服务器,这个流程中, 有两个地方可能需要重点关注。结合不同场景,安全汇总到2个方案。
1.1 数据传输安全
数据传输安全,对于HTTP明文传输而言,HTTPS属于较优解决方案,这个过程中可以,有效防止数据泄漏。 但是要注意HTTPS,主要场景是针对传输安全,也就是,固件和服务器通信,通过非对称加密,先确保对端 的身份,再协商对称加密的密钥,然后进行对称加密传输内容。 实际开发过程中: 主要是嵌入式开发场景,由于存储根证书需要较多空间,很多平台会阉割掉,证书校验。部分黑客就会利用 这个漏洞,伪造服务端,侵入网络后,会对这个传输数据进行篡改,导致异常问题出现。这个我在实践中遇到, 某些芯片厂商的sdk里面,未对服务器证书进行有效校验,导致伪造服务器下发数据到固件。【备注:欧洲18031,遇到了,这种场景】
1.2 数据加密安全
针对上面这种场景,其实一旦不认证,就有可能导致https传输的数据,被截获,修改,所以对https传输数据 进行加密也很重要,在以往的经验中,对数据进行加密,有涉及到,如何有效的协商一个加密的密钥,这个 密钥不能网络传输(有风险),所以有涉及到无需传输对称密钥,客户端、服务器端进行协商对称密钥。 这种方法有多种,比如UECC库、mbedtls ecc库。都可以有效的进行密钥协商。
下面我们就先重点关注,如何进行不传输密钥下,固件和服务器端协商一个对称加密的密钥。也是要保证固件 唯一性,每个固件都有不同的私钥。
2. 实际案例
首先使用最简单的 UECC进行实现,先了解一下公钥私钥概念 公钥和私钥是通过密码学算法(如 uECC 基于的 ECC 椭圆曲线算法)生成的一对 “数学关联但无法互推” 的数据, 两者的核心特征完全相反。 简述:UECC库可以采用的是ECDH(椭圆曲线迪菲 - 赫尔曼)密钥协商算法, 通过自身私钥+对端的公钥,生成一个对称 密钥,从而用于加密数据。
// 初始化随机数种子,确保每次运行生成不同随机数
srand((unsigned int)time(NULL));
//RNG函数---一定要随机性高,可以硬件生成或者硬件AD采集噪声生成
uECC_set_rng(&RNG);
// 选择ECC曲线:这里用secp160r1(160位椭圆曲线)
const struct uECC_Curve_t * curve = uECC_secp160r1();
// 定义私钥和公钥缓冲区(大小由曲线决定)
uint8_t private1[20];// secp160r1私钥为20字节(160位)
uint8_t private2[20];
uint8_t public1[40];// 未压缩公钥为40字节(x和y坐标各20字节)
uint8_t public2[40];
uint8_t secret1[20];// 存储第一方计算的共享密钥
uint8_t secret2[20];// 存储第二方计算的共享密钥
// 生成第一对密钥:public1(公钥)由private1(私钥)推导
uECC_make_key(public1, private1, curve);
printf("---create private key: \r\n");
for(int i=0; i < 20; ++i)
{
printf("%hhx", private1[i]);//十六精致小写
}
printf("\r\n");
printf("---create public key: \r\n");
for(int i=0; i < 40; ++i)
{
printf("%hhx", public1[i]);
}
printf("\r\n");
// 生成第二对密钥:public2(公钥)由private2(私钥)推导
uECC_make_key(public2, private2, curve);
// 第一方用自己的私钥(private1)和对方的公钥(public2)计算共享密钥
int r = uECC_shared_secret(public2, private1, secret1, curve);
if (!r) {
printf("shared_secret() failed (1)\n");
return 0;
}
printf("-calculate secret1: ");
for(int i =0;i<20;++i)
{
printf("%hhx",secret1[i]);
}
printf("\r\n");
//-------------------计算共享密钥(ECDH 核心)-------------------//
// 生成第二对密钥:public2(公钥)由private2(私钥)推导
r = uECC_shared_secret(public1, private2, secret2, curve);
if (!r) {
printf("shared_secret() failed (2)\n");
return 0;
}
printf("-calculate secret2: ");
for(int i =0;i<20;++i)
{
printf("%hhx",secret2[i]);
}
printf("\r\n");
// 比较两个共享密钥是否相同
//若相同,说明 ECDH 协商成功,双方可基于此密钥进行对称加密通信。
if (memcmp(secret1, secret2, 20) != 0) {
printf("Shared secrets are not identical!\n");
} else {
printf("Shared secrets are identical\n");
}
上面的demo实际例子,用在嵌入式场景就比如,固件端要存储一个私钥,这个私钥需要存放在加密区域,不能被 随意获取,固件端的公钥,需要注册记录到服务器端,在服务器端在通信过程中,通过其它信息(比如IMEI,SN字段), 获取到对应的公钥,这样固件端在对传输数据加密处理过程中,就可以用自身私钥+服务器给的公钥,协商对称密钥, 再对数据进行AES等对称加密传输给服务端,服务端,通过服务器私钥+查询到固件的公钥,协商对称密钥,解密数据。 从而完成对数据的加密、解密操作。
3.反思优化
上面的方案中,也有一个非常明显的缺陷,那就是一旦这个固件的私钥发生泄漏,或破解,对于高安全场景而言,以往的 所有数据都会被解密,这个时候,就需要进一步优化,引入临时的密钥对。
4.改进策略
如果使用长期的私钥+对端公钥,进行ECDH,协商密钥的话,每次通信都是一个固定的对称密钥,一旦泄漏,也是 会导致通信破解。因此需要,进一步保证每次会话都使用新的对称密钥。
- 1)固件和服务器,生成长期密钥对,将长期固定公钥给到服务端(录入系统),服务端的长期公钥写入固件本地
- 2)固件通信时,生成一个临时密钥对,将固件临时公钥 + 使用固件长期私钥签名的固件临时公钥,同步给到服务器
- 3)服务器使用固件端的长期公钥,验证收到的临时公钥,保证这个临时公钥没有被篡改。
- 4)确认没有篡改后,进行固件临时私钥+服务器的临时公钥,做ECDH,协商临时通信的对称密钥
- 5)这个密钥可以在处理一下,对通信数据进行对称加密(国内SM4\国外AES) 核心总结:
- 1、不是用长期公私钥算对称密钥,是用临时密钥对做 ECDH 协商
- 2、长期密钥只负责签名验签、身份认证(也就是验证临时生成的公钥,传输中是否又被修改)
您还没有登录,请您登录后发表评论。