1

I have node js server for my android app and I want the communication to be secured using DH key exchange. I am able to generate the Prime p and the Base g in java same with nodejs(can be corrected if not right)

The Problem:

1. I want to use the secret p and g on both client side and server side since there will be encryption and decryption from both sides yet crypto.createDiffieHellman() nodejs function shown in documentation only takes the parameter of bitlength so I have hard-coded these params after finding that other method signatures exist. From the documentation, I understand only public key should be shared. These p and g are generated initially on client side

2. In the sample code below, the key and secret generation works well in nodejs however when I test with public key from the client(generated using new String(Hex.encodeHex(kp.getPublic().getEncoded()))), I get the Error: Supplied key is too large at DiffieHellman.dhComputeSecret

Fore more than a day, my thorough research reveals no examples/demo of DH key exchange between two different languages but one-language-examples with several comments/articles discussing the existing wiki docs and less or no code. I think this is a new question on cross language client-server with DH keys.

Nodejs:

var crypto = require("crypto");
    var assert = require("assert");
// the prime is shared by everyone
    var p ="7287927445664946359687239486223244248530331441696747442753348226106279800740207968417650493105155177035805265358863967196769895080354517146585830093038907";
    var g="3519494160132765249824212078020429853238178237852596056897517714427148886750491954575952859563331424777669938328249242087811536189850912851560984760609308";
    let server = crypto.createDiffieHellman(Buffer.from(p,).toString("hex"),"hex",Buffer.from(g,).toString("hex"),"hex");
    let prime = server.getPrime();
    console.log("Generate Alice's keys...");
    console.log(Buffer.from(prime).toString());
    let alice = crypto.createDiffieHellman(prime);
    let alicePublicKey = alice.generateKeys();
    console.log("Generate Bob's keys...");
    let bob = crypto.createDiffieHellman(prime);
    //let bobPublicKey = bob.generateKeys();
    //THIS IS PUBLIC KEY FROM JAVA IN HEX FORMAT
    let bobPublicKey = Buffer.from("3081df30819706092a864886f70d0103013081890241008b26a5b60fd75471c366a0e8f67abe84d6f4c16b0dc97a602937420af3a658b34bb484df2d85281417a1bde4c0c51a7f97e8ac3a70fb34f092c2b1ebba01253b02404332edd9db0820ef954487cc1b327c638e2876b88a0cabc498811b42f7528af7fe58286a521f2190e9cc8785feaa5a8f019624bf88587a8b5b79854a11bcea1c02020180034300024012e116bc4fbd90542e03c1a5a130f923e579b65a50b8ed02e61f6369375f3a17ef270b3d05c52085ffe6e185ec7b19ea3cba7fe40d87e62254dc15f0e6db63b5","hex");
    console.log("Exchange and generate the secret...");
    let aliceBobSecret = alice.computeSecret(bobPublicKey);
    let bobAliceSecret = bob.computeSecret(alicePublicKey);
    // let davidAliceSecret = david.computeSecret(alicePublicKey, "latin1");
    // let aliceDavidSecret = alice.computeSecret(davidPublicKey, "latin1");
 console.log("alicePublicKey", alicePublicKey);
 console.log("bobPublicKey", bobPublicKey);
    assert.notEqual(alicePublicKey, bobPublicKey);
    console.log("alicePublicKey and bobPublicKey NOT equal");
// console.log("aliceBobSecret", aliceBobSecret.toString("latin1"));
// console.log("bobAliceSecret", bobAliceSecret.toString("latin1"));
    assert.equal(aliceBobSecret.toString("hex"), bobAliceSecret.toString("hex"));
    console.log("aliceBobSecret and bobAliceSecret equal");

Java:

//THIS COMMENTED CODE IS USED TO INITIALLY GENERATE p AND g
/*AlgorithmParameterGenerator paramGen = AlgorithmParameterGenerator.getInstance("DH");
    paramGen.init(512); // number of bits
    AlgorithmParameters params = paramGen.generateParameters();
    DHParameterSpec dhSpec = (DHParameterSpec) params.getParameterSpec(DHParameterSpec.class);
    BigInteger p512 = dhSpec.getP();
    BigInteger g512 = dhSpec.getG();*/
    BigInteger p512 = new BigInteger(Hex.decodeHex("008b26a5b60fd75471c366a0e8f67abe84d6f4c16b0dc97a602937420af3a658b34bb484df2d85281417a1bde4c0c51a7f97e8ac3a70fb34f092c2b1ebba01253b"));
    BigInteger g512 = new BigInteger(Hex.decodeHex("4332edd9db0820ef954487cc1b327c638e2876b88a0cabc498811b42f7528af7fe58286a521f2190e9cc8785feaa5a8f019624bf88587a8b5b79854a11bcea1c"));
//A
    KeyPairGenerator akpg = KeyPairGenerator.getInstance("DiffieHellman");
    DHParameterSpec param = new DHParameterSpec(p512, g512);
    String a = p512 + "";
    String b = g512 + "";
    System.out.println("Prime: " + new String(Hex.encodeHex(p512.toByteArray())));
    System.out.println("PrimeH: " + p512);
    System.out.println("Base: " + new String(Hex.encodeHex(g512.toByteArray())));
    System.out.println("BaseH: " + g512);
    akpg.initialize(param);
    KeyPair kp = akpg.generateKeyPair();
//B
    KeyPairGenerator bkpg = KeyPairGenerator.getInstance("DiffieHellman");
    DHParameterSpec param2 = new DHParameterSpec(p512, g512);
    System.out.println("Prime: " + p512);
    System.out.println("Base: " + g512);
    bkpg.initialize(param2);
    KeyPair kp2 = bkpg.generateKeyPair();
    KeyAgreement aKeyAgree = KeyAgreement.getInstance("DiffieHellman");
    KeyAgreement bKeyAgree = KeyAgreement.getInstance("DiffieHellman");
    aKeyAgree.init(kp.getPrivate());
    bKeyAgree.init(kp2.getPrivate());
    System.out.println("2Key: " +new String(Hex.encodeHex(kp.getPublic().getEncoded())));
    aKeyAgree.doPhase(kp2.getPublic(), true);
    bKeyAgree.doPhase(kp.getPublic(), true);
//System.out.println("Alice Secret Key: " + aKeyAgree.generateSecret());
//System.out.println("Bob's Secret Key: " + bKeyAgree.generateSecret());
    MessageDigest hash = MessageDigest.getInstance("SHA-256");
    /*byte[] ASharedSecret = hash.digest(aKeyAgree.generateSecret());
    byte[] BSharedSecret = hash.digest(bKeyAgree.generateSecret());*/
    byte[] ASharedSecret = aKeyAgree.generateSecret();
    byte[] BSharedSecret = bKeyAgree.generateSecret();
    System.out.println("Alice's Shared Secret: " + Arrays.toString(ASharedSecret));
    System.out.println("Bob's Shared Secret: " + Arrays.toString(BSharedSecret));
Geek Guy
  • 710
  • 1
  • 8
  • 28
  • 1
    The public key from Java side is obtained from a call to `Key.getEncoded()`. By also calling `Key.getFormat()` you would have found the format of the output of `getEncoded` is X.509, also known as SubjectPublicKeyInfo (see RFC 5280). Evidently, node's crypto module doesn't accept that format. Since the node [documentation appears to be garbage](https://nodejs.org/api/crypto.html#crypto_diffiehellman_computesecret_otherpublickey_inputencoding_outputencoding), I can't tell what node expects. – President James K. Polk Dec 26 '18 at 17:27
  • @JamesKPolk so does that mean that I might need to write personal DH key exchange implementation in node since I know what it expects? – Geek Guy Dec 26 '18 at 18:34
  • It means you must find a compatible way to exchange public DH keys between Java and node. Maybe you can look at the node crypto module source code and figure out what it expects, or maybe you can find the information somewhere. Nobody does this stuff by hand anymore anyway, the correct answer is almost always to use SSL/TLS unless you are already a cryptographer. – President James K. Polk Dec 26 '18 at 18:39
  • Possible solution then, would it be prudent if I convert the `X.509` back to Biginteger then encode it in a format node can use? – Geek Guy Dec 26 '18 at 18:42
  • Okay, I get it, let me go through the source code – Geek Guy Dec 26 '18 at 18:44
  • @GeekGuy, have you found any solution? – Vladlen Volkov Dec 10 '19 at 11:56
  • @ВладВолков This approach did not work for me but I have plans of getting back to it soon now that node's crypto module was updated. Feel free to advance it – Geek Guy Dec 10 '19 at 14:11

1 Answers1

2

Here is a working example of DH key exchange within NodeJS and Java.

Node:

// Step 1. Generate Alice DH key in NodeJS side
let crypto = require('crypto');
let dh = crypto.getDiffieHellman('modp14');
dh.generateKeys();

let alicePublicKey = dh.getPublicKey().toString('hex');
console.log(alicePublicKey);    // 308a5cff.....e6ded9d5  ----> send to Bob

// Step 5. Check shared secret
let bobPublicKey = "00a8e459...61e210d9";     // <---- receive from Bob   
let shared = dh.computeSecret(Buffer.from(bobPublicKey, "hex"), null, 'hex');   
console.log(shared);    // dd83430d...22156ebd - must be the same as in the Java side

Java:

void testDH() {
    // Step 2. Configure DH params on Java side
    DHParameterSpec modp14 = modp14();

    // Step 3. Encode public key from Alice
    byte[] alicePubKeyEnc = hexStringToByteArray("308a5cff.....e6ded9d5");
    BigInteger alicePublic = new BigInteger(alicePubKeyEnc);

    KeyFactory bobKeyFactory = KeyFactory.getInstance("DH");
    DHPublicKeySpec bobPubKeySpecs = new DHPublicKeySpec(
        alicePublic,        // <---- use Alice's public
        modp14.getP(), 
        modp14.getG());        
    DHPublicKey alicePubKey = (DHPublicKey)bobKeyFactory.generatePublic(bobPubKeySpecs);

    // Step 4. Generate Bob DH key in Java side
    KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH");
    bobKpairGen.initialize(modp14);
    KeyPair bobKpair = bobKpairGen.generateKeyPair();
    System.out.println("Bob public key: " 
        + bytesToHex(((DHPublicKey)bobKpair.getPublic()).getY().toByteArray()));      
    // 00a8e459...61e210d9

    // Step 6. Generates shared secret
    KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH");
    bobKeyAgree.init(bobKpair.getPrivate());
    bobKeyAgree.doPhase(alicePubKey, true);

    byte[] bobSharedSecret = new byte[alicePubKeyEnc.length];
    bobKeyAgree.generateSecret(bobSharedSecret, 0);
    System.out.println("Bob's shared secret: "
         +  bytesToHex(bobSharedSecret));     // dd83430d...22156ebd
}

public static DHParameterSpec modp14() {
    final BigInteger p =
            new BigInteger(
                    "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1c" +
                            "d129024e088a67cc74020bbea63b139b22514a08798e3404" +
                            "ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c2" +
                            "45e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7" +
                            "edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b" +
                            "3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf" +
                            "5f83655d23dca3ad961c62f356208552bb9ed52907709696" +
                            "6d670c354e4abc9804f1746c08ca18217c32905e462e36ce" +
                            "3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52" +
                            "c9de2bcbf6955817183995497cea956ae515d2261898fa05" +
                            "1015728e5a8aacaa68ffffffffffffffff",
                    16);
    final BigInteger g = new BigInteger("2");
    return new DHParameterSpec(p, g);
}
  • Tested using NodeJS v17.0.1 and OpenJDK 14.0.1.
  • See this SO question about using modp14
Alexander
  • 434
  • 4
  • 12