在 cryptography (Python) 和 GMP (C语言) 之间转换 RSA-4096 密钥需要注意两者的密钥表示格式差异。以下是详细的转换方法:
密钥格式对比
特性 | cryptography (Python) | GMP (C语言) |
私钥格式 | PKCS#8 PEM/DER | 自定义格式 (通常为原始大整数) |
公钥格式 | X.509 SubjectPublicKeyInfo PEM/DER | 自定义格式 (通常为 (e, n) 对) |
典型文件扩展名 | .pem, .der | 自定义 (常为二进制或文本数字) |
方法 1,以原始整数形式在 GMP 和 Cryptography 之间转换
(1)在 python 中,从 cryptography 导出供 GMP 使用的密钥
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
def export_for_gmp(private_key):
# 获取RSA参数
private_numbers = private_key.private_numbers()
public_numbers = private_key.public_key().public_numbers()
return {
'n': public_numbers.n, # 模数
'e': public_numbers.e, # 公钥指数
'd': private_numbers.d, # 私钥指数
'p': private_numbers.p, # 第一个素数
'q': private_numbers.q, # 第二个素数
'dmp1': private_numbers.dmp1, # d mod (p-1)
'dmq1': private_numbers.dmq1, # d mod (q-1)
'iqmp': private_numbers.iqmp # q^-1 mod p
}
def generate_rsa_keypair():
"""生成RSA-4096密钥对"""
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=4096, # 使用4096位密钥
backend=default_backend()
)
public_key = private_key.public_key()
return private_key, public_key
# 1. 生成密钥对
private_key, public_key = generate_rsa_keypair()
params = export_for_gmp(private_key)
# 将参数写入文件供C程序使用
with open('rsa_params.txt', 'w') as f:
f.write(f"n={params['n']}\n")
f.write(f"e={params['e']}\n")
f.write(f"d={params['d']}\n")
f.write(f"p={params['p']}\n")
f.write(f"q={params['q']}\n")
f.write(f"dmp1={params['dmp1']}\n")
f.write(f"dmq1={params['dmq1']}\n")
f.write(f"iqmp={params['iqmp']}\n")
(2)在C语言(GMP)中加载这些参数
#include <gmp.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct {
mpz_t n; // 模数
mpz_t e; // 公钥指数
mpz_t d; // 私钥指数
mpz_t p; // 第一个素数
mpz_t q; // 第二个素数
mpz_t dmp1; // d mod (p-1)
mpz_t dmq1; // d mod (q-1)
mpz_t iqmp; // q^-1 mod p
} RSAKey;
void load_rsa_key(RSAKey *key, const char *filename) {
FILE *file = fopen(filename, "r");
if (!file) {
perror("无法打开文件");
exit(1);
}
// 初始化所有mpz_t变量
mpz_inits(key->n, key->e, key->d, key->p, key->q,
key->dmp1, key->dmq1, key->iqmp, NULL);
char line[1024];
while (fgets(line, sizeof(line), file)) {
if (sscanf(line, "n=%Zd", key->n) == 1) continue;
if (sscanf(line, "e=%Zd", key->e) == 1) continue;
if (sscanf(line, "d=%Zd", key->d) == 1) continue;
if (sscanf(line, "p=%Zd", key->p) == 1) continue;
if (sscanf(line, "q=%Zd", key->q) == 1) continue;
if (sscanf(line, "dmp1=%Zd", key->dmp1) == 1) continue;
if (sscanf(line, "dmq1=%Zd", key->dmq1) == 1) continue;
if (sscanf(line, "iqmp=%Zd", key->iqmp) == 1) continue;
}
fclose(file);
}
// 使用示例
int main() {
RSAKey key;
load_rsa_key(&key, "rsa_params.txt");
// 现在可以使用这些参数进行加密/解密操作
// ...
// 清理
mpz_clears(key.n, key.e, key.d, key.p, key.q,
key.dmp1, key.dmq1, key.iqmp, NULL);
return 0;
}
方法 2. 直接PEM格式转换
(1)python 中导出 PEM 格式文件
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
def generate_rsa_keypair():
"""生成RSA-4096密钥对"""
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=4096, # 使用4096位密钥
backend=default_backend()
)
public_key = private_key.public_key()
return private_key, public_key
def export_keypair_pem(private_key, public_key, private_file, public_file):
"""打印PEM格式的密钥"""
# 序列化私钥(PKCS8格式)
pem_private = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
# 序列化公钥
pem_public = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
print("=== RSA-4096 私钥 ===")
print(pem_private.decode('utf-8'))
print("=== RSA-4096 公钥 ===")
print(pem_public.decode('utf-8'))
with open(private_file, 'w') as f:
f.write(pem_private.decode('utf-8'))
with open(public_file, 'w') as f:
f.write(pem_public.decode('utf-8'))
return
if __name__ == "__main__":
# 1. 生成密钥对
private_key, public_key = generate_rsa_keypair()
export_keypair_pem(private_key, public_key, "pem_private_key.txt", "pem_public_key.txt")
(2)C 语言中加载并解析 PEM 格式文件方式 1
依赖库OpenSSL(用于解析PEM格式文件)和GMP(用于大整数运算)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gmp.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/err.h>
// 初始化GMP大整数并从BIGNUM转换
void bn_to_mpz(const BIGNUM *bn, mpz_t mpz)
{
int size = BN_num_bytes(bn);
unsigned char *buf = malloc(size);
BN_bn2bin(bn, buf);
mpz_import(mpz, size, 1, 1, 1, 0, buf);
free(buf);
}
// 加载RSA私钥PEM文件并转换为GMP格式
int load_rsa_private_key(const char *filename, mpz_t n, mpz_t e, mpz_t d)
{
FILE *fp = fopen(filename, "r");
if (!fp)
{
fprintf(stderr, "无法打开文件: %s\n", filename);
return 0;
}
RSA *rsa = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);
fclose(fp);
if (!rsa)
{
fprintf(stderr, "解析PEM文件失败:\n");
ERR_print_errors_fp(stderr);
return 0;
}
// 获取RSA密钥参数
const BIGNUM *rsa_n, *rsa_e, *rsa_d;
RSA_get0_key(rsa, &rsa_n, &rsa_e, &rsa_d);
// 转换为GMP格式
bn_to_mpz(rsa_n, n);
bn_to_mpz(rsa_e, e);
bn_to_mpz(rsa_d, d);
RSA_free(rsa);
return 1;
}
// 加载RSA公钥PEM文件并转换为GMP格式
int load_rsa_public_key(const char *filename, mpz_t n, mpz_t e)
{
FILE *fp = fopen(filename, "r");
if (!fp)
{
fprintf(stderr, "无法打开文件: %s\n", filename);
return 0;
}
RSA *rsa = PEM_read_RSA_PUBKEY(fp, NULL, NULL, NULL);
fclose(fp);
if (!rsa)
{
fprintf(stderr, "解析PEM文件失败:\n");
ERR_print_errors_fp(stderr);
return 0;
}
// 获取RSA公钥参数
const BIGNUM *rsa_n, *rsa_e;
RSA_get0_key(rsa, &rsa_n, &rsa_e, NULL);
// 转换为GMP格式
bn_to_mpz(rsa_n, n);
bn_to_mpz(rsa_e, e);
RSA_free(rsa);
return 1;
}
int main()
{
// 初始化GMP变量
mpz_t n, e, d;
mpz_inits(n, e, d, NULL);
// 加载私钥
if (load_rsa_private_key("pem_private_key.txt", n, e, d))
{
gmp_printf("私钥参数:\n");
gmp_printf("模数 n (%d位):\n%Zd\n", mpz_sizeinbase(n, 2), n);
gmp_printf("公钥指数 e: %Zd\n", e);
gmp_printf("私钥指数 d: %Zd\n", d);
}
// 加载公钥
mpz_t pub_n, pub_e;
mpz_inits(pub_n, pub_e, NULL);
if (load_rsa_public_key("pem_public_key.txt", pub_n, pub_e))
{
gmp_printf("\n公钥参数:\n");
gmp_printf("模数 n (%d位):\n%Zd\n", mpz_sizeinbase(pub_n, 2), pub_n);
gmp_printf("公钥指数 e: %Zd\n", pub_e);
}
// 验证n是否一致
if (mpz_cmp(n, pub_n) == 0)
{
printf("\n验证: 公私钥的模数n一致\n");
}
else
{
printf("\n警告: 公私钥的模数n不一致!\n");
}
// 清理资源
mpz_clears(n, e, d, pub_n, pub_e, NULL);
return 0;
}
编译指令
gcc -o pem_to_gmp pem_to_gmp.c -lgmp -lcrypto
代码说明
主要函数:bn_to_mpz()
:将OpenSSL的BIGNUM转换为GMP的mpz_t。
load_rsa_private_key()
:加载RSA私钥PEM文件。
load_rsa_public_key()
:加载RSA公钥PEM文件
错误处理:使用OpenSSL的ERR_print_errors_fp()输出详细错误信息。检查所有可能的失败情况
内存管理:正确初始化和清理GMP变量,释放所有分配的内存
注意事项
1, 确保PEM文件格式正确,支持以下格式:
- 传统RSA私钥格式(BEGIN RSA PRIVATE KEY)
- PKCS#8私钥格式(BEGIN PRIVATE KEY)
- 标准公钥格式(BEGIN PUBLIC KEY)
2, 如果遇到解析错误,可以使用OpenSSL命令检查PEM文件:
openssl rsa -in private.pem -text -noout
3, 对于生产环境,应考虑:
- 增加更严格的错误检查
- 保护私钥数据在内存中的安全
- 添加日志记录机制
(3)C 语言中加载并解析 PEM 格式文件方式
不依赖 openssh 的方案。
登录 · 语雀 《C语言解析PEM格式PKCS#8私钥的实现,不依赖OpenSSL。》
如果GMP端支持PEM格式:
cryptography → GMP
- 直接从cryptography导出PEM文件
- 在C程序中使用OpenSSL等库解析PEM
GMP → cryptography
- 使用OpenSSL在C端生成PEM文件
- 在Python中用cryptography直接加载PEM
注意事项
- 大整数处理:确保两端的整数表示一致,GMP和Python都能处理超大整数
- 字节序:跨平台时注意字节序问题
- 性能考虑:RSA-4096密钥转换可能涉及非常大的数字,处理时注意内存
- 安全存储:转换过程中密钥材料可能临时存储在文件中,确保安全删除
这种文本参数交换的方法虽然简单,但在生产环境中建议使用更安全的密钥交换机制,如PKCS#12或加密的密钥交换协议。