2. 生成设备唯一密钥对
任务目标
为每个设备生成唯一的 RSA-2048 非对称密钥对,私钥以 PEM 格式安全存储于设备本地(权限 600),公钥导出用于后续签名验证。本案例聚焦密钥生成与安全落盘,不涉及 HSM 硬件对接。
环境依赖
- OpenSSL 库(libssl-dev / openssl-devel)
- C 编译器(gcc)
- Linux 环境(用于文件权限控制)
关键完整代码
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#define KEY_DIR "/opt/device_keys"
#define PRIV_PATH KEY_DIR "/device_priv.pem"
#define PUB_PATH KEY_DIR "/device_pub.pem"
/* 生成唯一密钥对:基于设备序列号(此处用时间戳模拟唯一性) */
int generate_device_keypair(const char *serial) {
// 1. 创建安全目录(0700)
mkdir(KEY_DIR, 0700);
chmod(KEY_DIR, 0700);
// 2. 生成 RSA-2048 密钥对
EVP_PKEY *pkey = EVP_PKEY_new();
if (!pkey) return -1;
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
if (!ctx) { EVP_PKEY_free(pkey); return -1; }
if (EVP_PKEY_keygen_init(ctx) <= 0 ||
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048) <= 0 ||
EVP_PKEY_keygen(ctx, &pkey) <= 0) {
EVP_PKEY_CTX_free(ctx);
EVP_PKEY_free(pkey);
return -1;
}
EVP_PKEY_CTX_free(ctx);
// 3. 私钥加密写盘(AES-256-CBC,密码派生自设备序列号)
char pass[128];
snprintf(pass, sizeof(pass), "sn_%s", serial);
FILE *fp = fopen(PRIV_PATH, "w");
if (!fp) { EVP_PKEY_free(pkey); return -1; }
chmod(PRIV_PATH, 0600); // 仅属主可读写
if (!PEM_write_PrivateKey(fp, pkey, EVP_aes_256_cbc(),
(unsigned char *)pass, strlen(pass),
NULL, NULL)) {
fclose(fp);
EVP_PKEY_free(pkey);
return -1;
}
fclose(fp);
// 4. 公钥明文写盘
fp = fopen(PUB_PATH, "w");
if (!fp) { EVP_PKEY_free(pkey); return -1; }
chmod(PUB_PATH, 0644);
if (!PEM_write_PUBKEY(fp, pkey)) {
fclose(fp);
EVP_PKEY_free(pkey);
return -1;
}
fclose(fp);
EVP_PKEY_free(pkey);
return 0;
}
/* 验证:用公钥验证私钥签名 */
int verify_keypair(const char *serial) {
// 读取私钥(需密码)
char pass[128];
snprintf(pass, sizeof(pass), "sn_%s", serial);
FILE *fp = fopen(PRIV_PATH, "r");
if (!fp) return -1;
EVP_PKEY *priv = PEM_read_PrivateKey(fp, NULL, NULL, pass);
fclose(fp);
if (!priv) return -1;
// 读取公钥
fp = fopen(PUB_PATH, "r");
if (!fp) { EVP_PKEY_free(priv); return -1; }
EVP_PKEY *pub = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
fclose(fp);
if (!pub) { EVP_PKEY_free(priv); return -1; }
// 签名并验证
const char *msg = "device_keypair_test";
unsigned char sig[256];
size_t sig_len = 0;
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
int ok = 0;
if (EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, priv) == 1 &&
EVP_DigestSignUpdate(mdctx, msg, strlen(msg)) == 1 &&
EVP_DigestSignFinal(mdctx, sig, &sig_len) == 1) {
EVP_MD_CTX *mdctx2 = EVP_MD_CTX_new();
if (EVP_DigestVerifyInit(mdctx2, NULL, EVP_sha256(), NULL, pub) == 1 &&
EVP_DigestVerifyUpdate(mdctx2, msg, strlen(msg)) == 1 &&
EVP_DigestVerifyFinal(mdctx2, sig, sig_len) == 1) {
ok = 1;
}
EVP_MD_CTX_free(mdctx2);
}
EVP_MD_CTX_free(mdctx);
EVP_PKEY_free(priv);
EVP_PKEY_free(pub);
return ok ? 0 : -1;
}
int main() {
// 模拟设备序列号(实际应从设备硬件/OTP区读取)
const char *serial = "DEV-2024-A7F3";
if (generate_device_keypair(serial) != 0) {
fprintf(stderr, "密钥生成失败\n");
return 1;
}
if (verify_keypair(serial) != 0) {
fprintf(stderr, "密钥验证失败\n");
return 1;
}
printf("密钥对生成并验证成功\n");
return 0;
}
编译与运行
gcc -o keygen keygen.c -lssl -lcrypto
sudo mkdir -p /opt/device_keys # 确保目录存在
./keygen
验证方法与判定标准
- 文件检查:执行后确认
/opt/device_keys/下存在device_priv.pem(权限 600)和device_pub.pem(权限 644)。 - 私钥加密确认:运行
head -1 /opt/device_keys/device_priv.pem,输出应为-----BEGIN ENCRYPTED PRIVATE KEY-----,证明私钥已加密。 - 程序自验证:程序内部完成“私钥签名→公钥验签”闭环,输出
密钥对生成并验证成功即表示密钥对匹配且可用。 - 唯一性验证:修改
serial值后重新编译运行,对比两次生成的公钥文件(sha256sum device_pub.pem),哈希值应不同,证明每设备密钥唯一。
安全说明
本案例使用设备序列号派生加密密码,实际产品中应从安全硬件(如 OTP/HSM)获取更高级别的密钥派生因子。私钥文件权限已设为 600,目录权限 700,防止其他用户读取。