I am attempting to take a public key generated from Windows Hello and Verify the signature with the CryptoPP library. One of the issues is according to Windows Hello documentation a dev does not have access to private keys, so I need to use the signature given to me from Windows Hello (RequestSignAsync()) as well as the public key.
Is this possible?
I have created a WinRT version of a Windows Hello interface based on this C# sample:
https://github.com/Microsoft/Windows-universal-samples/tree/main/Samples/MicrosoftPassport
I have my reasons for not wanting to us the server code that is provided in the example, so I am attempting to verify signature with Crypto++.
Then sending the signature and public key to this CryptoPP setup:
CryptoPP::RSA::PublicKey keyPublic;
keyPublic.Load(CryptoPP::StringSource(pubkey, true, new CryptoPP::Base64Decoder()).Ref() );
CryptoPP::RSASS<CryptoPP::PKCS1v15, CryptoPP::SHA256>::Verifier verifier(keyPublic);
bool bSignatureVerified = false;
std::string sigdata;
CryptoPP::StringSource ss(sig,true,
new CryptoPP::Base64Decoder(
new CryptoPP::StringSink(sigdata)
)); // Base64Decoder
CryptoPP::StringSource ss2(sigdata, true,
new CryptoPP::SignatureVerificationFilter(
verifier,
new CryptoPP::ArraySink((CryptoPP::byte*)&bSignatureVerified,
sizeof(bSignatureVerified)
)
)
);
if(!bSignatureVerified)
{
return -2; //signed message not valid
}
else
{
return 0;
}
The public key and signature are being sent over from a Windows Hello interface. I am getting a failed verification here for some reason.
Here is the Windows Hello documentation:
https://learn.microsoft.com/en-us/windows/uwp/security/microsoft-passport
Seems to match with my logic for Crypto++ (PKCS1v1.5, SHA256,ASN.1-encoded) but I must be missing something.
It looks like was not understanding that I needed to pass the signature AND the message.
This seemed to work:
CryptoPP::StringSource ss(sig,true,
new CryptoPP::Base64Decoder(
new CryptoPP::StringSink(sigdata)
)); // Base64Decoder
CryptoPP::StringSource ssmsg(msg,true,
new CryptoPP::Base64Decoder(
new CryptoPP::StringSink(msgdata)
)); // Base64Decoder
CryptoPP::StringSource ss2(sigdata+msgdata, true,
new CryptoPP::SignatureVerificationFilter(
verifier,
new CryptoPP::ArraySink((CryptoPP::byte*)&bSignatureVerified,
sizeof(bSignatureVerified)
)
)
);
I'm trying to Store ECIES num0 PrivateKey with DEREncodePrivateKey to a std::string and reload it in num1 PrivateKey Object for testing.
Problem is when key is loaded with BERDecodePrivateKey in second PrivateKey object it can't be validated (also tested encryption and decrypting without validation and didn't decrypt )
here's the code
using namespace CryptoPP;
CryptoPP::AutoSeededRandomPool prng;
ECIES<ECP>::PrivateKey pp;
pp.Initialize(prng, ASN1::secp256k1());
/* returns true*/
bool val=pp.Validate(prng, 3);
std::string saves;
StringSink savesink(saves);
pp.DEREncodePrivateKey(savesink);
/*additional unnecessary steps to make sure the key is written completely */
savesink.MessageEnd();
savesink.Flush(true);
ECIES<ECP>::PrivateKey pro;
StringSource savesSource(saves, true);
pro.BERDecodePrivateKey(savesSource,true,savesSource.MaxRetrievable());
/*here the exception is thrown */
pro.ThrowIfInvalid(prng, 3);
finally found what the problem is
as #maarten-bodewes mentioned in comment the DER encoded private exponent doesn't determine the curve OID for the privateKey Object , so before BER Decoding and importing key we need to somehow determine the OID for the Object;
the simplest way is to determine it when Initializing new Object
above code changes to :
ECIES<ECP>::PrivateKey pro;
StringSource savesSource(saves, true);
auto rett = savesSource.MaxRetrievable();
pro.Initialize(prng, ASN1::secp256k1());
pro.BERDecodePrivateKey(savesSource,true,savesSource.MaxRetrievable());
also you AccessGroupParameters().Initialize(/*OID*/); or Initialize(/*OID*/) for existing object
I'm trying to write helper functions for a program I'm making and I need to return the keys as strings. Found a way to convert the RSA keys from PrivateKey/PublicKey to Base64 string.
int main()
{
//Generate params
AutoSeededRandomPool rng;
InvertibleRSAFunction params;
params.Initialize(rng, 4096);
//Generate Keys
RSA::PrivateKey privKey(params);
RSA::PublicKey pubKey(params);
//Encode keys to Base64
string encodedPriv, encodedPub;
Base64Encoder privKeySink(new StringSink(encodedPriv));
privKey.DEREncode(privKeySink);
Base64Encoder pubKeySink(new StringSink(encodedPub));
privKey.DEREncode(pubKeySink);
RSA::PrivateKey pvKeyDecoded;
RSA::PublicKey pbKeyDecoded;
//how to decode...
system("pause");
return 0;
}
Now, how do I load the encoded keys back? I wasn't able to find any information on that.
RSA::PrivateKey pvKeyDecoded;
RSA::PublicKey pbKeyDecoded;
//how to decode...
You can do something like:
StringSource ss(encodedPriv, true, new Base64Decoder);
pvKeyDecoded.BERDecode(ss);
You should also fix this:
Base64Encoder pubKeySink(new StringSink(encodedPub));
privKey.DEREncode(pubKeySink); // pubKey.DEREncode
And you should call MessageEnd() once the key is written:
Base64Encoder privKeySink(new StringSink(encodedPriv));
privKey.DEREncode(privKeySink);
privKeySink.MessageEnd();
Base64Encoder pubKeySink(new StringSink(encodedPub));
pubKey.DEREncode(pubKeySink);
pubKeySink.MessageEnd();
You might also find Keys and Formats helpful from the Crypto++ wiki.
I have successfully used this some lines ago in my program:
string tmp;
StringSource(msg, true, new PK_EncryptorFilter(*rng, *encryptor, new CryptoPP::HexEncoder(new StringSink(tmp))));
return tmp;
So you know that the Crypto++ objects are well created and so.
Now I want to encrypt a whole binary file and save it to an adjacent file:
FileSource(file.c_str(), true, new PK_EncryptorFilter(*rng, *encryptor, new FileSink((file+".xx").c_str(), true)),true);
But this last line crashes with a debug error stating that abort() has been called.
Hunting down the error, I tried to change the second argument to the FileSource call to false, leading to the following code:
FileSource(file.c_str(), false, new PK_EncryptorFilter(*rng, *encryptor, new FileSink((file+".xx").c_str(), true)),true);
And then the error gone, but the destination file weights 0 bytes, nothing was read/wrote.
I do not know what can can the key to the problem, so, I hope someone can help a little bit.
EDIT: I am using Visual Studio 2013 Pro.
EDIT2: I hunted the error further.
This works and the file binary content is correctly printed on screen:
string s;
FileSource file2("C:\\test.jpg", true, new StringSink(s));
std::cout << s << std::endl;
But this don't work and ends with the mentioned crash.
string s;
FileSource file2("C:\\test.jpg", true, new PK_EncryptorFilter(*rng, *encryptor, new StringSink (s)));
std::cout << s << std::endl;
This is so strange since the same PK_EncryptorFilter filter is used in another method without trouble, as I stated at the beginning of the post.
Anyway, I post here my entire class, so as to get a clear idea of what is going on:
RSASystem::RSASystem()
{
std::string pubkey = "...OMITED...";
rng = new AutoSeededRandomPool;
CryptoPP::HexDecoder decoder;
decoder.Put((byte*)pubkey.c_str(), pubkey.size());
decoder.MessageEnd();
CryptoPP::HexDecoder decoder2;
decoder2.Put((byte*)pubkey.c_str(), pubkey.size());
decoder2.MessageEnd();
verifier = new RSASSA_PKCS1v15_SHA_Verifier;
encryptor = new RSAES_OAEP_SHA_Encryptor;
verifier->AccessKey().Load(decoder);
encryptor->AccessKey().Load(decoder2);
}
string RSASystem::encrypt(string msg)
{
string tmp;
StringSource(msg, true, new PK_EncryptorFilter(*rng, *encryptor, new CryptoPP::HexEncoder(new StringSink(tmp))));
return tmp;
}
void RSASystem::encryptFile(string file)
{
FileSource(file.c_str(), true, new PK_EncryptorFilter(*rng, *encryptor, new FileSink((file+".xx").c_str(), true)),true);
}
EDIT 3: After surrounding the code with try..catch() I got this error:
RSA/OAEP-MGF1(SHA-1): message length of 490986 exceeds the maximum of 214 for this public key
Which now I think can be easily solved.
FileSource(file.c_str(), false,
new PK_EncryptorFilter(*rng, *encryptor,
new FileSink((file+".xx").c_str(), true)
),
true);
This does not look right. new FileSink((file+".xx").c_str() returns a char*, and you need a pointer to a Sink. Plus, there's an extra false in there I'm not used to seeing. Something like:
FileSource fs1(filename, true,
new PK_EncryptorFilter(rng, encryptor,
new FileSink(filename, true)
) // PK_EncryptorFilter
); // StringSource
There's a couple of examples on the Crypto++ wiki. See RSA Cryptography and RSA Encryption Schemes.
The following is an example from the Crypto++ wiki using RSA. But you can use the code for any cryptosystem that adheres to PK_Encryptor and PK_Decryptor (Sources (like StringSource and FileSource) and Sinks (like StringSink and FileSink) are also interchangeable):
////////////////////////////////////////////////
// Generate keys
AutoSeededRandomPool rng;
InvertibleRSAFunction params;
params.GenerateRandomWithKeySize( rng, 1536 );
RSA::PrivateKey privateKey( params );
RSA::PublicKey publicKey( params );
string plain="RSA Encryption", cipher, recovered;
////////////////////////////////////////////////
// Encryption
RSAES_OAEP_SHA_Encryptor e( publicKey );
StringSource ss1( plain, true,
new PK_EncryptorFilter( rng, e,
new StringSink( cipher )
) // PK_EncryptorFilter
); // StringSource
////////////////////////////////////////////////
// Decryption
RSAES_OAEP_SHA_Decryptor d( privateKey );
StringSource ss2( cipher, true,
new PK_DecryptorFilter( rng, d,
new StringSink( recovered )
) // PK_DecryptorFilter
); // StringSource
assert( plain == recovered );
Also, don't use anonymous declarations. Some versions of GCC has problems with them. That is, use:
StringSource ss1( plain, true,
...
rather than:
StringSource( plain, true,
...
I had already pending the encryption and security subject so I wasn't aware of the limitation on the length of the message of the RSA scheme.
https://security.stackexchange.com/questions/44702/whats-the-limit-on-the-size-of-the-data-that-public-key-cryptos-can-handle
So the solution passes by implementing an Integrated or Hybrid Encryption Scheme, like ECIES.
I've done this successfully with Crypto++ using: http://www.cryptopp.com/wiki/Elliptic_Curve_Integrated_Encryption_Scheme
Thanks to jww to point to the correct decision.
OK, I think I know where you might be having problems. But I'd need to see all your code and not just the encryption.
I could coax a BER Decode error by omitting encoder1.MessageEnd and encoder2.MessageEnd. Apparently, I was able to read the key before it was fully written. I assume it was fully written after leaving main (and the destructors ran) because the file sizes looked OK with ls.
In the code below, the message was encrypted under publicKey1 and then decrypted with privateKey2 to ensure the keys were round-tripping.
try {
////////////////////////////////////////////////
// Generate keys
AutoSeededRandomPool rng;
InvertibleRSAFunction params;
params.GenerateRandomWithKeySize(rng, 1024);
RSA::PrivateKey privateKey1(params);
RSA::PublicKey publicKey1(privateKey1);
////////////////////////////////////////////////
// Save/Load keys
HexEncoder encoder1(new FileSink("private-key-der.txt", true));
HexEncoder encoder2(new FileSink("public-key-der.txt", true));
privateKey1.Save(encoder1);
publicKey1.Save(encoder2);
// Must have these. Otherwise, the full key (hex encoded)
// is not written until destructors are run
encoder1.MessageEnd();
encoder2.MessageEnd();
FileSource fs1("private-key-der.txt", true, new HexDecoder);
FileSource fs2("public-key-der.txt", true, new HexDecoder);
RSA::PrivateKey privateKey2;
RSA::PublicKey publicKey2;
privateKey2.Load(fs1);
bool valid = privateKey2.Validate(rng, 3);
if(!valid)
throw Exception(Exception::OTHER_ERROR, "Failed to validate key 1");
publicKey2.Load(fs2);
valid = publicKey2.Validate(rng, 3);
if(!valid)
throw Exception(Exception::OTHER_ERROR, "Failed to validate key 2");
////////////////////////////////////////////////
// Scratch
string plain="RSA Encryption", cipher, recovered;
////////////////////////////////////////////////
// Encryption
RSAES_OAEP_SHA_Encryptor encryptor(publicKey1);
StringSource ss1(plain, true,
new PK_EncryptorFilter(rng, encryptor,
new StringSink(cipher)
) // PK_EncryptorFilter
); // StringSource
////////////////////////////////////////////////
// Decryption
RSAES_OAEP_SHA_Decryptor decryptor(privateKey2);
StringSource ss2(cipher, true,
new PK_DecryptorFilter(rng, decryptor,
new StringSink(recovered)
) // PK_DecryptorFilter
); // StringSource
cout << "Recovered plain text: " << recovered << endl;
} catch (const Exception& ex) {
cerr << ex.what() << endl;
}
What I'm trying to do is generate random RSA keys and then store them before my program terminates. This part is working just fine using RSA_generate_key, PEM_write_bio_RSAPrivateKey and PEM_write_bio_RSA_PUBKEY. I can also encrypt/decrypt just find using the RSA structure returned by RSA_generate_key.
However, my problem comes when my program restarts and I want to read back in the keys that I stored previously. I can use PEM_read_bio_RSAPrivateKey and PEM_read_bio_RSA_PUBKEY to pull the keys in, but I need to get them into the same RSA structure, similar to how they are stored by RSA_generate_key.
My code is shown below. I have the keys stored in memory along with a small header that tell me how large the keys are. The private key start right after the header and the public key is stored right after the private key.
privateKey = (uint8_t *) ( buffer + rsaStruct->hdrSize );
publicKey = (uint8_t *) ( privateKey + rsaStruct->privateKeyLength );
bioPrivate = BIO_new_mem_buf( (void *) privateKey, rsaStruct->privateKeyLength );
bioPublic = BIO_new_mem_buf( (void *) publicKey, rsaStruct->publicKeyLength );
bioPrivate = BIO_new_mem_buf( (void *) privateKey, rsaStruct->privateKeyLength + rsaStruct->publicKeyLength );
if( bioPrivate == NULL || bioPublic == NULL ) {
fprintf( stderr, "%s: BIO_new_mem_buf failed!\n", __FUNCTION__ );
return ECE_RSA_ERROR_BIO_CREATION_FAILED;
}
PEM_read_bio_RSAPrivateKey( bioPrivate, &keyPair, NULL, NULL );
PEM_read_bio_RSA_PUBKEY( bioPublic, &keyPair, NULL, NULL );
BIO_free( bioPrivate );
BIO_free( bioPublic );
If I try to just send in the same RSA structure, it doesn't seem to work. I'm able to encrypt just fine, but my decryption fails. This could likely be due to the fact that the public key is the last key retrieve and the one used for encryption. If the second call over-writes the address of my RSA struct, I would end up with an RSA structure that has nothing but the public key.
Anyway, if anyone could tell me how to get both the public and private key into the same RSA structure, that would be great!
Comparing to RSA private key, public key additionaly contains only the public exponent. So just copy it from public key to private key structure, and everything should work.