This small example shows how to verify the integrity of a message. We follow the digital signature algorithm (DSA) and generate a pair of keys, private and public (the public key is actually not unique). We get the signature by signing the message with the private key (which should never be shared). A recipient can check if the message is unmodified by using the signature and just a public key. However, the public key is not enough to change the message and sign it again.
The presented code uses C++ and the open source library Crypto++. Since the keys need to be embedded in text files, all cryptographic elements are converted to a hex representation. Crypto++ provides the necessary documentation but it is quite some work putting the pieces together; so maybe this example will help someone.
Additional reference: Some more theoretical background with a traditional (not judging!) implementation may be found on CodeProject. The code presented here uses the pipeline API of Crypto++ which gives a cleaner result.
Note: This code is just a technical demonstration and may not be good enough for a security relevant system.
For my purpose I use the Whirlpool hash, you might need a different one.
using Signer = CryptoPP::RSASS<CryptoPP::PSSR, CryptoPP::Whirlpool>::Signer;
using Verifier = CryptoPP::RSASS<CryptoPP::PSSR, CryptoPP::Whirlpool>::Verifier;
Pipeline API
From a technical point of view, the pipeline API might be the most interesting part. It supports the chaining of filters to model a data flow in a clean way. The flow starts at a source and ends in a sink while possibly passing multiple filters. Crypto++ supports this by providing a common interface for the relevant objects and by handling the lifetime of passed objects (Technical details). A typical example in this code is
CryptoPP::StringSource ss(aMessage, true,
new CryptoPP::SignerFilter(rng, signer,
new CryptoPP::HexEncoder(
new CryptoPP::StringSink(signature))));
This line takes the content of aMessage
and passes it to CryptoPP::SignerFilter
(with additional arguments). The result of the filter is passed to CryptoPP::HexEncoder
and finally the encoded result is passed to the variable signature
by CryptoPP::StringSink
. The equivalent code with the traditional API would be:
size_t length = signer.MaxSignatureLength();
CryptoPP::SecByteBlock signatureRaw(length);
// sign message
signer.SignMessage(rng, (const byte*)aMessage.data(),
aMessage.size(), signatureRaw);
// hex encode signature
CryptoPP::HexEncoder encoder;
std::string signature;
encoder.Attach(new CryptoPP::StringSink(signature));
encoder.Put(signatureRaw.data(), signatureRaw.size());
encoder.MessageEnd();
Complete example
crypto.h
#ifndef CRYPTO_H
#define CRYPTO_H
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wextra"
#include <hex.h>
#include <osrng.h>
#include <pssr.h>
#include <rsa.h>
#include <whrlpool.h>
#pragma GCC diagnostic pop
#include <string>
// see http://www.cryptopp.com/wiki/RSA
struct KeyPairHex {
std::string publicKey;
std::string privateKey;
};
using Signer = CryptoPP::RSASS<CryptoPP::PSSR, CryptoPP::Whirlpool>::Signer;
using Verifier = CryptoPP::RSASS<CryptoPP::PSSR, CryptoPP::Whirlpool>::Verifier;
//==============================================================================
inline KeyPairHex RsaGenerateHexKeyPair(unsigned int aKeySize) {
KeyPairHex keyPair;
// PGP Random Pool-like generator
CryptoPP::AutoSeededRandomPool rng;
// generate keys
CryptoPP::RSA::PrivateKey privateKey;
privateKey.GenerateRandomWithKeySize(rng, aKeySize);
CryptoPP::RSA::PublicKey publicKey(privateKey);
// save keys
publicKey.Save( CryptoPP::HexEncoder(
new CryptoPP::StringSink(keyPair.publicKey)).Ref());
privateKey.Save(CryptoPP::HexEncoder(
new CryptoPP::StringSink(keyPair.privateKey)).Ref());
return keyPair;
}
//==============================================================================
inline std::string RsaSignString(const std::string &aPrivateKeyStrHex,
const std::string &aMessage) {
// decode and load private key (using pipeline)
CryptoPP::RSA::PrivateKey privateKey;
privateKey.Load(CryptoPP::StringSource(aPrivateKeyStrHex, true,
new CryptoPP::HexDecoder()).Ref());
// sign message
std::string signature;
Signer signer(privateKey);
CryptoPP::AutoSeededRandomPool rng;
CryptoPP::StringSource ss(aMessage, true,
new CryptoPP::SignerFilter(rng, signer,
new CryptoPP::HexEncoder(
new CryptoPP::StringSink(signature))));
return signature;
}
//==============================================================================
inline bool RsaVerifyString(const std::string &aPublicKeyStrHex,
const std::string &aMessage,
const std::string &aSignatureStrHex) {
// decode and load public key (using pipeline)
CryptoPP::RSA::PublicKey publicKey;
publicKey.Load(CryptoPP::StringSource(aPublicKeyStrHex, true,
new CryptoPP::HexDecoder()).Ref());
// decode signature
std::string decodedSignature;
CryptoPP::StringSource ss(aSignatureStrHex, true,
new CryptoPP::HexDecoder(
new CryptoPP::StringSink(decodedSignature)));
// verify message
bool result = false;
Verifier verifier(publicKey);
CryptoPP::StringSource ss2(decodedSignature + aMessage, true,
new CryptoPP::SignatureVerificationFilter(verifier,
new CryptoPP::ArraySink((byte*)&result,
sizeof(result))));
return result;
}
#endif // CRYPTO_H
main.cpp
#include "crypto.h"
#include <iostream>
#include <string>
int main(int, char **) {
auto keys = RsaGenerateHexKeyPair(3072);
std::cout << "Private key: " << std::endl << keys.privateKey << "\n" << std::endl;
std::cout << "Public key: " << std::endl << keys.publicKey << "\n" << std::endl;
std::string message("secret message");
std::cout << "Message:" << std::endl;
std::cout << message << "\n" << std::endl;
// generate a signature for the message
auto signature(RsaSignString(keys.privateKey, message));
std::cout << "Signature:" << std::endl;
std::cout << signature << "\n" << std::endl;
// verify signature against public key
if (RsaVerifyString(keys.publicKey, message, signature)) {
std::cout << "Signatue valid." << std::endl;
} else {
std::cout << "Signatue invalid." << std::endl;
}
}