ECDSA:为什么 ssh-keygen 和 Java 生成的公钥大小不同?

信息安全 SSH 爪哇
2021-09-02 05:13:10

我正在使用ssh-keygen -t ecdsa -b 256 它为公共部分生成这个:

ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNaHgNjXShzF/hmZOuhTCCsokgpSCyFohETHT4+OQMxW5g+d9nZCYxpDhwivuWbsoXTYpQWlLATXxjbQr2Y3KRY= t0132456@L541918

我需要在 Java 中做同样的事情。我正在使用 BouncyCastle:

ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC");
g.initialize(ecSpec, new SecureRandom());
KeyPair pair = g.generateKeyPair();

ECPublicKey publicKey = (ECPublicKey)pair.getPublic();
System.out.println("kpub=" + Base64.toBase64String(publicKey.getEncoded()));

但公钥较小,并且不以相同的标头开头:

kpub=MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvHW0nR1hAzy3BuQR8mMkHqgGvvdXLZWAXS29fkFbhshr8y6xybHh0LRDoaUciYgr3w7WGKpfxFSSFqSjdG8bww==

如何以与 ssh-genkey 相同的方式生成但在 Java 中?

1个回答

尺寸不同,因为格式不同。您显示的字符串都是用Base64编码的二进制值。在所有这些消息中,我都在谈论二进制值,即我假设您首先将 Base64 字符串解码为二进制。

在数学上,ECDSA 公钥是椭圆曲线上的一个点。一个点有两个坐标,称为XY,它们通过曲线方程相互绑定(在这种情况下,Y 2 = X 3 + aX + b用于定义特定曲线的两个常数ab )。在这里,您使用 NIST P-256 曲线,它被指定在 256 位字段中工作,即XY是两个 256 位整数。ANSI X9.62 标准定义了一个 65 字节的格式来表示这样XY; 基本上,值 0x04 的第一个字节,然后是正好 32 个字节X(以大端表示法),然后另外 32 个字节的Y。

到现在为止还挺好。然后,Java 和 SSH 都将该 65 字节值包装到一个结构中,该结构还通过一些符号标识符明确标识曲线本身。他们这样做的方式不同。

Java 遵循同样在 ANSI X9.62 中指定的基于 ASN.1 的编码。有几个选项,但是,在你的情况下,你有一个SEQUENCE内容是嵌套的 sub- SEQUENCE,然后是 a BIT STRING嵌套子SEQUENCE包含两个OBJECT IDENTIFIER值,第一个是 1.2.840.10045.2.1(表示“这是一个椭圆曲线公钥”),第二个是 1.2.840.10045.3.1.7,它指定 NIST P-256曲线。内容BIT STRING就是曲线点的 65 字节编码。简而言之:Java 值在经过 Base64 解码后会产生一个长度为 91 个字节的值,其中最后 65 个字节是XY的编码。

另一方面,OpenSSH 以对标准的慢性过敏而闻名,当然他们发明了自己的格式。然后它被记录在RFC 5656, section 3.1中。基本上,编码的密钥由三个“字符串”组成,一个接一个。每个字符串包含一个 32 位标头(4 个字节,大端表示法),后跟字符串值;标头包含值长度,以字节为单位。第一个字符串是“ecdsa-sha2-nistp256”的 ASCII 编码(这是标识签名算法)。第二个字符串是“nistp256”的 ASCII 编码(这标识了曲线,与第一个字符串冗余)。第三个字符串有一个 65 字节的值,你猜对了,这就是XY的 65 字节编码. 总长度为 104 个字节,XY编码使用这 104 个字节中的最后 65 个。


所以基本上,在这两种格式中,有趣的部分正是最后 65 个字节;其余的只是所涉及曲线的标识符,在两种不同的方言中。如果您使用 生成许多密钥ssh-keygen,您会注意到它们仅在最后 65 个字节上有所不同(同样,Base64 解码之后)。与在 Java 中生成许多密钥类似。

因此,从 Java 生成的公钥转换为与 SSH 兼容的公钥就像获取该密钥的最后 65 个字节(在 Base64 解码之后,如果适用),并将它们连接到 SSH 的 39 字节标头之后钥匙。