0

I am trying to replicate this method here in Nodejs. I would like to know its equivalent in Node similar to Replicating Java password hashing code in Node.js (PBKDF2WithHmacSHA1).

Java Code:

  private final byte[] _createSaltedPassword(String password, byte[] salt, int iterationCount) {
    
    byte[] dk;    
    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterationCount, 256);
    SecretKeyFactory key = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    dk = key.generateSecret(keySpec).getEncoded();
    return dk; 
  }

I'm keeping the user (user), client nonce (aabbccddaabbccdd), password (Password123), salt, and iteration count (4096) the same for the sake of this example. My last attempt to get the equivalent in Node using crypto.

  const crypto = require("crypto");

  const saltedPassword = crypto.pbkdf2Sync(password, salt, iterationCount, 32, 'sha256').toString('base64');
  console.log('Salted Password:', saltedPassword);

Its for a digest authentication. Output log from Java program:

Output log from Java program

Java Salted Password: nA0hWFpZshs+iME/leUy+e2gM5mjIgo6PYJ8eNjRdhY=

Node Salted Password: PYgn6rcngiE0HSMwzmWhd2W5qTdaRilw4PEeuy3OyRo=

Thank you.

pb46
  • 13
  • 3

1 Answers1

0

Sorry for being too lazy to review the linked code, but I'm providing two examples for Java and NodeJS with Crypto lib that generate a secret key from a passphrase using PBKDF2 algorithm. You can run both codes in an online compiler - Java code and NodeJs code.

This is the output with a fixed = unsecure salt:

Generate a 32 byte long AES key with PBKDF2
aesKeySha256 length:  32  data:  e1ea3e4b0376c0f9bf93b94fe71719a099317297b79108aacd88c8a355d7a3d4

Security warning: below codes do not have any exception handling and are for educational purpose only. Do not use the fixed salt in production, it is used only to show that both platforms generate the same secret key.

Java code:

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;
import java.security.spec.KeySpec;

public class Main {
    public static void main(String[] args) throws Exception {
        System.out.println("Generate a 32 byte long AES key with PBKDF2");

        // get the password as char array
        char[] passwordChar = "secret password".toCharArray();
        final int PBKDF2_ITERATIONS = 15000; // number of iterations, higher is better but slower
        // ### security warning - never use a fixed salt in production, this is for compare reasons only
        byte[] salt = generateFixedSalt32Byte();
        // please use below generateSalt32Byte()
        //byte[] salt = generateSalt32Byte();
        byte[] aesKeySha256 = generateAes256KeyPbkdf2Sha256(passwordChar, PBKDF2_ITERATIONS, salt);
        System.out.println("aesKeySha256 length: " + aesKeySha256.length + " data: " + bytesToHex(aesKeySha256));
    }

    public static byte[] generateAes256KeyPbkdf2Sha256(char[] password, int iterations, byte[] salt) throws Exception {
        SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec keySpec = new PBEKeySpec(password, salt, iterations, 32 * 8);
        return secretKeyFactory.generateSecret(keySpec).getEncoded();
    }

    private static byte[] generateSalt32Byte() {
        SecureRandom secureRandom = new SecureRandom();
        byte[] salt = new byte[32];
        secureRandom.nextBytes(salt);
        return salt;
    }

    private static byte[] generateFixedSalt32Byte() {
        // ### security warning - never use this in production ###
        byte[] salt = new byte[32]; // 32 x0's
        return salt;
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuffer result = new StringBuffer();
        for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
        return result.toString();
    }
}

NodeJs code

console.log("Generate a 32 byte long AES key with PBKDF2");
var crypto = require('crypto');

var password = "secret password";
var PBKDF2_ITERATIONS = 15000; // number of iterations, higher is better but slower

// ### security warning - never use a fixed salt in production, this is for compare reasons only
var salt = generateFixedSalt32Byte();
// please use below generateSalt32Byte()
//var salt = generateSalt32Byte();

var aesKeySha256 = generateAes256KeyPbkdf2Sha256(password, PBKDF2_ITERATIONS, salt);
console.log('aesKeySha256 length: ',
  aesKeySha256.length, ' data: ', bytesToHex(aesKeySha256));

function generateAes256KeyPbkdf2Sha256(password, iterations, salt) {
  return crypto.pbkdf2Sync(password, salt, iterations, 32, 'sha256');
}

function generateSalt32Byte() {
  return crypto.randomBytes(32);
}

function generateFixedSalt32Byte() {
  return Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex');
}

function bytesToHex(input) {
  return input.toString('hex');
}
Michael Fehr
  • 5,827
  • 2
  • 19
  • 40
  • Thank your that helped. I think I was going wrong in not converting my salt: `Buffer.from(salt, 'base64')` Then it worked! – pb46 Jan 15 '21 at 13:14
  • Just to follow up on this...its working great...my question is regarding subsequent requests passing the header: `Authorization: Bearer authToken=abcd...` I want to avoid authenticating every time I make a subsequent request. Is there something I should maintain like a cookie, increment some counter, or something else entirely? – pb46 Jan 23 '21 at 13:39
  • @pb46: I'm sorry, but I'm not a specialist for those kind of questions and you should ask this in another question, thanks + have a nice day. – Michael Fehr Jan 23 '21 at 14:20