嵌入式系统中的加解密签名

笔者来了解一下嵌入式系统中的加解密

1、背景与名词解释

笔者最近在做安全升级相关的模块,碰到了一些相关的概念和一些应用场景,特来学习记录一下。

1.1 名词解释

  1. 对称加密:对称加密是一种加密方法,使用相同的密钥(称为密钥)进行加密和解密。发送方和接收方必须共享同一个密钥。对称加密算法的优点是速度快,适合大量数据的加密,但密钥分发和管理可能会面临安全挑战。
  2. 非对称加密:非对称加密使用成对的密钥:公钥和私钥。公钥用于加密数据,只有持有配对的私钥才能解密。私钥用于对数据进行签名,公钥用于验证签名。非对称加密算法通常较慢适合于相对较小的数据量,但在密钥分发身份验证方面具有很大的优势。
  3. 签名:数字签名是非对称加密的一种应用,用于验证数据的完整性和来源。发送方使用其私钥对数据进行签名,接收方使用发送方的公钥来验证签名。如果验证成功,则可以确信数据未被篡改过。有点像这个数据被签名了一样,无法抵赖,就是从某个人发出的。
  4. 证书:证书是用于验证公钥拥有者身份的数字文件。证书包含公钥及其拥有者的信息,并由权威认证机构(CA)签名,用于证明该公钥确实属于指定的实体。证书常用于HTTPS连接中,以及公钥基础设施(PKI)中确保通信安全。

1.1.1 对称加密算法

  1. AES(Advanced Encryption Standard)
    AES是一种对称加密算法,广泛应用于保护敏感数据。它支持不同的密钥长度(如AES-128、AES-192、AES-256),速度快且安全性高,被广泛认可为目前最安全的对称加密算法之一。
  2. DES(Data Encryption Standard)
    DES是一种早期的对称加密算法,已不再被推荐使用,因为其56位密钥长度对现代计算能力来说安全性较低。
  3. 3DES(Triple DES)
    3DES是对DES的改进,通过多次对数据块应用DES算法来增强安全性。虽然安全性较高,但由于效率低下和对现代计算能力来说密钥长度较短的缺点,现在也逐渐被AES所取代。

1.1.2 非对称加密算法

  1. RSA(Rivest-Shamir-Adleman)
    RSA是最广为人知的非对称加密算法之一,用于加密、数字签名和密钥交换。它基于大素数的难解性,用于生成公钥和私钥对。RSA在加密和签名方面都有应用。
  2. DSA(Digital Signature Algorithm)
    DSA是一种用于生成和验证数字签名的非对称加密算法。它通常与SHA-1或SHA-2等哈希算法结合使用,用于确保数据的完整性和来源。
  3. ECC(Elliptic Curve Cryptography)
    ECC是一种基于椭圆曲线数学的非对称加密算法。它提供了与RSA相当的安全性,但使用更短的密钥长度,因此在资源受限的环境下更为适用。
  4. DH(Diffie-Hellman)
    Diffie-Hellman是一种密钥交换协议,虽然本身不是加密算法,但用于安全地交换对称加密算法中使用的密钥。它的安全性基于离散对数问题的难解性。
    这些算法都在不同的应用场景中发挥着重要作用,选择合适的加密算法取决于安全需求、性能要求以及特定的应用环境。

总结如下图所示:
在这里插入图片描述
常见的哈希散列算法:
在这里插入图片描述
在这里插入图片描述
接着把SSL和TLS协议也简单介绍一下。
在这里插入图片描述

1.2 为什么对称加密有风险

在这里插入图片描述

如上图所示,甲和乙通过对称加密算法进行通信,

  1. 首先甲向乙发送对称秘钥,约定使用该秘钥进行加密和通讯
  2. 然后发送加密的消息通信,
  3. 由于网上有窃听者会监听数据,所以一旦秘钥在网上传输,就有泄露的风险,泄露之后,数据就和明文在网上传输没什么区别,有风险。
  4. 有些人会问,我不在网络上传输秘钥不就行了吗? 那不在网上传输,乙如何知道甲使用什么方式加密,又合入通信呢?所以该方式一定有风险。

1.3 为什么非对称加密安全性更高

基于以上的想法,一个秘钥肯定不行,那就有两个秘钥,一个永远保存在本地,另外一个可以再网上传输,一个加密,另外一个解密,不就可以保证秘钥不泄露吗?

在这里插入图片描述
如上图所示,私钥永远在本地,公钥在网上传输,公钥加密的数据,私钥才可以解密,所以即使网上知道了公钥,也无用,因为没有私钥,没法解密查看到数据。

在这里插入图片描述

1.4 为什么需要签名

因为上面公钥是公开的,所以任何人都可以用公钥加密给甲或者乙发数据,就导致甲或者乙无法验证对方的身份是否正确,假如窃听者模仿甲或者乙向对方发错误的指令,那可能也有严重的后果。

在这里插入图片描述

所以首先是验证身份,然后才是通信数据,这时候就有了签名的说法,类似你在通信数据上面做了签名,无法抵赖。
签名的验证是通过私钥加密,公钥验证签名的过程来实现,只要公钥可以验证过了,那说明对方一定是甲或者乙发出的,不会是第三方窃听者发出来。
在这里插入图片描述

1.5 为什么还需要证书

假如窃听者 在身份验证时,就截获了数据,然后甲和乙拿到的不是对端的公钥,而且窃听者的公钥,然后在窃听者那边去转换数据。
在这里插入图片描述

如上图所示,这样即使有了身份验证也无效,关键原因就是拿到的公钥不对,如果公钥有一定的公信力,那么就可以认可对方的身份,因此就有了证书的这个概念。

公钥被放到有公信力的证书里面,保证拿到的一定是对的公钥。证书一般有证书颁发机构来发放,即CA(certificate Authority)。证书包括了公钥,同时证书也被签名了,保证证书没有被篡改。

通常被内置在操作系统或浏览器中,这些受信任的根证书(Root Certificate)构成了信任链的基础,用于验证由该CA签发的所有证书的有效性。
在这里插入图片描述

2、嵌入式系统中的安全升级

为什么嵌入式系统重需要安全升级呢?

  • 未经授权的固件更改
  • 无法验证启动加载项的真实性
  • 恶意软件注入风险
  • 难以检测篡改
  • ………

有了签名的理解,我们就可以利用数字签名等知识来解决这个问题。
在这里插入图片描述

2.1 嵌入式系统升级

嵌入式系统中的升级讲解相对较多,可以参考笔者之前的文章,BootLoader的理解与实现Bootloader学习理解----跳转优化异常Bootloader学习理解学习–加强版

在这里插入图片描述

2.2 嵌入式系统安全升级

像上面嵌入式设备升级,如果设备相同,完全可以模拟同样的嵌入式设备下发升级文件,必须进行签名认证信息,才可以。
在这里插入图片描述
如上图所示:

  1. 芯片在升级时,升级包会在固定位置写入整个bin包的签名,然后随固件一块下发,
  2. ROM code在接受完成后,除了验证基本的格式之外,会对整个bin的签名进行验证,如果验证通过,才会写入到Flash当中。
  3. 验签过程中,会涉及到公钥的信息,ROM Code也会进行校验,一般是出厂有相关的配置信息,比如eFuse,可以烧录一次公钥的信息,之后就无法修改了,在验签过程中,会校验公钥以及私钥等信息,然后再校验签名等信息
  4. 升级完成之后,Boot的时候,同样加载起来FW 的bin之后,会进行代码的签名验证,
  5. 一般有两级boot代码,第一级是rom code,第二级是用户的bootloader,做一些定制化的初始化操作,比如外设等,然后最终才跳到FW的固件代码
  6. 同样用户bootloader在跳转到FW时,也会进行签名验证,然后才升级。
  7. 也可以在线升级,上面一般叫ISP,系统升级,下面为IAP,通过FW去升级用户代码,同样需要验证签名秘钥登信息。
    在这里插入图片描述

2.3 Python代码中的RSA加密与签名验证

在python里面,我们用到了Cryptography以及OpenSSL这个库。

  1. 生成一对秘钥
    生成私钥时,public_exponent=65537指的是公钥指数,通常是一个小的奇数,用于加密或者验证签名,
    Key的size为2048位,也就是256Byte
    最后转化为明文显示
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'))

在这里插入图片描述

  1. 私钥对文件进行签名
    签名的时候,指明的padding是v1.5版本的PKCS,一个应用相对广泛标准化的填充方案,适用于RSA签名和加密操作。还有其他版本的填充,比如1.2版本的,以及PSS算法填充等等。
    填充是保证数据块大小符合算法要求的一种方案,加密算法要求数据大小符合256Byte,比如长度是2048位的计算。
    在这里插入图片描述
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)
  1. 公钥对文件进行验签
    验证签名时,一定要和加密时候的填充方案一致,选择PKCSv15,不然会验签失败。
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库来 进行签名和验签工作。

# 生成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编译完成后,会生成三个库文件,之后就可以根据头文件,链接这三个库进行加解密。
在这里插入图片描述

例如下面的代码,可以编译生成来进行测试,生成签名和验签。

2.4.1 生成签名

生成签名的代码如下图所示:先SHA256,然后签名,填充是PKCS V1.5

#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;

}
2.4.2 验证签名

验证签名代码如下图所示:

/**
 * @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;

}
2.4.3 测试代码
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);
    }

}
static void test_rsa_verify_sign(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);
}

 int main(void)
{
    test_rsa_pkcs1_sign();
    test_rsa_verify_sign();
	return 0;
}


  • 25
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张一西

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值