我在密码学方面几乎是新手,并且在将
ed25519
公钥字符串加载到 java.security.PublicKey
中时遇到问题。当我尝试使用 openssl
的密钥时它有效,但当我加载使用 ssh-keygen
生成的密钥时它不起作用。为了提供更多背景信息,我们有一个 apache-mina
SFTP 服务器,客户端可以使用公钥登录。
代码如下(用Scala编写),我可能犯了一个根本性错误,我只是没有看到它。如有任何帮助,我们将不胜感激。
def main(args: Array[String]): Unit = {
// ssh key generated from openssl:
// openssl genpkey -algorithm ed25519 -out private_key.pem
// openssl pkey -in private_key.pem -pubout -out public_key.pem
val sshKey = "MCowBQYDK2VwAyEA4LSmWoy4ZBYJuwRttwzSLu0KQAYBKGRMHqPNBAun0gA="
println(stringToKey(sshKey))
// ssh key generated using ssh-keygen (ssh-keygen -t ed25519 -C "martin")
val sshKeyGen = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGpjh408HMAa1uE60DFrUs0GcgRflP1Hc3iLJlesVfCb martin"
val Array(_, keyData, _) = sshKeyGen.split(" ", 3) // Split the key
println(stringToKey(keyData)) // Not working
}
def stringToKey(sshKey: String): PublicKey = {
Security.addProvider(new BouncyCastleProvider())
val serializedKey: Array[Byte] = Base64.getDecoder.decode(sshKey)
val kf: KeyFactory = KeyFactory.getInstance("Ed25519", "BC")
val keySpec: X509EncodedKeySpec = new X509EncodedKeySpec(serializedKey)
val key: PublicKey = kf.generatePublic(keySpec)
key
}
输出
**OpenSSL Key**
Ed25519 Public Key [70:e6:65:14:c0:99:2d:13:58:87:e1:cc:95:9b:2c:93:39:03:39:9f]
public data: e0b4a65a8cb8641609bb046db70cd22eed0a40060128644c1ea3cd040ba7d200
**SSH Keygen Key**
Exception in thread "main" java.security.spec.InvalidKeySpecException: encoded key spec not recognized: failed to construct sequence from byte[]: unexpected end-of-contents marker
at org.bouncycastle.jcajce.provider.asymmetric.util.BaseKeyFactorySpi.engineGeneratePublic(Unknown Source)
at org.bouncycastle.jcajce.provider.asymmetric.edec.KeyFactorySpi.engineGeneratePublic(KeyFactorySpi.java:224)
at java.base/java.security.KeyFactory.generatePublic(KeyFactory.java:345)
OpenSSH 公钥格式是非标准的。
对于所有算法的公钥,OpenSSL 使用由 X.509/PKIX 在
RFC5280中定义的 ASN.1 结构
SubjectPublicKeyInfo
,这也是 Java 加密本机使用的。虽然“SPKI”一般来说可能很复杂,但 Ed25519 的情况却非常简单。 OpenSSH 使用自己的格式,基于 SSH 协议线路格式,Ed25519 也很简单。
您可以将 OpenSSH 格式的 Ed25519 密钥转换为 Java 支持的形式,如下所示:
// in Java because I don't scale, but you should be able to convert
String osshpub = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGpjh408HMAa1uE60DFrUs0GcgRflP1Hc3iLJlesVfCb martin";
byte[] rawpub = Base64.getDecoder().decode( osshpub.split(" ")[1] );
byte[] prefix = { 0x30,0x2a,0x30,0x05,0x06,0x03,0x2b,0x65,0x70,0x03,0x21,0x00 };
// or equivalent and perhaps easier
//byte[] prefix = Base64.getDecoder().decode("MCowBQYDK2VwAyEA");
byte[] spki = Arrays.copyOf(prefix, prefix.length+32);
System.arraycopy(rawpub,19, spki,prefix.length, 32);
PublicKey javapub = KeyFactory.getInstance("Ed25519").generatePublic(new X509EncodedKeySpec(spki));
// in Java 15 up the standard provider(s) support Ed25519 and you don't need BouncyCastle
// below that or to force Bouncy, add ,"BC" in getInstance
但是如果使用 Apache mina,则不需要;它本身支持 OpenSSH 'authorized_keys' 格式;参见例如https://github.com/apache/mina-sshd/blob/master/sshd-common/src/main/java/org/apache/sshd/common/config/keys/AuthorizedKeyEntry 中
readAuthorizedKeys
的重载。 java。客户端使用的“known_hosts”格式在前面有一个字段(用于主机名/地址),但在其他方面是相同的,并且也受支持。