CC-20200227

Crypto Core Intermediate February 27, 2020

Back to All Tasks

Problem Statement

Design and implement a simple digital signature system for verifying messages. Your system should allow a user to sign a message with their private key, and anyone else to verify that signature using the corresponding public key. Use SHA-256 as your hash function and RSA for asymmetric encryption. The implementation should include functions for generating key pairs, signing messages, and verifying signatures.

Requirements:
1. Generate RSA keys (public and private) of at least 2048 bits.
2. Create a signMessage function that takes in the message and private key as inputs and outputs the digital signature.
3. Implement a verifySignature function that takes in the message, public key, and signature as inputs and returns true if the signature is valid or false otherwise.

Concepts

  • hash functions
  • public key cryptography
  • digital signatures

Constraints

  • Use SHA-256 for hashing messages
  • RSA encryption must be used with a minimum of 2048 bits
  • Ensure your implementation can handle different input sizes for messages

Security Notes

  • Avoid using hardcoded keys in production code. Generate and securely store them.
  • Be cautious about how you hash and sign data; improper handling can lead to vulnerabilities like padding oracle attacks or signature forgery.

Solutions

Cpp Solution

#include <iostream>
#include <string>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/sha.h>
#include <openssl/err.h>
#include <sstream>

// Function to initialize OpenSSL
void initOpenSSL() {
	ERR_load_crypto_strings();
	OpenSSL_add_all_algorithms();
}

// Function to clean up OpenSSL
void cleanupOpenSSL() {
	EVP_cleanup();
	ERR_free_strings();
}

// Function to generate RSA key pair of 2048 bits
RSA* generateKeyPair() {
	BIGNUM *e = BN_new();
	RSA *rsa = RSA_new();
	if (!BN_set_word(e, RSA_F4)) { // F4 is the exponent commonly used
		std::cerr << "Failed to set public exponent\n";
		return nullptr;
	}
	if (RSA_generate_key_ex(rsa, 2048, e, NULL) != 1) {
		std::cerr << "Key generation failed\n";
		BN_free(e);
		RSA_free(rsa);
		return nullptr;
	}
	BN_free(e);
	return rsa;
}

// Function to sign a message using the private key
std::string signMessage(const std::string& message, RSA* privateKey) {
	unsigned char hash[SHA256_DIGEST_LENGTH];
	SHA256_CTX sha256;
	SHA256_Init(&sha256);
	SHA256_Update(&sha256, message.c_str(), message.size());
	SHA256_Final(hash, &sha256);

	unsigned char* sig = new unsigned char[RSA_size(privateKey)];
	int siglen;
	if (RSA_sign(NID_sha256, hash, SHA256_DIGEST_LENGTH, sig, (unsigned int*)&siglen, privateKey) != 1) {
		std::cerr << "Error signing message\n";
		delete[] sig;
		return "";
	}

	std::ostringstream oss;
	for (int i = 0; i < siglen; ++i)
		ostringstream << std::hex << static_cast<int>(sig[i]);
	delete[] sig;
	return oss.str();
}

// Function to verify a signature using the public key
bool verifySignature(const std::string& message, RSA* publicKey, const std::string& signature) {
	unsigned char hash[SHA256_DIGEST_LENGTH];
	SHA256_CTX sha256;
	SHA256_Init(&sha256);
	SHA256_Update(&sha256, message.c_str(), message.size());
	SHA256_Final(hash, &sha256);

	unsigned char* sig = new unsigned char[RSA_size(publicKey)];
	for (size_t i = 0; i < signature.length() / 2; ++i)
		sig[i] = static_cast<unsigned char>(std::stoi(signature.substr(i * 2, 2), nullptr, 16));

	int result = RSA_verify(NID_sha256, hash, SHA256_DIGEST_LENGTH, sig, signature.length() / 2, publicKey);
	delete[] sig;
	return result == 1;
}

int main() {
	initOpenSSL();

	RSA* keyPair = generateKeyPair();
	if (!keyPair) {
		std::cerr << "Failed to generate RSA keys\n";
		cleanupOpenSSL();
		return 1;
	}

	std::string message = "Hello, world!";
	std::string signature = signMessage(message, keyPair);
	if (signature.empty()) {
		std::cerr << "Failed to sign message\n";
		RSA_free(keyPair);
		cleanupOpenSSL();
		return 1;
	}

	bool isValid = verifySignature(message, keyPair, signature);
	if (isValid)
		std::cout << "Signature is valid.\n";
	else
		std::cout << "Signature is invalid.\n";

	RSA_free(keyPair);
	cleanupOpenSSL();
	return 0;
}

This C++ program demonstrates a simple digital signature system using RSA for asymmetric encryption and SHA-256 for hashing messages. The program includes functions to generate RSA key pairs, sign messages with a private key, and verify signatures using the corresponding public key.
The initOpenSSL and cleanupOpenSSL functions are used to initialize and clean up OpenSSL libraries respectively. The generateKeyPair function creates a new 2048-bit RSA key pair which is required for signing and verifying operations.
The signMessage function takes a message string and the private RSA key as input, computes its SHA-256 hash, and then signs the hash using the RSA_sign function. The resulting signature is converted to a hexadecimal string format before returning.
The verifySignature function accepts the original message, public RSA key, and signed message (in hexadecimal form) as parameters. It computes the SHA-256 hash of the input message and verifies it against the provided digital signature using the RSA_verify function. If the verification process is successful, it returns true indicating that the signature is valid; otherwise, it returns false.
In the main function, we first initialize OpenSSL libraries, generate an RSA key pair, sign a sample message with the private key, verify the generated signature using the public key, and print whether the signature is valid or not. Finally, we clean up all allocated resources before exiting the program.