C++/암호학

[CryptoPP] SRP 프로토콜 (Secure Remote Password Protocol)

로파이 2025. 8. 30. 15:35

클라이언트- 서버 모델에서 유저 이름 (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);
}



참고: 
Secure Remote Password protocol - Wikipedia

'C++ > 암호학' 카테고리의 다른 글

[CryptoPP] SHA hash  (0) 2025.08.30