
大家好,我是良许。
在嵌入式系统开发中,数据安全越来越受到重视。
无论是智能家居设备、汽车电子系统,还是工业控制设备,都需要保护敏感数据不被窃取或篡改。
加密算法和密钥管理是保障数据安全的两大核心技术。
今天我们就来聊聊这个话题,看看在嵌入式开发中如何正确使用加密算法,以及如何安全地管理密钥。
对称加密是指加密和解密使用相同密钥的算法。
这类算法的优点是速度快、效率高,非常适合嵌入式系统资源受限的场景。
常见的对称加密算法包括AES、DES、3DES等,其中AES(高级加密标准)是目前最广泛使用的对称加密算法。
在STM32等嵌入式平台上,我们经常使用AES算法来加密敏感数据。
AES支持128位、192位和256位三种密钥长度,密钥越长安全性越高,但计算开销也越大。
对于大多数嵌入式应用来说,AES-128已经足够安全,而且性能表现良好。
举个实际的例子,假设我们要在STM32上加密一段传感器数据,可以使用HAL库提供的加密功能:
#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 size)
{
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,
size, (uint32_t *)ciphertext, 1000);
return status;
}
// AES解密函数
HAL_StatusTypeDef AES_Decrypt(uint8_t *ciphertext, uint8_t *key,
uint8_t *plaintext, uint16_t size)
{
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,
size, (uint32_t *)plaintext, 1000);
return status;
}这段代码展示了如何在STM32上使用硬件加密模块进行AES加密和解密。
需要注意的是,不是所有STM32型号都带有硬件加密模块,如果你的芯片不支持,可以使用软件实现的AES库,比如mbedTLS或者TinyCrypt。
非对称加密使用一对密钥:公钥和私钥。
公钥用于加密,私钥用于解密,或者反过来用于数字签名。
常见的非对称加密算法有RSA、ECC(椭圆曲线加密)等。
非对称加密的优点是密钥分发方便,公钥可以公开传输,不用担心被截获。
但缺点是计算量大,速度慢,不适合加密大量数据。
在嵌入式系统中,我们通常使用非对称加密来交换对称密钥,或者用于数字签名验证固件的完整性。
比如在汽车电子系统中,ECU(电子控制单元)固件更新时,就需要验证固件的数字签名,确保固件来自可信的来源,没有被篡改。
这个过程就用到了非对称加密算法。
#include "mbedtls/rsa.h"
#include "mbedtls/sha256.h"
// 验证固件签名
int verify_firmware_signature(uint8_t *firmware, uint32_t fw_size,
uint8_t *signature, uint8_t *public_key)
{
mbedtls_rsa_context rsa;
unsigned char hash[32];
int ret;
// 计算固件的SHA-256哈希值
mbedtls_sha256(firmware, fw_size, hash, 0);
// 初始化RSA上下文
mbedtls_rsa_init(&rsa, MBEDTLS_RSA_PKCS_V15, 0);
// 导入公钥
ret = mbedtls_rsa_import_raw(&rsa,
public_key, 256, // N
NULL, 0, // P
NULL, 0, // Q
NULL, 0, // D
public_key + 256, 4); // E
if (ret != 0)
{
mbedtls_rsa_free(&rsa);
return -1;
}
// 验证签名
ret = mbedtls_rsa_pkcs1_verify(&rsa, NULL, NULL,
MBEDTLS_RSA_PUBLIC,
MBEDTLS_MD_SHA256,
32, hash, signature);
mbedtls_rsa_free(&rsa);
return ret;
}这个例子展示了如何使用RSA算法验证固件签名。
实际应用中,我们会先用SHA-256计算固件的哈希值,然后用RSA公钥验证签名是否正确。
如果验证通过,说明固件确实来自可信的发布者,并且没有被篡改。
哈希算法虽然不是加密算法,但在密码学中同样重要。
哈希算法可以将任意长度的数据转换成固定长度的哈希值,具有单向性和抗碰撞性。
常见的哈希算法有MD5、SHA-1、SHA-256等,其中SHA-256是目前推荐使用的安全哈希算法。
在嵌入式系统中,哈希算法常用于密码存储、数据完整性校验、消息认证码(MAC)等场景。
比如我们在设备上存储用户密码时,不应该直接存储明文密码,而是存储密码的哈希值。
当用户登录时,我们计算输入密码的哈希值,与存储的哈希值比对,如果一致则认证通过。
#include "mbedtls/sha256.h"
#include <string.h>
// 存储的密码哈希值
uint8_t stored_password_hash[32];
// 设置密码(初始化时调用)
void set_password(const char *password)
{
mbedtls_sha256((uint8_t *)password, strlen(password),
stored_password_hash, 0);
}
// 验证密码
int verify_password(const char *input_password)
{
uint8_t input_hash[32];
// 计算输入密码的哈希值
mbedtls_sha256((uint8_t *)input_password, strlen(input_password),
input_hash, 0);
// 比对哈希值
if (memcmp(stored_password_hash, input_hash, 32) == 0)
{
return 1; // 密码正确
}
else
{
return 0; // 密码错误
}
}这种方式即使数据库被攻击者获取,攻击者也无法直接得到用户的明文密码,因为哈希算法是单向的,无法从哈希值反推出原始密码。
当然,实际应用中我们还会加盐(salt)来进一步提高安全性,防止彩虹表攻击。
密钥的安全性直接决定了加密系统的安全性。
一个好的密钥应该具有足够的随机性和熵值。
在嵌入式系统中,我们可以使用硬件随机数生成器(TRNG)来生成高质量的随机密钥。
STM32系列芯片通常都集成了硬件随机数生成器,我们可以利用它来生成密钥:
#include "stm32f4xx_hal.h"
RNG_HandleTypeDef hrng;
// 初始化随机数生成器
void RNG_Init(void)
{
__HAL_RCC_RNG_CLK_ENABLE();
hrng.Instance = RNG;
if (HAL_RNG_Init(&hrng) != HAL_OK)
{
Error_Handler();
}
}
// 生成AES密钥
HAL_StatusTypeDef generate_aes_key(uint8_t *key, uint16_t key_size)
{
uint32_t random_number;
HAL_StatusTypeDef status;
for (uint16_t i = 0; i < key_size; i += 4)
{
status = HAL_RNG_GenerateRandomNumber(&hrng, &random_number);
if (status != HAL_OK)
{
return status;
}
// 将32位随机数拆分成4个字节
key[i] = (random_number >> 0) & 0xFF;
key[i+1] = (random_number >> 8) & 0xFF;
key[i+2] = (random_number >> 16) & 0xFF;
key[i+3] = (random_number >> 24) & 0xFF;
}
return HAL_OK;
}使用硬件随机数生成器生成的密钥具有很好的随机性,可以有效抵御暴力破解攻击。
如果芯片不支持硬件随机数生成器,我们也可以使用软件方法,比如收集系统运行时的各种不确定因素(如ADC噪声、定时器计数值等)作为熵源来生成随机数。
密钥的存储是密钥管理中最关键的环节。
如果密钥被攻击者获取,整个加密系统就形同虚设。
在嵌入式系统中,我们有几种常见的密钥存储方案:
第一种是将密钥存储在Flash中。
这是最简单的方法,但安全性较低,因为Flash的内容可以通过调试接口读取。
为了提高安全性,我们可以启用Flash读保护功能,防止通过JTAG或SWD接口读取Flash内容。
// 在Flash中定义密钥存储区域(使用__attribute__指定地址)
__attribute__((section(".key_section")))
const uint8_t aes_key[16] = {
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c
};
// 启用Flash读保护
void enable_flash_protection(void)
{
FLASH_OBProgramInitTypeDef ob_config;
HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();
ob_config.OptionType = OPTIONBYTE_RDP;
ob_config.RDPLevel = OB_RDP_LEVEL_1; // 设置读保护级别1
if (HAL_FLASHEx_OBProgram(&ob_config) != HAL_OK)
{
Error_Handler();
}
HAL_FLASH_OB_Launch(); // 使配置生效
HAL_FLASH_OB_Lock();
HAL_FLASH_Lock();
}第二种是使用安全元件或加密芯片。
这些专用芯片内部有安全存储区域,密钥存储在芯片内部,永远不会以明文形式暴露出来。
加密操作也在芯片内部完成,即使攻击者物理接触到设备,也很难提取出密钥。
常见的安全芯片有ATECC608、SE050等。
第三种是使用芯片的OTP(一次性可编程)区域或安全存储区域。
比如STM32L5系列芯片提供了TrustZone技术,可以将密钥存储在安全区域,只有安全代码才能访问。
在实际应用中,我们经常需要在多个设备之间共享密钥,或者动态更新密钥。
密钥分发和交换是一个复杂的问题,需要仔细设计。
对于对称加密,我们可以使用密钥交换协议,比如Diffie-Hellman密钥交换协议。
这个协议允许两个设备在不安全的信道上协商出一个共享密钥,而不用担心被窃听。
#include "mbedtls/dhm.h"
mbedtls_dhm_context dhm;
// 初始化DH密钥交换
int dhm_init(void)
{
const char *dhm_P = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
"83655D23DCA3AD961C62F356208552BB9ED529077096966D"
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
"15728E5A8AACAA68FFFFFFFFFFFFFFFF";
const char *dhm_G = "02";
mbedtls_dhm_init(&dhm);
return mbedtls_mpi_read_string(&dhm.P, 16, dhm_P) ||
mbedtls_mpi_read_string(&dhm.G, 16, dhm_G);
}
// 生成公钥
int dhm_make_public(uint8_t *output, size_t *olen)
{
int ret;
ret = mbedtls_dhm_make_public(&dhm, 256, output, *olen,
mbedtls_ctr_drbg_random, &ctr_drbg);
if (ret == 0)
{
*olen = mbedtls_mpi_size(&dhm.GX);
}
return ret;
}
// 计算共享密钥
int dhm_calc_secret(uint8_t *peer_public, size_t peer_len,
uint8_t *output, size_t *olen)
{
int ret;
ret = mbedtls_dhm_read_public(&dhm, peer_public, peer_len);
if (ret != 0)
{
return ret;
}
ret = mbedtls_dhm_calc_secret(&dhm, output, *olen, olen,
mbedtls_ctr_drbg_random, &ctr_drbg);
return ret;
}通过DH密钥交换,两个设备可以各自生成一对公私钥,交换公钥后计算出相同的共享密钥。
这个共享密钥可以用作AES等对称加密算法的密钥。
另一种常见的方案是使用非对称加密来传输对称密钥。
发送方用接收方的公钥加密对称密钥,接收方用自己的私钥解密得到对称密钥。
这种方式结合了非对称加密的密钥分发优势和对称加密的高效性。
密钥不应该永久使用,定期更新密钥可以降低密钥泄露的风险。
在嵌入式系统中,我们可以设计密钥更新机制,比如每隔一定时间或者传输一定数量的数据后自动更新密钥。
#include <time.h>
typedef struct {
uint8_t key[16];
time_t creation_time;
uint32_t usage_count;
} key_info_t;
key_info_t current_key;
#define KEY_LIFETIME_SECONDS (24 * 3600) // 密钥有效期24小时
#define KEY_USAGE_LIMIT 10000 // 密钥使用次数限制
// 检查是否需要更新密钥
int check_key_update_needed(void)
{
time_t current_time = time(NULL);
// 检查密钥是否过期
if (current_time - current_key.creation_time > KEY_LIFETIME_SECONDS)
{
return 1;
}
// 检查密钥使用次数是否超限
if (current_key.usage_count > KEY_USAGE_LIMIT)
{
return 1;
}
return 0;
}
// 更新密钥
int update_key(void)
{
// 生成新密钥
if (generate_aes_key(current_key.key, 16) != HAL_OK)
{
return -1;
}
// 更新密钥信息
current_key.creation_time = time(NULL);
current_key.usage_count = 0;
// 保存新密钥到安全存储
save_key_to_flash(¤t_key);
return 0;
}
// 使用密钥进行加密
int encrypt_with_key_management(uint8_t *plaintext, uint8_t *ciphertext,
uint16_t size)
{
// 检查是否需要更新密钥
if (check_key_update_needed())
{
if (update_key() != 0)
{
return -1;
}
}
// 执行加密
if (AES_Encrypt(plaintext, current_key.key, ciphertext, size) != HAL_OK)
{
return -1;
}
// 增加使用计数
current_key.usage_count++;
return 0;
}这个例子展示了一个简单的密钥更新机制,根据时间和使用次数来决定是否更新密钥。
实际应用中,密钥更新还需要考虑与对端设备的同步问题,确保双方使用相同的密钥。
在我之前做汽车电子项目时,ECU固件升级是一个非常重要的功能。
我们需要确保只有经过授权的固件才能被安装到ECU上,防止恶意固件被植入。
整个流程是这样的:首先,固件在发布时会用私钥进行签名。
ECU在接收到新固件后,会用预先存储的公钥验证签名。
只有签名验证通过的固件才会被安装。
同时,固件本身是加密的,使用AES加密,密钥通过安全信道传输到ECU。
typedef struct {
uint32_t version;
uint32_t size;
uint8_t signature[256];
uint8_t encrypted_firmware[];
} firmware_package_t;
// 固件升级流程
int firmware_upgrade(firmware_package_t *package)
{
uint8_t *decrypted_firmware;
uint8_t aes_key[16];
// 1. 验证固件签名
if (verify_firmware_signature(package->encrypted_firmware,
package->size,
package->signature,
public_key) != 0)
{
printf("Signature verification failed!\n");
return -1;
}
// 2. 获取解密密钥(通过安全信道)
if (get_decryption_key(aes_key) != 0)
{
printf("Failed to get decryption key!\n");
return -1;
}
// 3. 解密固件
decrypted_firmware = malloc(package->size);
if (AES_Decrypt(package->encrypted_firmware, aes_key,
decrypted_firmware, package->size) != HAL_OK)
{
printf("Firmware decryption failed!\n");
free(decrypted_firmware);
return -1;
}
// 4. 写入Flash
if (write_firmware_to_flash(decrypted_firmware, package->size) != 0)
{
printf("Failed to write firmware to flash!\n");
free(decrypted_firmware);
return -1;
}
free(decrypted_firmware);
printf("Firmware upgrade successful!\n");
return 0;
}这种方案结合了非对称加密(数字签名)和对称加密(AES加密固件),既保证了固件来源的可信性,又保护了固件内容不被窃取。
在物联网设备中,设备与云端服务器之间的通信需要加密保护。
我们通常使用TLS/SSL协议来建立安全连接。
TLS协议内部就综合使用了对称加密、非对称加密和哈希算法。
在嵌入式Linux系统上,我们可以使用OpenSSL或mbedTLS库来实现TLS通信:
#include "mbedtls/net_sockets.h"
#include "mbedtls/ssl.h"
mbedtls_net_context server_fd;
mbedtls_ssl_context ssl;
mbedtls_ssl_config conf;
// 建立TLS连接
int establish_tls_connection(const char *server_addr, const char *server_port)
{
int ret;
// 初始化
mbedtls_net_init(&server_fd);
mbedtls_ssl_init(&ssl);
mbedtls_ssl_config_init(&conf);
// 连接服务器
ret = mbedtls_net_connect(&server_fd, server_addr, server_port,
MBEDTLS_NET_PROTO_TCP);
if (ret != 0)
{
printf("Failed to connect to server\n");
return -1;
}
// 配置SSL
ret = mbedtls_ssl_config_defaults(&conf,
MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT);
if (ret != 0)
{
printf("Failed to set SSL config\n");
return -1;
}
mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_REQUIRED);
mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL);
mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
ret = mbedtls_ssl_setup(&ssl, &conf);
if (ret != 0)
{
printf("Failed to setup SSL\n");
return -1;
}
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)
{
printf("SSL handshake failed\n");
return -1;
}
}
printf("TLS connection established\n");
return 0;
}
// 通过TLS发送数据
int tls_send_data(const uint8_t *data, size_t len)
{
int ret;
while ((ret = mbedtls_ssl_write(&ssl, data, len)) <= 0)
{
if (ret != MBEDTLS_ERR_SSL_WANT_READ &&
ret != MBEDTLS_ERR_SSL_WANT_WRITE)
{
printf("Failed to send data\n");
return -1;
}
}
return ret;
}
// 通过TLS接收数据
int tls_receive_data(uint8_t *buffer, size_t max_len)
{
int ret;
ret = mbedtls_ssl_read(&ssl, buffer, max_len);
if (ret == MBEDTLS_ERR_SSL_WANT_READ ||
ret == MBEDTLS_ERR_SSL_WANT_WRITE)
{
return 0;
}
if (ret < 0)
{
printf("Failed to receive data\n");
return -1;
}
return ret;
}TLS协议在握手阶段使用非对称加密交换密钥,在数据传输阶段使用对称加密保护数据,同时使用哈希算法确保数据完整性。
这种设计兼顾了安全性和性能。
在实际开发中,我总结了一些加密算法和密钥管理的最佳实践:
第一,永远不要自己实现加密算法。
加密算法的实现非常复杂,稍有不慎就会引入安全漏洞。
应该使用经过广泛验证的加密库,比如mbedTLS、OpenSSL、wolfSSL等。
第二,不要在代码中硬编码密钥。
硬编码的密钥很容易被反编译工具提取出来。
应该使用安全存储方案,比如安全芯片、OTP区域等。
第三,使用足够长的密钥。
对于AES算法,至少使用128位密钥;对于RSA算法,至少使用2048位密钥。
密钥长度直接影响安全强度。
第四,定期更新密钥。
不要让一个密钥永久使用,应该根据实际情况设计密钥更新策略。
第五,保护密钥的整个生命周期。
从密钥生成、存储、使用到销毁,每个环节都要考虑安全性。
特别是密钥销毁时,要确保密钥被彻底清除,不能留下任何痕迹。
第六,使用硬件加密加速器。
现代MCU通常都集成了硬件加密模块,使用硬件加密不仅速度快,而且更安全,因为密钥操作在硬件内部完成,不容易被侧信道攻击。
第七,进行安全测试。
在产品发布前,应该进行充分的安全测试,包括密码学分析、渗透测试等,确保加密系统没有明显的安全漏洞。
加密算法和密钥管理是一个复杂的话题,涉及的知识点很多。
但只要我们遵循安全最佳实践,选择合适的加密方案,就能够为嵌入式系统构建起坚固的安全防线。
在这个万物互联的时代,数据安全变得越来越重要,作为嵌入式开发者,我们有责任保护好用户的数据安全。
更多编程学习资源
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。