Reputation: 5660
Is it possible to store a RSA Private/Public Key in the source for example in a byte[]
or string
or any other container
and use this key for encryption / decryption?
A decode function from file would look like:
void Decode(const string& filename, BufferedTransformation& bt)
{
// http://www.cryptopp.com/docs/ref/class_file_source.html
FileSource file(filename.c_str(), true /*pumpAll*/);
file.TransferTo(bt);
bt.MessageEnd();
}
That loads the key from file which is not what I want.
I know it's gotta be possible since I can create the key with AutoSeededRandomPool
.
I just don't know how to use an existing one.
Maybe I'm overlooking this part in the documentation.
Upvotes: 12
Views: 16248
Reputation: 614
How would I load a Private / Public Key from a string / byte array or any other container
//Create Cryptopp StringSource From Std::string
std::string PublicKeyString = "<Your key as std::string value>";
CryptoPP::StringSource PKeyStringSource(PublicKeyString, true);
CryptoPP::RSA::PublicKey publicKey;
publicKey.Load(PKeyStringSource);
Though, I am not sure if cryptopp has support for native containers of CryptoPP::StringSource. But I believe, storing the container as std::vector > This should serve the purpose here.
Upvotes: 0
Reputation: 892
If you create DSA keys as follows, you will end up with two files, one containing the private key and the other the public key.
void CreateDsaKeys(std::string folder)
{
AutoSeededRandomPool rng;
// Generate Private Key
DSA::PrivateKey privateKey;
privateKey.GenerateRandomWithKeySize(rng, 1024);
// Generate Public Key
DSA::PublicKey publicKey;
publicKey.AssignFrom(privateKey);
if (!privateKey.Validate(rng, 3) || !publicKey.Validate(rng, 3))
{
throw runtime_error("DSA key generation failed");
}
std::string publicPath = folder + "/publickey.txt";
std::string privatePath = folder + "/privatekey.txt";
SaveHexPublicKey(publicPath, publicKey);
SaveHexPrivateKey(privatePath, privateKey);
}
Copy the contents of these two files into your source code and put them into strings:
std::string publickey("308201B73082012C...F752BB791");
std::string privatekey("3082014C0201003...0B8E805D83E9708");
Then you can use HexDecoder to convert the strings into bytes and create the public and private keys using these bytes:
bool LoadDsaKeysFromStringsAndTest()
{
AutoSeededRandomPool rng;
HexDecoder decoderPublic;
decoderPublic.Put((byte*)publickey.data(), publickey.size());
decoderPublic.MessageEnd();
HexDecoder decoderPrivate;
decoderPrivate.Put((byte*)privatekey.data(), privatekey.size());
decoderPrivate.MessageEnd();
DSA::PublicKey publicKey;
publicKey.Load(decoderPublic);
DSA::PrivateKey privateKey;
privateKey.Load(decoderPrivate);
string message = "DSA Signature";
string signature;
try {
DSA::Signer signer( privateKey );
StringSource ss1( message, true,
new SignerFilter( rng, signer,
new StringSink( signature )
) // SignerFilter
); // StringSource
bool result = false;
DSA::Verifier verifier1( publicKey );
StringSource ss(message+signature, true,
new SignatureVerificationFilter(verifier1,
new ArraySink((uint8_t*)&result, sizeof(result)),
SignatureVerificationFilter::PUT_RESULT | SignatureVerificationFilter::SIGNATURE_AT_END)
);
return result;
}
catch(const CryptoPP::Exception& e)
{
std::cerr << e.what() << std::endl;
}
return false;
}
These are the other routines needed to save the keys
void Save(const string& filename, const BufferedTransformation& bt)
{
FileSink file(filename.c_str());
bt.CopyTo(file);
file.MessageEnd();
}
void SaveHex(const string& filename, const BufferedTransformation& bt)
{
HexEncoder encoder;
bt.CopyTo(encoder);
encoder.MessageEnd();
Save(filename, encoder);
}
void SaveHexPrivateKey(const string& filename, const PrivateKey& key)
{
ByteQueue queue;
key.Save(queue);
SaveHex(filename, queue);
}
void SaveHexPublicKey(const string& filename, const PublicKey& key)
{
ByteQueue queue;
key.Save(queue);
SaveHex(filename, queue);
}
Upvotes: 0
Reputation: 102205
How would I load a Private / Public Key from a string / byte array or any other container
The Crypto++ library has built-in support for std:strings
. But other C++ containers are going to be trickier. ArraySource
was added at Crypto++ 5.6, but its just a typedef
for StringSource
.
If you are using sensitive material, then you should also consider using a SecByteBlock
. It will wipe or zeroize the sensitive material when the destructor runs.
string and StringSource (load)
string spki = ...;
StringSource ss(spki, true /*pumpAll*/);
RSA::PublicKey publicKey;
publicKey.Load(ss);
vector and ArraySource (load)
vector<byte> spki = ...;
ArraySource as(&spki[0], spki.length(), true /*pumpAll*/);
RSA::PublicKey publicKey;
publicKey.Load(as);
string and StringSink (save)
string spki;
StringSink ss(spki);
RSA::PublicKey publicKey(...);
publicKey.Save(ss);
vector (save)
See code below.
Below is an example of saving to and loading from a std::vector
. You have to use an intermediate ByteQueue
to save because you can't easily create a VectorSink
.
AutoSeededRandomPool prng;
RSA::PrivateKey pk1, pk2;
pk1.Initialize(prng, 1024);
ByteQueue queue;
pk1.Save(queue);
vector<byte> spki;
spki.resize(queue.MaxRetrievable());
ArraySink as1(&spki[0], spki.size());
queue.CopyTo(as1);
ArraySource as2(&spki[0], spki.size(), true);
pk2.Load(as2);
bool valid = pk2.Validate(prng, 3);
if(valid)
cout << "Validated private key" << endl;
else
cout << "Failed to validate private key" << endl;
We don't have an explicit VectorSink
, and we can't easily create one because of an implicit expectation of traits_type::char_type
. For example:
using CryptoPP::StringSinkTemplate;
typedef StringSinkTemplate< std::vector<byte> > VectorSink;
In file included from cryptopp-test.cpp:65:
In file included from /usr/local/include/cryptopp/files.h:5:
/usr/local/include/cryptopp/filters.h:590:22: error: no member named
'traits_type' in 'std::vector<unsigned char, std::allocator<unsigned char>
>'
typedef typename T::traits_type::char_type char_type;
~~~^
cryptopp-test.cpp:243:20: note: in instantiation of template class
'CryptoPP::StringSinkTemplate<std::vector<unsigned char,
std::allocator<unsigned char> > >' requested here
VectorSink vs(spki);
You can create the VectorSource
and VectorSink
, its just going to take some work. You can get an idea about the work involved by looking at StringSource
and StringSink
source code in filters.h and filters.cpp.
Upvotes: 4
Reputation: 4052
The Crypto++ Keys and Formats and Crypto++ RSA Cryptography pages may be of interest.
If you're generating the RSA parameters like this:
AutoSeededRandomPool rng;
InvertibleRSAFunction params;
params.GenerateRandomWithKeySize(rng, 2048);
You can use the use the DEREncode
and BERDecode
methods of InvertibleRSAFunction
to encode and decode all the parameters respectively:
{
FileSink output("rsaparams.dat");
params.DEREncode(output);
}
InvertibleRSAFunction params2;
{
FileSource input("rsaparams.dat", true);
params2.BERDecode(input);
}
To encode/decode the private and public material separately, use the DEREncode
and BERDecode
methods on the RSA::PrivateKey
and RSA::PublicKey
objects themselves:
// Initialize keys from generated params
RSA::PrivateKey rsaPrivate(params);
RSA::PublicKey rsaPublic(params);
// Write keys to file
{
FileSink output("rsaprivate.dat");
rsaPrivate.DEREncode(output);
}
{
FileSink output("rsapublic.dat");
rsaPublic.DEREncode(output);
}
// Read keys from file into new objects
RSA::PrivateKey rsaPrivate2;
RSA::PublicKey rsaPublic2;
{
FileSource input("rsaprivate.dat", true);
rsaPrivate2.BERDecode(input);
}
{
FileSource input("rsapublic.dat", true);
rsaPublic2.BERDecode(input);
}
FileSource
and FileSink
are just example source and sink objects you could use. The encode/decode routines take BufferedTransformation
objects as parameters, so you can use any other suitable implementations of that interface.
For example, ArraySink
can be used to write data into a memory buffer you supply, and StringSource
(also aliased as ArraySource
) can be used to read from a buffer.
Here's some code showing use of ArraySink
and ArraySource
to round-trip the private key material through a std::vector<byte>
:
RSA::PrivateKey rsaPrivate(params);
std::vector<byte> buffer(8192 /* buffer size */);
ArraySink arraySink(&buffer[0], buffer.size());
rsaPrivate.DEREncode(arraySink);
// Initialize variable with the encoded key material
// (excluding unwritten bytes at the end of our buffer object)
std::vector<byte> rsaPrivateMaterial(
&buffer[0],
&buffer[0] + arraySink.TotalPutLength());
RSA::PrivateKey rsaPrivate2;
ArraySource arraySource(
&rsaPrivateMaterial[0],
rsaPrivateMaterial.size(),
true);
rsaPrivate2.BERDecode(arraySource);
(See also @jww's answer for an example which avoids the fixed-size buffer by using a ByteQueue
).
And another example using a std::string
to store the key material and using the StringSink
class to write to this, which avoids some of the buffer management (the string will be resized automatically to match the amount of data that is encoded). Note that this is still binary data even though it's in a std::string
object.
RSA::PrivateKey rsaPrivate(params);
std::string rsaPrivateMaterial;
StringSink stringSink(rsaPrivateMaterial);
rsaPrivate.DEREncode(stringSink);
RSA::PrivateKey rsaPrivate2;
StringSource stringSource(rsaPrivateMaterial, true);
rsaPrivate2.BERDecode(stringSource);
Alternatively, if you want to control the format yourself, you can use the methods of the InvertibleRSAFunction
object or the key objects to extract the individual parameters (as shown in the "Crypto++ RSA Cryptography" link above) and use that to extract the values for storage in your own format:
const Integer& n = params.GetModulus();
const Integer& p = params.GetPrime1();
const Integer& q = params.GetPrime2();
const Integer& d = params.GetPrivateExponent();
const Integer& e = params.GetPublicExponent();
These could then be restored to a new InvertibleRSAFunction
or RSA::*Key
instance when read from the file or container, by using the corresponding setter methods (SetModulus()
, SetPrime1()
, etc.).
Upvotes: 20