问题
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
orpassword_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#.
回答1:
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
回答2:
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;
}
}
回答3:
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!
回答4:
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"))
回答5:
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
回答6:
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)
}
回答7:
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 | sha512sum | head -c 128)
PASS=$(echo -n $SALT$PASS | xxd -r -p | base64 | tr -d '\n')
echo $PASS
}
echo encode_password "some password"
回答8:
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;
}
回答9:
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```
来源:https://stackoverflow.com/questions/41306350/how-to-generate-password-hash-for-rabbitmq-management-http-api