BCECPublicKey 到指纹
BCECPublicKey to fingerprint
当连接到 "new" SSH 服务器时,使用命令行,将显示指纹:
The authenticity of host 'test.com (0.0.0.0)' can't be established.
ECDSA key fingerprint is
SHA256:566gJgmcB43EXimrT0exEKfxSd3xc7RBS6EPx1XZwYc.
Are you sure you want to continue connecting (yes/no)?
我了解到指纹是 public 密钥的 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);
但是我怎样才能用 BCECPublicKey
做到这一点?
更新
我发现 BCECPublicKey
与 RSAPublicKey
完全不同。我从来不知道 SSH 服务器 public 密钥是 ECDSA 而客户端 public 密钥是 RSA。
字节的结构方式也大不相同。 RSA public 密钥以 header(ssh-rsa
) 开头。 header 长度可以从前 4 个字节(readInt()
)读取。但是,当我使用 ECDSA 执行此操作时,长度太长以表示 header...
补充回答
要复制粘贴的内容:
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);
OpenSSH public密钥格式(以及它所基于的 SSH 有线格式)确实以类型开头,但对于 ECDSA,类型包括曲线ID。例如,我的一个测试系统有一个 ecdsa/p256 键,如下所示:
$ awk '{print }' <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.-.@
包括:
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 格式的发布点,在 SEC1 中复制更方便,这是 1 字节 04=未压缩,N 字节 X 坐标,N 字节 Y 坐标,其中 N 是表示所需的(固定)大小曲线的基础字段为无符号。
这些主要在 rfc5656 section 3.1 和 6.1 中的 curveid 中定义。 RFC 允许压缩点格式,但 OpenSSH 不使用该选项。
BCECPublicKey.getEncoded()
(像所有 Java PublicKey classes)returns 所谓的 X.509(实际上是 SubjectPublicKeyInfo,SPKI)编码,用于 EC 包括 X9.62 未压缩格式的public 点,但您需要一些解析来提取它。既然你有 BC,那么使用它最简单:
byte[] point = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(encoded)).getPublicKeyData().getOctets();
或者 .getW()
和 .getQ()
将点作为 JCE 或 BC class,您可以从其中任何一个获得(仿射)X 和 Y 坐标 BigInteger
分别。 ECFieldElement
依次产生 BigInteger
,并且每个 BigInteger
都可以转换为 可变大小 字节数组,然后您必须向左填充或左截断到正确的大小。
结果就是要散列的数据。如果您不知道,只有 OpenSSH 6.8 版以上使用 base64(sha256(pubkey)) 作为指纹(默认情况下)。在此之前它是带冒号的十六进制(md5(pubkey)),并且较新的版本可以使用旧指纹以实现兼容性(请参阅 ssh_config
中的选项 FingerprintHash
for ssh
和标志 -E
在 ssh-keygen
).
需要说明的是,这只是 OpenSSH 指纹。密钥指纹也用于 PGP 和 X.509/PKIX(SSL/TLS、CMS/SMIME 等)领域,它们完全不同。
当连接到 "new" SSH 服务器时,使用命令行,将显示指纹:
The authenticity of host 'test.com (0.0.0.0)' can't be established.
ECDSA key fingerprint is SHA256:566gJgmcB43EXimrT0exEKfxSd3xc7RBS6EPx1XZwYc.
Are you sure you want to continue connecting (yes/no)?
我了解到指纹是 public 密钥的 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);
但是我怎样才能用 BCECPublicKey
做到这一点?
更新
我发现 BCECPublicKey
与 RSAPublicKey
完全不同。我从来不知道 SSH 服务器 public 密钥是 ECDSA 而客户端 public 密钥是 RSA。
字节的结构方式也大不相同。 RSA public 密钥以 header(ssh-rsa
) 开头。 header 长度可以从前 4 个字节(readInt()
)读取。但是,当我使用 ECDSA 执行此操作时,长度太长以表示 header...
补充回答
要复制粘贴的内容:
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);
OpenSSH public密钥格式(以及它所基于的 SSH 有线格式)确实以类型开头,但对于 ECDSA,类型包括曲线ID。例如,我的一个测试系统有一个 ecdsa/p256 键,如下所示:
$ awk '{print }' <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.-.@
包括:
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 格式的发布点,在 SEC1 中复制更方便,这是 1 字节 04=未压缩,N 字节 X 坐标,N 字节 Y 坐标,其中 N 是表示所需的(固定)大小曲线的基础字段为无符号。
这些主要在 rfc5656 section 3.1 和 6.1 中的 curveid 中定义。 RFC 允许压缩点格式,但 OpenSSH 不使用该选项。
BCECPublicKey.getEncoded()
(像所有 Java PublicKey classes)returns 所谓的 X.509(实际上是 SubjectPublicKeyInfo,SPKI)编码,用于 EC 包括 X9.62 未压缩格式的public 点,但您需要一些解析来提取它。既然你有 BC,那么使用它最简单:
byte[] point = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(encoded)).getPublicKeyData().getOctets();
或者 .getW()
和 .getQ()
将点作为 JCE 或 BC class,您可以从其中任何一个获得(仿射)X 和 Y 坐标 BigInteger
分别。 ECFieldElement
依次产生 BigInteger
,并且每个 BigInteger
都可以转换为 可变大小 字节数组,然后您必须向左填充或左截断到正确的大小。
结果就是要散列的数据。如果您不知道,只有 OpenSSH 6.8 版以上使用 base64(sha256(pubkey)) 作为指纹(默认情况下)。在此之前它是带冒号的十六进制(md5(pubkey)),并且较新的版本可以使用旧指纹以实现兼容性(请参阅 ssh_config
中的选项 FingerprintHash
for ssh
和标志 -E
在 ssh-keygen
).
需要说明的是,这只是 OpenSSH 指纹。密钥指纹也用于 PGP 和 X.509/PKIX(SSL/TLS、CMS/SMIME 等)领域,它们完全不同。