带加密的嵌入式通讯协议

0 评论
/
3 阅读
/
11981 字
14 2025-05

缘起

在之前看到的一个公众号文章介绍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
    }
}
    暂无数据