Search code examples
pythonnode.jshashrabbitmqcryptojs

RabbitMQ password hashing in NodeJS


I am using RabbitMQ with Docker. I would like to update the configurations directly in the definitions.json file. The users should have their password stored there with rabbit_password_hashing_sha256 hashing algorithm. I have found a useful Python script for hashing the password but I was not able to reproduce it's logic in NodeJS with Crypto library.

Python script:

#!/usr/bin/env python3

# RabbitMQ password hashing algorith as laid out in:
# http://lists.rabbitmq.com/pipermail/rabbitmq-discuss/2011-May/012765.html

from __future__ import print_function
import base64
import os
import hashlib
import struct
import sys

# The plain password to encode
password = sys.argv[1]

# Generate a random 32 bit salt
salt = os.urandom(4)

# Concatenate with the UTF-8 representation of plaintext password
tmp0 = salt + password.encode('utf-8')

# Take the SHA256 hash and get the bytes back
tmp1 = hashlib.sha256(tmp0).digest()

# Concatenate the salt again
salted_hash = salt + tmp1

# Convert to base64 encoding
pass_hash = base64.b64encode(salted_hash)

# Print to the console (stdout)
print(pass_hash.decode("utf-8"))

Output: python hash-password.py test >> t7+JG/ovWbTd9lfrYrPXdFhNZLcO+y56x4z0d8S2OutE6XTE

First implementation failure:

const crypto = require('crypto');

this.password = process.argv[2];

this.salt = crypto.randomBytes(16).toString('hex');

this.password_hash = crypto.pbkdf2Sync(this.password.trim(), this.salt, 1000, 24, `sha256`).toString(`hex`);

console.log(this.password_hash);

Output: node password.js test >> 7611058fb147f5e7a0faab8a806f56f047c1a091d8355544

I was not able to reproduce it in NodeJS, so I collected the stdout result of the executed child process, which is not too elegant.

Second implementation failure:

const crypto = require('crypto');
const utf8 = require('utf8');

this.password = process.argv[2];

this.salt = crypto.randomBytes(4);

this.tmp0 = this.salt + utf8.encode(this.password);

this.tmp1 = crypto.createHash(`sha256`).digest();

this.salted_hash = this.salt + this.tmp1;

this.pass_hash = Buffer.from(this.salted_hash).toString('base64');

console.log(utf8.decode(this.pass_hash));

Output: node password.js test >> Mu+/ve+/vWnvv73vv71C77+977+9HBTvv73vv73vv73ImW/vv70kJ++/vUHvv71k77+977+9TO+/ve+/ve+/vRt4Uu+/vVU=

Can anyone help with the right implementation?


Solution

  • You can do the port to NodeJS more or less 1:1:

    var crypto = require('crypto') 
    
    // The plain password to encode
    var password = Buffer.from('my passphrase', 'utf8') // sample password
    
    //  Generate a random 32 bit salt
    var salt = crypto.randomBytes(4);
    //var salt = Buffer.from('1234', 'utf8'); // for testing, gives pass_hash = MTIzNNcAIpZVAOz2It9VMePU/k4wequLpsQVl+aYDdJa6y9r  
    
    // Concatenate with the UTF-8 representation of plaintext password
    var tmp0 = Buffer.concat([salt, password])
    
    // Take the SHA256 hash and get the bytes back
    var tmp1 = crypto.createHash('sha256').update(tmp0).digest()
    
    // Concatenate the salt again
    var salted_hash = Buffer.concat([salt, tmp1])
    
    // Convert to base64 encoding
    pass_hash = salted_hash.toString('base64')
    
    // Print to the console (stdout)
    console.log(pass_hash)
    

    The code above uses as example password my passphrase. You need to replace the password with yours.

    Note that even if the passwords are identical, you cannot directly compare the results of Python and NodeJS code because of the random salt.
    Therefore, the commented out line with the UTF-8 encoded salt 1234 can be used to produce a result for comparison with the Python code: MTIzNNcAIpZVAOz2It9VMePU/k4wequLpsQVl+aYDdJa6y9r


    The issue in your first implementation is, among other things, the use of PBKDF2, which is not applied in the Python code.

    The second implementation is closer to the actual solution. One problem is that the hashing does not take into account the data to be hashed.
    Another defect is the use of strings, where some operations implicitly apply UTF-8 encoding, which corrupts the (arbitrary binary) data. To prevent this, binary must be used as encoding instead of UTF-8. Just because of the possible encoding issues, the implementation with strings is less robust here than with buffers.