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:
- Hashing
- Hashed Key
- Password Key
- 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:
- Client Sends message to service with username.
- Server Sends a hashed message with a salt for the client.
- Client computes a response based on the salt and the password it has sends it to server.
- 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