You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)解析二进制结构:

操作步骤

  1. 清理装甲内容:移除公钥块头尾标记(-----BEGIN PGP PUBLIC KEY BLOCK-----/-----END PGP PUBLIC KEY BLOCK-----)、版本行(Version: GnuPG v1.2.1 (GNU/Linux))及末尾校验和行(如=DeVs),仅保留中间连续的Base64字符(去除所有换行)。
  2. 解码为二进制:将清理后的Base64字符串解码为bytes类型的二进制数据。
  3. 解析公钥包结构:
    • 跳过包头部(1字节包类型,公钥包为0x06;1/2/5字节的包长度,需根据编码方式处理),定位到公钥内容起始位。
    • 读取1字节算法标识:ElGamal加密算法ID为0x10,确认后继续。
    • 依次解析三个多精度整数(MPI),对应p、g、h:
      • 每个MPI前2字节为位长度(大端序),如1024位对应0x04 0x00。
      • 后续为对应长度的字节数据(位长度非8的倍数时,首字节高位补0),将字节转成整数即为目标参数。

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)打包成二进制结构后生成装甲:

操作步骤

  1. 转为MPI格式:将c1、c2分别转成大端序字节串,计算位长度并生成2字节大端序的位长度标识,拼接位长度和字节数据得到每个整数的MPI二进制。
  2. 拼接MPI数据:将c1和c2的MPI二进制拼接为完整二进制数据。
  3. 生成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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 23:05:17