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

如何在Java中获取EC证书的公钥信息(无需调用OpenSSL)

问题:Java中无需调用OpenSSL打印EC证书公钥信息

已知可在Java中读取PEM证书并解析得到ECPublicKey对象,但不知道如何从该对象的ECParameter和ECPoint数据中提取出OpenSSL输出里的公钥格式(带04前缀、冒号分隔的十六进制字节串,对应256位EC密钥的未压缩公钥)。

OpenSSL打印的公钥示例:

pub: <<
04:7a:ef:a5:53:5a:f7:a4:15:a9:ca:5a:63:6b:c9: <<
f7:d3:1a:22:c0:71:df:01:dc:ff:62:c8:aa:97:f6: <<
66:52:a4:31:08:d2:a5:5b:d5:6e:54:25:fc:89:d3: <<
0c:c2:95:1c:94:a6:96:e7:e8:b2:5e:fc:f7:48:5d: <<
42:3a:ca:81:79 <<


核心原理

EC公钥的未压缩格式遵循X.509规范:以字节04开头,随后依次拼接公钥点的x坐标和y坐标的字节数组(256位密钥对应每个坐标32字节)。从ECPublicKey的getW()方法可获取ECPoint对象,从中提取x、y坐标即可生成目标公钥串。


实现步骤

  1. 从ECPublicKey获取ECPoint公钥点;
  2. 提取x、y坐标的BigInteger值,转换为固定32字节的数组(补前导零);
  3. 拼接04前缀、x字节数组、y字节数组,得到完整未压缩公钥字节数组;
  4. 将字节数组格式化为OpenSSL风格的冒号分隔十六进制字符串,按每行16字节拆分。

代码示例

import java.math.BigInteger;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECPoint;
import java.io.ByteArrayInputStream;

public class ECPubKeyPrinter {
    public static void main(String[] args) throws Exception {
        // 输入PEM证书内容
        String pemCert = "-----BEGIN CERTIFICATE-----\n" +
                "MIIBzDCCAXGgAwIBAgIBATAKBggqhkjOPQQDAjBcMQswCQYDVQQGEwJVUzELMAkG\n" +
                "A1UECBMCQ0ExCzAJBgNVBAcTAlBBMQswCQYDVQQKEwJCQzEMMAoGA1UECxMDVkNG\n" +
                "MRgwFgYDVQQDEw9uby1rdS10ZXN0LWhvc3QwIBcNMjQwMjIzMTc1ODAwWhgPMjEy\n" +
                "NDAyMjMxNzU4MDBaMFwxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTELMAkGA1UE\n" +
                "BxMCUEExCzAJBgNVBAoTAkJDMQwwCgYDVQQLEwNWQ0YxGDAWBgNVBAMTD25vLWt1\n" +
                "LXRlc3QtaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHrvpVNa96QVqcpa\n" +
                "Y2vJ99MaIsBx3wHc/2LIqpf2ZlKkMQjSpVvVblQl/InTDMKVHJSmlufosl7890hd\n" +
                "QjrKgXmjIjAgMAkGA1UdEwQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwCgYIKoZI\n" +
                "zj0EAwIDSQAwRgIhAOHnrVfm+K1Q0g5ds4tiMZyV00jWybqJIIlPyB4dhqemAiEA\n" +
                "pMJa4NY0J+3c1wVNQ6l3C5AVJsg8ld/EL11TaochQ+U=\n" +
                "-----END CERTIFICATE-----";

        // 解析PEM证书
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate cert = (X509Certificate) cf.generateCertificate(
                new ByteArrayInputStream(pemCert.getBytes())
        );

        // 验证并获取EC公钥
        if (!(cert.getPublicKey() instanceof ECPublicKey)) {
            throw new IllegalArgumentException("证书中的公钥不是EC类型");
        }
        ECPublicKey ecPubKey = (ECPublicKey) cert.getPublicKey();
        ECPoint ecPoint = ecPubKey.getW();

        // 提取x、y坐标并转换为固定32字节数组
        byte[] xBytes = toFixedLengthBytes(ecPoint.getAffineX(), 32);
        byte[] yBytes = toFixedLengthBytes(ecPoint.getAffineY(), 32);

        // 拼接未压缩格式公钥
        byte[] uncompressedPub = new byte[65];
        uncompressedPub[0] = 0x04;
        System.arraycopy(xBytes, 0, uncompressedPub, 1, 32);
        System.arraycopy(yBytes, 0, uncompressedPub, 33, 32);

        // 格式化为OpenSSL风格输出
        String formattedPub = formatToOpenSSLStyle(uncompressedPub);
        System.out.println("pub:                                                      <<");
        System.out.println(formattedPub);
    }

    // 将BigInteger转换为固定长度字节数组,补前导零
    private static byte[] toFixedLengthBytes(BigInteger num, int length) {
        byte[] rawBytes = num.toByteArray();
        int startIdx = rawBytes[0] == 0 ? 1 : 0;
        int actualLen = rawBytes.length - startIdx;

        if (actualLen > length) {
            throw new IllegalArgumentException("坐标长度超过指定限制");
        }

        byte[] result = new byte[length];
        System.arraycopy(rawBytes, startIdx, result, length - actualLen, actualLen);
        return result;
    }

    // 格式化为OpenSSL风格的十六进制字符串
    private static String formatToOpenSSLStyle(byte[] data) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < data.length; i++) {
            if (i > 0) sb.append(':');
            sb.append(String.format("%02X", data[i]));

            // 每16字节换行并添加标记
            if ((i + 1) % 16 == 0 && i != data.length - 1) {
                sb.append(":         <<\n    ");
            }
        }
        sb.append("                                        <<");
        return sb.toString();
    }
}

代码说明

  • toFixedLengthBytes:处理BigInteger.toByteArray()的输出,确保生成固定长度的字节数组(256位密钥对应32字节),补前导零以符合X.509规范;
  • formatToOpenSSLStyle:将字节数组转换为冒号分隔的十六进制字符串,按每行16字节拆分,完全模拟OpenSSL的输出格式;
  • 核心逻辑:通过ECPublicKey.getW()获取公钥点,提取坐标后拼接成未压缩格式的公钥字节数组。

内容的提问来源于stack exchange,提问作者Mark Boon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 00:49:52