4

While performing Elliptic Curve cryptography with secp256k1 curve, I noticed that while the code and test cases compile on the Android Studio IDE they do not compile on the android device since the curve isn't defined in the jre/jdk that the mobile device uses. Changing the curve to a prime256v1 I seem to be facing difficulties in converting a hex string of the publicKey to PublicKey object.

Given the hex string of a PublicKey.getEncoded() which is in a database. I want an android client to convert the byte[] from converting the hex string into a PublicKey object. I am converting the byte[] using X509EncodedKeySpec() as follows:

public static PublicKey getPublicKey(byte[] pk) throws NoSuchAlgorithmException, InvalidKeySpecException {
    EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pk);
    KeyFactory kf = KeyFactory.getInstance("EC");
    PublicKey pub = kf.generatePublic(publicKeySpec);
    return pub;
}

The conversion from a hex string to a byte[] happens as follows:

public static byte[] hexStringToByteArray(String hexString) {
    byte[] bytes = new byte[hexString.length() / 2];

    for(int i = 0; i < hexString.length(); i += 2){
        String sub = hexString.substring(i, i + 2);
        Integer intVal = Integer.parseInt(sub, 16);
        bytes[i / 2] = intVal.byteValue();
        String hex = "".format("0x%x", bytes[i / 2]);
    }
    return bytes;
}

The conversion from a byte[] to Hex string is as follows:

public static String convertBytesToHex(byte[] bytes) {
    char[] hexChars = new char[bytes.length * 2];
    for ( int j = 0; j < bytes.length; j++ ) {
        int v = bytes[j] & 0xFF;
        hexChars[j * 2] = hexArray[v >>> 4];
        hexChars[j * 2 + 1] = hexArray[v & 0x0F];
    }
    return new String(hexChars).toLowerCase();
}

However when I run this on the android app (7.0, API 24) I get the following System Error

W/System.err: java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0c0000b9:ASN.1 encoding routines:OPENSSL_internal:WRONG_TAG
                  at com.android.org.conscrypt.OpenSSLKey.getPublicKey(OpenSSLKey.java:295)
                  at com.android.org.conscrypt.OpenSSLECKeyFactory.engineGeneratePublic(OpenSSLECKeyFactory.java:47)
                  at java.security.KeyFactory.generatePublic(KeyFactory.java:357)

What is the recommended approach for converting a Hex string to a PublicKey for EC instance on an android device.

Here's sample code that you can execute:

ECDSA.java

public class ECDSA {

    public static KeyPair generateKeyPair() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
        ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime256v1");
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        keyGen.initialize(ecSpec, random);
        KeyPair pair = keyGen.generateKeyPair();
        return pair;
    }

    public static PublicKey getPublicKey(byte[] pk) throws NoSuchAlgorithmException, InvalidKeySpecException {
        EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pk);
        KeyFactory kf = KeyFactory.getInstance("EC");
        PublicKey pub = kf.generatePublic(publicKeySpec);
        return pub;
    }

    public static PrivateKey getPrivateKey(byte[] privk) throws NoSuchAlgorithmException, InvalidKeySpecException {
        EncodedKeySpec privateKeySpec = new X509EncodedKeySpec(privk);
        KeyFactory kf = KeyFactory.getInstance("EC");
        PrivateKey privateKey = kf.generatePrivate(privateKeySpec);
        return privateKey;
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {

    KeyPair keyPair = ECDSA.generateKeyPair();
    PublicKey publicKey = keyPair.getPublic();
    PrivateKey privateKey = keyPair.getPrivate();
    // Converting byte[] to Hex
    publicKeyHex = convertBytesToHex(publicKey.getEncoded());
    privateKeyHex = convertBytesToHex(privateKey.getEncoded());
    // Trying to convert Hex to PublicKey/PrivateKey objects
    PublicKey pkReconstructed = ECDSA.getPublicKey(hexStringToByteArray(publicKeyHex));
    PrivateKey skReconstructed = ECDSA.getPrivateKey(hexStringToByteArray(privateKeyHex));
    // This throws an error when running on an android device
    // because there seems to be some library mismatch with 
    // java.security.* vs conscrypt.OpenSSL.* on android.
}
Sudheesh Singanamalla
  • 2,283
  • 3
  • 19
  • 36
  • The problem might be in the code that converts the bytes into a hex string, which you haven't shown. In any event, if you just use base64 encoding you never have to write and debug your own code. – President James K. Polk May 10 '18 at 20:48
  • @JamesKPolk, Thanks, It could be an issue but this does work with the JDK on the system and fails when run on android specifically because the relevant classes don't exist or are mapped to some other class (OpenSSL)? I edited the question to include the conversion of the bytes to hex. Would love it if you took another look – Sudheesh Singanamalla May 11 '18 at 02:03
  • Well, the code you've shown seems to work just fine. Therefore the problem is occurring in some other portion on the code not shown. So a bounty isn't going to help someone guess the problem. Perhaps you can produce an [MCVE](https://stackoverflow.com/help/mcve) that shows the problem. – President James K. Polk May 14 '18 at 00:53
  • @JamesKPolk Seems to work fine when building/compiling it with JDK with Android Studio and not once we run the same on the Activity on the android app. I've updated the code to include a MVCE containing the activity and the ECDSA class methods I am using. – Sudheesh Singanamalla May 14 '18 at 03:17

1 Answers1

4

Finally we get a real MCVE and we can now see that you are not using the correct class for encoded private keys. X509EncodedKeySpec is only for public keys. From the javadocs (emphasis mine):

This class represents the ASN.1 encoding of a public key, encoded according to the ASN.1 type SubjectPublicKeyInfo.

For private keys, the correct encoding is usually a PKCS8EncodedKeySpec. The encoding can be determined by examining the output of Key.getFormat(). Therefore, change your method getPrivateKey of ECDSA to

public static PrivateKey getPrivateKey(byte[] privk) throws NoSuchAlgorithmException, InvalidKeySpecException {
        EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privk);
        KeyFactory kf = KeyFactory.getInstance("EC");
        PrivateKey privateKey = kf.generatePrivate(privateKeySpec);
        return privateKey;
    }
President James K. Polk
  • 40,516
  • 21
  • 95
  • 125