我想在 esp32 中创建一个带有 aes-128-ecb 加密查询参数的 URL,并在 node.js 中对其进行解密。这是我在 ESP32 上的方法:
uint64_t variableToEncrypt = 27483611521760; // it comes as a 64 bit unsgined int from efuse
std::string AES_KEY = "0123456789ABCDEF"; // hex format
const char* serverBaseURL = "https://testNodeJS.com/";
void generateURL(){
// Determine the size of the variable
int bufferSize = snprintf(NULL, 0, "%" PRIu64, variableToEncrypt);
// Make static arrays
char URL_BUFFER[270];
char stringVariableBuffer[bufferSize + 1];
// Convert the variableToEncrypt to a string
snprintf(stringVariableBuffer, bufferSize + 1, "%" PRIu64, variableToEncrypt);
// Encrypt the variable
std::string encryptedHex = cipher.encrypt(stringVariableBuffer, AES_KEY);
// Construct the URL
snprintf(URL_BUFFER, sizeof(URL_BUFFER), "%testDecrypt?key=%s", serverBaseURL, encryptedHex.c_str());
Serial.printf("Raw variable (uint64_t): %llu\n", variableToEncrypt);
Serial.printf("Variable (string): %s\n", stringVariableBuffer);
Serial.printf("Encrypted Hex: %s\n", encryptedHex.c_str());
Serial.printf("URL Buffer: %s\n", URL_BUFFER);
}
该函数产生以下输出:
Raw variable(uint64_t): 27483611521760
Variable(string): 27483611521760
Encrypted Hex: 4221458b03cc8692c969e5aa9aac4f31
URL Buffer: https://testNodeJS.com/testDecrypt?key=4221458b03cc8692c969e5aa9aac4f31
我用在线工具确认没问题。它能够将加密的十六进制字符串解密回原始变量字符串。
现在,如果我单击此链接,它将带我进入我想要解密的 Node.js 测试环境
const crypto = require('crypto');
function decryptAES(ciphertextHex, key) {
try{
// Convert ciphertext from hex string to buffer
const ciphertext = Buffer.from(ciphertextHex, 'hex');
// Create decipher object
const decipher = crypto.createDecipheriv('aes-128-ecb', Buffer.from(key, 'hex'), Buffer.alloc(0));
// Decrypt ciphertext
let decrypted = decipher.update(ciphertext);
decrypted = Buffer.concat([decrypted, decipher.final()]);
// Convert decrypted buffer to string
const plaintext = decrypted.toString();
return plaintext;
}catch(error){
console.log(error);
return null;
}
}
app.get('/testDecrypt', (req, res) => {
const decryptedVariable = decryptAES(req.query.key, process.env.AES_KEY);
console.log('Decrypted variable:', decryptedVariable , "Raw query variable: ", req.query.key, "AES key: ", process.env.AES_KEY);
if(!decryptedVariable){
return res.status(401).json({ message: 'Wrong variable!' });
}
return res.json({message:"ok"});
});
但是node.js的decryptAES函数总是返回null并出现以下错误:
RangeError: Invalid key length
at Decipheriv.createCipherBase (node:internal/crypto/cipher:121:19)
at Decipheriv.createCipherWithIV (node:internal/crypto/cipher:140:3)
at new Decipheriv (node:internal/crypto/cipher:289:3)
at Object.createDecipheriv (node:crypto:154:10)
at decryptAES (/opt/render/project/src/server.js:44:33)
at /opt/render/project/src/server.js:74:29
at Layer.handle [as handle_request] (/opt/render/project/src/node_modules/express/lib/router/layer.js:95:5)
at next (/opt/render/project/src/node_modules/express/lib/router/route.js:144:13)
at Route.dispatch (/opt/render/project/src/node_modules/express/lib/router/route.js:114:3)
at Layer.handle [as handle_request] (/opt/render/project/src/node_modules/express/lib/router/layer.js:95:5) {
code: 'ERR_CRYPTO_INVALID_KEYLEN'
}
Decrypted variable: null Raw query variable: 4221458b03cc8692c969e5aa9aac4f31 AES key: 0123456789ABCDEF
process.env.AES_KEY 在 env 文件中存储如下:AES_KEY=0123456789ABCDEF
这是我在 esp32 上使用 mbedtls 的加密函数
#include "mbedtls/aes.h"
std::string CryptoCipher::encrypt(const std::string& plaintext, const std::string& key) {
mbedtls_aes_context aes_ctx;
mbedtls_aes_init(&aes_ctx);
// Set encryption key
mbedtls_aes_setkey_enc(&aes_ctx, reinterpret_cast<const unsigned char*>(key.c_str()), 128);
// Pad plaintext to block size if necessary
int padded_length = ((plaintext.length() + 15) / 16) * 16;
std::string padded_plaintext = plaintext;
padded_plaintext.resize(padded_length, '\0');
// Allocate memory for ciphertext
std::string ciphertext(padded_length, '\0');
// Perform encryption
for (size_t i = 0; i < padded_length; i += 16) {
mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT, reinterpret_cast<const unsigned char*>(padded_plaintext.c_str() + i), reinterpret_cast<unsigned char*>(ciphertext.data() + i));
}
// Convert ciphertext to hex string
std::string hex_string;
hex_string.reserve(ciphertext.length() * 2);
for (unsigned char c : ciphertext) {
char buf[3];
snprintf(buf, sizeof(buf), "%02x", static_cast<unsigned int>(c));
hex_string.append(buf);
}
mbedtls_aes_free(&aes_ctx);
return hex_string;
}
密文的解密表明加密中使用了零填充,请参见例如这里与 CyberChef 一起。
相比之下,NodeJS 默认使用 PKCS#7 填充;不支持零填充。对于使用 NodeJS 解密,必须禁用标准 PKCS#7 填充。如果必须执行取消填充,则必须手动删除填充字节。
此外,密钥必须是 ASCII/UTF-8 编码而不是十六进制解码(如评论中已指出)。
可能的修复:
...
const decipher = crypto.createDecipheriv('aes-128-ecb', Buffer.from(key, 'utf8'), Buffer.alloc(0)); // Fix 1: ASCII/UTF8 encode key
decipher.setAutoPadding(false); // Fix 2: disable PKCS#7 default padding
...
return plaintext.replace(/\x00+$/g, ''); // Fix 3: unpad (if required)
...
请注意,与 PKCS#7 填充相比,零填充是不可靠的,即通常无法区分末尾的 0x00 填充字节和常规 0x00 字节。