For a software product I’ve been working on, I ad to do some RSA signing and verifying operations. The signature verifying code is to run only on Windows environments (Windows xp and beyond) and, as the product is written in c/c++, I implemented the RSA signature verifying operation using the Windows Crypto API. Plain good old c code, no external dependencies and, after some hacking, working code. Or so I thought.
Code was distributed to clients and all worked fine there. Signature generation was done using a Windows executable, as the signing entity was running Windows and existing code could be almost fully reused. My initial thought was something in the lines of “Ok, this is RSA, standard stuff, I can generate signatures anywhere if needed in the future”. And the future came and it was needed to generate signatures on a Linux box. However I spent 3 (three) stupid days to make signatures validate correctly. If you ever have to develop with Windows Crypto API and plan to receive data from external sources… Read this carefully.
The procedure that is used on Windows to verify RSA signatures is:
- Acquire a cryptography context by calling cryptAquireContext.
- Import the signer«s public key using cryptImportKey or something similar (that is simpler to use. Some Openssl builds can even convert PEM or DER formatted keys to Microsoft proprietary key format).
- Create a hash object using cryptCreateHash (I used SHA1 hashing, others should work) and add data to it with cryptHashData.
- Verify the data with signature cryptVerifySignature.
The windows signing code did almost the same thing, replacing public key with private key and verify with sign operation.
Then, I coded the Linux signer tool with Python and the Pycrypto library. After some research (googling stackoverflow and many random sources) I found that windows crypto API uses PKCS1 v1.5 schema for RSA signature padding, and wrote something like the following:
from Crypto.Hash import SHA as SHA1 from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 data = "my sample data" # Import Private key for signing: key = RSA.importKey("mykey.pem") # hash data: hash = SHA1.new(data) # Create PKCS1 padding object and sign pkcs = PKCS1_v1_5.new(key) signatureBytes = pkcs.sign(hash)
Then the signature was sent to windows for testing and… not valid. Not valid. After many iterations through the code… Not valid. But on the end of the 3rd day I made myself aware of two terribly obvious things: I can’t read API docs properly and Microsoft “changes his mind like a girl changes..” whatever. I won’t quote “her”. From cryptVerifySignature function documentation:
If you generate a signature by using the .NET Framework APIs and try to verify it by using the CryptVerifySignature function, the function will fail and GetLastError will return NTE_BAD_SIGNATURE. This is due to the different byte orders between the native Win32 API and the .NET Framework API.
The native cryptography API uses little-endian byte order while the .NET Framework API uses big-endian byte order. If you are verifying a signature generated by using a .NET Framework API, you must swap the order of signature bytes before calling the CryptVerifySignature function to verify the signature.
signatureBytes = signatureBytes[::-1] # No swap macros with "tmp" variables! Well done!
This is my story. If someone finds this useful (if that someone finds this in the first place) please thank Google or whatever sent you here.