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

如何使用Bouncy Castle创建兼容OpenSSH的ED25519密钥?

现有实现失效的核心问题
  • 算法选型错误:代码中调用的Curve25519是BouncyCastle实现的Montgomery曲线,对应X25519密钥交换场景,和SSH标准使用的ED25519(基于Edwards25519的Ed25519签名算法)属于不同的曲线体系,哪怕底层域参数同源,密钥结构和编码逻辑完全不兼容。
  • 格式构造错误:代码将ASN.1结构的EC私钥直接套上OPENSSH PRIVATE KEY的PEM标识,没有按照OpenSSH自定义的二进制序列化规则构造私钥内容,结构不符合规范时SSH客户端会直接判定文件无效。
符合OpenSSH兼容要求的实现逻辑
  • 密钥生成阶段直接使用BC提供的原生Ed25519算法生成器,不通过通用EC接口拼接X25519曲线参数。
  • 严格按照OpenSSH私钥序列化规范构造二进制内容:
    • 开头写入固定魔术字符串openssh-key-v1\0
    • 依次写入序列化后的无加密配置:算法标识none、空的密钥派生参数、空认证块、密钥对数量1
    • 序列化公钥部分:写入算法标识ssh-ed25519、32字节原始公钥值
    • 序列化私钥部分:写入和公钥匹配的校验随机数、算法标识ssh-ed25519、32字节原始公钥值、64字节完整私钥值(32字节种子+32字节公钥拼接)、空注释字符串
    • 所有长度字段均使用SSH协议规定的无符号32位大端整数格式编码
  • 最终将构造完成的二进制内容按64字符分行做Base64编码,包装为带-----BEGIN OPENSSH PRIVATE KEY-----头尾的PEM文件,和ssh-keygen -t ed25519生成的id_ed25519格式完全一致。
可直接运行的参考实现
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Security;

public class OpenSshEd25519KeyGenerator {
    static {
        Security.insertProviderAt(new BouncyCastleProvider(), 1);
    }

    // 按SSH序列化规则写入带长度前缀的字节块
    private static void writeSshEncodedBlock(DataOutputStream out, byte[] data) throws Exception {
        out.writeInt(data.length);
        out.write(data);
    }

    public static String generateCompatiblePrivateKey() throws Exception {
        // 生成原生Ed25519密钥对
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("Ed25519", "BC");
        KeyPair keyPair = keyGen.generateKeyPair();

        Ed25519PrivateKeyParameters privateParams = new Ed25519PrivateKeyParameters(keyPair.getPrivate().getEncoded(), 0);
        Ed25519PublicKeyParameters publicParams = privateParams.generatePublicKey();
        byte[] privateSeed = privateParams.getEncoded();
        byte[] publicKeyRaw = publicParams.getEncoded();

        // 构造OpenSSH私钥二进制结构
        ByteArrayOutputStream keyBuffer = new ByteArrayOutputStream();
        DataOutputStream keyStream = new DataOutputStream(keyBuffer);

        // 写入格式魔术头
        keyStream.write("openssh-key-v1\0".getBytes());
        // 写入无加密配置
        writeSshEncodedBlock(keyStream, "none".getBytes());
        writeSshEncodedBlock(keyStream, new byte[0]);
        writeSshEncodedBlock(keyStream, new byte[0]);
        keyStream.writeInt(1); // 单密钥对

        // 序列化公钥段
        ByteArrayOutputStream pubBuffer = new ByteArrayOutputStream();
        DataOutputStream pubStream = new DataOutputStream(pubBuffer);
        writeSshEncodedBlock(pubStream, "ssh-ed25519".getBytes());
        writeSshEncodedBlock(pubStream, publicKeyRaw);
        pubStream.close();
        writeSshEncodedBlock(keyStream, pubBuffer.toByteArray());

        // 序列化私钥段
        int checkValue = (int) (Math.random() * Integer.MAX_VALUE);
        keyStream.writeInt(checkValue);
        keyStream.writeInt(checkValue);

        ByteArrayOutputStream privBuffer = new ByteArrayOutputStream();
        DataOutputStream privStream = new DataOutputStream(privBuffer);
        writeSshEncodedBlock(privStream, "ssh-ed25519".getBytes());
        writeSshEncodedBlock(privStream, publicKeyRaw);
        // 拼接私钥+公钥的完整64字节字段
        byte[] fullPrivate = new byte[64];
        System.arraycopy(privateSeed, 0, fullPrivate, 0, 32);
        System.arraycopy(publicKeyRaw, 0, fullPrivate, 32, 32);
        writeSshEncodedBlock(privStream, fullPrivate);
        writeSshEncodedBlock(privStream, new byte[0]); // 空注释
        privStream.close();
        writeSshEncodedBlock(keyStream, privBuffer.toByteArray());

        keyStream.close();
        byte[] keyBinary = keyBuffer.toByteArray();

        // 包装为标准PEM格式
        String base64Content = Base64.toBase64String(keyBinary);
        StringBuilder pemBuilder = new StringBuilder("-----BEGIN OPENSSH PRIVATE KEY-----\n");
        for (int pos = 0; pos < base64Content.length(); pos += 64) {
            int end = Math.min(pos + 64, base64Content.length());
            pemBuilder.append(base64Content, pos, end).append("\n");
        }
        pemBuilder.append("-----END OPENSSH PRIVATE KEY-----");
        return pemBuilder.toString();
    }
}

生成的密钥文件可直接被OpenSSH客户端识别使用,无需格式转换。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.24 02:06:00