Wednesday, October 12, 2016

A license system using Crypto++ with RSA keys

If someone really wants to hack your software they can. Sony tried to make copying impossible and ended up in court. My take is the same as when I lock my bike. It is not difficult to take the bike but you have to make a thief of yourself in order to do it.

Crypto++ is found here.

Step one in this process is to obtain some specifics regarding the hardware of the PC running your software. Serial numbers from hard drives, volume serial numbers, amount/speed of RAM. Anything can be used, note also the possible hassle your paying customers will suffer if they do anything to their PC at a later stage.

Hash this number, for Crypto++ this could be done something like this:

std::string SHA256HashString(std::string aString) {
 std::string digest;
 CryptoPP::SHA256 hash;

 CryptoPP::StringSource foo(aString, true,
  new CryptoPP::HashFilter(hash,
   new CryptoPP::HexEncoder(
    new CryptoPP::StringSink(digest))));

 return digest;
}
Make your customer send you this number. It is not feasible to recreate their hardware ID from this hash.


Step two is preparing your private and public keys.

 AutoSeededRandomPool rng;

 ...

 RSA::PrivateKey rsa_private;
 rsa_private.GenerateRandomWithKeySize(rng, 512); //select your keysize here
 bool isok = rsa_private.Validate(rng, 3);//Always check certificates your
       //program reads from external sources
 ByteQueue private_queue;
 rsa_private.Save(private_queue);

 FileSink private_file_sink("private.bin");
 private_queue.CopyTo(private_file_sink);
 private_file_sink.MessageEnd();

Just save the private key as a binary, nobody will look into this file, you could choose to do the same with the public key. I chose to hard code this file into my application, so I will save it as a base64 encoded string.

 RSA::PublicKey rsa_public(rsa_private);
 isok = rsa_public.Validate(rng, 3);
 ByteQueue public_queue;
 rsa_public.Save(public_queue);
 string ss_base64;
 Base64Encoder base64encoder_sink(new StringSink(ss_base64));
 public_queue.CopyTo(base64encoder_sink);
 base64encoder_sink.MessageEnd();
 cout << ss_base64 << endl;

 ofstream file_b64("public.b64");
 file_b64 << ss_base64;
 file_b64.close();

Step three is signing a file containing the information from step one. For me this a line containing the customers name and a line containing the hardware id hash.

 RSASSA_PKCS1v15_SHA_Signer signer(rsa_private);
// Create signature space
 size_t length = signer.MaxSignatureLength();
 SecByteBlock signature(length);

 // Sign message
 length = signer.SignMessage(rng, (const byte*)message.c_str(),
  message.length(), signature);

 // Resize now we know the true size of the signature
 signature.resize(length);

 string sig_string((char*)signature.data(), signature.size());
 Base64Encoder encoder;
 encoder.Put((byte*)sig_string.c_str(), sig_string.size());
 encoder.MessageEnd();
 word64 size = encoder.MaxRetrievable();
 string encoded;
 if (size)
 {
  encoded.resize(size);
  encoder.Get((byte*)encoded.data(), encoded.size());
 }
 cout << encoded << endl;
 ofstream file_b642("signature.b64");
 file_b642 << encoded;
 file_b642.close();

In order to keep everything in one file I now append the base 64 signature to the customer information.


 Customer name, place
 hardware id hash
 ----
 mflkdhjgs6fdli9456fjgdklSDGty5ftgd
 MDSWFrt+wegGgGmorebase64charshere


I will call this file "license.txt" and send it to the customer.

Step four. My application reads the hardware info, hashes it and compares it with the hash in the license.txt file. If it matches I will verify the hash with the signature. This way I know the hardware hash is not just copied.
A routine to decode base 64:

string b64_decoder(string b64_str)
{
 Base64Decoder b64_decoder;
 b64_decoder.Put((byte*)b64_str.data(), b64_str.size());
 b64_decoder.MessageEnd();
 string decoded_str;
 word64 size = b64_decoder.MaxRetrievable();
 if (size && size <= SIZE_MAX)
 {
  decoded_str.resize(size);
  b64_decoder.Get((byte*)decoded_str.data(), decoded_str.size());
 }
 else
 {
  decoded_str = "";
 }
 return decoded_str;
}
Step five recreates the public certificate, creates a verifier and checks the message vs the signature. Begin by debase64 both the public key and the signature:


 size_t pos = license_txt.find("----", 0); //Check your findings...
 string message_txt = trim(license_txt.substr(0, pos));
 string signature = b64_decoder(trim(license_txt.substr(pos + 4)));

 string public_b64 = "hm+560dXdR4dmorebase64charshere"

//rsa_public
 string rsa_public_str = b64_decoder(public_b64);
 RSA::PublicKey rsa_public;
 StringSource stringSource(rsa_public_str, true);
 rsa_public.BERDecode(stringSource);
 if (!rsa_public.Validate(rng, 3))
 {
  //if this is wrong someone has actually tampered with the code
 }
// Verifier object
 RSASSA_PKCS1v15_SHA_Verifier verifier(rsa_public);

 // Verify
 bool result = verifier.VerifyMessage((const byte*)message_txt.c_str(),
  message_txt.length(), (const byte*)signature.data(), signature.size());

 // Result
 if (true == result) {
  cout << "All OK" << endl;
 }
 else {
  cout << "bah bah baaaaa" << endl;
 }
Well, this is my implemetion at least.