笔者来了解一下嵌入式系统中的加解密
1、背景与名词解释
笔者最近在做安全升级相关的模块,碰到了一些相关的概念和一些应用场景,特来学习记录一下。
1.1 名词解释
- 对称加密:对称加密是一种加密方法,使用相同的密钥(称为密钥)进行加密和解密 。发送方和接收方必须共享同一个密钥。对称加密算法的优点是速度快 ,适合大量数据的加密,但密钥分发和管理可能会面临安全挑战。
- 非对称加密:非对称加密使用成对的密钥:公钥和私钥 。公钥用于加密数据,只有持有配对的私钥才能解密。私钥用于对数据进行签名,公钥用于验证签名。非对称加密算法通常较慢 ,适合于相对较小的数据量 ,但在密钥分发 和身份验证方面具有很大的优势。
- 签名:数字签名是非对称加密的一种应用,用于验证数据的完整性和来源。发送方使用其私钥对数据进行签名,接收方使用发送方的公钥来验证签名。如果验证成功,则可以确信数据未被篡改过。有点像这个数据被签名了一样,无法抵赖,就是从某个人发出的。
- 证书:证书是用于验证公钥拥有者身份的数字文件。证书包含公钥及其拥有者的信息,并由权威认证机构(CA)签名,用于证明该公钥确实属于指定的实体。证书常用于HTTPS连接中,以及公钥基础设施(PKI)中确保通信安全。
1.1.1 对称加密算法
- AES(Advanced Encryption Standard)
AES是一种对称加密算法,广泛应用于保护敏感数据。它支持不同的密钥长度(如AES-128、AES-192、AES-256),速度快且安全性高,被广泛认可为目前最安全的对称加密算法之一。 - DES(Data Encryption Standard)
DES是一种早期的对称加密算法,已不再被推荐使用,因为其56位密钥长度对现代计算能力来说安全性较低。 - 3DES(Triple DES)
3DES是对DES的改进,通过多次对数据块应用DES算法来增强安全性。虽然安全性较高,但由于效率低下和对现代计算能力来说密钥长度较短的缺点,现在也逐渐被AES所取代。
1.1.2 非对称加密算法
- RSA(Rivest-Shamir-Adleman)
RSA是最广为人知的非对称加密算法之一,用于加密、数字签名和密钥交换。它基于大素数的难解性,用于生成公钥和私钥对。RSA在加密和签名方面都有应用。 - DSA(Digital Signature Algorithm)
DSA是一种用于生成和验证数字签名的非对称加密算法。它通常与SHA-1或SHA-2等哈希算法结合使用,用于确保数据的完整性和来源。 - ECC(Elliptic Curve Cryptography)
ECC是一种基于椭圆曲线数学的非对称加密算法。它提供了与RSA相当的安全性,但使用更短的密钥长度,因此在资源受限的环境下更为适用。 - DH(Diffie-Hellman)
Diffie-Hellman是一种密钥交换协议,虽然本身不是加密算法,但用于安全地交换对称加密算法中使用的密钥。它的安全性基于离散对数问题的难解性。
这些算法都在不同的应用场景中发挥着重要作用,选择合适的加密算法取决于安全需求、性能要求以及特定的应用环境。
1.2 为什么对称加密有风险
如上图所示,甲和乙通过对称加密算法进行通信,
- 首先甲向乙发送对称秘钥,约定使用该秘钥进行加密和通讯
- 然后发送加密的消息通信,
- 由于网上有窃听者会监听数据,所以一旦秘钥在网上传输,就有泄露的风险,泄露之后,数据就和明文在网上传输没什么区别,有风险。
- 有些人会问,我不在网络上传输秘钥不就行了吗? 那不在网上传输,乙如何知道甲使用什么方式加密,又合入通信呢?所以该方式一定有风险。
1.3 为什么非对称加密安全性更高
基于以上的想法,一个秘钥肯定不行,那就有两个秘钥,一个永远保存在本地,另外一个可以再网上传输,一个加密,另外一个解密,不就可以保证秘钥不泄露吗?
如上图所示,私钥永远在本地,公钥在网上传输,公钥加密的数据,私钥才可以解密,所以即使网上知道了公钥,也无用,因为没有私钥,没法解密查看到数据。
1.4 为什么需要签名
因为上面公钥是公开的,所以任何人都可以用公钥加密给甲或者乙发数据,就导致甲或者乙无法验证对方的身份是否正确,假如窃听者模仿甲或者乙向对方发错误的指令,那可能也有严重的后果。
所以首先是验证身份,然后才是通信数据,这时候就有了签名的说法,类似你在通信数据上面做了签名,无法抵赖。
签名的验证是通过私钥加密,公钥验证签名的过程来实现,只要公钥可以验证过了,那说明对方一定是甲或者乙发出的,不会是第三方窃听者发出来。
1.5 为什么还需要证书
假如窃听者 在身份验证时,就截获了数据,然后甲和乙拿到的不是对端的公钥,而且窃听者的公钥,然后在窃听者那边去转换数据。
如上图所示,这样即使有了身份验证也无效,关键原因就是拿到的公钥不对,如果公钥有一定的公信力,那么就可以认可对方的身份,因此就有了证书的这个概念。
公钥被放到有公信力的证书里面,保证拿到的一定是对的公钥。证书一般有证书颁发机构来发放,即CA(certificate Authority)。证书包括了公钥,同时证书也被签名了,保证证书没有被篡改。
通常被内置在操作系统或浏览器中,这些受信任的根证书(Root Certificate)构成了信任链的基础,用于验证由该CA签发的所有证书的有效性。
2、嵌入式系统中的安全升级
2.1 嵌入式系统升级
嵌入式系统中的升级讲解相对较多,可以参考笔者之前的文章,BootLoader的理解与实现,Bootloader学习理解----跳转优化异常。Bootloader学习理解学习--加强版。
2.2 嵌入式系统安全升级
像上面嵌入式设备升级,如果设备相同,完全可以模拟同样的嵌入式设备下发升级文件,必须进行签名认证信息,才可以。
2.3 Python代码中的RSA加密与签名验证
在python里面,我们用到了Cryptography以及OpenSSL这个库。
- 生成一对秘钥
生成私钥时,public_exponent=65537指的是公钥指数,通常是一个小的奇数,用于加密或者验证签名,
Key的size为2048位,也就是256Byte
最后转化为明文显示
python
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
# 生成私钥
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
# 生成公钥
public_key = private_key.public_key()
# 将私钥和公钥序列化为PEM格式
private_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
)
public_pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
# 打印私钥和公钥
print(private_pem.decode('utf-8'))
print(public_pem.decode('utf-8'))
- 私钥对文件进行签名
签名的时候,指明的padding是v1.5版本的PKCS,一个应用相对广泛标准化的填充方案,适用于RSA签名和加密操作。还有其他版本的填充,比如1.2版本的,以及PSS算法填充等等。
填充是保证数据块大小符合算法要求的一种方案,加密算法要求数据大小符合256Byte,比如长度是2048位的计算。
python
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
# 已经有了private_key和要签名的文件1.txt
# 读取文件内容
with open('1.txt', 'rb') as f:
data = f.read()
# 使用私钥对文件进行签名
signature = private_key.sign(
data,
padding.PKCS1v15()
hashes.SHA256()
)
# 将签名写入文件
with open('signature', 'wb') as f:
f.write(signature)
- 公钥对文件进行验签
验证签名时,一定要和加密时候的填充方案一致,选择PKCSv15,不然会验签失败。
python
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
# 使用公钥验证签名
try:
public_key.verify(
signature,
message,
padding.PKCS1v15(),
hashes.SHA256()
)
print("签名验证成功,消息未被篡改。")
except Exception as e:
print(f"签名验证失败: {e}")
当然也可以用openssl库来 进行签名和验签工作。
sh
# 生成2048位的RSA私钥
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
# 从私钥中提取公钥
openssl rsa -pubout -in private_key.pem -out public_key.pem
# 使用私钥签名文件,生成签名文件
openssl dgst -sha256 -sign private_key.pem -out signature.bin example.txt
# 验证签名
openssl dgst -sha256 -verify public_key.pem -signature signature.bin example.txt
2.4 嵌入式系统中的加解密
笔者最近了解到一个mbedtls,适合于嵌入式的一个安全加密库,C语言编写,OpenSSL是互联网上面应用的,相对较大,不适合嵌入式。
仓库地址:https://github.com/Mbed-TLS/mbedtls。
mbedtls编译完成后,会生成三个库文件,之后就可以根据头文件,链接这三个库进行加解密。
例如下面的代码,可以编译生成来进行测试,生成签名和验签。
c
#include "mbedtls/pk.h"
#include "mbedtls/md.h"
#include "mbedtls/base64.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "string.h"
#include <stdlib.h>
#define SIGNATURE_MAX_SIZE 256
int rsa_pkcs1v15_sha256_sign(const unsigned char *msg, size_t msg_len,
const char *priavte_key_pem, char *sign_base64, int sign_len)
{
mbedtls_pk_context pk;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
uint8_t sig_buff[SIGNATURE_MAX_SIZE];
unsigned char hash[32] = {0};
size_t sig_len = 0;
int ret = 0;
char *b64_out = NULL;
int b64_len = 0;
const char *pers = "mbedtls_pk_sign"; // Personalization data,
// that is device-specific identifiers. Can be NULL.
// 初始化随机数生成器
mbedtls_entropy_init( &entropy );
mbedtls_ctr_drbg_init( &ctr_drbg );
//初始化上下文
mbedtls_pk_init( &pk );
mbedtls_ctr_drbg_seed( &ctr_drbg,
mbedtls_entropy_func,
&entropy,
(const unsigned char *) pers,
strlen( pers ) );
//导入私钥
ret = mbedtls_pk_parse_key(&pk, (const unsigned char *)priavte_key_pem,\
strlen(priavte_key_pem)+1,\
NULL, 0, NULL, 0);
if(ret != 0)
{
ret = -1;
goto exit;
}
// 计算 sha256 消息摘要
ret = mbedtls_md(mbedtls_md_info_from_type( MBEDTLS_MD_SHA256 ),
(const unsigned char *)msg, msg_len, hash);
if(ret != 0)
{
ret = -1;
goto exit;
}
// 签名
ret = mbedtls_pk_sign(&pk, MBEDTLS_MD_SHA256, hash, sizeof (hash), sig_buff, sizeof(sig_buff), &sig_len, mbedtls_ctr_drbg_random, &ctr_drbg);
if(ret != 0)
{
ret = -1;
goto exit;
}
b64_out = malloc(sig_len*2);
if(b64_out == NULL)
{
ret = -1;
goto exit;
}
// 对签名数据进行 base64 编码
ret = mbedtls_base64_encode((unsigned char *)b64_out, sig_len*2,
(size_t *)&b64_len, (unsigned char *)sig_buff, (size_t)sig_len);
if(ret != 0)
{
ret = -1;
goto exit;
}
if(sign_len<b64_len)
{
ret = -1;
goto exit;
}
strncpy(sign_base64, b64_out, sign_len);
exit:
if(b64_out)
{
free(b64_out);
}
mbedtls_pk_free( &pk );
mbedtls_ctr_drbg_free( &ctr_drbg );
mbedtls_entropy_free( &entropy );
return ret;
}
/**
* @brief rsa_pkcs1v15_sha256_verify
*
* @param [in] msg
* @param [in] msg_len
* @param [in] public_key_pem
* @param [in] sign_base64
* @return int
* -- 0 verify pass
* -- -1 verify faild
*/
int rsa_pkcs1v15_sha256_verify(const unsigned char *msg, size_t msg_len,
const char *public_key_pem, const char *sign_base64)
{
mbedtls_pk_context pk = {0};
unsigned char hash[32] = {0};
int ret = 0;
size_t sign_len = 0;
size_t b64out_len = 0;
unsigned char *b64out_data = NULL;
// 初始化上下文
mbedtls_pk_init( &pk);
// 导入公钥
ret = mbedtls_pk_parse_public_key(&pk, (const unsigned char *)public_key_pem, strlen(public_key_pem)+1);
if(ret != 0)
{
ret = -1;
goto exit;
}
// 对需要验签的数据进行 sha256 计算,生成消息摘要数据
ret = mbedtls_md(mbedtls_md_info_from_type( MBEDTLS_MD_SHA256 ),
(const unsigned char *)msg, msg_len, hash);
if(ret != 0)
{
ret = -1;
goto exit;
}
// 对原始签名数据进行 base64 解码
sign_len = strlen(sign_base64);
b64out_data = malloc(sign_len*2);
memset(b64out_data, 0, sign_len*2);
ret = mbedtls_base64_decode(b64out_data, sign_len*2, &b64out_len, (const unsigned char *)sign_base64, sign_len);
if(ret != 0)
{
ret = -1;
goto exit;
}
// 验证签名
ret = mbedtls_pk_verify(&pk, MBEDTLS_MD_SHA256, hash, sizeof (hash), b64out_data, b64out_len);
exit:
if(b64out_data)
{
free(b64out_data);
}
mbedtls_pk_free( &pk );
return ret;
}
static void test_rsa_pkcs1_sign(void)
{
int ret = 0;
char *private_key = "-----BEGIN RSA PRIVATE KEY-----\n"
"MIICXQIBAAKBgQDTt8tp4xNp29CMxy6QS0NzpR6t8bAcv7ei3NkVM/Nzg3K5wWZR\n"
"aBTMovbzKCXdXYdC6GutVkG+CEetO3XHM4LhDqW0vwISTO65/XrvR3zqXD5ZjrJF\n"
"mtCAvkCwtMAPjqXZ/RJnd8yrXuoz5cRqVgKmq5TZlGIIiTPIklxGIGof8QIDAQAB\n"
"AoGAFf1BJoiD5+sBdFmsq6ZxhUWZU+ImEzpTUZpD/riEWNNGe2YLoTlg7acgZH1f\n"
"P2hbJ9cZdemfTuQvw52JHE0sktCUM6R0wq5rlbDj740+5yZYzs9FlUntm6UtoU9w\n"
"tpd62/iPxovFkguunJB2KBbtP8q0dYQntATEce1TZuS3trUCQQDl7VRYygSb3/HY\n"
"ij2ya1592WpgNWgmPvbpmUjGGBvjmnO8Ye1lEy6x69RmGjRrLvFfhWYwcF2HpmYQ\n"
"9wXKEwT1AkEA67nc/CdeT4j9jRE/QFXlhVrW8Gq8IfjXFGbGK5BqlTRbty3OpW+L\n"
"M9GPqiMC2XxN60peEiANlQ8aUnvbHZexjQJAcz4RGK+ov7fvL+maIuNN6SYf+zjJ\n"
"iuHkQBFkOGW9FMdFWxZ6Nj73GJZrTwGzZEWTFZ13KrAnMOZmIfquHCqMQQJBAL+u\n"
"x9ATg1FRqDyKBdEfCCDEmXuuj4VggCUK3aKXMNRbWyk9iohkh+F/Sz+icLLBreri\n"
"8lPy1JidS14/cRJDRBECQQCT4oNvmV5CYzqkqbgwtLPi/FIjc6Zi26DGxBzL01V+\n"
"yTO1ZlOOUOtY4dPBnU4COkdq6hWqum/Q6kiVj91qAUHN\n"
"-----END RSA PRIVATE KEY-----";
char *msg = "A message for signing";
char sign[1024] = {0};
ret = rsa_pkcs1v15_sha256_sign((const unsigned char *)msg, strlen(msg), private_key, sign, sizeof (sign));
printf("rsa_pkcs1v15_sha256_sign ret=%d\r\n", ret);
if(ret == 0)
{
printf("sign:%s\r\n", sign);
}
}
int main(void)
{
int ret = 0;
// 公钥
char *pub_key = "-----BEGIN PUBLIC KEY-----\n"
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDTt8tp4xNp29CMxy6QS0NzpR6t\n"
"8bAcv7ei3NkVM/Nzg3K5wWZRaBTMovbzKCXdXYdC6GutVkG+CEetO3XHM4LhDqW0\n"
"vwISTO65/XrvR3zqXD5ZjrJFmtCAvkCwtMAPjqXZ/RJnd8yrXuoz5cRqVgKmq5TZ\n"
"lGIIiTPIklxGIGof8QIDAQAB\n"
"-----END PUBLIC KEY-----";
// 原始消息
char *msg = "A message for signing";
// base64 编码之后的签名数据
char * sign = "KYiZF/C18O3wgCZvDptfM8Vh/OPMrcAf6ne9eszSuxgGMK57cKCQuWc33JF8iQmKWrSo"
"+ezzkPJIfXGTj3z3Js9vv1DC2tX3oBh9CdZF+yc5MqZAT5LEEqmwNKWiT4iNwwnbXiJt"
"NSy8/T2PRRN0PBy/TZn3HKc1AMKMYMLUjf8=";
ret = rsa_pkcs1v15_sha256_verify((const unsigned char *)msg, strlen(msg), pub_key, sign);
printf("rsa_pkcs1v15_sha256_verify ret=%d\r\n", ret);
test_rsa_pkcs1_sign();
return 0;
}