1

I am currently playing around a little bit with MongooseIM and want to use HTTP auth together with scram. I am using python passlib to create scram hashes:

import sys
from passlib.hash import scram


def main():
    hash = scram.encrypt(sys.argv[1], rounds=4096, salt_size=16)
    print hash


if __name__ == "__main__":
    main()

Then I end up with something like this:

$scram$4096$BmAsRcgZA4AwRijl3FtLyQ$sha-1=AXh5FzYzEnf6PaVQNR79AZhkwz8,sha-256=YZceXCVhfCBrr8sM9k3eS.5bztHugerGzjO97emvn20,sha-512=2NyVspiE7MP6xBAEycAV5Z/nIbBlki3sHfWvVUPPnEkMt5b4VbZfDZ0s8lvE/ns0scPGWmfKhUobmZbjfFH6RA

Unfortunately this format is not accepted by MongooseIM's HTTP auth. I had a look at the code and tried to find out how the serialzed form of scram hashed passwords looks like here: https://github.com/esl/MongooseIM/blob/master/apps/ejabberd/src/scram.erl

deserialize(<<?SCRAM_SERIAL_PREFIX, Serialized/binary>>) ->
    case catch binary:split(Serialized, <<",">>, [global]) of
        [StoredKey, ServerKey,Salt,IterationCount] ->
            {ok, #scram{storedkey = StoredKey,
                        serverkey = ServerKey,
                        salt = Salt,
                        iterationcount = binary_to_integer(IterationCount)}};
        _ ->
            ?WARNING_MSG("Incorrect serialized SCRAM: ~p, ~p", [Serialized]),
            {error, incorrect_scram}
    end;

From passlib I get the salt, the iteration count and the actual digest (sha-1, sha-256, sha-512) of the salted (hashed) password as far as I understood, but what about the StoredKey and the ServerKey from the Erlang code? How would a correct serialized HTTP body returned by host/get_password look like?

Thanks in advance, Magnus

Magnus
  • 390
  • 2
  • 12

2 Answers2

2

That's a very good question which actually made me realise that we don't have it documented. We'll fix it.

As for now the expected format looks like this (you can use MongooseIM's debug shell to generate it).

scram:serialize(scram:password_to_scram(<<"ala_ma_kota">>, 4096)).
<<"==SCRAM==,xB2++RvZklv0rV5I1iuCpoxLqL0=,sKXBkOFrtyGxKqYo/dlzeKfYszU=,oplvMJ5VDxQ7rJZuIj0ZfA==,4096">>

In other words, MongooseIM expects the format to be:

==SCRAM==,StoredKey,ServerKey,Salt,IterationCount

the ==SCRAM== prefix is constant, other parts depends on the password.

Hope that helps.

michalwski
  • 359
  • 1
  • 3
  • Thanks, but what exactly is StoredKey and ServerKey? How can I generate the salted password hash accordingly using my own auth service seperated from the MongooseIM server? – Magnus Feb 26 '16 at 14:12
  • You can check the [password_to_scram]( https://github.com/esl/MongooseIM/blob/master/apps/ejabberd/src/scram.erl#L139-L147). Basically this SCRAM auth method is implementation of SCRAM-SHA-1 mechanism described here [RFC 5802](https://tools.ietf.org/html/rfc5802) – michalwski Feb 26 '16 at 18:49
  • Passlib can generate those for you. – DylanYoung Apr 28 '21 at 22:44
1

so I figured it out and wrote a little python script to generate the expected format.

import base64
import hashlib
import hmac
import sys
from passlib.hash import scram


# password_to_scram(Password, IterationCount) ->
#     Salt = crypto:rand_bytes(?SALT_LENGTH),
#     SaltedPassword = salted_password(Password, Salt, IterationCount),
#     StoredKey = stored_key(scram:client_key(SaltedPassword)),
#     ServerKey = server_key(SaltedPassword),
#     #scram{storedkey = base64:encode(StoredKey),
#            serverkey = base64:encode(ServerKey),
#            salt = base64:encode(Salt),
#            iterationcount = IterationCount}.
def main():
    rounds = 4096
    hash = scram.encrypt(sys.argv[1], rounds=rounds, salt_size=16)
    hash = scram.encrypt('1234', rounds=rounds, salt='salt')
    salt, iterations, salted_password = scram.extract_digest_info(hash, "sha-1")

    # server_key(SaltedPassword) ->
    # crypto:hmac(sha, SaltedPassword, <<"Server Key">>).
    server_key = hmac.new(key=salted_password, msg='Server Key', digestmod=hashlib.sha1).digest()

    # client_key(SaltedPassword) ->
    # crypto:hmac(sha, SaltedPassword, <<"Client Key">>).
    client_key = hmac.new(key=salted_password, msg='Client Key', digestmod=hashlib.sha1).digest()

    # StoredKey = stored_key(scram:client_key(SaltedPassword)),
    stored_key = hashlib.sha1(client_key).digest()

    result = '==SCRAM==,%s,%s,%s,%d' % \
          (base64.b64encode(stored_key), base64.b64encode(server_key), base64.b64encode(salt), rounds)

    print result

if __name__ == '__main__':
    main()

Verification:

(mongooseim@localhost)2> base64:encode(scram:salted_password(<<"1234">>, <<"salt">>, 4096)).    
<<"vbpf4gmRPxs/+ru4TZJO3toJdw0=">>

(mongooseim@localhost)4> base64:encode(scram:stored_key(scram:client_key(scram:salted_password(<<"1234">>, <<"salt">>, 4096)))).
<<"bXKEekOUoWNAx0f21H/fIZ4dj6Y=">>

(mongooseim@localhost)3> base64:encode(scram:server_key(scram:salted_password(<<"1234">>, <<"salt">>, 4096))).
<<"eVwl7wTir232HDy7Tzq3SXZHn+4=">>

==SCRAM==,bXKEekOUoWNAx0f21H/fIZ4dj6Y=,eVwl7wTir232HDy7Tzq3SXZHn+4=,c2FsdA==,4096
Magnus
  • 390
  • 2
  • 12