BCECPublicKey指纹

Jan*_*tze 6 java ssh bouncycastle public-key

使用命令行连接到"新"SSH服务器时,将显示指纹:

无法建立主机'test.com(0.0.0.0)'的真实性.
ECDSA密钥指纹为SHA256:566gJgmcB43EXimrT0exEffxSd3xc7RBS6EPx1XZwYc.
您确定要继续连接(是/否)吗?

我知道指纹是公钥的SHA256哈希的Base64字符串.

我知道如何使用以下方法生成此指纹RSAPublicKey:

    RSAPublicKey publicKey = ...;

    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream);

    dataOutputStream.writeInt("ssh-rsa".getBytes().length);
    dataOutputStream.write("ssh-rsa".getBytes());
    dataOutputStream.writeInt(publicKey.getPublicExponent().toByteArray().length);
    dataOutputStream.write(publicKey.getPublicExponent().toByteArray());
    dataOutputStream.writeInt(publicKey.getModulus().toByteArray().length);
    dataOutputStream.write(publicKey.getModulus().toByteArray());

    MessageDigest digest = MessageDigest.getInstance("SHA256");
    byte[] result = digest.digest(byteArrayOutputStream.toByteArray());

    String fingerprint = Base64.getEncoder().encodeToString(result);
Run Code Online (Sandbox Code Playgroud)

但我怎么能这样做BCECPublicKey呢?

更新
我发现它BCECPublicKey根本不相似RSAPublicKey.我从来不知道SSH服务器公钥是ECDSA,客户端公钥是RSA.

字节结构的方式也是不同的.RSA公钥以头(ssh-rsa)开头.标头长度可以从前4个字节(readInt())中读取.但是,当我使用ECDSA执行此操作时,长度是表示标题的长度...

除了回答
要复制粘贴的东西:

    BCECPublicKey publicKey = ...;

    byte[] point = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(publicKey.getEncoded())).getPublicKeyData().getOctets();

    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream);

    dataOutputStream.writeInt("ecdsa-sha2-nistp256".getBytes().length);
    dataOutputStream.write("ecdsa-sha2-nistp256".getBytes());
    dataOutputStream.writeInt("nistp256".getBytes().length);
    dataOutputStream.write("nistp256".getBytes());
    dataOutputStream.writeInt(point.length);
    dataOutputStream.write(point);

    MessageDigest digest = MessageDigest.getInstance("SHA256");
    byte[] result = digest.digest(byteArrayOutputStream.toByteArray());

    String fingerprint = Base64.getEncoder().encodeToString(result);
Run Code Online (Sandbox Code Playgroud)

dav*_*085 3

OpenSSH 公钥格式(以及它所基于的 SSH 有线格式)确实以类型开头,但对于 ECDSA,类型包括曲线 id。例如,我的一个测试系统有一个 ecdsa/p256 密钥,如下所示:

$ awk '{print $2}' <id_ecdsa.pub |openssl base64 -d -A |xxd
0000000: 0000 0013 6563 6473 612d 7368 6132 2d6e  ....ecdsa-sha2-n
0000010: 6973 7470 3235 3600 0000 086e 6973 7470  istp256....nistp
0000020: 3235 3600 0000 4104 8141 9c28 53e7 532e  256...A..A.(S.S.
0000030: 8c4b 9781 c6a5 1820 f41a bc95 4e62 13a9  .K..... ....Nb..
0000040: 8356 a517 be55 6ebc fbf4 de74 e216 8f17  .V...Un....t....
0000050: 6222 011c 5920 a3fc caed c880 4298 46d5  b"..Y ......B.F.
0000060: dd39 396e d72d 1e40                      .99n.-.@
Run Code Online (Sandbox Code Playgroud)

包括:
4 个字节 00000013 bigendian int = 19:类型长度
19 个字节 'ecdsa-sha2-nistp256' 类型
4 个字节 00000008 bigendian int = 8:curveid 长度
8 个字节 'nistp256' curveid (冗余,但这是有线格式)
4 字节 00000041 bigendian int = 65: pub 点的长度
65 个字节开始 04: X9.62 格式的 pub 点,在 SEC1 中复制更方便,即 1 个字节 04=未压缩,N 个字节 X 坐标,N 个字节 Y-坐标,其中 N 是将曲线的基础字段表示为无符号所需的(固定)大小。

这些主要在rfc5656 第 3.1 节中定义,而 curveid 在 6.1 节中定义。RFC 允许压缩点格式,但 OpenSSH 不使用该选项。

BCECPublicKey.getEncoded()(与所有 Java PublicKey 类一样)返回所谓的 X.509(实际上是SubjectPublicKeyInfo,SPKI)编码,对于 EC,它包括X9.62 未压缩格式的公共点,但您需要一些解析来提取它。既然你有 BC,那么使用它是最简单的:

byte[] point = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(encoded)).getPublicKeyData().getOctets();
Run Code Online (Sandbox Code Playgroud)

或者.getW().getQ()将点作为 JCE 或 BC 类获取,从中您可以分别获取(仿射)X 和 Y 坐标BigIntegerECFieldElement依次产生BigInteger,并且每个都BigInteger可以转换为可变大小的字节数组,然后您必须将其左填充或左截断到正确的大小。

结果是要散列的数据。如果您不知道,只有 OpenSSH 6.8 及以上版本使用 base64(sha256(pubkey)) 作为指纹(默认情况下)。在此之前它是 hex-with-colons(md5(pubkey)),较新的版本可以使用旧的指纹以实现兼容性(请参阅forFingerprintHash中的选项和中的flag )。ssh_configssh-Essh-keygen

需要明确的是,这只是OpenSSH指纹。密钥指纹也用于 PGP 和 X.509/PKIX(SSL/TLS、CMS/SMIME 等)领域,并且它们完全不同。