如何将Java生成的ECDSA公钥转换为SSH2公钥?
实现Java ECPublicKey到SSH2格式公钥的转换
已生成ECDSA密钥对,现需将其中的ECPublicKey转换为SSH2格式公钥。生成密钥对的代码如下:
final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA"); ECGenParameterSpec params = new ECGenParameterSpec("secp384r1"); keyGen.initialize(params); final KeyPair keyPair = keyGen.generateKeyPair(); // 需将公钥格式化为SSH2格式 final ECPublicKey ecPublicKey = (ECPublicKey) keyPair.getPublic(); writeJavaECPublicKeyToSSH2(ecPublicKey); // 如何实现这个方法?
实现思路
SSH2格式的ECDSA公钥遵循RFC 5656规范,核心结构分为三部分:
- 密钥算法标识(如
ecdsa-sha2-nistp384) - 椭圆曲线参数标识(如
nistp384) - 非压缩格式的公钥点DER编码数据
完整实现代码
import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.interfaces.ECPublicKey; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; import java.util.Base64; public void writeJavaECPublicKeyToSSH2(ECPublicKey ecPublicKey) throws IOException { // 1. 映射Java曲线名称到SSH标准名称 ECParameterSpec params = ecPublicKey.getParams(); String curveName = getSshCurveName(params.getName()); String algorithm = "ecdsa-sha2-" + curveName; // 2. 序列化非压缩格式的公钥点:0x04 + X坐标字节 + Y坐标字节 ECPoint ecPoint = ecPublicKey.getW(); BigInteger x = ecPoint.getAffineX(); BigInteger y = ecPoint.getAffineY(); int keySizeBytes = params.getCurve().getField().getFieldSize() / 8; byte[] xBytes = padToFixedLength(x.toByteArray(), keySizeBytes); byte[] yBytes = padToFixedLength(y.toByteArray(), keySizeBytes); ByteArrayOutputStream pointOut = new ByteArrayOutputStream(); pointOut.write(0x04); // 非压缩格式标记 pointOut.write(xBytes); pointOut.write(yBytes); byte[] publicKeyPoint = pointOut.toByteArray(); // 3. 按SSH2格式组装数据:每个字段先写4字节大端长度,再写字节内容 ByteArrayOutputStream sshOutput = new ByteArrayOutputStream(); writeString(sshOutput, algorithm); writeString(sshOutput, curveName); writeString(sshOutput, publicKeyPoint); // 4. 生成最终SSH2公钥字符串 String base64Encoded = Base64.getEncoder().encodeToString(sshOutput.toByteArray()); String ssh2PublicKey = algorithm + " " + base64Encoded; // 示例:打印公钥,可替换为写入文件等操作 System.out.println(ssh2PublicKey); } // 补全BigInteger字节数组到固定长度(补前导0) private byte[] padToFixedLength(byte[] input, int length) { byte[] result = new byte[length]; int start = length - input.length; if (start < 0) { throw new IllegalArgumentException("输入字节数组长度超过目标长度"); } System.arraycopy(input, 0, result, start, input.length); return result; } // 写入SSH格式字符串:先写4字节大端长度,再写字节内容 private void writeString(ByteArrayOutputStream out, String value) throws IOException { byte[] bytes = value.getBytes(StandardCharsets.UTF_8); writeInt(out, bytes.length); out.write(bytes); } // 写入4字节大端整数 private void writeInt(ByteArrayOutputStream out, int value) throws IOException { out.write((value >> 24) & 0xFF); out.write((value >> 16) & 0xFF); out.write((value >> 8) & 0xFF); out.write(value & 0xFF); } // 映射Java曲线名称到SSH标准名称 private String getSshCurveName(String javaCurveName) { return switch (javaCurveName) { case "secp256r1" -> "nistp256"; case "secp384r1" -> "nistp384"; case "secp521r1" -> "nistp521"; default -> throw new IllegalArgumentException("不支持的椭圆曲线: " + javaCurveName); }; }
关键说明
- 曲线名称映射:Java中使用的
secp384r1对应SSH标准的nistp384,必须转换才能被SSH客户端识别。 - 公钥点格式:SSH强制要求使用非压缩格式的公钥点,必须以
0x04开头,X/Y坐标需补前导0保证固定长度。 - SSH2格式组装:所有字段都遵循「4字节大端长度+内容」的结构,最后整体Base64编码并拼接算法前缀,得到标准SSH2公钥字符串。
内容的提问来源于stack exchange,提问作者Vicky
相关产品推荐
相关产品推荐

