缘起

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#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

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175

#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

使用示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 预共享密钥(存储在 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
    }
}