基于XTEA算法的ESP8266挑战响应认证方案实现问询
这个挑战响应认证的思路非常扎实,刚好能解决你ESP8266之间的授权控制问题。我结合Arduino环境给你拆解成可落地的步骤和代码示例,帮你快速实现:
挑战响应认证方案落地指南(ESP8266 Arduino)
整体流程细化
咱们把你说的思路拆成更明确的逻辑闭环:
- 开发板B向A发送初始控制请求(比如
GET /request-challenge) - 开发板A生成一个4字节的随机挑战值,临时缓存起来,然后把这个挑战返回给B
- 开发板B用预共享的XTEA密钥加密这个挑战值,得到加密后的token
- 开发板B向A发送验证请求(比如
GET /toggle-relay?token=<加密后的十六进制字符串>) - 开发板A用同样的密钥加密缓存的挑战值,和收到的token对比:一致就触发继电器切换,不一致则拒绝请求
关键实现细节
1. XTEA算法实现(两个设备都要用到)
XTEA是轻量级块加密算法,非常适合资源有限的ESP8266。下面是适配Arduino的简化实现:
#include <stdint.h> #include <cstring> // 预共享密钥,两个设备必须完全一致!建议用16个任意字节 const uint32_t XTEA_KEY[4] = {0x12345678, 0x9ABCDEF0, 0xFEDCBA98, 0x87654321}; // XTEA加密函数(4字节块) void xtea_encrypt(uint32_t *v, const uint32_t *k) { uint32_t v0 = v[0], v1 = v[1], sum = 0, delta = 0x9E3779B9; for (int i = 0; i < 32; i++) { v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + k[sum & 3]); sum += delta; v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + k[(sum >> 11) & 3]); } v[0] = v0; v[1] = v1; } // 将字节数组转成十六进制字符串,方便HTTP传输 String bytesToHex(const uint8_t *data, size_t length) { String hexStr; for (size_t i = 0; i < length; i++) { char hex[3]; sprintf(hex, "%02X", data[i]); hexStr += hex; } return hexStr; } // 将十六进制字符串转成字节数组 bool hexToBytes(const String &hexStr, uint8_t *data, size_t maxLength) { if (hexStr.length() % 2 != 0) return false; size_t length = hexStr.length() / 2; if (length > maxLength) return false; for (size_t i = 0; i < length; i++) { char hexByte[3] = {hexStr[2*i], hexStr[2*i+1], '\0'}; data[i] = strtol(hexByte, NULL, 16); } return true; }
2. 开发板A(HTTP服务器)代码核心片段
#include <ESP8266WiFi.h> #include <ESP8266WebServer.h> ESP8266WebServer server(80); uint32_t currentChallenge = 0; // 存储当前挑战值(4字节) unsigned long challengeTimestamp = 0; const unsigned long CHALLENGE_TIMEOUT = 5000; // 挑战5秒内有效,防止重放攻击 // 继电器控制引脚 const int RELAY_PIN = D1; void setup() { pinMode(RELAY_PIN, OUTPUT); digitalWrite(RELAY_PIN, LOW); // 初始状态关闭 // 初始化WiFi(替换成你的SSID和密码) WiFi.begin("你的WiFi名称", "你的WiFi密码"); while (WiFi.status() != WL_CONNECTED) { delay(500); } // 初始化随机种子 randomSeed(analogRead(A0)); // 注册路由 server.on("/request-challenge", handleRequestChallenge); server.on("/toggle-relay", handleToggleRelay); server.begin(); } void loop() { server.handleClient(); } // 处理挑战请求 void handleRequestChallenge() { // 生成随机挑战值 currentChallenge = random(0, UINT32_MAX); challengeTimestamp = millis(); // 将挑战值转成十六进制字符串返回 server.send(200, "text/plain", bytesToHex((uint8_t*)¤tChallenge, 4)); } // 处理继电器切换请求 void handleToggleRelay() { // 检查挑战是否超时 if (millis() - challengeTimestamp > CHALLENGE_TIMEOUT) { server.send(403, "text/plain", "Challenge expired"); return; } // 获取传入的token if (!server.hasArg("token")) { server.send(400, "text/plain", "Missing token"); return; } String tokenStr = server.arg("token"); // 将token转成字节数组 uint8_t tokenBytes[4]; if (!hexToBytes(tokenStr, tokenBytes, 4)) { server.send(400, "text/plain", "Invalid token format"); return; } // 加密当前挑战值,生成预期的token uint32_t challengeToEncrypt = currentChallenge; xtea_encrypt((uint32_t*)&challengeToEncrypt, XTEA_KEY); // 对比token if (memcmp(&challengeToEncrypt, tokenBytes, 4) == 0) { // 验证通过,切换继电器 digitalWrite(RELAY_PIN, !digitalRead(RELAY_PIN)); server.send(200, "text/plain", "Relay toggled successfully"); } else { server.send(403, "text/plain", "Invalid token"); } // 挑战值使用后失效,防止重复使用 currentChallenge = 0; }
3. 开发板B(HTTP客户端)代码核心片段
#include <ESP8266WiFi.h> #include <ESP8266HTTPClient.h> // 开发板A的IP地址(替换成你的实际IP) const char* SERVER_IP = "192.168.1.100"; void setup() { Serial.begin(115200); // 初始化WiFi(替换成你的SSID和密码) WiFi.begin("你的WiFi名称", "你的WiFi密码"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("WiFi connected"); } void loop() { // 模拟触发控制请求(你可以改成按键触发等) toggleRelay(); delay(10000); // 每10秒触发一次 } void toggleRelay() { HTTPClient http; // 第一步:获取挑战值 String challengeUrl = "http://" + String(SERVER_IP) + "/request-challenge"; if (http.begin(challengeUrl)) { int httpCode = http.GET(); if (httpCode == HTTP_CODE_OK) { String challengeStr = http.getString(); http.end(); // 将挑战字符串转成字节数组 uint8_t challengeBytes[4]; if (hexToBytes(challengeStr, challengeBytes, 4)) { uint32_t challenge = *(uint32_t*)challengeBytes; // 第二步:用XTEA加密挑战值生成token xtea_encrypt((uint32_t*)&challenge, XTEA_KEY); String tokenStr = bytesToHex((uint8_t*)&challenge, 4); // 第三步:发送验证请求,切换继电器 String toggleUrl = "http://" + String(SERVER_IP) + "/toggle-relay?token=" + tokenStr; if (http.begin(toggleUrl)) { httpCode = http.GET(); if (httpCode == HTTP_CODE_OK) { Serial.println("Relay toggled successfully"); } else { Serial.print("Toggle failed: "); Serial.println(httpCode); } http.end(); } else { Serial.println("Failed to connect to toggle endpoint"); } } else { Serial.println("Invalid challenge format"); } } else { Serial.print("Failed to get challenge: "); Serial.println(httpCode); } } else { Serial.println("Failed to connect to challenge endpoint"); } }
额外注意事项
- 密钥保密:
XTEA_KEY必须在两个设备上完全一致,且绝对不能泄露给第三方,否则认证就失效了。 - 重放攻击防护:设置的
CHALLENGE_TIMEOUT很重要,确保每个挑战只能在短时间内使用,避免攻击者截获token后重复使用。 - 随机数可靠性:ESP8266的
random()依赖模拟引脚的噪声初始化,如果你需要更高的随机性,可以考虑用硬件随机数(部分ESP8266型号支持)。 - 通信安全:如果是在公网环境使用,建议把HTTP换成HTTPS(ESP8266可以用
ESP8266WiFiSecure库),防止挑战和token被明文截获。
内容的提问来源于stack exchange,提问作者cdoub
相关产品推荐
相关产品推荐

