嵌入式安全和加密技术

大家好,我是良许。

最近几年,随着物联网设备的爆发式增长,嵌入式系统的安全问题越来越受到重视。

我记得几年前做汽车电子项目的时候,客户对安全的要求还比较宽松,但现在不一样了,几乎每个项目都会被问到"你们的加密方案是什么?

""如何防止固件被破解?"这些问题。

今天就和大家聊聊嵌入式安全和加密技术这个话题。

1. 为什么嵌入式系统需要安全保护

1.1 嵌入式系统面临的安全威胁

嵌入式设备不像服务器那样有专人维护,它们往往部署在各种环境中,面临的威胁也更加多样化。

我在做汽车电子的时候就遇到过这样的案例:有黑客通过 CAN 总线注入恶意指令,导致车辆的某些功能异常。

这还只是冰山一角,实际上嵌入式系统面临的威胁包括:

物理攻击:攻击者可以直接接触到设备,通过 JTAG、UART 等调试接口读取内存数据,甚至可以用显微镜观察芯片内部结构进行侧信道攻击。

我见过有人用热风枪把 Flash 芯片拆下来,直接用编程器读取固件。

网络攻击:现在的嵌入式设备大多联网,黑客可以通过网络漏洞入侵设备。

比如很多智能家居设备使用弱密码或者默认密码,很容易被攻破。

固件逆向:攻击者获取固件后,可以通过反汇编、反编译等手段分析代码逻辑,找出加密算法和密钥,甚至可以修改固件植入后门。

供应链攻击:在生产、运输、维护等环节,设备可能被植入恶意代码或硬件木马。

1.2 安全防护的必要性

对于我们做嵌入式开发的来说,安全不再是可选项,而是必选项。

特别是在汽车、医疗、工控等关键领域,一旦出现安全问题,后果不堪设想。

我之前在外企做项目的时候,有一次因为安全测试没通过,整个项目延期了三个月,损失了上百万。

所以现在我做项目,都会在设计阶段就把安全考虑进去,而不是等到最后才想起来打补丁。

2. 嵌入式加密技术基础

2.1 对称加密算法

对称加密是最常用的加密方式,加密和解密使用同一个密钥。

在嵌入式系统中,AES(高级加密标准)是最流行的对称加密算法。

AES 加密原理:AES 支持 128 位、192 位、256 位三种密钥长度,加密过程包括字节替换、行移位、列混淆、轮密钥加等步骤。

对于嵌入式系统来说,AES-128 通常就足够了,因为它在安全性和性能之间取得了很好的平衡。

下面是一个使用 STM32 的硬件 AES 加密的示例代码:

复制代码
#include "stm32f4xx_hal.h"
​
CRYP_HandleTypeDef hcryp;
​
// 初始化AES硬件加密模块
void AES_Init(void)
{
    __HAL_RCC_CRYP_CLK_ENABLE();
    
    hcryp.Instance = CRYP;
    hcryp.Init.DataType = CRYP_DATATYPE_8B;
    hcryp.Init.KeySize = CRYP_KEYSIZE_128B;
    hcryp.Init.Algorithm = CRYP_AES_ECB;
    
    if (HAL_CRYP_Init(&hcryp) != HAL_OK)
    {
        Error_Handler();
    }
}
​
// AES加密函数
HAL_StatusTypeDef AES_Encrypt(uint8_t *plaintext, uint8_t *key, 
                               uint8_t *ciphertext, uint16_t length)
{
    HAL_StatusTypeDef status;
    
    // 设置密钥
    hcryp.Init.pKey = (uint32_t *)key;
    
    if (HAL_CRYP_Init(&hcryp) != HAL_OK)
    {
        return HAL_ERROR;
    }
    
    // 执行加密
    status = HAL_CRYP_Encrypt(&hcryp, (uint32_t *)plaintext, 
                              length/4, (uint32_t *)ciphertext, 1000);
    
    return status;
}
​
// AES解密函数
HAL_StatusTypeDef AES_Decrypt(uint8_t *ciphertext, uint8_t *key, 
                               uint8_t *plaintext, uint16_t length)
{
    HAL_StatusTypeDef status;
    
    hcryp.Init.pKey = (uint32_t *)key;
    
    if (HAL_CRYP_Init(&hcryp) != HAL_OK)
    {
        return HAL_ERROR;
    }
    
    status = HAL_CRYP_Decrypt(&hcryp, (uint32_t *)ciphertext, 
                              length/4, (uint32_t *)plaintext, 1000);
    
    return status;
}

这段代码展示了如何使用 STM32 的硬件加密模块进行 AES 加密和解密。

使用硬件加密的好处是速度快、功耗低,而且密钥不会暴露在软件中,安全性更高。

我在实际项目中,只要芯片支持硬件加密,都会优先使用。

2.2 非对称加密算法

非对称加密使用一对密钥:公钥和私钥。

公钥用于加密,私钥用于解密。

最常用的非对称加密算法是 RSA 和 ECC(椭圆曲线加密)。

RSA vs ECC:在嵌入式系统中,我更倾向于使用 ECC 而不是 RSA,原因很简单:ECC 在相同安全强度下,密钥长度更短,计算速度更快,占用的存储空间也更小。

比如 256 位的 ECC 密钥,安全强度相当于 3072 位的 RSA 密钥。

下面是一个使用 mbedTLS 库进行 ECC 签名验证的示例:

复制代码
#include "mbedtls/ecdsa.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
​
// ECC签名验证函数
int verify_firmware_signature(uint8_t *firmware, size_t fw_len, 
                               uint8_t *signature, size_t sig_len)
{
    int ret;
    mbedtls_ecdsa_context ctx;
    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctr_drbg;
    uint8_t hash[32];
    
    // 初始化
    mbedtls_ecdsa_init(&ctx);
    mbedtls_entropy_init(&entropy);
    mbedtls_ctr_drbg_init(&ctr_drbg);
    
    // 计算固件的SHA256哈希
    mbedtls_sha256(firmware, fw_len, hash, 0);
    
    // 加载公钥(这里假设公钥已经存储在设备中)
    // 实际项目中,公钥通常烧录在OTP区域或者安全存储区
    ret = load_public_key(&ctx);
    if (ret != 0)
    {
        goto cleanup;
    }
    
    // 验证签名
    ret = mbedtls_ecdsa_read_signature(&ctx, hash, sizeof(hash),
                                       signature, sig_len);
    
cleanup:
    mbedtls_ecdsa_free(&ctx);
    mbedtls_entropy_free(&entropy);
    mbedtls_ctr_drbg_free(&ctr_drbg);
    
    return ret;
}

这个函数用于验证固件的数字签名,确保固件没有被篡改。

在实际项目中,我会在 Bootloader 阶段就进行签名验证,只有验证通过的固件才允许运行。

2.3 哈希算法

哈希算法用于生成数据的"指纹",常用的有 SHA-256、SHA-3 等。

哈希算法有两个重要特性:单向性(不可逆)和抗碰撞性(很难找到两个不同的输入产生相同的输出)。

在嵌入式系统中,哈希算法主要用于:

  • 固件完整性校验

  • 密码存储(存储密码的哈希值而不是明文)

  • 数字签名(先对数据进行哈希,再对哈希值签名)

    #include "mbedtls/sha256.h"

    // 计算固件的SHA256哈希值
    void calculate_firmware_hash(uint8_t *firmware, size_t length,
    uint8_t *hash_output)
    {
    mbedtls_sha256_context ctx;

    复制代码
      mbedtls_sha256_init(&ctx);
      mbedtls_sha256_starts(&ctx, 0); // 0表示SHA-256
      mbedtls_sha256_update(&ctx, firmware, length);
      mbedtls_sha256_finish(&ctx, hash_output);
      mbedtls_sha256_free(&ctx);

    }

    // 验证固件完整性
    int verify_firmware_integrity(uint8_t *firmware, size_t length,
    uint8_t *expected_hash)
    {
    uint8_t calculated_hash[32];

    复制代码
      calculate_firmware_hash(firmware, length, calculated_hash);
      
      // 比较计算出的哈希值和预期的哈希值
      if (memcmp(calculated_hash, expected_hash, 32) == 0)
      {
          return 0; // 验证通过
      }
      else
      {
          return -1; // 验证失败
      }

    }

3. 密钥管理

3.1 密钥存储

密钥的安全存储是整个加密系统的基础。

如果密钥泄露,再强的加密算法也没用。

在嵌入式系统中,密钥存储有几种方案:

硬编码在代码中:这是最不安全的方式,但我见过很多项目都这么做。

攻击者只要反编译固件,就能轻松找到密钥。

千万不要这样做!

存储在 Flash 中:比硬编码稍好一点,但 Flash 的内容可以被读取出来。

如果一定要存在 Flash 中,至少要对密钥进行加密存储,使用一个主密钥来加密其他密钥。

存储在 OTP 区域:OTP(One-Time Programmable)区域只能写入一次,写入后无法修改,而且通常有读保护功能。

这是比较安全的方式,我在项目中经常使用。

使用安全芯片:最安全的方式是使用专门的安全芯片(如 TPM、SE 等)来存储密钥。

这些芯片有防篡改机制,即使攻击者物理接触到芯片,也很难提取出密钥。

3.2 密钥派生

在实际应用中,我们通常不会直接使用原始密钥,而是通过密钥派生函数(KDF)从主密钥派生出多个子密钥。

这样做的好处是:

  • 不同的功能使用不同的密钥,即使一个密钥泄露,其他功能仍然安全

  • 可以定期更换子密钥,而不需要更换主密钥

    #include "mbedtls/hkdf.h"

    // 使用HKDF派生密钥
    int derive_key(uint8_t *master_key, size_t master_key_len,
    uint8_t *info, size_t info_len,
    uint8_t *derived_key, size_t derived_key_len)
    {
    const mbedtls_md_info_t *md_info;

    复制代码
      md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
      
      return mbedtls_hkdf(md_info, NULL, 0, 
                          master_key, master_key_len,
                          info, info_len,
                          derived_key, derived_key_len);

    }

    // 示例:派生不同用途的密钥
    void generate_session_keys(uint8_t *master_key)
    {
    uint8_t encryption_key[16];
    uint8_t authentication_key[32];

    复制代码
      // 派生加密密钥
      derive_key(master_key, 32, 
                 (uint8_t *)"encryption", 10,
                 encryption_key, 16);
      
      // 派生认证密钥
      derive_key(master_key, 32,
                 (uint8_t *)"authentication", 14,
                 authentication_key, 32);

    }

4. 安全启动(Secure Boot)

4.1 安全启动的原理

安全启动是嵌入式系统安全的第一道防线。

它的核心思想是:在系统启动的每个阶段,都验证下一阶段代码的完整性和真实性,形成一条信任链。

信任链的建立

  1. 芯片厂商在芯片出厂时,会在 ROM 中烧录一段不可修改的启动代码(Boot ROM),这是信任的根(Root of Trust)
  2. Boot ROM 验证 Bootloader 的签名,确保 Bootloader 没有被篡改
  3. Bootloader 验证应用程序的签名,确保应用程序是可信的
  4. 只有验证通过,系统才会继续启动,否则进入安全模式或者拒绝启动

4.2 实现安全启动

下面是一个简化的安全启动流程示例:

复制代码
#define APP_START_ADDR    0x08010000
#define APP_SIZE          0x00070000
#define SIGNATURE_ADDR    0x08080000
​
typedef void (*app_func_t)(void);
​
// 安全启动主函数
void secure_boot(void)
{
    uint8_t *app_code = (uint8_t *)APP_START_ADDR;
    uint8_t *signature = (uint8_t *)SIGNATURE_ADDR;
    int ret;
    
    // 1. 验证应用程序签名
    ret = verify_firmware_signature(app_code, APP_SIZE, 
                                     signature, 64);
    
    if (ret != 0)
    {
        // 签名验证失败,进入安全模式
        enter_safe_mode();
        return;
    }
    
    // 2. 验证应用程序哈希值
    uint8_t expected_hash[32];
    uint8_t calculated_hash[32];
    
    // 从安全存储区读取预期的哈希值
    read_expected_hash(expected_hash);
    calculate_firmware_hash(app_code, APP_SIZE, calculated_hash);
    
    if (memcmp(expected_hash, calculated_hash, 32) != 0)
    {
        // 哈希验证失败
        enter_safe_mode();
        return;
    }
    
    // 3. 验证通过,跳转到应用程序
    app_func_t app = (app_func_t)(*(uint32_t *)(APP_START_ADDR + 4));
    
    // 设置应用程序的栈指针
    __set_MSP(*(uint32_t *)APP_START_ADDR);
    
    // 跳转到应用程序
    app();
}
​
// 安全模式处理
void enter_safe_mode(void)
{
    // 记录错误日志
    log_security_error();
    
    // 可以尝试恢复出厂固件
    // 或者等待通过安全通道更新固件
    
    while(1)
    {
        // 闪烁LED指示错误
        HAL_GPIO_TogglePin(ERROR_LED_GPIO_Port, ERROR_LED_Pin);
        HAL_Delay(500);
    }
}

5. 通信安全

5.1 TLS/DTLS 协议

对于需要网络通信的嵌入式设备,使用 TLS(传输层安全)或 DTLS(数据报传输层安全)协议是必须的。

TLS 用于 TCP 连接,DTLS 用于 UDP 连接。

在嵌入式 Linux 系统中,我通常使用 mbedTLS 库来实现 TLS 通信。

下面是一个简单的 TLS 客户端示例:

复制代码
#include "mbedtls/net_sockets.h"
#include "mbedtls/ssl.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
​
int tls_client_connect(const char *server_addr, const char *server_port)
{
    int ret;
    mbedtls_net_context server_fd;
    mbedtls_ssl_context ssl;
    mbedtls_ssl_config conf;
    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctr_drbg;
    
    // 初始化
    mbedtls_net_init(&server_fd);
    mbedtls_ssl_init(&ssl);
    mbedtls_ssl_config_init(&conf);
    mbedtls_entropy_init(&entropy);
    mbedtls_ctr_drbg_init(&ctr_drbg);
    
    // 初始化随机数生成器
    ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, 
                                 &entropy, NULL, 0);
    if (ret != 0)
    {
        return -1;
    }
    
    // 连接到服务器
    ret = mbedtls_net_connect(&server_fd, server_addr, 
                              server_port, MBEDTLS_NET_PROTO_TCP);
    if (ret != 0)
    {
        return -1;
    }
    
    // 设置SSL配置
    mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT,
                                MBEDTLS_SSL_TRANSPORT_STREAM,
                                MBEDTLS_SSL_PRESET_DEFAULT);
    
    mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
    
    // 设置SSL上下文
    mbedtls_ssl_setup(&ssl, &conf);
    mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send,
                        mbedtls_net_recv, NULL);
    
    // 执行SSL握手
    while ((ret = mbedtls_ssl_handshake(&ssl)) != 0)
    {
        if (ret != MBEDTLS_ERR_SSL_WANT_READ && 
            ret != MBEDTLS_ERR_SSL_WANT_WRITE)
        {
            return -1;
        }
    }
    
    // 握手成功,可以开始安全通信
    return 0;
}

5.2 消息认证码(MAC)

对于一些资源受限的设备,可能无法使用完整的 TLS 协议。

这时候可以使用消息认证码(MAC)来保证消息的完整性和真实性。

常用的 MAC 算法有 HMAC(基于哈希的消息认证码)。

复制代码
#include "mbedtls/md.h"
​
// 计算HMAC
int calculate_hmac(uint8_t *key, size_t key_len,
                   uint8_t *message, size_t msg_len,
                   uint8_t *mac_output)
{
    const mbedtls_md_info_t *md_info;
    
    md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
    
    return mbedtls_md_hmac(md_info, key, key_len, 
                          message, msg_len, mac_output);
}
​
// 验证消息的HMAC
int verify_message_hmac(uint8_t *key, size_t key_len,
                        uint8_t *message, size_t msg_len,
                        uint8_t *received_mac)
{
    uint8_t calculated_mac[32];
    
    calculate_hmac(key, key_len, message, msg_len, calculated_mac);
    
    if (memcmp(calculated_mac, received_mac, 32) == 0)
    {
        return 0; // 验证通过
    }
    else
    {
        return -1; // 验证失败
    }
}

6. 防护措施和最佳实践

6.1 代码混淆和加固

即使使用了加密技术,如果攻击者能够轻松逆向你的代码,找出加密算法的实现细节,安全性还是会大打折扣。

因此,代码混淆和加固也很重要。

代码混淆技术

  • 控制流平坦化:把代码的控制流打乱,让逆向分析变得困难
  • 虚假控制流:插入永远不会执行的代码分支,干扰静态分析
  • 字符串加密:把代码中的字符串常量加密,运行时再解密

防调试技术

  • 检测调试器:通过检测调试寄存器、时间差异等方式判断是否在调试状态
  • 反 JTAG:禁用或加密 JTAG 接口
  • 读保护:启用芯片的读保护功能,防止通过调试接口读取 Flash 内容

6.2 安全更新机制

再安全的系统也可能存在漏洞,因此需要有安全的固件更新机制。

我在项目中实现 OTA(Over-The-Air)更新时,会遵循以下原则:

双区更新:Flash 分为两个区域,一个运行区,一个备份区。

更新时先把新固件写入备份区,验证通过后再切换。

这样即使更新失败,也能回退到旧版本。

增量更新:对于大型固件,可以只传输变化的部分,减少传输时间和流量消耗。

安全通道:固件传输必须使用加密通道,防止中间人攻击。

签名验证:新固件必须经过签名验证,确保来自可信的源。

复制代码
// OTA更新流程
int ota_update(uint8_t *new_firmware, size_t fw_size, 
               uint8_t *signature)
{
    // 1. 验证签名
    if (verify_firmware_signature(new_firmware, fw_size, 
                                   signature, 64) != 0)
    {
        return -1; // 签名验证失败
    }
    
    // 2. 写入备份区
    if (write_to_backup_area(new_firmware, fw_size) != 0)
    {
        return -2; // 写入失败
    }
    
    // 3. 验证写入的数据
    if (verify_backup_area(new_firmware, fw_size) != 0)
    {
        return -3; // 验证失败
    }
    
    // 4. 设置启动标志,下次启动时切换到新固件
    set_boot_flag(BOOT_FROM_BACKUP);
    
    // 5. 重启系统
    NVIC_SystemReset();
    
    return 0;
}

6.3 日志和审计

安全事件的记录和审计也很重要。我在项目中会记录以下事件:

  • 启动失败(签名验证失败、哈希验证失败等)
  • 非法访问尝试
  • 固件更新记录
  • 密钥更换记录

这些日志可以帮助我们及时发现安全问题,分析攻击手段。

当然,日志本身也需要保护,防止被篡改。

7. 总结

嵌入式安全是一个系统工程,需要从硬件、软件、通信等多个层面来考虑。

我这些年做项目的经验告诉我,安全不是事后补救,而应该在设计阶段就融入到系统中。

虽然增加安全机制会带来一些额外的开发工作量和成本,但相比于出现安全问题后的损失,这些投入是完全值得的。

最后给大家几点建议:第一,不要自己发明加密算法,使用经过验证的标准算法。

第二,密钥管理是重中之重,一定要妥善保护。

第三,保持学习,安全技术在不断发展,攻击手段也在不断进化,我们需要持续关注最新的安全动态。

希望这篇文章能够帮助大家更好地理解嵌入式安全和加密技术,在实际项目中应用这些知识,开发出更安全可靠的嵌入式系统。

更多编程学习资源

相关推荐
用户962377954481 天前
VulnHub DC-3 靶机渗透测试笔记
安全
叶落阁主2 天前
Tailscale 完全指南:从入门到私有 DERP 部署
运维·安全·远程工作
用户962377954484 天前
DVWA 靶场实验报告 (High Level)
安全
数据智能老司机4 天前
用于进攻性网络安全的智能体 AI——在 n8n 中构建你的第一个 AI 工作流
人工智能·安全·agent
数据智能老司机4 天前
用于进攻性网络安全的智能体 AI——智能体 AI 入门
人工智能·安全·agent
用户962377954484 天前
DVWA 靶场实验报告 (Medium Level)
安全
red1giant_star4 天前
S2-067 漏洞复现:Struts2 S2-067 文件上传路径穿越漏洞
安全
用户962377954484 天前
DVWA Weak Session IDs High 的 Cookie dvwaSession 为什么刷新不出来?
安全
cipher6 天前
ERC-4626 通胀攻击:DeFi 金库的"捐款陷阱"
前端·后端·安全
一次旅行9 天前
网络安全总结
安全·web安全