java.security.spec.InvalidKeySpecException: java.i

2019-09-15 20:24发布

问题:

I'm trying to convert a .pub file's contents to a PublicKey and then convert the PublicKey back into a String in order to determine if the conversion is working and does not change the key in the process.

id_rsa.pub:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0zszKhcZTC8xJidUszmRn4Tr/FxPs04wpCzEstebfTW7Bvqgtt+OdvxoNyYM0LAEnxEF4XhAWcsX7VJJqstZLpDqlKDXFr2d0aVIjksCpZt+ftVRwYHRoERhEOP/UmPFb5rKIkhQbED2kTWg11mW9soc6BhwB3THn/Cyo3t1u2vWjEySgPhKeA3Xzh+5eqV7CUD8V6S7OAT7T9ijf7sRV0R8rwHgTLWJ8+dETnY3L3N0fEaNuaayeNblHqrL53/1+tsBBUF3bAS+1GE6oniSeM/yhtfzf2x+O5MDlVVMbOCC/v+FnfIIEKLA+v1xDSAha7C5cHh82TxToWXsbjqGD me@mail

Converter.java

public static final synchronized PublicKey base64ToPublicKey(final String algorithm, final String base64) throws GeneralSecurityException, IOException {
        BASE64Decoder decoder = new BASE64Decoder();
        byte[] sigBytes2 = decoder.decodeBuffer(base64);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(sigBytes2);
        KeyFactory keyFact = KeyFactory.getInstance(algorithm, "BC");
        return keyFact.generatePublic(x509KeySpec);
    }

    public static final synchronized String publicKeyToBase64(final PublicKey publicKey) throws GeneralSecurityException, IOException {
        byte[] publicKeyBytes = publicKey.getEncoded();
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(publicKeyBytes);
    }

When I run:

PublicKey test1 = base64ToPublicKey("RSA", "AAAAB3NzaC1yc2EAAAADAQABAAABAQC0zszKhcZTC8xJidUszmRn4Tr/FxPs04wpCzEstebfTW7Bvqgtt+OdvxoNyYM0LAEnxEF4XhAWcsX7VJJqstZLpDqlKDXFr2d0aVIjksCpZt+ftVRwYHRoERhEOP/UmPFb5rKIkhQbED2kTWg11mW9soc6BhwB3THn/Cyo3t1u2vWjEySgPhKeA3Xzh+5eqV7CUD8V6S7OAT7T9ijf7sRV0R8rwHgTLWJ8+dETnY3L3N0fEaNuaayeNblHqrL53/1+tsBBUF3bAS+1GE6oniSeM/yhtfzf2x+O5MDlVVMbOCC/v+FnfIIEKLA+v1xDSAha7C5cHh82TxToWXsbjqGD");

I get back:

java.security.spec.InvalidKeySpecException: java.io.IOException: unexpected end-of-contents marker
    at org.bouncycastle.jce.provider.JDKKeyFactory.engineGeneratePublic(Unknown Source)
    at org.bouncycastle.jce.provider.JDKKeyFactory$RSA.engineGeneratePublic(Unknown Source)
    at java.security.KeyFactory.generatePublic(KeyFactory.java:328)
    at base64ToPublicKey(Converter.java:216)
    at main(Converter.java:283)

回答1:

OpenSSH public key files (id_*.pub also the entries in known_hosts and authorized_keys) for SSH2 use an OpenSSH-specific variant of an SSH-specific format, see rfc4716 which is in turn based on the SSH2 wire format (as linked) rfc4253 6.6, which is not the 'X.509' format Java crypto uses. (OpenSSH file formats for SSH1 were different, but SSH1 is long broken and should not be used.)

To convert this in Java see convert openSSH rsa key to javax.crypto.Cipher compatible format .

It's easier to avoid the problem.

Bypass 1: If you have reasonably recent OpenSSH (6.0 is okay, not sure for earlier), use

ssh-keygen -e -m PKCS8 -f id_rsa.pub >pub.pem # change filename as needed

to convert to 'X.509' (really SubjectPublicKeyInfo aka SPKI) in PEM form. (Yes they do use the name PKCS8 to mean SPKI; it's crazy.) Then read this in Java by discarding the BEGIN and END lines, decode everything in between (less the line breaks) from base64 to byte[], and put that in X509EncodedKeySpec as you have now. Or if you have OpenSSL you can convert to DER form

openssl rsa -pubin -in pub.pem -out pub.der -outform der # any version
openssl pkey -pubin -in pub.pem -out pub.der -outform der # 1.0.0 up

and then read the DER file with no change at all into an X509EncodedKeySpec.

Bypass 2: if you have the private key, and it is NOT OpenSSH's 'new' format (optional since 6.5 (edit) and default since 7.8), and you have OpenSSL, get the public key in SPKI (Java-friendly) format with one of

openssl rsa -in id_rsa -pubout -out pub.pem # default PEM
openssl rsa -in id_rsa -pubout -out pub.der -outform der # DER
openssl pkey -in id_rsa -pubout -out pub.pem # default PEM, 1.0.0 up
openssl pkey -in id_rsa -pubout -out pub.der -outform der # DER, 1.0.0