Search code examples
javabouncycastleopensshecdsa

OpenSSH ecdsa to BCECPublicKey


I would like to convert an OpenSSH ecdsa public key string(.pub file) to a BCECPublicKey instance.

What I want to achieve it the reverse of this code:

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);

String base64 = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());

This is what I've tried:

    // Valid ecdsa-sha2-nistp256 public key string from a .pub file.
    String base64 = "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBG93uDMAjwxpPFXgLFFs7FzWZXrQRaXnBMqmHaRN/5JRzljuqYAUAkW98HvFxGKrnb2JdW3X785AxLNzVhiiw+4=";
    byte[] bytes = Base64.getDecoder().decode(base64);
    ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256r1");

    // java.lang.IllegalArgumentException: Incorrect length for infinity encoding
    ECPoint point = ecSpec.getCurve().decodePoint(bytes);
    ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(point, ecSpec);
    KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", "BC");
    PublicKey pk = keyFactory.generatePublic(publicKeySpec);

But this doesn't seem to work.

Is there an easy way to do this with bouncy castle?


Solution

  • You know you created the blob by concatenating six things, only the sixth of which was the actual point encoding, so how on Earth could you imagine that using all of the blob as a point encoding would be correct?

    The clean and robust way is to parse the blob back into its pieces and extract the point encoding; the dirty way is to just assume the blob is, as expected, for ecdsa-sha2-nistp256 (and uncompressed) so the last 65 bytes are the point encoding:

    String base64 = "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBG93uDMAjwxpPFXgLFFs7FzWZXrQRaXnBMqmHaRN/5JRzljuqYAUAkW98HvFxGKrnb2JdW3X785AxLNzVhiiw+4=";
    byte[] bytes = Base64.getDecoder().decode(base64), temp;
    
    if( clean ){
        DataInputStream instr = new DataInputStream (new ByteArrayInputStream (bytes));
        temp = new byte[instr.readInt()]; instr.read(temp);
        if( !Arrays.equals(temp,"ecdsa-sha2-nistp256".getBytes())) throw new Exception ("bad key");
        temp = new byte[instr.readInt()]; instr.read(temp);
        if( !Arrays.equals(temp,"nistp256".getBytes())) throw new Exception ("bad key");
        temp = new byte[instr.readInt()]; instr.read (temp);
    }else{
        temp = Arrays.copyOfRange(bytes, bytes.length-65, bytes.length);
    }
    ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec ("secp256r1");
    org.bouncycastle.math.ec.ECPoint point = ecSpec.getCurve().decodePoint (temp);
    KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", "BC");
    PublicKey pk = keyFactory.generatePublic(new org.bouncycastle.jce.spec.ECPublicKeySpec(point, ecSpec));