For an academic network application, I'd like to set up an RSA key exchange between 2 virtual machines. I am using Crypto++ to generate the RSA::PublicKey
, and I must now send it within a custom layer-2 frame (the packet will be crafted with libcrafter
).
The thing is, I have no idea of how write the key in the network, such as the receiver, sniffing the packet, is able to re-build, somehow, the RSA::PublicKey
.
I tried to save it raw in a string, but as they say here, the PublicKey
class contains other data, then simply the raw key (data that I don't need). nevertheless, I manage to success that, but at the reception I can't simply rebuild the PublicKey...
Could it be possible, somehow, to concatenate the modulus, the primes and the public exponent, in order to rebuild the publicKey
at the reception?
Sender
Here is the code I use at the sender. It's the essential lines, but my program has other functionality, and it would be too long to post it entirely here).
AutoSeededRandomPool rng;
RSA::PrivateKey privateKey;
privateKey.GenerateRandomWithKeySize(rng, 3072);
RSA::PublicKey publicKey(privateKey);
cout << ">> Key generated" <<endl;
/* Convert key to string then to const char* */
std::string publicKeyString;
publicKey.BEREncode( StringSink(publicKeyString).Ref() );
const char * publicKeyChar = publicKeyString.c_str();
cout <<"key size : "<<publicKeyString.size()<< endl;
/* Send Packet */
Crafter::RawLayer type1("K");
Crafter::RawLayer key_send(publicKeyChar);
//Crafter::RawLayer key_send(publicKeyString.c_str(), publicKeyString.length());
Crafter::Packet packet_key (ether_header / type1 / key_send);
packet_key.Send(iface);
Receiver
And here is my attempt to recover the key.
/* Extract Payload */
PayloadLayer *payload_rcv = pack_recu.getLayerOfType<PayloadLayer>();
size_t payload_size = payload_rcv->getPayloadLen() ;
Crafter::byte *payload = payload_rcv->getPayload();
cout << ">> Public Key recieved"<<endl;
// Convert into RSA::PublicKey
stringstream ss;
for (int i=0; i< payload_size; i++)
ss << payload[i];
string payload_string = ss.str();
cout << "Payload Size: "<<payload_size<<endl;
cin.get();
StringSource stringSource(payload_string, true);
RSA::PublicKey publicKey2;
publicKey2.BERDecode(stringSource);
data->publicKey = publicKey2;
And here is the result of running the program:
terminate called after throwing an instance of 'CryptoPP::BERDecodeErr'
what(): BER decode error
I'm sure the error comes from the conversion from string to publicKey...
The BERDecode
function war originally thought to recover the key from a file...
Does anyone has a solution ? I think that sending apart all the elements to rebuild the key could be better, but I can't figure how to do it...
publicKey.BEREncode( StringSink(publicKeyString).Ref() );
const char * publicKeyChar = publicKeyString.c_str();
A BER encoding likely has an embedded NULL
, so you cannot use customary C-string operations on it:
const char * publicKeyChar = publicKeyString.c_str();
...
Crafter::RawLayer key_send(publicKeyChar);
When writing the encoded public key, the following looks correct. You should uncomment it and use it (I use data
and size
because it logically separates from C-strings and length).
Crafter::RawLayer key_send(publicKeyString.data(), publicKeyString.size());
So the whole Crypto++ thing might look like the following:
// Host's private key, generate or Load() it...
RSA::PrivateKey privKey;
...
// Create a public key from the private key
RSA::PublicKey pubKey(privKey);
// Temporaries
string spki;
StringSink ss(spki);
// Use Save to DER encode the Subject Public Key Info (SPKI)
pubKey.Save(ss);
Crafter::RawLayer key_send(spki.data(), spki.size());
Then, to reconstruct it:
// Key payload
const PayloadLayer& payload_rcv = *pack_recu.getLayerOfType<PayloadLayer>();
// Get a contiguous array (I don't know what this is called in Crafter)
payload_rcv.PullUp();
// Use the array directly to avoid the copy
ArraySource as(payload_rcv.data(), payload_rcv.size(), true /*pumpAll*/);
RSA::PublicKey pubKey;
// Use Load to BER decode the Subject Public Key Info (SPKI)
pubKey.Load(as);
// Validate it before using it
AutoSeededRandomPool prng;
pubKey.ThrowIfInvalid(prng);
I think its important to use the Subject Public Key Info (SPKI) rather than just the Public Key. The SPKI includes an algorithm identifier by way of an OID. It will make algorithm agility a little easier later on. Later, you can switch to a ECDSA key or an ed25519 key, and they key type will be part of the key payload.
terminate called after throwing an instance of 'CryptoPP::BERDecodeErr' what(): BER decode error
Obviously, you should set up a try/catch
, and catch a BERDecodeErr
:
try
{
// Use Load to BER decode the Subject Public Key Info (SPKI)
pubKey.Load(as);
// Validate it before using it
AutoSeededRandomPool prng;
pubKey.ThrowIfInvalid(prng);
}
catch(const BERDecodeErr& ex)
{
cerr << ex.what() << endl;
}
catch(const InvalidMaterial& ex)
{
cerr << ex.what() << endl;
}
And here's what the Subject Public Key Info looks like:
$ cat cryptopp-test.cpp
...
int main(int argc, char* argv[])
{
AutoSeededRandomPool prng;
RSA::PrivateKey rsaPrivate;
rsaPrivate.GenerateRandomWithKeySize(prng, 3072);
RSA::PublicKey rsaPublic(rsaPrivate);
FileSink sink("rsa-public.der");
rsaPublic.Save(sink);
return 0;
}
And then use something like Peter Gutmann's dumpasn1
:
$ dumpasn1 rsa-public.der
0 416: SEQUENCE {
4 13: SEQUENCE {
6 9: OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
17 0: NULL
: }
19 397: BIT STRING, encapsulates {
24 392: SEQUENCE {
28 385: INTEGER
: 00 CE B0 19 0D 0C EB 87 BD 6B 51 6C BB 00 9C EE
: 1D 75 9C 28 DC 0E 8E 88 9A 95 8A 3B 6C BD 1F 3F
: 03 05 22 8E 3D 19 33 D7 C5 A3 28 4F 13 3D 9E BF
: 5A 54 51 AE D6 DA C3 AC 1D 9C 4C A3 47 C0 04 8F
: 9D 0A DD 38 60 56 E3 9C DB 7C EA A8 3F 52 93 99
: 40 90 14 41 0A 3B 58 F2 13 9F 38 64 18 DD 62 55
: D2 32 53 A0 D5 1A 54 E7 8D 23 01 E0 97 ED F9 C7
: 68 9F E2 00 48 99 53 40 6E 7E 5C DA 47 39 4A 41
: [ Another 257 bytes skipped ]
417 1: INTEGER 17
: }
: }
: }
0 warnings, 0 errors.