botan icon indicating copy to clipboard operation
botan copied to clipboard

Creating RSA keypair and Encrypting/Decrypting

Open Marko-Sanchez opened this issue 2 years ago • 2 comments

I have been having trouble locating a definitive guide or code examples to both create a RSA keypair and Encrypt/ decrypt using said keys.

My first method was using the given AEAD mode example given in the wiki but when I tried changing the code up to work with an RSA key. I was unable to find documentation or tutorials on how to do so.

What I'm attempting to do is:

  1. Create a private and public RSA key.
  2. Encrypt with public key.
  3. Decrypt with private key.

One of the few reasonable examples I could find of creating a RSA keypair is below which I then attempt to convert to a string, to then be passed to the encryption and decryption functions:

   Botan::AutoSeeded_RNG rng;    // Random number generator:
   Botan::RSA_PrivateKey keyPair(rng, 1024);

   std::string privatekey = Botan::base64_encode(Botan::PKCS8::BER_encode(keyPair));
   std::string publickey = Botan::base64_encode(Botan::X509::BER_encode(keyPair));

   std::cout << "private key: " << privatekey << std::endl
             << "public key: " << publickey << std::endl;

   return {privatekey, publickey};

The encryption algorithm I was originally using was AES-256/OCB.

I've looked at the documentation, but it seems to always point me elsewhere. Help with this would be appreciated alongside, some where to find examples of botan-2 aswell.

Marko-Sanchez avatar Mar 24 '22 22:03 Marko-Sanchez

What you are trying to implement is a hybrid encryption scheme. Because RSA on its own cannot encrypt arbitrarily large data streams. In short you should: encrypt/authenticate your payload data with a symmetric cipher (preferable an AEAD, e.g. AES-256/OCB, AES-256/GCM or similar) under a random symmetric key. And then encrypt that random symmetric key using your RSA public key.

Maybe the example code below might help your understanding:

#include <iostream>

#include <botan/aead.h>
#include <botan/rsa.h>
#include <botan/auto_rng.h>
#include <botan/secmem.h>
#include <botan/pubkey.h>


struct EncryptedData {
   Botan::secure_vector<uint8_t> ciphertext;
   std::vector<uint8_t>          nonce;
   std::vector<uint8_t>          encryptedKey;
};


std::unique_ptr<Botan::Private_Key>
generate_keypair(const size_t                  bits,
                 Botan::RandomNumberGenerator& rng)
{
   return std::make_unique<Botan::RSA_PrivateKey>(rng, bits);
}


EncryptedData encrypt(const Botan::secure_vector<uint8_t>& data,
                      std::unique_ptr<Botan::Public_Key>   pubkey,
                      Botan::RandomNumberGenerator&        rng)
{
   auto sym_cipher = Botan::AEAD_Mode::create_or_throw("AES-256/GCM", Botan::ENCRYPTION);

   EncryptedData d;

   // prepare random key material for the symmetric encryption/authentication
   rng.random_vec(d.nonce, sym_cipher->default_nonce_length());
   const auto key = rng.random_vec(sym_cipher->minimum_keylength());
   d.ciphertext = data;

   // encrypt/authenticate the data symmetrically
   sym_cipher->set_key(key);
   sym_cipher->start(d.nonce);
   sym_cipher->finish(d.ciphertext);

   // encrypt the symmetric key using RSA
   Botan::PK_Encryptor_EME asym_cipher(*pubkey, rng, "EME-OAEP(SHA-256,MGF1)");
   d.encryptedKey = asym_cipher.encrypt(key, rng);

   return d;
}


Botan::secure_vector<uint8_t>
decrypt(const EncryptedData&          encdata,
        const Botan::Private_Key&     privkey,
        Botan::RandomNumberGenerator& rng)
{
   // prepare random key material for the symmetric encryption/authentication
   Botan::secure_vector<uint8_t> plaintext = encdata.ciphertext;

   // decrypt the symmetric key
   Botan::PK_Decryptor_EME asym_cipher(privkey, rng, "EME-OAEP(SHA-256,MGF1)");
   const auto key = asym_cipher.decrypt(encdata.encryptedKey);

   // decrypt the data symmetrically
   auto sym_cipher = Botan::AEAD_Mode::create_or_throw("AES-256/GCM", Botan::DECRYPTION);
   sym_cipher->set_key(key);
   sym_cipher->start(encdata.nonce);
   sym_cipher->finish(plaintext);

   return plaintext;
}


template <typename Out, typename In>
Out as(const In& data)
{
   return Out(data.data(), data.data() + data.size());
}


int main()
{
   Botan::AutoSeeded_RNG rng;

   const auto privkey = generate_keypair(2048 /*  bits */, rng);

   const std::string plaintext = "The quick brown fox jumps over the lazy dog.";
   const auto ciphertext       = encrypt(as<Botan::secure_vector<uint8_t>>(plaintext), privkey->public_key(), rng);
   const auto new_plaintext    = decrypt(ciphertext, *privkey, rng);

   std::cout << as<std::string>(new_plaintext) << std::endl;

   return 0;
}

reneme avatar Mar 25 '22 08:03 reneme

Thank you, I have applied your suggestions, they have been very help full. I am wondering if there is anything I am doing incorrect, in my work. In terms of correctly applying Botan-2 elements, if this is out-of-scope for this thread, then we could close this thread. Thank you.

The areas of concern for me are the conversion of RSA key pairs to std::string and the thought process of encrypting large messages like paragraphs (5 sentences), if anything different needs to be done (in case there are some limitation on how much AEAD can encrypt).

Marko-Sanchez avatar Mar 27 '22 22:03 Marko-Sanchez

@reneme I think this ticket can be closed but before that happens could you please open a PR adding the above example code you wrote to src/examples?

randombit avatar Mar 24 '23 22:03 randombit