2
\$\begingroup\$

Looking around for modern Crypto libraries.
Could not find anything good.

I know I probably did this all wrong so work with me here. There will be four different reviews for four structures that build on each other:

  1. Hashing
  2. Hashed Key
  3. Password Key
  4. Salted Challenge Response

This is an implementation of the scram protocol that makes sure we don't send the password across the wire. Underneath it uses pbkdf2 to make sure that multiple attempts are appropriately punished. This is a multi-phase verification:

  1. Client Sends message to service with username.
  2. Server Sends a hashed message with a salt for the client.
  3. Client computes a response based on the salt and the password it has sends it to server.
  4. Server verifies Client and then generates a response to prove that it also knows the password.

Note: There may be an optional part of communication between client and server to negotiate the protocol that is bing used. I have set the 'ClientScram' to use Pbkdf2<HMac<Sha1>> if I get some experience on other protocols I may adjust these.

The data structures and implementation presented in these questions are based on RFC2104 and this post on codeproject.

Usage Example

MyPersonalNonceGenerator    noncer;
ClientScram                 scram("Loki", noncer);

std::string clientFirstMessage = scram.getFirstMessage();
std::string serverFirstMessage = /* send client message to server and get response */;
std::string proof              = scram.getProofMessage("password", serverFirstMessage);
std::string serverProof        = /* send client prrof to server and get response *./

if (scram.verifyServer(serverProof))
{
    // You have validated that you and the server know the same password for the user
}

scram.h

#ifndef THORSANVIL_CRYPTO_SCRAM_H
#define THORSANVIL_CRYPTO_SCRAM_H

#include "hash.h"
#include "hmac.h"
#include "pbkdf2.h"
#include "base64.h"

// RFC-5801 Salted Challenge Response Authentication Mechanism (SCRAM) SASL and GSS-API Mechanisms

namespace ThorsAnvil::Crypto
{

enum class DBInfoType
{
    Password,
    Salt
};

using NonceGenerator = std::function<std::string()>;
using DBInfoAccess   = std::function<std::string(DBInfoType, std::string const&)>;

// See below in ScramClient and SCramServer for examples of Hi/HMAC/H
// Hi   = Pbkdf2<HMac<Sha1>>
// HMAC = HMac<Sha1>
// H    = Sha1
template<typename Hi, typename HMAC, typename H>
class ScramBase
{
    std::string getMessageBody(std::string const& section, std::string const& message) const
    {
        auto findSection = message.find(section);
        if (findSection == std::string::npos || findSection + 2 >= message.size())
        {
            using std::literals::string_literals::operator""s;
            return ""s;
        }
        findSection += 2;
        auto sectionEnd = message.find(',', findSection);
        if (sectionEnd == std::string::npos)
        {
            sectionEnd = message.size();
        }
        return message.substr(findSection, (sectionEnd - findSection));
    }

    std::string     clientFirstMessageBare;
    std::string     serviceFirstMessage;
    std::string     serverSignature64;
    std::string     clientProof64;
    NonceGenerator  nonceGenerator;

    public:
        ScramBase(std::string const& clientFirstMessageBare, NonceGenerator&& nonceGenerator)
            : clientFirstMessageBare(clientFirstMessageBare)
            , nonceGenerator(std::move(nonceGenerator))
        {}

    protected:
        std::size_t getServiceIteration()                                       const     {using std::literals::string_literals::operator""s;return std::stol(getMessageBody("i=", serviceFirstMessage));}
        std::string getUserFromMessage()                                        const     {using std::literals::string_literals::operator""s;return getMessageBody("n="s, clientFirstMessageBare);}
        std::string getServiceSalt()                                            const     {using std::literals::string_literals::operator""s;return getMessageBody("s="s, serviceFirstMessage);}
        std::string getClientNonce()                                            const     {using std::literals::string_literals::operator""s;return getMessageBody("r="s, clientFirstMessageBare);}
        std::string getServiceNonce()                                           const     {using std::literals::string_literals::operator""s;return getMessageBody("r="s, serviceFirstMessage);}
        std::string getVerification(std::string const& message)                 const     {using std::literals::string_literals::operator""s;return getMessageBody("v="s, message);}
        std::string getProofFromClinet(std::string const& message)              const     {using std::literals::string_literals::operator""s;return getMessageBody("p="s, message);}
        std::string normalize(std::string const& text)                          const     {return text;}

        std::string getClientFinalMessageWithoutProof()                         const     {using std::literals::string_literals::operator""s;return "c=biws,r="s + getServiceNonce();}
        std::string getAuthMessage()                                            const     {using std::literals::string_literals::operator""s;return clientFirstMessageBare + ","s + serviceFirstMessage + ","s + getClientFinalMessageWithoutProof();}

        std::string const& getClientFirstMessageBare()                          const     {return clientFirstMessageBare;}
        std::string const& getClientProof()                                     const     {return clientProof64;}
        std::string const& getServerSignature()                                 const     {return serverSignature64;}

        bool validateNonce(std::string const& message)                          const     {using std::literals::string_literals::operator""s;return getServiceNonce() == getMessageBody("r="s, message);}

        void setServiceFirstMessage(std::string const& sfm)                               {serviceFirstMessage = sfm;}
        std::string generateNonce()                                                       {return nonceGenerator();}

        void calculateClientScramHash(std::string const& password)
        {
            Hi              hi;
            HMAC            hmac;
            H               hasher;
            Digest<Hi>      saltedPassword;
            Digest<HMAC>    clientKey;
            Digest<HMAC>    serverKey;
            Digest<HMAC>    clientSignature;
            Digest<HMAC>    serverSignature;
            Digest<H>       storedKey;

            using std::literals::string_literals::operator""s;
            std::string    saltBase64      = getServiceSalt();
            std::string    salt              (make_decode64(std::begin(saltBase64)), make_decode64(std::end(saltBase64)));
            std::string    authMessage     = getAuthMessage();

            hi.hash(normalize(password), salt, getServiceIteration(), saltedPassword);
            hmac.hash(saltedPassword.view(), "Client Key"s, clientKey);
            hmac.hash(saltedPassword.view(), "Server Key"s, serverKey);
            hasher.hash(clientKey, storedKey);
            hmac.hash(storedKey.view(), authMessage, clientSignature);
            hmac.hash(serverKey.view(), authMessage, serverSignature);

            for (int loop = 0 ; loop < HMAC::digestSize; ++loop)
            {
                clientKey[loop] = clientKey[loop] ^ clientSignature[loop];
            }
            clientProof64     = std::string(make_encode64(std::begin(clientKey)), make_encode64(std::end(clientKey)));
            serverSignature64 = std::string(make_encode64(std::begin(serverSignature)), make_encode64(std::end(serverSignature)));
        }
};

class ScramClient: public ScramBase<Pbkdf2<HMac<Sha1>>, HMac<Sha1>, Sha1>
{
    public:
        ScramClient(std::string const& userName, NonceGenerator&& nonceGenerator = [](){using std::literals::string_literals::operator""s;return "fyko+d2lbbFgONRv9qkxdawL"s;})
            : ScramBase(std::string("n=") + userName + ",r=" + nonceGenerator(), std::move(nonceGenerator))
        {}
        std::string getFirstMessage()
        {
            using std::literals::string_literals::operator""s;
            return "n,,"s + getClientFirstMessageBare();;
        }
        std::string getProofMessage(std::string const& password, std::string const& sfm)
        {
            using std::literals::string_literals::operator""s;
            setServiceFirstMessage(sfm);
            calculateClientScramHash(password);
            return getClientFinalMessageWithoutProof() + ",p="s + getClientProof();
        }
        bool verifyServer(std::string const& serverProof)
        {
            return getServerSignature() == getVerification(serverProof);
        }
};

class ScramServer: public ScramBase<Pbkdf2<HMac<Sha1>>, HMac<Sha1>, Sha1>
{
    long            iterationCount;
    DBInfoAccess    dbInfo;

    public:
        ScramServer(std::string const& clientFirstMessage,
                    std::size_t iterationCount = 4096,
                    NonceGenerator&& nonceGenerator = [](){using std::literals::string_literals::operator""s;return "3rfcNHYJY1ZVvWVs7j"s;},
                    DBInfoAccess&&   dbInfo         = [](DBInfoType type, std::string const& /*user*/){using std::literals::string_literals::operator""s;return type == DBInfoType::Password ? "pencil"s : "QSXCR+Q6sek8bf92"s;})
            : ScramBase(clientFirstMessage.substr(3), std::move(nonceGenerator))
            , iterationCount(iterationCount)
            , dbInfo(std::move(dbInfo))
        {}
        std::string getFirstMessage()
        {
            using std::literals::string_literals::operator""s;
            std::string message = "r="s + getClientNonce() + generateNonce() + ",s="s + dbInfo(DBInfoType::Salt, getUserFromMessage()) + ",i="s + std::to_string(iterationCount);
            setServiceFirstMessage(message);
            return message;
        }
        std::string getProofMessage(std::string const& clientProof)
        {
            using std::literals::string_literals::operator""s;
            calculateClientScramHash(normalize(dbInfo(DBInfoType::Password, getUserFromMessage())));
            if (getClientProof() == getProofFromClinet(clientProof)  && validateNonce(clientProof))
            {
                return "v="s + getServerSignature();
            }
            return "";
        }
};


}

#endif
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Why the negative vote? \$\endgroup\$ – Martin York Aug 25 '20 at 22:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.