Azure函数适配GnuPG v1.2.1 ElGamal密钥PGP加解密问题
解决PGP ElGamal密钥解析与加密结果转ASCII装甲问题
问题1:解析PGP公钥块获取ElGamal参数
直接对Base64解码后转UTF-8是错误的——PGP公钥块的Base64内容是二进制数据而非文本,转UTF-8必然出现乱码。要提取ElGamal的p、g、h参数,需严格按照OpenPGP规范(RFC 4880)解析二进制结构:
操作步骤
- 清理装甲内容:移除公钥块头尾标记(
-----BEGIN PGP PUBLIC KEY BLOCK-----/-----END PGP PUBLIC KEY BLOCK-----)、版本行(Version: GnuPG v1.2.1 (GNU/Linux))及末尾校验和行(如=DeVs),仅保留中间连续的Base64字符(去除所有换行)。 - 解码为二进制:将清理后的Base64字符串解码为
bytes类型的二进制数据。 - 解析公钥包结构:
- 跳过包头部(1字节包类型,公钥包为
0x06;1/2/5字节的包长度,需根据编码方式处理),定位到公钥内容起始位。 - 读取1字节算法标识:ElGamal加密算法ID为
0x10,确认后继续。 - 依次解析三个多精度整数(MPI),对应
p、g、h:- 每个MPI前2字节为位长度(大端序),如1024位对应
0x04 0x00。 - 后续为对应长度的字节数据(位长度非8的倍数时,首字节高位补0),将字节转成整数即为目标参数。
- 每个MPI前2字节为位长度(大端序),如1024位对应
- 跳过包头部(1字节包类型,公钥包为
Python示例代码
import base64 import struct def parse_elgamal_pgp_public_key(armored_key): # 清理装甲内容 lines = armored_key.splitlines() start_idx = lines.index('-----BEGIN PGP PUBLIC KEY BLOCK-----') + 1 end_idx = lines.index('-----END PGP PUBLIC KEY BLOCK-----') b64_data = '' for line in lines[start_idx:end_idx]: if line.startswith('Version:') or not line: continue if line.startswith('='): break b64_data += line.strip() # 解码为二进制 binary_data = base64.b64decode(b64_data) ptr = 0 # 解析包类型 packet_type = binary_data[ptr] ptr += 1 # 解析包长度(此处假设1字节编码,实际需兼容其他长度格式) packet_len = binary_data[ptr] ptr += 1 # 验证算法ID algo_id = binary_data[ptr] ptr += 1 assert algo_id == 0x10, "输入不是ElGamal公钥" # 解析p p_bit_len = struct.unpack('>H', binary_data[ptr:ptr+2])[0] ptr += 2 p_byte_len = (p_bit_len + 7) // 8 p = int.from_bytes(binary_data[ptr:ptr+p_byte_len], byteorder='big') ptr += p_byte_len # 解析g g_bit_len = struct.unpack('>H', binary_data[ptr:ptr+2])[0] ptr += 2 g_byte_len = (g_bit_len + 7) // 8 g = int.from_bytes(binary_data[ptr:ptr+g_byte_len], byteorder='big') ptr += g_byte_len # 解析h h_bit_len = struct.unpack('>H', binary_data[ptr:ptr+2])[0] ptr += 2 h_byte_len = (h_bit_len + 7) // 8 h = int.from_bytes(binary_data[ptr:ptr+h_byte_len], byteorder='big') return {'p': p, 'g': g, 'h': h}
问题2:将ElGamal加密结果转为ASCII装甲格式
不能直接对大数字字符串做Base64编码,需按照OpenPGP规范将加密结果(两个大整数c1、c2)打包成二进制结构后生成装甲:
操作步骤
- 转为MPI格式:将
c1、c2分别转成大端序字节串,计算位长度并生成2字节大端序的位长度标识,拼接位长度和字节数据得到每个整数的MPI二进制。 - 拼接MPI数据:将
c1和c2的MPI二进制拼接为完整二进制数据。 - 生成ASCII装甲:
- 对拼接后的二进制做Base64编码,按每行76字符拆分。
- 添加装甲头、版本行、空行、Base64内容、CRC24校验和行(格式
=XXX)、装甲尾,组成标准PGP装甲格式。
Python示例代码
import base64 import struct def int_to_mpi(num): # 将整数转为MPI格式的bytes num_bytes = num.to_bytes((num.bit_length() + 7) // 8, byteorder='big') bit_len = num.bit_length() len_bytes = struct.pack('>H', bit_len) return len_bytes + num_bytes def elgamal_result_to_armored(c1, c2): # 生成MPI数据 mpi_c1 = int_to_mpi(c1) mpi_c2 = int_to_mpi(c2) binary_data = mpi_c1 + mpi_c2 # Base64编码并分行 b64_encoded = base64.b64encode(binary_data).decode('utf-8') lines = [b64_encoded[i:i+76] for i in range(0, len(b64_encoded), 76)] # 计算OpenPGP标准CRC24校验和 crc = 0xb704ce for byte in binary_data: crc ^= byte << 16 for _ in range(8): crc <<= 1 if crc & 0x1000000: crc ^= 0x1864cfb crc &= 0xffffff crc_bytes = struct.pack('>I', crc)[1:] # 取后3字节 crc_b64 = base64.b64encode(crc_bytes).decode('utf-8')[:2] # 组装装甲内容 armored = [ '-----BEGIN PGP MESSAGE-----', 'Version: Custom ElGamal Implementation', '', *lines, f'={crc_b64}', '-----END PGP MESSAGE-----' ] return '\n'.join(armored)
注意事项
- CRC24计算严格遵循OpenPGP规范,确保装甲格式兼容标准PGP工具。
- 标准ElGamal加密输出为
(c1, c2)两个整数,若你的加密结果是单个大数字,需检查实现是否符合规范。
内容的提问来源于stack exchange,提问作者Maxime
相关产品推荐
相关产品推荐

