Here is an RSA encryption example. It uses unsigned, fixed-length integers with the "secure" option.
Please note that this example, while fairly complete, is minimal. It does not include signing or verification of signatures, and does nothing to prevent a known attack method (the "low encryption exponent" attack).
You can find this example as the file rsa.cpp
in the example
directory.
#include <iostream> #include <string> #include <sstream> #include <fstream> #include <boost/limits.hpp> #include <boost/integer.hpp> #include <boost/xint/integer.hpp> #include <boost/static_assert.hpp> namespace xopt = boost::xint::options; using boost::xint::callback_t; using boost::xint::no_callback; static const std::size_t cbits = std::numeric_limits<char>::digits; template <std::size_t Bits> class Rsa { public: // We're going to use a fixed-length type for this example, primarily to // show how they would be used. The calculations require intermediate // results that are between two and three times the bit-size of the number // though. We'll also make it unsigned, and use secure mode to reduce the // chance that the key data gets written to disk anywhere. typedef typename boost::xint::integer_t<xopt::fixedlength<Bits * 3>, xopt::secure, xopt::negative_modulus> KeyNumber; // We also need the smallest type that can hold a Bits-length number of // characters. typedef typename boost::uint_value_t<(Bits + cbits - 1) / cbits>::least SizeT; // There must be at least enough bits to handle one character and a SizeT. BOOST_STATIC_ASSERT(Bits > ((sizeof(SizeT) + 1) * cbits)); // The number of bits needs to be even. BOOST_STATIC_ASSERT((Bits & 0x01) == 0); Rsa(const std::string keys); std::string publickey() const; std::string privatekey() const; std::string encrypt(const std::string data) const; std::string decrypt(const std::string data) const; static Rsa generate(callback_t callback = no_callback); private: static std::size_t calculate_blocksize(KeyNumber n); Rsa(const KeyNumber _n, const KeyNumber _d, const KeyNumber _e): n(_n), d(_d), e(_e) { blocksize = calculate_blocksize(n); }; std::string number_to_binary_string(const KeyNumber num) const; KeyNumber binary_string_to_number(const std::string s) const; KeyNumber n, d, e; std::size_t blocksize; }; template <std::size_t Bits> std::size_t Rsa<Bits>::calculate_blocksize(KeyNumber n) { // Round the size of n (in bits) down to the next lower multiple of the // number of bits in a character. That's how many characters we can fit into // a single block, for encryption purposes. std::size_t size_in_bits = log2(n) - 1; return (size_in_bits + cbits - 1) / cbits; } template <std::size_t Bits> std::string Rsa<Bits>::number_to_binary_string(const KeyNumber num) const { boost::xint::binary_t b = to_binary(num); std::string s; std::copy(b.begin(), b.end(), std::back_inserter(s)); return s; } template <std::size_t Bits> typename Rsa<Bits>::KeyNumber Rsa<Bits>::binary_string_to_number(const std::string s) const { boost::xint::binary_t b; std::copy(s.begin(), s.end(), std::back_inserter(b)); return KeyNumber(b); } template <std::size_t Bits> Rsa<Bits>::Rsa(const std::string keys) { // Make sure it's a proper key, by checking the signature. bool goodkey = true; if (keys.substr(0, 4) == "{RSA") { std::istringstream str(keys.substr(4)); std::size_t recordedbits = 0; char c1 = 0, c2 = 0, c3 = 0, c4 = 0; str >> recordedbits >> c1 >> e >> c2 >> n >> c3; if (c1 == ',' && c2 == ',') { if (c3 == ',') { // It's a private key, including the decryption key. str >> d >> c4; if (c4 != '}') goodkey = false; } else { // It's a public key, no decryption key is included. if (c3 != '}') goodkey = false; } } else goodkey = false; // Make sure it's the right size if (goodkey && recordedbits != Bits) throw std::out_of_range("Wrong number of bits"); } else goodkey = false; if (!goodkey) throw std::invalid_argument("Not a valid key"); blocksize = calculate_blocksize(n); } template <std::size_t Bits> std::string Rsa<Bits>::publickey() const { std::ostringstream str; str << "{RSA" << Bits << ',' << e << ',' << n << '}'; return str.str(); } template <std::size_t Bits> std::string Rsa<Bits>::privatekey() const { std::ostringstream str; str << "{RSA" << Bits << ',' << e << ',' << n << ',' << d << '}'; return str.str(); } template <std::size_t Bits> std::string Rsa<Bits>::encrypt(const std::string data) const { // A proper implementation would pad the message with additional random // data, to avoid the low encryption exponent attack. This example // implementation does not. // The message may contain up to (blocksize - 1) extra bytes when it's // decrypted. Prepend a SizeT with the number of bytes to remove from the // end. const unsigned char mask = (unsigned char)(-1); std::string msg; SizeT trimblock = blocksize - ((data.length() + sizeof(SizeT)) % blocksize); if (trimblock == blocksize) trimblock = 0; for (std::size_t i = sizeof(SizeT); i > 0; --i) { msg += static_cast<char>(trimblock & mask); trimblock >>= cbits; } msg += data; // Split the message into blocks of blocksize and encrypt each one. std::string encrypted_msg; for (std::size_t block = 0; block * blocksize < msg.length(); ++block) { // Grab a block of blocksize bytes. std::string tblock = msg.substr(block * blocksize, blocksize); // Turn it into a KeyNumber. KeyNumber mnumber = binary_string_to_number(tblock); // Encrypt that number mnumber = powmod(mnumber, e, n); // Append the encrypted data to the return value, padded to the proper // block length. tblock = number_to_binary_string(mnumber); if (tblock.length() < blocksize) tblock += std::string(blocksize - tblock.length(), 0); encrypted_msg += tblock; } return encrypted_msg; } template <std::size_t Bits> std::string Rsa<Bits>::decrypt(const std::string encrypted_msg) const { std::string decrypted_msg; // Split the message into blocks of blocksize and decrypt each one. for (std::size_t block = 0; block * blocksize < encrypted_msg.length(); ++block) { // Grab a block of blocksize bytes. std::string tblock = encrypted_msg.substr(block * blocksize, blocksize); // Turn it into a KeyNumber. KeyNumber mnumber = binary_string_to_number(tblock); // Decrypt that number mnumber = powmod(mnumber, d, n); // Append the encrypted data to the return value, padded to the proper // block length. tblock = number_to_binary_string(mnumber); if (tblock.length() < blocksize) tblock += std::string(blocksize - tblock.length(), 0); decrypted_msg += tblock; } // Trim the added bytes off of it. SizeT trimblock = 0; for (std::size_t i = 0; i < sizeof(SizeT); ++i) trimblock |= (SizeT(decrypted_msg[i]) << (i * cbits)); decrypted_msg = decrypted_msg.substr(sizeof(SizeT), decrypted_msg.length() - trimblock - sizeof(SizeT)); return decrypted_msg; } template <std::size_t Bits> Rsa<Bits> Rsa<Bits>::generate(callback_t callback) { // Use the system's strong random number generator, via the XInt-provided // convenience class. boost::xint::strong_random_generator gen; // Generate two random prime numbers, each containing no more than half of // the requested bits, and compute the product. KeyNumber p = KeyNumber::random_prime(gen, Bits / 2, callback); KeyNumber q = KeyNumber::random_prime(gen, Bits / 2, callback); assert(p != q); // If they're identical, there's almost certainly a problem // Compute the product of the primes. KeyNumber n(p * q); // Select an encryption key e, such that e and (p - 1) * (q - 1) are // relatively prime. Encryption goes a lot faster if you use small primes // for this value, and 65537 is recommended by X.509 and PKCS #1. KeyNumber e(65537); // Compute the decryption key. KeyNumber d(invmod(e, (p - 1) * (q - 1))); // That's all we have to do. Just plug those numbers into an Rsa object and // return it. return Rsa<Bits>(n, d, e); } using std::cout; using std::endl; using std::flush; const int max_statusline_length = 40; void clearline(int length) { cout << '\r' << std::string(length, ' ') << '\r' << flush; } bool callback() { static int n = 0; if (++n == max_statusline_length) { clearline(max_statusline_length); n = 0; } cout << '.' << flush; return true; } int main() { typedef Rsa<512> Cipher; // In this test program, we'll generate a new key every time. 512-bit keys // should generate within 100 attempts or less, most of the time, so it'll // be pretty quick. You would normally generate a new key only once, store // the private key securely, and publish the public key. Cipher c = Cipher::generate(callback); clearline(max_statusline_length); std::string source("This is a test message."); std::string encrypted = c.encrypt(source); std::string decrypted = c.decrypt(encrypted); if (decrypted == source) { cout << "Decryption was successful!" << endl << endl; cout << "The key is: " << c.privatekey() << endl; } else { cout << "Error in decryption!" << endl; cout << source << " (" << source.length() << ')' << endl; cout << decrypted << " (" << decrypted.length() << ')' << endl; } return EXIT_SUCCESS; }
© Copyright Chad Nelson, 2010-2011. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)