Advanced C++

[C++] CryptoPP 암호화/복호화 라이브러리

로파이 2022. 8. 11. 00:45

CryptoPP 라이브러리를 사용해본다.

https://www.cryptopp.com/

 

Crypto++ Library 8.7 | Free C++ Class Library of Cryptographic Schemes

hash functions BLAKE2b, BLAKE2s, Keccack (F1600), SHA-1, SHA-2, SHA-3, SHAKE (128/256), SipHash, LSH (128/256), Tiger, RIPEMD (128/160/256/320), SM3, WHIRLPOOL

www.cryptopp.com


SHA(Secure Hash Algorithm) 해시 함수

미 국가보안국(NSA)에서 개발한 암호화 해시 생성 알고리즘으로 Merkle–Damgård construction 에 기반한 단방향 압축 함수로 만들어졌다.

 

SHA 해시 함수는 압축 길이 digest에 따라 종류가 나뉘며 SHA-2 family 그룹은 SHA-224, 256, 384, 512 bits 종류가 있다.

SHA 뒤에 붙은 숫자는 최종 암호화 되어 압축된 결과의 길이를 의미하고 길수록 보안성이 높다. 

 

보안성이 높은 해시 함수는 digest가 길이 L인 해시를 맞추기 위해 굉장히 높은 난이도를 가져야한다. 

1) 무작위 시도 (brute-force)로 2의 L제곱 만큼 시도하여 해시를 맞출 수 있다. 이는 L 길이가 클 수록 난이도가 매우 높아진다.

2) 서로 다른 내용을 암호화 하여 만들어낸 두 해시가 충돌하면 안된다. 충돌 가능성이 있는 해시는 같지 않은 비밀번호로 같은 해시를 만들어내어 보안에 문제를 일으킬 수 있다.

 

CryptoPP 라이브러리

DLL 버전과 Static 라이브러리 버전이 있다.

 

DLL 버전은 빌드 시 FIPS라는 국제 정보 처리 기준에 맞도록 DLL 파일 자체를 다른 곳에서 이동,복사하여 쓰면 오류가 나도록 되어 있다.

DLL 버전 빌드시 전처리기 중 CRYPTOPP_ENABLE_COMPLIANCE_WITH_FIPS_140_2=0으로 설정하고 빌드하면 경고만 나오고 오류가 나오지 않으나 필요한 함수에 따라 DLL 버전에서는 없을 수도 있다.


Static 라이브러리 빌드 시 모든 기능을 사용할 수 있으며 보안성이 낮은 MD5 계열 해시도 사용가능하다. 

 

다른 헤더를 포함하기 전에 "dll.h"를 포함하도록 한다.

 

SHA-512를 이용한 해시값 만들기

static string* get_hash(const string& password)
{
	CryptoPP::SHA512 sha;
	CryptoPP::byte digest[CryptoPP::SHA512::DIGESTSIZE];

	sha.CalculateDigest(digest, (CryptoPP::byte*)password.c_str(), password.size());

	std::string* result = new string();

	CryptoPP::HexEncoder encoder;
	encoder.Attach(new CryptoPP::StringSink(*result));
	encoder.Put(digest, sizeof(digest));
	encoder.MessageEnd();

	return result;
}

 CryptoPP에서 기본 메모리 관리는 동적 할당한 어떤 인스턴스를 매개변수로 생성자로 만든 경우, 동적 할당한 인스턴스를 소유한 인스턴스가 메모리 삭제 책임을 갖는다.

  • string* result -> StringSink
  • StringSink -> HexEncoder

파일 해시 만들어내기

template<class Hash>
static std::string* compute_hash(const std::string& filepath)
{
	std::string* digest = new std::string();
	Hash hash;

	CryptoPP::FileSource source(
		filepath.c_str(),
		true,
		new CryptoPP::HashFilter(hash,
			new CryptoPP::HexEncoder(
				new CryptoPP::StringSink(*digest))));

	return digest;
}

static void example_file_hash()
{
	ofstream out("test.txt");

	out << "hello world!" << endl;

	out.close();

	auto sha512 = compute_hash<CryptoPP::SHA512>("test.txt");
	auto sha256 = compute_hash<CryptoPP::SHA256>("test.txt");
	auto sha224 = compute_hash<CryptoPP::SHA224>("test.txt");
	auto sha1 = compute_hash<CryptoPP::SHA1>("test.txt");

	cout << "file SHA512 hash : " << *sha512 << endl;
	cout << "file SHA256 hash : " << *sha256 << endl;
	cout << "file SHA224 hash : " << *sha224 << endl;
	cout << "file SHA1 hash : " << *sha1 << endl;
}

 

그 밖에도 특정 키를 이용하여 암호화/복호화 RSA 파일 서명 및 대조 등등 기능을 제공하며

여러 어플리케이션에서 유용한 AES 블록 암호화 기법도 쉽게 사용가능하다.

 

/*
reference : https://cryptopp.com/wiki/Advanced_Encryption_Standard
*/
static void example_aes_block_cypher()
{
	using namespace CryptoPP;

	AutoSeededRandomPool prng;
	HexEncoder encoder(new FileSink(std::cout));

	SecByteBlock key(AES::DEFAULT_KEYLENGTH);
	SecByteBlock iv(AES::BLOCKSIZE);

	prng.GenerateBlock(key, key.size());
	prng.GenerateBlock(iv, iv.size());

	std::string plain = "CBC Mode Test";
	std::string cipher, recovered;

	std::cout << "plain text: " << plain << std::endl;

	/*********************************\
	\*********************************/

	try
	{
		CBC_Mode< AES >::Encryption e;
		e.SetKeyWithIV(key, key.size(), iv);

		StringSource s(plain, true,
			new StreamTransformationFilter(e,
				new StringSink(cipher)
			) // StreamTransformationFilter
		); // StringSource
	}
	catch (const Exception& e)
	{
		std::cerr << e.what() << std::endl;
		exit(1);
	}

	/*********************************\
	\*********************************/

	std::cout << "key: ";
	encoder.Put(key, key.size());
	encoder.MessageEnd();
	std::cout << std::endl;

	std::cout << "iv: ";
	encoder.Put(iv, iv.size());
	encoder.MessageEnd();
	std::cout << std::endl;

	std::cout << "cipher text: ";
	encoder.Put((const byte*)&cipher[0], cipher.size());
	encoder.MessageEnd();
	std::cout << std::endl;

	/*********************************\
	\*********************************/

	try
	{
		CBC_Mode< AES >::Decryption d;
		d.SetKeyWithIV(key, key.size(), iv);

		StringSource s(cipher, true,
			new StreamTransformationFilter(d,
				new StringSink(recovered)
			) // StreamTransformationFilter
		); // StringSource

		std::cout << "recovered text: " << recovered << std::endl;
	}
	catch (const Exception& e)
	{
		std::cerr << e.what() << std::endl;
	}
}