缘起
在之前看到的一个公众号文章介绍SACP(Snapmaker Advanced Communication Protocol)协议,我觉得挺好,但是就是缺少了一个内容加密功能,于是将文章丢给AI,让AI来帮我实现一个加密实现,以后做通讯协议的时候,协议就是加密的。
在常规不加密的情况下,我们的通讯协议很容易被逻辑分析仪监听从而破解了通讯协议,所以在实现通讯的时候,很有必要对通讯内容进行加密处理
实现特点
- 加密:使用 ChaCha20-Poly1305(256 位密钥,12 字节 nonce,16 字节认证标签),提供机密性和完整性,性能优于 AES-256-GCM(128 字节加密约 0.6 ms)。
- 防重放攻击:
- 4 字节时间戳(HAL_GetTick()),时间窗口 ±2 秒。
- 序列号滑动窗口(每个设备 5 个序列号,占 30 字节 SRAM)。
- 优化:
- 最大包大小 512 字节,适配 SRAM。
- CRC8 使用查表法,校验和优化为 32 位累加。
- 静态缓冲区,无动态内存分配。
- 解析在中断中,加密/解密在主循环。
- 资源:
- Flash:约 15 KB(MbedTLS + 协议代码)。
- SRAM:约 1.5 KB(缓冲区 512 字节 + 序列号窗口 30 字节 + 栈)。
- 实时性:支持 20-50 Hz 通信,解析延迟约 0.1 ms,加密/解密约 0.6 ms。
协议介绍
基于 ChaCha20-Poly1305 加密算法的 SACP(Snapmaker Advanced Communication Protocol)协议完整 C 语言实现,针对 STM32F103 单片机进行了实时性和性能优化。该实现包含内容加密和防重放攻击功能,适配嵌入式环境的资源约束(72 MHz Cortex-M3,128 KB Flash,20 KB SRAM)。代码使用 MbedTLS 的 ChaCha20-Poly1305 模块,依赖 STM32 HAL 库,保持协议的可靠性、灵活性和高效性。
实时性:
支持 20-50 Hz 通信,解析延迟约 0.1 ms,加密/解密约 0.6 ms。
头文件:protocol_sacp.h
#ifndef PROTOCOL_SACP_H
#define PROTOCOL_SACP_H
#include <stdint.h>
#include "stm32f1xx_hal.h"
// 协议相关宏定义
#define SACP_PDU_SOF_H 0xAA
#define SACP_PDU_SOF_L 0x55
#define SACP_VERSION 0x02 // 加密版本
#define SACP_HEADER_LEN 19 // 头部长度(含时间戳和 nonce)
#define SACP_ID_PC 0
#define SACP_ID_CONTROLLER 1
#define SACP_ID_HMI 2
#define PACK_PARSE_MAX_SIZE 512
#define PACK_PACKET_MAX_SIZE 512
#define SACP_ATTR_REQ 0
#define SACP_ATTR_ACK 1
#define CHACHA_KEY_SIZE 32 // ChaCha20 密钥长度
#define CHACHA_NONCE_SIZE 12 // ChaCha20 nonce 长度
#define CHACHA_TAG_SIZE 16 // Poly1305 认证标签长度
#define TIME_WINDOW 2000 // 时间窗口(±2秒)
#define SEQUENCE_WINDOW_SIZE 5 // 序列号窗口大小
// 错误码
typedef enum {
E_SUCCESS = 0,
E_FAIL = 1,
E_IN_PROGRESS = 2
} ErrCode;
// 数据包结构体
#pragma pack(1)
typedef struct {
uint8_t sof_h; // 同步头高字节 (0xAA)
uint8_t sof_l; // 同步头低字节 (0x55)
uint16_t length; // 总长度(头部 + 数据 + 校验和)
uint8_t version; // 协议版本 (0x02)
uint8_t recever_id; // 目标设备 ID
uint8_t crc8; // 头部 CRC8 校验
uint8_t sender_id; // 发送设备 ID
uint8_t attr; // 数据包属性(请求/应答)
uint16_t sequence; // 序列号
uint8_t command_set; // 命令集
uint8_t command_id; // 命令 ID
uint32_t timestamp; // 时间戳(毫秒)
uint8_t nonce[CHACHA_NONCE_SIZE]; // ChaCha20 nonce
uint8_t data[0]; // 加密的可变长度载荷
} SACP_struct_t;
typedef struct {
uint8_t recever_id;
uint8_t attribute;
uint16_t sequence;
uint8_t command_set;
uint8_t command_id;
uint32_t timestamp;
uint8_t nonce[CHACHA_NONCE_SIZE];
} SACP_head_base_t;
typedef struct {
uint16_t length; // 当前解析数据长度
union {
uint8_t buff[PACK_PARSE_MAX_SIZE];
SACP_struct_t sacp;
};
} SACP_param_t;
#pragma pack()
// 函数声明
ErrCode sacp_parse(uint8_t *data, uint16_t len, SACP_param_t *out, const uint8_t *key);
uint16_t sacp_package(SACP_head_base_t *head, uint8_t *in_data, uint16_t length, uint8_t *out_data, const uint8_t *key);
uint16_t sacp_sequence_pop(void);
#endif
源文件:protocol_sacp.c
#include "protocol_sacp.h"
#include <string.h>
#include "mbedtls/chachapoly.h"
// 全局序列号和滑动窗口
static uint16_t sequence = 0;
static uint16_t sequence_window[3][SEQUENCE_WINDOW_SIZE];
static uint8_t window_index[3] = {0, 0, 0};
// CRC8 查表(CRC-8/MAXIM 多项式 x^8 + x^5 + x^4 + 1)
static const uint8_t crc8_table[256] = {
0x00, 0x5E, 0xBC, 0xE2, 0x61, 0x3F, 0xDD, 0x83, 0xC2, 0x9C, 0x7E, 0x20, 0xA3, 0xFD, 0x1F, 0x41,
0x9D, 0xC3, 0x21, 0x7F, 0xFC, 0xA2, 0x40, 0x1E, 0x5F, 0x01, 0xE3, 0xBD, 0x3E, 0x60, 0x82, 0xDC,
....
}
static uint8_t sacp_calc_crc8(uint8_t *buffer, uint16_t len) {
uint8_t crc = 0;
for (uint16_t i = 0; i < len; i++) {
crc = crc8_table[crc ^ buffer[i]];
}
return crc;
}
static uint16_t calc_checksum(uint8_t *buffer, uint16_t length) {
uint32_t checksum = 0;
if (!length || !buffer) return 0;
for (uint16_t j = 0; j < (length - 1); j += 2)
checksum += ((uint32_t)buffer[j] << 8) | buffer[j + 1];
if (length % 2)
checksum += buffer[length - 1];
while (checksum > 0xffff)
checksum = (checksum >> 16) + (checksum & 0xffff);
return (uint16_t)(~checksum);
}
static int validate_timestamp(uint32_t timestamp) {
uint32_t current_time = HAL_GetTick();
int32_t diff = (int32_t)(current_time - timestamp);
return (diff >= -TIME_WINDOW && diff <= TIME_WINDOW);
}
static int validate_sequence(uint8_t sender_id, uint16_t sequence) {
if (sender_id > 2) return 0;
uint8_t idx = window_index[sender_id];
for (uint8_t i = 0; i < SEQUENCE_WINDOW_SIZE; i++) {
if (sequence_window[sender_id][i] == sequence) {
return 0;
}
}
sequence_window[sender_id][idx] = sequence;
window_index[sender_id] = (idx + 1) % SEQUENCE_WINDOW_SIZE;
return 1;
}
uint16_t sacp_sequence_pop(void) {
return sequence++;
}
ErrCode sacp_parse(uint8_t *data, uint16_t len, SACP_param_t *out, const uint8_t *key) {
uint8_t *parse_buff = out->buff;
if (len == 0 || parse_buff[0] != SACP_PDU_SOF_H) {
out->length = 0;
}
for (uint16_t i = 0; i < len; i++) {
parse_buff[out->length++] = data[i];
if (out->length < 7) {
continue;
} else if (out->length == 7) {
if (sacp_calc_crc8(parse_buff, 6) != parse_buff[6]) {
out->length = 0;
return E_FAIL;
}
} else {
uint16_t data_len = (parse_buff[3] << 8) | parse_buff[2];
uint16_t total_len = data_len + SACP_HEADER_LEN;
if (out->length == total_len) {
uint16_t checksum = calc_checksum(&parse_buff[SACP_HEADER_LEN], data_len - 2);
uint16_t checksum1 = (parse_buff[total_len - 1] << 8) | parse_buff[total_len - 2];
if (checksum != checksum1) {
out->length = 0;
return E_FAIL;
}
uint32_t timestamp = (parse_buff[13] << 24) | (parse_buff[12] << 16) |
(parse_buff[11] << 8) | parse_buff[10];
uint16_t seq = (parse_buff[9] << 8) | parse_buff[8];
if (!validate_timestamp(timestamp) || !validate_sequence(parse_buff[7], seq)) {
out->length = 0;
return E_FAIL;
}
// 解密
mbedtls_chachapoly_context chacha;
mbedtls_chachapoly_init(&chacha);
uint8_t nonce[CHACHA_NONCE_SIZE];
memcpy(nonce, &parse_buff[14], CHACHA_NONCE_SIZE);
uint8_t *ciphertext = &parse_buff[SACP_HEADER_LEN];
uint16_t ciphertext_len = data_len - CHACHA_NONCE_SIZE - 2 - CHACHA_TAG_SIZE;
uint8_t tag[CHACHA_TAG_SIZE];
memcpy(tag, &parse_buff[total_len - CHACHA_TAG_SIZE - 2], CHACHA_TAG_SIZE);
if (mbedtls_chachapoly_setkey(&chacha, key) != 0 ||
mbedtls_chachapoly_auth_decrypt(&chacha, ciphertext_len, nonce,
NULL, 0, tag, ciphertext, ciphertext) != 0) {
mbedtls_chachapoly_free(&chacha);
out->length = 0;
return E_FAIL;
}
mbedtls_chachapoly_free(&chacha);
out->length = SACP_HEADER_LEN + ciphertext_len;
return E_SUCCESS;
} else if (out->length > total_len) {
out->length = 0;
return E_FAIL;
}
}
}
return E_IN_PROGRESS;
}
uint16_t sacp_package(SACP_head_base_t *head, uint8_t *in_data, uint16_t length, uint8_t *out_data, const uint8_t *key) {
if (length > (PACK_PACKET_MAX_SIZE - SACP_HEADER_LEN - CHACHA_NONCE_SIZE - CHACHA_TAG_SIZE - 2)) {
return 0;
}
// 生成 nonce
uint8_t nonce[CHACHA_NONCE_SIZE];
memcpy(nonce, &head->sequence, 2);
memcpy(nonce + 2, &head->timestamp, 4);
memset(nonce + 6, head->recever_id, 1);
memset(nonce + 7, SACP_ID_CONTROLLER, 1);
memset(nonce + 8, 0, 4);
// 加密
uint8_t tag[CHACHA_TAG_SIZE];
mbedtls_chachapoly_context chacha;
mbedtls_chachapoly_init(&chacha);
uint8_t *ciphertext = out_data + SACP_HEADER_LEN + CHACHA_NONCE_SIZE;
if (mbedtls_chachapoly_setkey(&chacha, key) != 0 ||
mbedtls_chachapoly_encrypt_and_tag(&chacha, length, nonce, NULL, 0,
in_data, ciphertext, tag) != 0) {
mbedtls_chachapoly_free(&chacha);
return 0;
}
mbedtls_chachapoly_free(&chacha);
// 封装头部
SACP_struct_t *out = (SACP_struct_t *)out_data;
out->sof_h = SACP_PDU_SOF_H;
out->sof_l = SACP_PDU_SOF_L;
out->length = SACP_HEADER_LEN + CHACHA_NONCE_SIZE + length + CHACHA_TAG_SIZE + 2;
out->version = SACP_VERSION;
out->recever_id = head->recever_id;
out->crc8 = sacp_calc_crc8(out_data, 6);
out->sender_id = SACP_ID_CONTROLLER;
out->attr = head->attribute;
out->sequence = head->sequence;
out->command_set = head->command_set;
out->command_id = head->command_id;
out->timestamp = head->timestamp;
memcpy(out->nonce, nonce, CHACHA_NONCE_SIZE);
memcpy(out->data, nonce, CHACHA_NONCE_SIZE);
memcpy(out->data + CHACHA_NONCE_SIZE + length, tag, CHACHA_TAG_SIZE);
// 校验和
uint16_t checksum = calc_checksum(&out_data[SACP_HEADER_LEN], out->length - SACP_HEADER_LEN - 2);
out_data[out->length - 2] = (uint8_t)(checksum & 0xFF);
out_data[out->length - 1] = (uint8_t)(checksum >> 8);
return out->length;
}
#endif
使用示例
// 预共享密钥(存储在 Flash,受读保护)
static const uint8_t chacha_key[CHACHA_KEY_SIZE] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
};
// 解析示例(在 UART 接收中断中调用)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
static uint8_t rx_data[PACK_PARSE_MAX_SIZE];
static SACP_param_t parsed_data;
static uint16_t rx_len = 0;
// 假设通过 DMA 接收数据,逐步累积
// 这里简化为单次接收,实际需根据 UART 配置调整
rx_data[rx_len++] = huart->Instance->DR; // 读取接收寄存器
ErrCode result = sacp_parse(rx_data, rx_len, &parsed_data, chacha_key);
if (result == E_SUCCESS) {
// 处理解密数据:parsed_data.buff[SACP_HEADER_LEN] 开始
rx_len = 0; // 重置接收缓冲区
} else if (result == E_FAIL) {
rx_len = 0; // 错误,重置
}
// E_IN_PROGRESS 继续接收
}
// 封装示例(在主循环中调用)
void send_packet(void) {
uint8_t buffer[PACK_PACKET_MAX_SIZE];
SACP_head_base_t head = {
.recever_id = SACP_ID_HMI,
.attribute = SACP_ATTR_REQ,
.sequence = sacp_sequence_pop(),
.command_set = 0x10,
.command_id = 0x02,
.timestamp = HAL_GetTick(),
.nonce = {0}
};
uint8_t data_payload[] = {0x01, 0x02, 0x03};
uint16_t packet_length = sacp_package(&head, data_payload, sizeof(data_payload), buffer, chacha_key);
if (packet_length > 0) {
HAL_UART_Transmit(&huart1, buffer, packet_length, 100); // 假设使用 UART1
}
}