Search code examples
javanode.jscryptographynode-crypto

Converting Java crypto code to NodeJS


I am working on implementing the Walmart API in NodeJS. Walmart only provides JAVA examples. I am having issues getting it right. My signature is a bit longer and not accepted when compared to using the Java executable they provide.

I'd appreciate any help. Even figuring out what format the Walmart provided secret is would help.

Here's working Java code:

import org.apache.commons.codec.binary.Base64;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;

public class SHA256WithRSAAlgo {
    private static String consumerId = "b68d2a72....";   // Trimmed for security reason
    private static String baseUrl = "https://marketplace.walmartapis.com/v2/feeds";
    private static String privateEncodedStr = "MIICeAIBADANBgkqhkiG9w0BAQEFAA......";       //Trimmed for security reasons
    public static void main(String[] args) {
        String httpMethod = "GET";
        String timestamp = String.valueOf(System.currentTimeMillis());
        String stringToSign = consumerId + "\n" +
                                baseUrl + "\n" +
                                httpMethod + "\n" +
                                timestamp + "\n";
        String signedString = SHA256WithRSAAlgo.signData(stringToSign, privateEncodedStr);
        System.out.println("Signed String: " + signedString);
    }
    public static String signData(String stringToBeSigned, String encodedPrivateKey) {
        String signatureString = null;
        try {
            byte[] encodedKeyBytes = Base64.decodeBase64(encodedPrivateKey);
            PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(encodedKeyBytes);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            PrivateKey myPrivateKey = kf.generatePrivate(privSpec);
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initSign(myPrivateKey);
            byte[] data = stringToBeSigned.getBytes("UTF-8");
            signature.update(data);
            byte[] signedBytes = signature.sign();
            signatureString = Base64.encodeBase64String(signedBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return signatureString;
    }
}

Here's my Node code:

const PK_HEADER = '\n-----BEGIN PRIVATE KEY-----\n'
const PK_FOOTER = '\n-----END PRIVATE KEY-----\n'

const consumerId = 'b68d2a72....'  
const baseUrl = 'https://marketplace.walmartapis.com/v2/feeds'
const privateEncodedStr = 'MIICeAIBADANBgkqhkiG9w0BAQEFAA......'      

const privateKey = `${PK_HEADER}${config.walmart.secret}${PK_FOOTER}`

const privateEncodedKey = Buffer.from(privateKey, 'base64')
const timestamp = Date.now()

const stringToSign = consumerId + '\n' +
                     baseUrl + '\n' +
                     httpMethod + '\n' +
                     timestamp + '\n'

const sign = createSign('RSA-SHA256')
sign.update(stringToSign)
const signedString = sign.sign(privateKey, 'hex')

console.log(signedString)

Some notes. I tried passing the privateEncodedStr as is but Crypto balks at not having the PEM headers so I had to add those.

Using the same timestamp the Java code yielded something like:

bhG0q4Es7iOJtBvepJ2Ao6zPRllf6nM+026dgEadPcaYDdIoCQBYxWWSXB16XcQXgCDcqZ1PW2xgAavHC57jchSXtsTYkuXcWBavQGTH+5YonxIJCzI0wimVKKbqtocKvz4sngXKvIDP7wKKUdXOT6zXVYOdjLfUTERTs7RVg=

My JS code:

219af9f3048ccef558d6ddeeb61d19ed8a968ade5125760d81717dbd62e8447dd831b123a52624d56bc35aef1b082c29585e6fece2aba0fb7853d6840f45e724489028415a9eab8a51e48037a5884f5a12a238ed61a16003e1c412f873d3cfd2f6336dec8c262b01c3ba2a234f0979b8073f096cd35c7d1425bbcfc4603ff05b

Here's what the Walmart secret looks kinda like:

MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMCCJeju5NFCHc/53N0AjZnmWq0UYZyrh3jRZH3UAISeZmLxcAgO65Cg8SfRact172Iy1uYnCB4NqKT5+x0BelWHA2fci5/7VqAdTJ3iZWB1g61lLUpf0IiesMWAycMBf7GwaVIsCGoPb+IC6l1P5IV2Mtb29WnivQyE3UM4dLvCvejdyPq8IoBIywTAgMBAAECgYEAnZ7yo1JXD+9usYcIC/wT9Nrji6uQcNMRTR9FhLE861k2w/Sjok0kzepZjanNojgwQS6OWIy3VEkRj2bTO7N4s6ApLa8yxoQt6ZrOSId9Ut7IenZQ39c6c/ig6e+awyjGvJlKdf7wtv2i4l4tiL8w23RnSECQQDy0wugM5hzV5wc/ejj+9cB8cqdEjfFG8yBZ200W1DAQwepIWFqSdiHbHW9xX5SiFa7JsDHyFWSdboYQToRDHuVAkEAyvQwDqAAz/FJn9oFKXznMjFfrtfoZ/BPrc/4BOUiyWjitxE6Ia5rNuTgCq3u1XxxP78t5spzMr5H5QdoUFHfBwJAbqpYZg6dsBOBhoUBmsWv26cyyOCEg4h9113oDh3MBPEXcQJAHd2JJN3OwMrU9rzyfYRv0ScK2YPUI1dtojo0WSQr2UafYTCCnEstN5vPvDoSvwXO1myk0COs1kAAbveHMIsf1jKVx7euXYP1J8Zwdfd9FjS2CQJAZ8L+jQyGlYIIdUQA7n0bbUblsGntsk1RKkTuZ31Q4w5gBnKO4dk3WMVhNdhmJnZRHRNflA41TITLNYh0EnrWheadVrhpBm2YBn7WFPQ==

Any idea what format this is? It appears to be a private key with the headers/footers removed.


Solution

  • Your code is right, you just need to output signedString in the proper format, which is base64.

    Just change:

    const signedString = sign.sign(privateKey, 'hex');

    To:

    const signedString = sign.sign(privateKey, 'base64');

    And there you go.