如何将Java中的ECPublicKey转换为OpenSSH格式的字符串表示
如何将Java中的ECPublicKey转换为OpenSSH格式的字符串表示
嘿,这个需求我之前也折腾过,其实OpenSSH的ECDSA公钥格式是有固定编码规则的,咱们一步步来实现就好:
首先得明白OpenSSH的EC公钥结构:最终的字符串是「算法标识 + 空格 + Base64编码的公钥数据 + 空格 + 注释」,而Base64里面的内容是由三个带长度前缀的字节段拼接而成的:
- 第一部分:算法名称(比如
ecdsa-sha2-nistp256)的UTF-8字节,前面加4字节的大端序长度值 - 第二部分:曲线名称(比如
nistp256)的UTF-8字节,同样前面加4字节大端序长度 - 第三部分:未压缩格式的EC公钥点字节(开头是0x04,接着是32字节的x坐标,再是32字节的y坐标)
接下来是具体的Java代码实现,直接拿过去就能用(注意要处理异常哦):
import java.nio.ByteBuffer; 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 class ECPublicKeyToOpenSSH { public static String convertToOpenSSH(ECPublicKey ecPublicKey, String comment) throws Exception { // 1. 从ECPublicKey中提取曲线参数和公钥坐标点 ECParameterSpec params = ecPublicKey.getParams(); ECPoint publicPoint = ecPublicKey.getW(); String algorithm = "ecdsa-sha2-" + params.getName(); String curveName = params.getName(); // 2. 组装算法名称的字节段:4字节长度前缀 + 算法名原节 byte[] algorithmBytes = algorithm.getBytes(StandardCharsets.UTF_8); ByteBuffer algorithmBuffer = ByteBuffer.allocate(4 + algorithmBytes.length); algorithmBuffer.putInt(algorithmBytes.length); algorithmBuffer.put(algorithmBytes); // 3. 组装曲线名称的字节段:4字节长度前缀 + 曲线名字节 byte[] curveBytes = curveName.getBytes(StandardCharsets.UTF_8); ByteBuffer curveBuffer = ByteBuffer.allocate(4 + curveBytes.length); curveBuffer.putInt(curveBytes.length); curveBuffer.put(curveBytes); // 4. 组装公钥点的字节段:0x04(未压缩标识) + 32字节x坐标 + 32字节y坐标 BigInteger x = publicPoint.getAffineX(); BigInteger y = publicPoint.getAffineY(); ByteBuffer keyBuffer = ByteBuffer.allocate(1 + 32 + 32); keyBuffer.put((byte) 0x04); // 把x、y坐标处理成32字节大端序,不足补0,过长则截掉多余前缀 keyBuffer.put(padTo32Bytes(x.toByteArray())); keyBuffer.put(padTo32Bytes(y.toByteArray())); // 5. 拼接所有字节段并做Base64编码 ByteBuffer fullBuffer = ByteBuffer.allocate( algorithmBuffer.capacity() + curveBuffer.capacity() + keyBuffer.capacity() ); fullBuffer.put(algorithmBuffer.array()); fullBuffer.put(curveBuffer.array()); fullBuffer.put(keyBuffer.array()); String base64Key = Base64.getEncoder().encodeToString(fullBuffer.array()); // 拼接成最终的OpenSSH格式字符串 return String.format("%s %s %s", algorithm, base64Key, comment); } // 辅助方法:将BigInteger的字节数组统一处理为32字节大端序 private static byte[] padTo32Bytes(byte[] input) { if (input.length == 32) { return input; } // 若输入带符号位前缀(长度超过32),则截掉前面的0x00 if (input.length > 32) { return java.util.Arrays.copyOfRange(input, input.length - 32, input.length); } // 长度不足32的话,在前面补0 byte[] padded = new byte[32]; System.arraycopy(input, 0, padded, 32 - input.length, input.length); return padded; } }
你可以像这样调用这个工具方法:
// 假设你已经通过其他方式获取到了ECPublicKey实例ecPublicKey String openSshPublicKey = ECPublicKeyToOpenSSH.convertToOpenSSH(ecPublicKey, "user@example.com"); // 输出结果就会是你想要的格式: // ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEAFuExXweUtKN3KYzoV+6eEyVfN9CLyM48FO2B9bZQ51bLtQvVo1MNVCXuW73dD2CgHXPryEwsTMyUR74GHN50= user@example.com
这里还有几个要注意的细节:
- 曲线名称和算法标识是对应的,比如
nistp256对应ecdsa-sha2-nistp256,直接从ECParameterSpec的getName()方法取就行,不用硬编码 - 处理BigInteger字节数组时,要注意去掉可能存在的符号位前缀,同时保证长度刚好32字节,不然Base64编码后的数据会不符合OpenSSH的要求
- 推荐用Java 8及以上的原生
Base64类,兼容性和稳定性都更靠谱
备注:内容来源于stack exchange,提问作者Gili
相关产品推荐
相关产品推荐

