How to generate password_hash for RabbitMQ Management HTTP API
Asked Answered
L

12

32

The beloved RabbitMQ Management Plugin has a HTTP API to manage the RabbitMQ through plain HTTP requests.

We need to create users programatically, and the HTTP API was the chosen way to go. The documentation is scarce, but the API it's pretty simple and intuitive.

Concerned about the security, we don't want to pass the user password in plain text, and the API offers a field to send the password hash instead. Quote from there:

[ GET | PUT | DELETE ] /api/users/name

An individual user. To PUT a user, you will need a body looking something like this:

{"password":"secret","tags":"administrator"}

or:

{"password_hash":"2lmoth8l4H0DViLaK9Fxi6l9ds8=", "tags":"administrator"}

The tags key is mandatory. Either password or password_hash must be set.

So far, so good, the problem is: how to correctly generate the password_hash?

The password hashing algorithm is configured in RabbitMQ's configuration file, and our is configured as the default SHA256.

I'm using C#, and the following code to generate the hash:

var cr = new SHA256Managed();
var simplestPassword = "1";
var bytes = cr.ComputeHash(Encoding.UTF8.GetBytes(simplestPassword));
var sb = new StringBuilder();
foreach (var b in bytes) sb.Append(b.ToString("x2"));
var hash = sb.ToString();

This doesn't work. Testing in some online tools for SHA256 encryption, the code is generating the expected output. However, if we go to the management page and set the user password manually to "1" then it works like a charm.

This answer led me to export the configurations and take a look at the hashes RabbitMQ are generating, and I realized a few things:

  • hash example of "1": "y4xPTRVfzXg68sz9ALqeQzARam3CwnGo53xS752cDV5+Utzh"
  • all the user's hashes have fixed length
  • the hashes change every time (even if the password is the same). I know PB2K also do this to passwords, but don't know the name of this cryptographic property.
  • if I pass the password_hash the RabbitMQ stores it without changes

I'm accepting suggestions in another programming languages as well, not just C#.

Leone answered 23/12, 2016 at 18:37 Comment(4)
I believe they hash not a password but encrypted password. They may use some RSA algorithm to get ecnrypted password and then calculate hash for it.Retral
To be able to generate hashes you need to find the key that is used for encrypting passwords.Retral
I tried to understand what they do in the source code ( github.com/rabbitmq/rabbitmq-management/blob/master/src/… ), but I know nothing of Erlang and functional languagesLeone
RabbitMQ 3.11.8 and newer includes a command and API to hash a string according to the currently configured password hashing algorithm - https://mcmap.net/q/427991/-how-to-generate-password_hash-for-rabbitmq-management-http-apiScrap
G
15

From: http://rabbitmq.1065348.n5.nabble.com/Password-Hashing-td276.html

However, the algorithm is quite simple if you want to implement it yourself. Here's a worked example:

Generate a random 32 bit salt:

CA D5 08 9B

Concatenate that with the UTF-8 representation of the password (in this case "simon"):

CA D5 08 9B 73 69 6D 6F 6E

Take the MD5 hash:

CB 37 02 72 AC 5D 08 E9 B6 99 4A 17 2B 5F 57 12

Concatenate the salt again:

CA D5 08 9B CB 37 02 72 AC 5D 08 E9 B6 99 4A 17 2B 5F 57 12

And convert to base64 encoding:

ytUIm8s3AnKsXQjptplKFytfVxI=

you should be able to modify your code to follow this process

Grouper answered 23/12, 2016 at 19:30 Comment(2)
Thanks Derick, it worked like a charm. Advice for future readers: it's not always MD5, it depends on what is configured on rabbitmq.config (default is SHA256 since v3.6.0)Leone
https://mcmap.net/q/427991/-how-to-generate-password_hash-for-rabbitmq-management-http-apiScrap
A
34

And for the fun the bash version !

#!/bin/bash

function encode_password()
{
    SALT=$(od -A n -t x -N 4 /dev/urandom)
    PASS=$SALT$(echo -n $1 | xxd -ps | tr -d '\n' | tr -d ' ')
    PASS=$(echo -n $PASS | xxd -r -p | sha256sum | head -c 128)
    PASS=$(echo -n $SALT$PASS | xxd -r -p | base64 | tr -d '\n')
    echo $PASS
}

encode_password "some-password"
Abraxas answered 6/11, 2018 at 15:42 Comment(3)
For Mac users: brew install coreutilsSediment
ha love this great answerTitrate
To whoever finds this answer useful: please check also Luke Bakken's answerLeone
G
15

From: http://rabbitmq.1065348.n5.nabble.com/Password-Hashing-td276.html

However, the algorithm is quite simple if you want to implement it yourself. Here's a worked example:

Generate a random 32 bit salt:

CA D5 08 9B

Concatenate that with the UTF-8 representation of the password (in this case "simon"):

CA D5 08 9B 73 69 6D 6F 6E

Take the MD5 hash:

CB 37 02 72 AC 5D 08 E9 B6 99 4A 17 2B 5F 57 12

Concatenate the salt again:

CA D5 08 9B CB 37 02 72 AC 5D 08 E9 B6 99 4A 17 2B 5F 57 12

And convert to base64 encoding:

ytUIm8s3AnKsXQjptplKFytfVxI=

you should be able to modify your code to follow this process

Grouper answered 23/12, 2016 at 19:30 Comment(2)
Thanks Derick, it worked like a charm. Advice for future readers: it's not always MD5, it depends on what is configured on rabbitmq.config (default is SHA256 since v3.6.0)Leone
https://mcmap.net/q/427991/-how-to-generate-password_hash-for-rabbitmq-management-http-apiScrap
S
14

NOTE: as of RabbitMQ 3.11.8 you can use these methods to generate a hashed password according to the currently configured password hashing algorithm:

  • rabbitmqctl hash_password foobar
  • curl -u api_user:api_pass rabbitmq-server:15672/api/auth/hash_password/foobar

Output from each:

$ rabbitmqctl hash_password foobar
Will hash password foobar
c9KkB60KtKFwksRUg3EBYzRCG7Te5l4t4PLaM/7D0DoTdxiZ

$ curl -4su guest:guest -X GET localhost:15672/api/auth/hash_password/foobar
{"ok":"erB0SI9prHWeqeHwcUFdJPziTYn4ZCcepfAFY7XWsjfN70Ln"}
Scrap answered 26/2, 2023 at 23:22 Comment(0)
A
13

For lazy people (like me ;) ), there is the code for computing RabbitMq password with Sha512 for the framework .Net Core.

public static class RabbitMqPasswordHelper
{
    public static string EncodePassword(string password)
    {
        using (RandomNumberGenerator rand = RandomNumberGenerator.Create())
        using (var sha512 = SHA512.Create())
        {
            byte[] salt = new byte[4];

            rand.GetBytes(salt);

            byte[] saltedPassword = MergeByteArray(salt, Encoding.UTF8.GetBytes(password));
            byte[] saltedPasswordHash = sha512.ComputeHash(saltedPassword);

            return Convert.ToBase64String(MergeByteArray(salt, saltedPasswordHash));
        }
    }

    private static byte[] MergeByteArray(byte[] array1, byte[] array2)
    {
        byte[] merge = new byte[array1.Length + array2.Length];
        array1.CopyTo(merge, 0);
        array2.CopyTo(merge, array1.Length);

        return merge;
    }
}
Abraxas answered 28/6, 2017 at 9:4 Comment(1)
It also works for sha256. You've to just replace SHA512 to SHA256.Shirl
D
13

Here is a small python script I stumbled across some time ago (attribution is in the script) that is great for quick hash generation. It doesn't do any error checking, so is quite simple:

#!/usr/bin/env python3

# rabbitMQ password hashing algo 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

# This is the password we wish to encode
password = sys.argv[1]

# 1.Generate a random 32 bit salt:
# This will generate 32 bits of random data:
salt = os.urandom(4)

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

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

# 4. Concatenate the salt again:
salted_hash = salt + tmp1

# 5. convert to base64 encoding:
pass_hash = base64.b64encode(salted_hash)

print(pass_hash.decode("utf-8"))
Desertion answered 26/10, 2018 at 20:59 Comment(1)
Fun fact: be careful when passing passwords in Powershell (in order to use this script): it interprets whatever is after an "$" character as a variabile.Blether
T
6

Here is a version of this script in bash that will work on BusyBox with openSSL

#!/bin/bash

function get_byte()
{
    local BYTE=$(head -c 1 /dev/random | tr -d '\0')

    if [ -z "$BYTE" ]; then
        BYTE=$(get_byte)
    fi

    echo "$BYTE"
}

function encode_password()
{
    BYTE1=$(get_byte)
    BYTE2=$(get_byte)
    BYTE3=$(get_byte)
    BYTE4=$(get_byte)

    SALT="${BYTE1}${BYTE2}${BYTE3}${BYTE4}"
    PASS="$SALT$1"
    TEMP=$(echo -n "$PASS" | openssl sha256 -binary)
    PASS="$SALT$TEMP"
    PASS=$(echo -n "$PASS" | base64)
    echo "$PASS"
}

encode_password $1
Topmast answered 23/5, 2019 at 6:53 Comment(0)
P
5

Just in case, full code from Waldo should be next

//Rextester.Program.Main is the entry point for your code. Don't change it.
//Compiler version 4.0.30319.17929 for Microsoft (R) .NET Framework 4.5

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Security.Cryptography;
using System.Text;

namespace Rextester
{
    public static class RabbitMqPasswordHelper
{
    public static string EncodePassword(string password)
    {
        using (RandomNumberGenerator rand = RandomNumberGenerator.Create())
        using (var sha256 = SHA256.Create())
        {
            byte[] salt = new byte[4];

            rand.GetBytes(salt);

            byte[] saltedPassword = MergeByteArray(salt, Encoding.UTF8.GetBytes(password));
            byte[] saltedPasswordHash = sha256.ComputeHash(saltedPassword);

            return Convert.ToBase64String(MergeByteArray(salt, saltedPasswordHash));
        }
    }

    private static byte[] MergeByteArray(byte[] array1, byte[] array2)
    {
        byte[] merge = new byte[array1.Length + array2.Length];
        array1.CopyTo(merge, 0);
        array2.CopyTo(merge, array1.Length);

        return merge;
    }
}

    public class Program
    {
        public static void Main(string[] args)
        {
            //Your code goes here
            Console.WriteLine(Rextester.RabbitMqPasswordHelper.EncodePassword("MyPassword"));
        }
    }
}

You can run it online on http://rextester.com/. Output of program will contain your hash.


Python version by christianclinton (https://gist.github.com/christianclinton/faa1aef119a0919aeb2e)

#!/bin/env/python
import hashlib
import binascii

# Utility methods for generating and comparing RabbitMQ user password hashes.
#
# Rabbit Password Hash Algorithm:
# 
# Generate a random 32 bit salt: 
# CA D5 08 9B 

# Concatenate that with the UTF-8 representation of the password (in this 
# case "simon"): 
# CA D5 08 9B 73 69 6D 6F 6E 

# Take the MD5 hash: 
# CB 37 02 72 AC 5D 08 E9 B6 99 4A 17 2B 5F 57 12 

# Concatenate the salt again: 
# CA D5 08 9B CB 37 02 72 AC 5D 08 E9 B6 99 4A 17 2B 5F 57 12 

# And convert to base64 encoding: 
# ytUIm8s3AnKsXQjptplKFytfVxI= 
# 
# Sources:
# http://rabbitmq.1065348.n5.nabble.com/Password-Hashing-td276.html
# http://hg.rabbitmq.com/rabbitmq-server/file/df7aa5d114ae/src/rabbit_auth_backend_internal.erl#l204 

# Test Case:
#   print encode_rabbit_password_hash('CAD5089B', "simon")
#   print decode_rabbit_password_hash('ytUIm8s3AnKsXQjptplKFytfVxI=')
#   print check_rabbit_password('simon','ytUIm8s3AnKsXQjptplKFytfVxI=')

def encode_rabbit_password_hash(salt, password):
    salt_and_password = salt + password.encode('utf-8').encode('hex')
    salt_and_password = bytearray.fromhex(salt_and_password)
    salted_md5 = hashlib.md5(salt_and_password).hexdigest()
    password_hash = bytearray.fromhex(salt + salted_md5)
    password_hash = binascii.b2a_base64(password_hash).strip()
    return password_hash

def decode_rabbit_password_hash(password_hash):
    password_hash = binascii.a2b_base64(password_hash)
    decoded_hash = password_hash.encode('hex')
    return (decoded_hash[0:8], decoded_hash[8:])

def check_rabbit_password(test_password, password_hash):
    salt, hash_md5sum = decode_rabbit_password_hash(password_hash)
    test_password_hash = encode_rabbit_password_hash(salt, test_password)
    return test_password_hash == password_hash

Have fun!

Preindicate answered 31/5, 2018 at 22:43 Comment(0)
L
4

Here's one in PowerShell - using SHA512 rather than MD5 has mentioned in @derick-bailey opener - but you can repro the intermediate steps by changing $hash and $salt

param (
    $password
)

$rand = [System.Security.Cryptography.RandomNumberGenerator]::Create()
$hash = [System.Security.Cryptography.SHA512]::Create()

[byte[]]$salt = New-Object byte[] 4
$rand.GetBytes($salt)

#Uncomment the next 2 to replicate derick baileys sample
#[byte[]]$salt = 0xCA, 0xD5, 0x08, 0x9B
#$hash = [System.Security.Cryptography.Md5]::Create()

#Write-Host "Salt"
#[System.BitConverter]::ToString($salt)

[byte[]]$utf8PasswordBytes = [Text.Encoding]::UTF8.GetBytes($password)
#Write-Host "UTF8 Bytes"
#[System.BitConverter]::ToString($utf8PasswordBytes)

[byte[]]$concatenated = $salt + $utf8PasswordBytes
#Write-Host "Concatenated"
#[System.BitConverter]::ToString($concatenated)

[byte[]]$saltedHash = $hash.ComputeHash($concatenated)
#Write-Host "SHA512:"
#[System.BitConverter]::ToString($saltedHash)

[byte[]]$concatenatedAgain = $salt + $saltedHash
#Write-Host "Concatenated Again"
#[System.BitConverter]::ToString($concatenatedAgain)

$base64 = [System.Convert]::ToBase64String($concatenatedAgain)
Write-Host "BASE64"
$base64
Lynn answered 2/5, 2019 at 11:20 Comment(0)
A
2

For those looking for a Go solution. The code below will generate a 32 byte random password or take in a flag with a given password and hash it so you can use in the "password_hash" field of the definitions file in rabbit

package main

import (
    "crypto/rand"
    "crypto/sha256"
    "encoding/base64"
    "flag"
    "fmt"
    mRand "math/rand"
    "time"
)

var src = mRand.NewSource(time.Now().UnixNano())

func main() {

    input := flag.String("password", "", "The password to be encoded. One will be generated if not supplied")

    flag.Parse()

    salt := [4]byte{}
    _, err := rand.Read(salt[:])
    if err != nil {
        panic(err)
    }

    pass := *input

    if len(pass) == 0 {
        pass = randomString(32)
    }

    saltedP := append(salt[:], []byte(pass)...)

    hash := sha256.New()

    _, err = hash.Write(saltedP)

    if err != nil {
        panic(err)
    }

    hashPass := hash.Sum(nil)

    saltedP = append(salt[:], hashPass...)

    b64 := base64.StdEncoding.EncodeToString(saltedP)

    fmt.Printf("Password: %s\n", string(pass))
    fmt.Printf("Hash: %s\n", b64)
}

const (
    letterBytes   = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    letterIdxBits = 6                    // 6 bits to represent a letter index
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
    letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
)

func randomString(size int) string {
    b := make([]byte, size)
    // A src.Int63() generates 63 random bits, enough for letterIdxMax letters!
    for i, cache, remain := size-1, src.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = src.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return string(b)

}
Ainu answered 31/7, 2018 at 8:22 Comment(1)
Any way to do it in the template language.?Ureter
B
0

Here, a way to do it in Java.

    /**
     * Generates a salted SHA-256 hash of a given password.
     */
    private String getPasswordHash(String password) {
        var salt = getSalt();
        try {
            var saltedPassword = concatenateByteArray(salt, password.getBytes(StandardCharsets.UTF_8));
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(saltedPassword);

            return Base64.getEncoder().encodeToString(concatenateByteArray(salt,hash));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }

    /**
     * Generates a 32 bit random salt.
     */
    private byte[] getSalt() {
        var ba = new byte[4];
        new SecureRandom().nextBytes(ba);
        return ba;
    }

    /**
     * Concatenates two byte arrays.
     */
    private byte[] concatenateByteArray(byte[] a, byte[] b) {
        int lenA = a.length;
        int lenB = b.length;
        byte[] c = Arrays.copyOf(a, lenA + lenB);
        System.arraycopy(b, 0, c, lenA, lenB);
        return c;
    }
Bunkum answered 9/4, 2019 at 9:1 Comment(1)
I didn't close the page hoping that someone must have posted a java solution for sure. Hope in Humanity restored. <3Shilling
S
0

Try using the HareDu API. If you are using .NET Core 2 or above use https://github.com/ahives/HareDu2/blob/master/docs/README.md. If you are using .NET 5 use https://github.com/ahives/HareDu3/blob/master/docs/broker-api.md. With both libraries there is a hash function whereby you can do the following:

var result = await services.GetService<IBrokerObjectFactory>()
.CreateUser("testuser3", "testuserpwd3", "gkgfjjhfjh".ComputePasswordHash(),
   x =>
   {
       x.WithTags(t =>
       {
           t.Administrator();
       });
   });
Susannsusanna answered 7/4, 2021 at 21:39 Comment(0)
U
0

This is an older issue now, but here is a way to do this in Ruby, in case that is helpful to anyone else. I originally got here looking for an easy way to do this with Chef, but am not all that familiar with Ruby, so it is very possible (likely, even) that this could be done better/more efficiently. Commented out are the example values used in the RabbitMQ documentation for this to verify it is all working correctly.

require 'securerandom' 
require 'digest'
require 'base64'

def generate_password_hash(plain_text)
  salt = SecureRandom.random_bytes(4).bytes.to_a
  pass = plain_text.bytes.to_a

  #Known sample values. Should return kI3GCqW5JLMJa4iX1lo7X4D6XbYqlLgxIs30+P6tENUV2POR
  # salt = ["908DC60A"].pack("H*").unpack("C*")
  # pass = "test12".bytes.to_a

  arr = salt + pass
  sha256 = Digest::SHA256.base64digest(arr.pack('C*').force_encoding('utf-8'))

  sha256_bytes = Base64.strict_decode64(sha256).bytes.to_a

  arr = salt + sha256_bytes
  password_hash = Base64.encode64(arr.pack('c*')).strip!

  return password_hash
end
Unprejudiced answered 21/2, 2022 at 18:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.