Microsoft CryptoAPI: how to convert PUBLICKEYBLOB to DER/PEM? - c++

I have a generated RSA key pair stored as PRIVATEKEYBLOB and PUBLICKEYBLOB, and I need to be able to convert these keys to DER or PEM formats so I could use it in PHP or Python. I figured out that I could use CryptEncodeObject function to convert my PRIVATEKEYBLOB to DER. In order to do that I need to use PKCS_RSA_PRIVATE_KEY encoding flag. But I couldn't find any clue on how to convert PUBLICKEYBLOB to DER.
Here is my code for PRIVATEKEYBLOB convertion:
LPCSTR type = PKCS_RSA_PRIVATE_KEY;
DWORD encd = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
DWORD dlen = 0;
if(!CryptEncodeObject(encd, type, key, null, &dlen))
{ LOG_ERROR(); return false; }
// Buffer allocation (der variable)
if(!CryptEncodeObject(encd, type, key, der, &dlen))
{ LOG_ERROR(); return false; }
I test my keys by comparing them to the output of openssl tool:
openssl rsa -pubin -inform MS\ PUBLICKEYBLOB -in pub.ms -outform DER -out pub.der
openssl rsa -inform MS\ PRIVATEKEYBLOB -in pri.ms -outform DER -out pri.der
ADDED: I tried RSA_CSP_PUBLICKEYBLOB with X509_ASN_ENCODING, but the result is different to the output of openssl tool, and the key import failes. The openssl's exported DER is 25 bytes longer, and only first 3 bytes are equal in both keys. Here is the picture of key comparison:
If we look closely at this picture, we can see that openssl's key version has some kind of additional 24 bytes header after the 3rd byte. Haven't figured out what is it as of yet, but if I concatinate this hardcoded header with the output I get from CryptEncodeObject with RSA_CSP_PUBLICKEYBLOB it all works fine. Not sure if that header is always the same or not though.

Use RSA_CSP_PUBLICKEYBLOB as documented in
https://msdn.microsoft.com/en-us/library/windows/desktop/aa378145(v=vs.85).aspx

I struggled with PUBLICKEYBLOB -> PEM/DER formats until I found a post about pulling one from a smart card and converting it. The gist of it is, MS PUBLICKEYBLOB needs to have the first 32 bytes removed, then reverse the order and add 02 03 01 00 01 to the end. That will give you DER format. You can then base64 encode to get PEM and then add the requisite begin/end public key lines.
You can refer to the original post for background.

Related

wc_RsaSSL_Verify returns BAD_FUNC_ARG, and I can't tell why

I am trying to RSA public key decrypt a signed file using wolfcrypt - yes, I may or may not be abusing the "sign/verify" power of RSA to encrypt a separate AES key using the private key and decrypt using the public key.
Unfortunately, I am stuck at wc_RsaSSL_Verify() - for the life of me I can't figure out why it is returning BAD_FUNC_ARG - I figured an error like that should be immediately visible to somebody else so I'm deciding to call upon the collective powers of StackOverflow.
As far as I can tell, I'm giving the function what it's asking for - an input buffer, an output buffer, the size of each, and a pointer to the RsaKey struct. Here is a code snippet from the function in question:
bool VerifyWorker::GetAESKey()
{
bool result = true;
uint8_t en_aes_file_buff[VerifyWorkerLocal::RSA_KEY_SIZE];
uint8_t de_aes_file_buff[VerifyWorkerLocal::RSA_KEY_SIZE];
uint8_t* aes_iv_ptr = NULL;
// keyfile filestream
std::fstream aes_file;
// rsa_key must be initialized
if(rsa_key == NULL)
{
result = false;
}
// Open the key file and read it into a local buffer, then decrypt it and use it to initialize the
// aes struct
if(result)
{
aes_file.open(this->aes_key_file, std::ios_base::in | std::ios_base::binary);
if(aes_file.fail())
{
// Unable to open file - perror?
perror("GetAESKey");
result = false;
}
else
{
aes_file.read(reinterpret_cast<char*>(en_aes_file_buff), VerifyWorkerLocal::RSA_KEY_SIZE + 1);
if(!aes_file.eof())
{
// we didn't have enough space to read the whole signature!
std::cerr << "aes_file read failed! " << aes_file.rdstate() << std::endl;
result = false;
}
}
}
// "Unsign" the aes key file with RSA verify, and load the aes struct with the result
if(result)
{
int wc_ret = 0;
wc_ret = wc_RsaSSL_Verify(const_cast<const byte*>(en_aes_file_buff),
VerifyWorkerLocal::RSA_KEY_SIZE, reinterpret_cast<byte*>(&de_aes_file_buff),
VerifyWorkerLocal::RSA_KEY_SIZE, rsa_key);
The rsa_key is a private member initialized (successfully, using wc_PublicKeyDecode()) in a separate function with a public key DER file. I generated both the public and private key using OpenSSL - which should properly pad my AES key and iv file using PKCS#1 v1.5 b default.
I should also mention that I am using wolfssl version 3.9.8. Thanks!
The issue, I found, was that the file that I had signed with my RSA key was not signed correctly. When I signed the file using OpenSSL, my cli invocation was
openssl rsautl -in keyfile -out keyfile -inkey private.pem -sign
Apparently, openssl does not like you to specify the same file for -in and -out. When I changed it to something like
openssl rsautl -in keyfile -out keyfile_signed -inkey private.pem -sign
I was actually able to verify the file using wc_RsaSSL_Verify.
So, like most stupid late-night, last hour software problems, I was looking in the wrong place entirely. I was a bit thrown off by the BAD_FUNC_ARG being returned and thought that it had to do explicitly with the format of the function arguments, not necessarily their content. Hopefully this answer is useful for somebody else, too.
It sounds like you are trying to use RSA_Sign to perform an "Encrypt" of an AES key. Then I assume you are sending to a remote partner or computer who will then run an RSA_Verify operation to decrypt the AES key do I understand the scenario correctly?
If so I apologize it did not show up if you searched on how to do this initially but we actually have an example of doing exactly that here:
https://github.com/wolfSSL/wolfssl-examples/tree/master/signature/encryption-through-signing
That example includes two separate applications. The first app, "rsa-private-encrypt-app.c", will sign (encrypt) the "fake Aes Key" and output the result to a file. The second app, "rsa-public-decrypt-app.c", then opens the file that was output and does a verify (decrypt) on the data contained in the file to recover the original "fake Aes Key".
I may or may not be abusing the "sign/verify" power of RSA to encrypt a separate AES key using the private key and decrypt using the public key.
No not at all, that is a valid use of RSA sign/verify ASSUMING you are working with fixed-length inputs such as an AES key.
That's why we created the example! We actually had a user ask a very similar question on our forums awhile back which led to us making the example.
One thing to make note of though on the issues you encountered with openssl and wolfssl is actually talked about in the README:
https://github.com/wolfSSL/wolfssl-examples/blob/master/signature/encryption-through-signing/README.md
... Keep in mind this is not a TRUE RSA ENCRYPT and will likely not inter-op with other libraries that offer a RSA_PRIVATE_ENCRYPT type API.
This is a true SIGN operation.
If you have any other questions feel free to post them here (and add the wolfssl tag of course) or you can also send us an email anytime at support#wolfssl.com
Disclaimer: I work for wolfSSL Inc.

Generate signature using private key with OpenSSL API

I'm trying to implement the SSL equivalent of:
openssl dgst -sha1 -binary -out digest.txt what.txt
openssl rsautl -sign -inkey key.pem -in what.txt -out signature.txt
I have my 7 lines private key, 304 chars:
const char* k = "-----BEGIN... \n... \n... END RSA.."
The code I use is this one:
BIO* bio = BIO_new_mem_buf((void*)k, (int)strlen(k));
RSA* privateKey = PEM_read_bio_RSAPrivateKey(bio, NULL, 0, NULL);
std::string appName = "hello";
unsigned char SHA[20];
SHA1((const unsigned char*)appName.c_str(), appName.size(), SHA);
Then I sign using the following code, passing the SHA and it's size as buffer and bufferSize (which should always be 20 in this case and it's correct) while the signature size is 32:
unsigned int privateKeySize = RSA_size(privateKey);
*signature = new unsigned char[ privateKeySize ];
RSA_sign(NID_sha1, buffer, bufferSize, *signature, signatureSize, privateKey);
RSA_sign return 0, I can't find any error code etc.
The SHA[] array content is the same of the file digest.txt, but the signature is totally wrong. I can't find any example of this.. Do you have any idea how this should work, or point me into the right direction?
Thank You.
First of all, the digest is never treated as hexadecimals in a signature, which is what you seem to assume by the extension of digest.txt. The output of the dgst command within OpenSSL is also binary (although the manual pages on my system seem to indicate otherwise - openssl's documentation isn't known to be very precise). This is only a superficial problem as the rest of your code does seem to treat the SHA-1 hash as binary.
What's probably hurting you is that, although openssl rsautl -sign seems to pad the data, it doesn't seem to surround it with the structure that indicates the hash algorithm used. You need to append this structure yourself. This is probably a legacy from OpenSSL's support for SSLv3 RSA authentication where this structure may be absent.
A quick and dirty fix that is actually present in the PKCS#1 standard is to prefix the hash before signing. In the case of SHA-1 you should prefix the hash with the binary value of the following hex representation:
30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14
this should be easy to do in your shell language of choice. Even the windows command line is able to do this through the COPY command.

Use PGP public key to cipher with Windows CryptoApi in C++

I don't know if this is possible at all. A pair of RSA keys are generated with GNUPG, and the public key is exported to a file. My program receives such file and then it has to encrypt some data with the public RSA key in that file. The program is written in C/C++ and it won't use external libraries, so all I can use is Windows CryptoApi functions (CryptStringToBinary, CryptDecodeObjectEx, CryptImportPublicKeyInfo, etc).
This method works with a public 2048 bit RSA key in PEM format encoded in base64:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnCEy2jOlwK8qVxAHddaD
J6u8u/D0h8nOexco6Xg8iu7DnZOrKPL/1pTL1pwH5GLp0bsb/NfkxetijIb/C4h7
37y6bZPC8V+Koi2jz2lNCNOF4jWuD9Dw8mYnOeH+HpVkKTDVry824i2+qihWM1s/
DwVNUh4C50asnFl64Qd9ycbE3jDr4+yzeBDC7Pirm21OFVUZhTzNzuT5UQzGidvw
2pomYnDM6NOwoIyrBOP0J4CCGbJnZMsf+Dsya/t9tR0cKgFl1Zh0W/V1eJ8Ud7Yq
vIwGeStNeIcjoVkPGh4Hu1Uj0YHXZeTyy4LYo8OUWIipQEJ/dL4TLd0/uD8cr1LR
TwIDAQAB
-----END PUBLIC KEY-----
But the key exported with GPG looks quite different:
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2
mQENBFSIwoUBCACpyzbyoFLtg8uPMOVl0a4oRdfSSyyNpVuZiDENvj60JINSLhQq
gkyfRW6KbSxp9rEPjsUWnjEVcZ8EYcTZjalGajaG090WrAM0iop4zagKSK3EjpTO
sdkA0wtX7abeK+s9WBiC4hbFk2Ds6iRHtVCU5zsYzZ2S/lD7+PA6UXjFYSpNpCGr
XjyATh7tiYelFjij7ocgK9MWCCDPv6ti9yRVqmsBJPqIqIfvjyyLxFncSOCAc4+E
2cLFUsDr5rGYZF8OsJ0AICxAWwJn50IXpEpuQVQNAOd5yZsrRj4tOJ6qCo/bL24a
UkzF9+zDkFQ6kPSKqUnpOx7wdby5625eBImTABEBAAG0ImdudXBnX2tleXMgPGdu
dXBna2V5c0B5b3BtYWlsLmNvbT6JATkEEwECACMFAlSIwoUCGwMHCwkIBwMCAQYV
CAIJCgsEFgIDAQIeAQIXgAAKCRD3/5Jn+Ps9o7JfCACfQqOVorlkcpya/N9uT/L0
2RA3Y2CwlMEdCpzxUtlEC9KlcAWEMBYfS68Cvq550VKKA+bz9v1XBta1rGPN5T/4
o8lxa7fEhQYCRcUJ4qBzHHOPJoLd7oYDNjGcMS0LNmt7L8Apm2+WguwufO1x61OA
bfOsxCzPt7kl/PjzQMLeNY3F03SuzgtkSQwDc0CpBgoRYhlbCyorxEuTIdRioZiL
h6G/Wvp4Me9prQiMpEqPkHHhp61+LKdAGKjaCcyOwDUB3Ec1GtY1CRKb3/VRveXQ
nN8hm9+VnbqVBy6HurMsz5rEM9rKbIWl1i2A3CFY4EKbJBcYdiGTB16p/QJ+Ll0o
uQENBFSIwoUBCACfH0Up/2Zf+WwH70Tk4WVmaEMNhEiiN1ivYsPT1RYSgSzsgRzx
LgD9CTWCFl9jf6Ko2YCHujZimTpx4Bd2hGNj07zF8VWl2fpW8nA964HkWg1isk5s
XYiSYRSG6foH6tn8D42fYsJad0A4yZo1P5OoPzql9MTJpH1nVjaWnxTOTRgoYmMo
mPW7DimIDnoKRp/A7yCdw3HiUogKqRedqWTxLzs2odhx1NKDx9A3lA81UQtNSA78
o6h1JGPQUHUU5yl3+EgntDL+qmcx4fW2J/PQ2ingIq+VueeDpUKYNomGcrvR7vvR
EAIUCD9UbQTos3xgzUAa6TZY+sLC/x6lTpq9ABEBAAGJAR8EGAECAAkFAlSIwoUC
GwwACgkQ9/+SZ/j7PaPd0gf/TNqUmBgQQaY+kgfUL2rCauNkBZboku59pNxJu6iJ
W+IEMYLbRg8qzIVS1ui9zxMY8pper88QX82jfZ27Xo5nbct9ZZCjDeWZRX5xJULx
CsK2fHlMA/CdvGZJdm5KMNmVFni+vVJlLzpij5eQ52j+8NvHAPFgL3NlcmdwWVhy
/y3XjG687bB+DnVhlfOb7JijA/WHThjXS6AFH659jlt/Z1FRti6O3cxEJSTN0rQU
bCXkjJPsqQNgEbsBDQ3f6hwZKnpqpQZt417qRahb/LrfIgxAJhiWLyFFWKp3XuX3
mR0t01lrPzIXTQMaY9lce3G3XSoQx+1gu29fBm/rkHvQIQ==
=R8UZ
-----END PGP PUBLIC KEY BLOCK-----
Although it's a 2048 bit key as well. If I trim the blank lines and the header, CryptStringToBinary succeeds and translates it into a binary format, but CryptDecodeObjectEx fails (GetLastError() returns 0x8009310B). Removing the checksum didn't work either.
I'm a bit lost here so, basically, Is there a way to obtain a RSA public key exported with GPG and use it to encrypt data with Windows CryptoApi?
Thank you.
Converting OpenPGP Keys to PEM
Extracting the RSA public key from an OpenPGP key and conterting it to PEM format is possible. Sysmisc has an article about converting to and from OpenPGP keys in different ways. For the way OpenPGP to PEM, it boils down to:
gpgsm -o secret-gpg-key.p12 --export-secret-key-p12 0xXXXXXXXX
openssl pkcs12 -in secret-gpg-key.p12 -nokeys -out gpg-certs.pem
OpenPGP has its own Message Exchange Format
Yet a caveat, if you want to encrypt to an OpenPGP user, he will not be able to read any (Open, ...)SSL encrypted information using an OpenPGP implementation like GnuPG. OpenPGP not only uses a different key format, but also another message exchange format.
If you want to send OpenPGP messages, use GPGME to interface GnuPG from C, there might also be other libraries for doing so.

Load PEM encoded private RSA key in Crypto++

Often times, user will have PEM encoded RSA private keys. Crypto++ requires that these keys be in DER format to load. I've been asking people to manually convert their PEM files to DER beforehand using openssl like this:
openssl pkcs8 -in in_file.pem -out out_file.der -topk8 -nocrypt -outform der
That works fine, but some people don't understand how to do that nor do they want to. So I would like to convert PEM files to DER files automatically within the program.
Is it as simple as striping the "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----" from the PEM or is some other transformation required as well? I've been told that between those markers that it's just b64 encoded DER. Here's some code that demonstrates the issue:
// load the private key
CryptoPP::RSA::PrivateKey PK;
CryptoPP::ByteQueue bytes;
try
{
CryptoPP::FileSource File( rsa.c_str(), true, new CryptoPP::Base64Decoder() );
File.TransferTo( bytes );
bytes.MessageEnd();
// This line Causes BERDecodeError when a PEM encoded file is used
PK.Load( bytes );
}
catch ( CryptoPP::BERDecodeErr )
{
// Convert PEM to DER and try to load the key again
}
I'd like to avoid making system calls to openssl and do the transformation entirely in Crypto++ so that users can provide either format and things "just work". Thanks for any advice.
Yes, it's a DER stream encoded with Base64. Note though, in addition to striping both BEGIN and END markers, in case of RSA key format you also need to strip any flags that may be inserted between the BEGIN marker and the encoded data. Only the remaining part can be successfully Base64 decoded. It appears that you feed the full certificate file to the decoder and that needs fixing.
... I would like to convert PEM files to DER files automatically within the program.
In July 2014, a PEM Pack was provided for the Crypto++ library. The PEM Pack is a partial implementation of message encryption which allows you to read and write PEM encoded keys and parameters, including encrypted private keys. The additional files include support for RSA, DSA, EC, ECDSA keys and Diffie-Hellman parameters.
Its an add-on to the library, and not part of the library proper. You download a ZIP and add five source files to the library. Then you build the library (Crypto++ automatically picks them up). The ZIP contains five additional source files, a script to create test keys using OpenSSL, a C++ program to test reading and writing the keys, and a script to verify the keys written by Crypto++ using OpenSSL.
Here's how you would use it:
CryptoPP::RSA::PrivateKey pk;
CryptoPP::FileSource file("<rsa-key-file.pem>", true);
CryptoPP::PEM_Load(file, pk);
CryptoPP::AutoSeededRandomPool prng;
bool = pk.Validate(prng, 3);
if (! valid)
throw ...
If the keys is encrypted, then here's how you would load it. The PEM Pack re-implement's OpenSSL's EVP_BytesToKey, so the key derivation will work and you can interop:
CryptoPP::RSA::PrivateKey pk;
CryptoPP::FileSource file("<rsa-key-file.pem>", true);
std::string pass = "<super secret password>";
CryptoPP::PEM_Load(file, pk, pass.data(), pass.size());
CryptoPP::AutoSeededRandomPool prng;
bool = pk.Validate(prng, 3);
if (! valid)
throw ...
There's also a PEM_Save, so you can write the keys directly from Crypto++. For example:
// Generate it or load it from somewhere
CryptoPP::RSA::PrivateKey pk = ...;
CryptoPP::FileSink file("<rsa-key-file.pem>", true);
CryptoPP::PEM_Save(file, pk);
And PEM_Save for an encrypted key (or key you intend to encrypt):
// Generate it or load it from somewhere
CryptoPP::RSA::PrivateKey pk = ...;
CryptoPP::FileSink file("<rsa-key-file.pem>", true);
std::string pass = "<super secret password>";
CryptoPP::PEM_Save(file, pk, "AES-128-CBC", pass.data(), pass.size());
PEM_Load does not need an algorithm because its encoded in the encapsulated header. PEM_Save needs an algorithm because there is no default algorithm.
I know this is an old question but other's might find this useful. Once you strip the markers you're left with the 'inner' key material. According to http://www.cryptopp.com/wiki/Keys_and_Formats#BER_and_DER_Encoding you can use BERDecodePrivateKey to load this. So, to load an openssl key that has had its markers stripped you can do something like
bool LoadKey(RandomNumberGenerator& rng, const std::string& file,
RSA::PrivateKey& key)
{
ByteQueue q;
FileSource KeyFile(file.c_str(), true, new Base64Decoder);
KeyFile.TransferTo(q);
key.BERDecodePrivateKey(q,false,0); // last 2 params unused
return key.Validate(rng, 2);
}

Converting between Windows CryptoAPI and OpenSSL x509 formats

I have a CERT_CONTEXT structure which I've extracted from a smart card on Windows via the CryptoAPI. I need to convert this structure into a DER encoded byte array which is consistent with OpenSSL. The closest match I've got so far is via CryptEncodeObject using X509_ASN_ENCODING and the X509_CERT_TO_BE_SIGNED modifier which takes the CERT_INFO structure as input.
The problem is that it doesn't match with the output produced by the OpenSSL i2d_X509 function. Using a 2048 bit x509 certificate as input, OpenSSL produces 1789 bytes of encoded output whilst the Windows CryptoAPI produces 1638 bytes of encoded output.
The only option left that I can see is to create an X509 cert on the fly using the values from the CERT_CONTEXT structure and the encode the resulting object directly with the i2d_X509 function. The only problem with this is that I can't extract the private key from the smart card, so this may cause problems with the x509 cert creation routines.
If anyone can provide any insight/advice/tips into these matters, I'd be much obliged.
DER encoded certificate can be obtained from (ctx->pbCertEncoded, ctx->cbCertEncoded) buffer where ctx is a PCCERT_CONTEXT object. Still you won't be able to recover the private key.