Search code examples
javascriptjavaandroidnode.jssecurity

InvalidKeySpecException In Java


I have a client (an Android app with Java) and a Node.js server. I want to generate a shared secret using the client's and server's Diffie-Hellman public keys.

The server and the client send their Diffie-Hellman public keys to each other over TCP (Transmission Control Protocol) so that they may generate a shared secret. When the server sends its public key to the client as a base64 string, I can successfully convert this string to bytes on the client-side.

The problem is that, when converting the bytes to a PublicKey instance on the marked Line 9, I get the error:

java.security.spec.InvalidKeySpecException: encoded key spec not recognized: failed to construct sequence from byte[]: DER length more than 4 bytes: 33

Here's my Java code:

public boolean generateSharedSecret() throws Exception {
        byte[] serverDiffieHellmanPublicKey = receiveDiffieHellmanPublicKey();

        Log.e("String Length", String.valueOf(Base64.encodeToString(serverDiffieHellmanPublicKey, Base64.DEFAULT).length())); // Outputs 90.
        Log.e("Bytes Length", String.valueOf(serverDiffieHellmanPublicKey.length)); // Outputs 64.

        KeyFactory keyFactory = KeyFactory.getInstance("DH");
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(serverDiffieHellmanPublicKey);
        PublicKey dhPublicKeyFromServer = keyFactory.generatePublic(x509EncodedKeySpec); // Line 9

        // ...
}

public byte[] receiveDiffieHellmanPublicKey() {
    try {
        Socket socket = new Socket("185.255.95.248", 1211);
        InputStream inputStream = socket.getInputStream();
        byte[] buffer = new byte[1024];
        int bytesRead = inputStream.read(buffer);
        String keyString = new String(buffer, 0, bytesRead);
        return Base64.decode(keyString, Base64.DEFAULT);
    } catch (Exception ignore) {}
}

generateSharedSecret();

Here's my Node.js code:

const net = require("net");
const crypto = require("crypto");
const dh = crypto.createDiffieHellman(512);
const serverDiffieHellman = crypto.generateDiffieHellman(dh.getPrime());

net.createServer((socket) => {
    socket.on("connect", () => socket.write(serverDiffieHellman.getPublicKey().toString("base64"));
}).listen(1211, "185.255.95.248", () => console.log("TCP server is running..."));

Any help will be pleasantly appreciated. Thanks, regards...


Solution

  • Ultimately, I was able to solve the full problem. The problem is caused by the format difference between the Node.js and Java Diffie-Hellman public keys. The public key in Node.js is raw, but that of Java is not raw due to the fact that some metadata is added to the Java public key. So I have solved the exact issue by creating the public key using the BigInteger class with the hex string of the prime, generator and public key coming from my TCP server.

    Here's my Java code for solving the issue:

    Socket socket = new Socket("185.255.95.248", 1211);
    InputStream inputStream = socket.getInputStream();
    byte[] buffer = new byte[16384];
    int bytesRead = inputStream.read(buffer);
    String serverResponse = new String(buffer, 0, bytesRead);
    JSONObject jsonObject = new JSONObject(serverResponse);
    BigInteger publicKey = new BigInteger(jsonObject.getString("public_key"), 16);
    BigInteger prime = new BigInteger(jsonObject.getString("prime"), 16);
    BigInteger generator = new BigInteger(jsonObject.getString("generator"), 16);
    DHParameterSpec spec = new DHParameterSpec(publicKey, prime, generator);
    KeyFactory keyFactory = KeyFactory.getInstance("DH");
    PublicKey key = keyFactory.generatePublic(spec);
    Log.d("Public Key", Base64.encodeToString(key.getEncoded(), Base64.DEFAULT));
    

    Here's my Node.js code for solving the issue:

    const net = require("net");
    const crypto = require("crypto");
    
    const dh = crypto.createDiffieHellman(512);
    const serverDiffieHellman = crypto.createDiffieHellman(dh.getPrime());
    
    net.createServer((socket) => {
        socket.on("connect", () => socket.write(JSON.stringify({
        public_key: serverDiffieHellman.getPublicKey("hex"),
        prime: serverDiffieHellman.getPrime("hex"),
        generator: serverDiffieHellman.getGenerator("hex")
    })));
    }).listen(1211, "185.255.95.248", () => console.log("TCP server is running..."));