深受喜爱的 RabbitMQ 管理插件 有一个 HTTP API 通过普通 HTTP 请求来管理 RabbitMQ。
我们需要以编程方式创建用户,并且选择了 HTTP API。文档很少,但 API 非常简单直观。
考虑到安全性,我们不想以纯文本形式传递用户密码,API 提供了一个字段来发送密码哈希值。引用自那里:
[获取|放置|删除] /api/users/名称
个人用户。要放置用户,您需要一个身体外观 像这样的:
{"password":"secret","tags":"administrator"}
或:
{"password_hash":"2lmoth8l4H0DViLaK9Fxi6l9ds8=", "tags":"administrator"}
标签键是强制性的。必须设置
或password
。password_hash
到目前为止,一切顺利,问题是:如何正确生成
password_hash
?
密码哈希算法在RabbitMQ的配置文件中配置,我们配置为默认的SHA256。
我使用 C# 和以下代码来生成哈希:
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();
这行不通。在一些 SHA256 加密在线工具中进行测试,代码正在生成预期的输出。但是,如果我们进入管理页面并将用户密码手动设置为“1”,那么它就像一个魅力。
这个答案让我导出配置并查看 RabbitMQ 生成的哈希值,我意识到了一些事情:
password_hash
,RabbitMQ 会不做任何更改地存储它我也接受其他编程语言的建议,而不仅仅是 C#。
为了好玩,还有 bash 版本!
#!/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"
来自:http://rabbitmq.1065348.n5.nabble.com/Password-Hashing-td276.html
不过,如果你想实现的话,算法还是很简单的 你自己。这是一个有效的例子:
生成随机 32 位盐:
CA D5 08 9B
将其与密码的 UTF-8 表示形式连接起来(在此示例中) 案例“西蒙”):
CA D5 08 9B 73 69 6D 6F 6E
获取 MD5 哈希值:
CB 37 02 72 AC 5D 08 E9 B6 99 4A 17 2B 5F 57 12
再次连接盐:
CA D5 08 9B CB 37 02 72 AC 5D 08 E9 B6 99 4A 17 2B 5F 57 12
并转换为base64编码:
ytUIm8s3AnKsXQjptplKFytfVxI=
您应该能够修改您的代码以遵循此过程
对于懒人(像我一样;)),有用于.Net Core框架的Sha512计算RabbitMq密码的代码。
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;
}
}
这是我前段时间偶然发现的一个小Python脚本(归属在脚本中),对于快速生成哈希值非常有用。它不进行任何错误检查,所以非常简单:
#!/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"))
注意:从 RabbitMQ
3.11.8
开始,您可以使用这些方法根据当前配置的密码哈希算法生成哈希密码:
rabbitmqctl hash_password foobar
curl -u api_user:api_pass rabbitmq-server:15672/api/auth/hash_password/foobar
每个输出:
$ 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"}
以防万一,接下来应该是 Waldo 的完整代码
//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"));
}
}
}
您可以在 http://rextester.com/ 上在线运行它。程序的输出将包含您的哈希值。
christianclinton 的 Python 版本 (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
玩得开心!
这是该脚本在 bash 中的一个版本,可在带有 openSSL 的 BusyBox 上运行
#!/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
这是 PowerShell 中的一个 - 使用 SHA512 而不是 MD5 在 @derick-bailey opener 中提到 - 但您可以通过更改
$hash
和 $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
对于那些寻找 Go 解决方案的人。下面的代码将生成一个 32 字节随机密码,或者采用给定密码的标志并对其进行散列,以便您可以在rabbit中定义文件的“password_hash”字段中使用
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)
}
这里有一种用 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;
}
尝试使用 HareDu API。如果您使用 .NET Core 2 或更高版本,请使用 https://github.com/ahives/HareDu2/blob/master/docs/README.md。如果您使用 .NET 5,请使用 https://github.com/ahives/HareDu3/blob/master/docs/broker-api.md。这两个库都有一个哈希函数,您可以通过它执行以下操作:
var result = await services.GetService<IBrokerObjectFactory>()
.CreateUser("testuser3", "testuserpwd3", "gkgfjjhfjh".ComputePasswordHash(),
x =>
{
x.WithTags(t =>
{
t.Administrator();
});
});
这是一个较旧的问题,但这里有一种在 Ruby 中执行此操作的方法,以防对其他人有帮助。我最初来到这里是为了寻找一种使用 Chef 来完成此操作的简单方法,但我对 Ruby 不太熟悉,因此很有可能(甚至可能)可以做得更好/更有效。注释掉的是 RabbitMQ 文档 中使用的示例值,用于验证其是否全部正常工作。
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