Reputation: 312400
Guacamole provides a default username and password (guacadmin
and guacadmin
) initialized in a postgres database like this:
INSERT INTO guacamole_user (entity_id, password_hash, password_salt, password_date)
SELECT
entity_id,
decode('CA458A7D494E3BE824F5E1E175A1556C0F8EEF2C2D7DF3633BEC4A29C4411960', 'hex'), -- 'guacadmin'
decode('FE24ADC5E11E2B25288D1704ABE67A79E342ECC26064CE69C5B3177795A82264', 'hex'),
CURRENT_TIMESTAMP
FROM guacamole_entity WHERE name = 'guacadmin' AND guacamole_entity.type = 'USER';
I'm trying to to understand how that password hash was generated. From the documentation:
Every user has a corresponding entry in the guacamole_user and guacamole_entity tables. Each user has a corresponding unique username, specified via guacamole_entity, and salted password. The salted password is split into two columns: one containing the salt, and the other containing the password hashed with SHA-256.
[...]
password_hash
The result of hashing the user’s password concatenated with the contents of password_salt using SHA-256. The salt is appended to the password prior to hashing.
password_salt
A 32-byte random value. When a new user is created from the web interface, this value is randomly generated using a cryptographically-secure random number generator.
And I think the corresponding Java code is here:
StringBuilder builder = new StringBuilder();
builder.append(password);
if (salt != null)
builder.append(BaseEncoding.base16().encode(salt));
// Hash UTF-8 bytes of possibly-salted password
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(builder.toString().getBytes("UTF-8"));
return md.digest();
I'm trying to reproduce this in Python. It looks like they're taking the password, appending the hex-encoded salt, and then calculating the sha256 checksum of the resulting byte string. That should be this:
>>> from hashlib import sha256
>>> password_salt = bytes.fromhex('FE24ADC5E11E2B25288D1704ABE67A79E342ECC26064CE69C5B3177795A82264')
>>> password_hash = sha256('guacadmin'.encode() + password_salt.hex().encode())
>>> password_hash.hexdigest()
'523912c05f1557e2da15350fae7217c04ee326edacfaa116248c1ee4e680bd57'
...but I'm not getting the same result. Am I misreading (or misunderstanding) the Java code?
Upvotes: 2
Views: 1633
Reputation: 1
I wrote this method and is valid for the latest apache guacamole.
def generate_guacmole_password_hash(password, salt):
"""
password: str
salt: bytes
return: hash content bytes
"""
salt_encode = base64.b16encode(salt)
passwd = password + salt_encode.decode()
sts = sha256(passwd.encode())
return sts.digest()
Upvotes: 0
Reputation: 312400
...and of course I figured it out right after posting the question. The difference is that BaseEncoding.base16().encode(...)
produces a hex encoding using upper-case characters, while Python's hex()
method uses lower case. That means the equivalent code is in fact:
>>> from hashlib import sha256
>>> password_salt = bytes.fromhex('FE24ADC5E11E2B25288D1704ABE67A79E342ECC26064CE69C5B3177795A82264')
>>> password_hash = sha256("guacadmin".encode() + password_salt.hex().upper().encode())
>>> password_hash.hexdigest()
'ca458a7d494e3be824f5e1e175a1556c0f8eef2c2d7df3633bec4a29c4411960'
In case anyone stumbles across the same issue, I was able to extract the Java code into a simple test case:
import com.google.common.io.BaseEncoding;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
class Main {
public static void main(String args[]) {
String password = "guacadmin";
byte[] salt = HexFormat.of().parseHex("FE24ADC5E11E2B25288D1704ABE67A79E342ECC26064CE69C5B3177795A82264");
try {
StringBuilder builder = new StringBuilder();
builder.append(password);
if (salt != null)
builder.append(BaseEncoding.base16().encode(salt));
System.out.println("builder is: " + builder.toString());
// Hash UTF-8 bytes of possibly-salted password
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(builder.toString().getBytes("UTF-8"));
System.out.println(BaseEncoding.base16().encode(md.digest()));
}
catch (UnsupportedEncodingException e) {
System.out.println("no such encoding");
}
catch (NoSuchAlgorithmException e) {
System.out.println("no such algorithm");
}
}
}
This gave me something to run interactively and check the output. This requires the guava library, and can be compiled like this:
$ javac -classpath .:guava-31.1-jre.jar -d . Main.java
And run like this:
$ java -classpath .:guava-31.1-jre.jar Main
Upvotes: 3