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;
  }
}