클라이언트- 서버 모델에서 유저 이름 (username)과 비밀번호 (password)를 기반으로 인증된 키를 교환하는 프로토콜. (PAKE)
간단히 요약하자면 Client 와 Server가 username, password를 알고 있을 때, 공개 키만을 교환하여 동일한 비공개 키를 각 진영에서 만들 수 있다.
절차
공통 알고 있는 사항 username (I), password (p)
H (A,...,Z) = 해시 알고리즘 (ex SHA-512) =
A, ... , Z 까지 숫자에 대한 해시
N = 충분히 큰 소수
g = 2 ; g ^ x mod N group을 만들어내는 generator
k = H (M, g) ; SRP 파라미터
1. 유저 DB 생성 (서버)
유저 s = salt, 특정 길이의 랜덤 문자열을 생성
비밀 키 x = H (s, I, p);
패스워드 검증기 v = pow (g, x, N); 비밀 번호를 직접 들고 있지 않는다.
2. 공개 키 교환
Client, Server에 각각 a, b는 public key를 만들기 위한 salt 생성
Client -> Server : username, client_public_key (A)
Server -> Client : server_public_key (B), user_salt (s)
3. 세션 키 생성
Client 는 Server 로 부터 받은 salt를 이용하여 비밀 키를 생성
x = H(s, I, p)
서버 공개 키 B 를 조합하여 세션 키를 생성
Client의 세션 키 = F_c ( a, B, x, N, k)
Server는 클라이언트 공개키 A를 이용하여 세션키를 생성
Server의 세션 키 = F_s (A, b, v, N)
이 때, Client 세션 키와 Server 세션 키는 같아야한다.
추가적으로 Client 쪽에서 client_proof_key를 Server에 제공하여 Server가 Client를 검증할 수 있고
Server가 server_proof_key를 Client에 제공하여 Client가 Server를 검증할 수 있다.
SPR 예제
SRP.h
#pragma once
#include <string>
#include <memory>
namespace Crypto {
class SRP final {
public:
class Client;
class Server;
SRP(const std::string_view& username, const std::string_view& password);
SRP(const std::string_view& username, const std::string_view& password, const std::string_view& user_salt);
~SRP();
static void Initialize(); // Initialize SRP parameters (N, g)
Client GetClient();
Server GetServer();
private:
class Impl;
std::unique_ptr<Impl> impl_;
public:
class Client {
public:
Client(Impl* impl);
~Client();
std::string GetPublicKey();
bool CalculateSessionKey(const std::string_view& server_public_key);
const std::string& GetSessionKey() const { return session_key_; }
std::string CalculateClientProof(const std::string_view& server_public_key);
bool VerifyServerProof(const std::string_view& server_public_key, const std::string_view& server_proof);
private:
Impl* impl_;
std::string a_hex_;
std::string A_hex_;
std::string session_key_;
};
class Server {
public:
Server(Impl* impl);
~Server();
std::string GetPublicKey() const;
std::string GetUserSalt() const;
bool CalculateSessionKey(const std::string_view& client_public_key);
const std::string& GetSessionKey() const { return session_key_; }
std::string CalculateServerProof(const std::string_view& client_public_key, const std::string_view& client_proof);
bool VerifyClientProof(const std::string_view& client_public_key, const std::string_view& client_proof);
private:
Impl* impl_;
std::string b_hex_;
std::string B_hex_;
std::string session_key_;
};
};
}
SRP.cpp
#include "stdafx.h"
#include "SRP.h"
#include "Core/Crypto/Hex.h"
#include "Core/Crypto/Salt.h"
#include "Core/Crypto/Hash/SHA512.h"
#include "Core/Crypto/Internal/Prime.h"
#include <cryptopp/integer.h>
#include <cryptopp/nbtheory.h>
#include <cryptopp/modarith.h>
#include <cryptopp/osrng.h>
namespace Crypto {
class SRPParam : public System::Singleton<SRPParam> {
public:
SRPParam(Protection) {
GeneratePrime(2048, 1024, &N);
}
CryptoPP::Integer N;
CryptoPP::Integer g = 2;
};
class SRP::Impl {
public:
Impl(const std::string_view& username, const std::string_view& password)
:
username_(username),
salt_hex_(ToHex(Salt(16))) {
const CryptoPP::Integer* SRP_N = &SRPParam::GetInstance().N;
const CryptoPP::Integer* SRP_g = &SRPParam::GetInstance().g;
// k = H(N | g)
std::string k_source = FORMAT("{}:{}", ToHex(*SRP_N), ToHex(*SRP_g));
std::string k_hash = SHA512::Compute(k_source);
std::string k_hex = ToHex(k_hash);
k_ = CryptoPP::Integer(k_hex.c_str());
std::string private_key_source = FORMAT("{}:{}:{}", salt_hex_, username, password);
std::string private_key_hash = SHA512::Compute(private_key_source);
std::string private_key_hex = ToHex(private_key_hash);
x_ = ToInteger(private_key_hex);
verfier_ = CryptoPP::ModularExponentiation(CryptoPP::Integer(2), x_, *SRP_N);
}
Impl(const std::string_view& username, const std::string_view& password, const std::string_view& user_salt)
:
username_(username),
salt_hex_(user_salt) {
const CryptoPP::Integer* SRP_N = &SRPParam::GetInstance().N;
const CryptoPP::Integer* SRP_g = &SRPParam::GetInstance().g;
// k = H(N | g)
std::string k_source = FORMAT("{}:{}", ToHex(*SRP_N), ToHex(*SRP_g));
std::string k_hash = SHA512::Compute(k_source);
std::string k_hex = ToHex(k_hash);
k_ = CryptoPP::Integer(k_hex.c_str());
std::string private_key_source = FORMAT("{}:{}:{}", salt_hex_, username, password);
std::string private_key_hash = SHA512::Compute(private_key_source);
std::string private_key_hex = ToHex(private_key_hash);
x_ = ToInteger(private_key_hex);
verfier_ = CryptoPP::ModularExponentiation(CryptoPP::Integer(2), x_, *SRP_N);
}
~Impl() {
}
private:
friend class SRP::Server;
friend class SRP::Client;
std::string username_;
CryptoPP::Integer k_; // k = H(N | g)
CryptoPP::Integer x_; // x = H(s | H(I | ":" | P))
CryptoPP::Integer verfier_; // v = g^x % N
std::string salt_hex_;
};
SRP::SRP(const std::string_view& username, const std::string_view& password)
:
impl_(std::make_unique<Impl>(username, password)) {
}
SRP::SRP(const std::string_view& username, const std::string_view& password, const std::string_view& user_salt)
:
impl_(std::make_unique<Impl>(username, password, user_salt))
{
}
SRP::~SRP() = default;
void SRP::Initialize() {
SRPParam::GetInstance();
}
SRP::Client SRP::GetClient() {
return SRP::Client(impl_.get());
}
SRP::Server SRP::GetServer() {
return Server(impl_.get());
}
SRP::Client::Client(Impl* impl)
:
impl_(impl) {
const CryptoPP::Integer* SRP_N = &SRPParam::GetInstance().N;
const CryptoPP::Integer* SRP_g = &SRPParam::GetInstance().g;
a_hex_ = ToHex(Salt(16));
CryptoPP::Integer a = ToInteger(a_hex_.c_str());
A_hex_ = ToHex(CryptoPP::ModularExponentiation(*SRP_g, a, *SRP_N));
}
SRP::Client::~Client() = default;
std::string SRP::Client::GetPublicKey() {
return A_hex_;
}
bool SRP::Client::CalculateSessionKey(const std::string_view& server_public_key/*B_hex*/) {
const CryptoPP::Integer* SRP_N = &SRPParam::GetInstance().N;
CryptoPP::Integer A = ToInteger(A_hex_);
CryptoPP::Integer B = ToInteger(server_public_key);
if (B % (*SRP_N) == 0) {
return false;
}
std::string u_hex = SHA512::Compute(FORMAT("{}:{}", A_hex_, server_public_key));
CryptoPP::Integer u = ToInteger(u_hex);
CryptoPP::Integer a = ToInteger(a_hex_);
CryptoPP::Integer exp = (a + u * impl_->x_);
CryptoPP::Integer base = (B - impl_->k_ * impl_->verfier_);
CryptoPP::Integer S = CryptoPP::ModularExponentiation(base, exp, *SRP_N);
session_key_ = ToHex(SHA512::Compute(ToHex(S)));
return true;
}
SRP::Server::Server(Impl* impl)
:
impl_(impl) {
const CryptoPP::Integer* SRP_N = &SRPParam::GetInstance().N;
const CryptoPP::Integer* SRP_g = &SRPParam::GetInstance().g;
b_hex_ = ToHex(Salt(16));
CryptoPP::Integer b = ToInteger(b_hex_);
CryptoPP::Integer gb = CryptoPP::ModularExponentiation(*SRP_g, b, *SRP_N);
CryptoPP::Integer k_mul_v = (impl_->k_ * impl_->verfier_);
CryptoPP::Integer B = (k_mul_v + gb) % (*SRP_N);
B_hex_ = ToHex(B);
}
SRP::Server::~Server() = default;
std::string SRP::Server::GetPublicKey() const {
return B_hex_;
}
std::string SRP::Server::GetUserSalt() const {
return impl_->salt_hex_;
}
bool SRP::Server::CalculateSessionKey(const std::string_view& client_public_key/*A_hex*/) {
const CryptoPP::Integer* SRP_N = &SRPParam::GetInstance().N;
std::string u_hex = SHA512::Compute(FORMAT("{}:{}", client_public_key, B_hex_));
CryptoPP::Integer A = ToInteger(client_public_key);
CryptoPP::Integer B = ToInteger(B_hex_);
CryptoPP::Integer u = ToInteger(u_hex);
if (A % (*SRP_N) == 0) {
return false;
}
CryptoPP::Integer exp = ToInteger(b_hex_);
CryptoPP::Integer base = (A * CryptoPP::ModularExponentiation(impl_->verfier_, u, *SRP_N));
CryptoPP::Integer S = CryptoPP::ModularExponentiation(base, exp, *SRP_N);
session_key_ = ToHex(SHA512::Compute(ToHex(S)));
return true;
}
std::string SRP::Client::CalculateClientProof(const std::string_view& server_public_key/*B_hex*/) {
// M1 = H(A | B | S_client)
return SHA512::Compute(FORMAT("{}:{}:{}", A_hex_, server_public_key, session_key_));
}
bool SRP::Client::VerifyServerProof(const std::string_view& server_public_key/*B_hex*/, const std::string_view& server_proof) {
// M2 = H(A | M1 | S_server)
std::string M1 = CalculateClientProof(server_public_key);
std::string expected_server_proof = SHA512::Compute(FORMAT("{}:{}:{}", A_hex_, M1, session_key_));
return expected_server_proof == server_proof;
}
std::string SRP::Server::CalculateServerProof(const std::string_view& client_public_key/*A_hex*/, const std::string_view& client_proof) {
// M2 = H(A | M1 | S_server)
return SHA512::Compute(FORMAT("{}:{}:{}", client_public_key, client_proof, session_key_));
}
bool SRP::Server::VerifyClientProof(const std::string_view& client_public_key/*A_hex*/, const std::string_view& client_proof) {
// M1 = H(A | B | S_client)
std::string expected_client_proof = SHA512::Compute(FORMAT("{}:{}:{}", client_public_key, B_hex_, session_key_));
return expected_client_proof == client_proof;
}
}
유닛 테스트로 알아보는 절차
TEST_METHOD(SRPTest)
{
Crypto::SRP srp_server("username", "password");
auto server = srp_server.GetServer();
Crypto::SRP srp_client("username", "password", server.GetUserSalt());
auto client = srp_client.GetClient();
std::string client_public_key = client.GetPublicKey();
std::string server_public_key = server.GetPublicKey();
bool client_session_ok = client.CalculateSessionKey(server_public_key);
bool server_session_ok = server.CalculateSessionKey(client_public_key);
Assert::IsTrue(client_session_ok);
Assert::IsTrue(server_session_ok);
std::string client_session_key = client.GetSessionKey();
std::string server_session_key = server.GetSessionKey();
Assert::AreEqual(client_session_key, server_session_key);
std::string client_proof = client.CalculateClientProof(server_public_key);
bool server_proof_ok = server.VerifyClientProof(client_public_key, client_proof);
std::string server_proof = server.CalculateServerProof(client_public_key, client_proof);
bool client_proof_ok = client.VerifyServerProof(server_public_key, server_proof);
Assert::IsTrue(client_proof_ok);
Assert::IsTrue(server_proof_ok);
}
'C++ > 암호학' 카테고리의 다른 글
| [CryptoPP] SHA hash (0) | 2025.08.30 |
|---|