Asymmetric encryption with VFP and Vista
Intro
Encryption algorithms come in two flavors: symmetric and asymmetric algorithms. When developers talk about encryption, they usually refer to symmetric algorithms like AES or Triple DES. You have a password that an algorithm uses to encrypt data. The same password is used to decrypt data.
This kind of algorithm is easy to picture mentally. After all, you use the same key to lock and unlock a door in real life, too. Asymmetric algorithms are like needing two keys for every door: one to lock the door, another one to unlock it. The most popular one of these algorithms is the RSA algorithm which is used for secure HTTP traffic, to validate certificates, and much more.
Asymmetric algorithms are like needing two keys for every door: one to lock the door, another one to unlock it
As sais, asymmetric algorithms use two keys: one is used to encrypt data, the other one is required to decrypt data. Usually one of them is kept secret. The other one can be safely published. That’s why such algorithms are called public key algorithms.
Exchanging secrets is the first step in encryption. If you want the other side to be able to decrypt data, you must provide them with the secret (password) key. Symmetric algorithms require that you use a secure channel for that. That makes symmetric keys ideal if you can somehow pre-share keys. But it rules out any type of secure communication between unknown parties such as web servers.
The real drawback of asymmetric algorithms is their speed, or rather non-speed. Encrypting data with a public private key scheme is significantly slower than the more common symmetric encryption, especially for large volumes of data. That’s the main reason why in reality we usually find a combination of algorithms.
When the goal is to protect data integrity, a common approach is to produce a hash value for the message. The hash value is a small value of 20 bytes, or a little more. This hash value is then encrypted with a private key. Everyone can validate the message by decrypting the hash value with the public key and recalculating the hash value. However, it’s impossible to alter the message when the private key is unknown.
Secure communication works similarly. One side sends the public key unencrypted over the network. The receiving side generates a random password, encrypts it with the public key and sends it back. Only the originating computer is able to decipher the password, because decryption requires knowledge of the private key. As a result only two partners have now access to a random password, even if the entire key exchange had been recorded. For performance reason both computers switch over to symmetric encryption using the secret key. This is how SSL works, for instance.
The Crypto API
The traditional Crypto API that comes with Windows doesn’t have support for asymmetric encryption. It does support RSA, but only for the use cases outlined above: digital signing and key exchange. When the Crypto API was first published in the mid-90s, computers were simply to slow to encrypt any significant amount of data with asymmetric algorithms.
Furthermore, real encryption wasn’t widely used. Most developers worked with homegrown algorithms or less secure libraries that were faster and easier to use.
In this decade the situation has changed significantly. Encryption isn’t used only by secret services and military organizations. Rather, the entire economy depends on digital signatures and encrypted data transfer. New laws and regulations require encryption in many software projects that somehow deal with personal or medical data.
Encryption isn’t used only by secret services and military organizations
One of the latest events is NSA’s publication of Suite B. Suite B is a set of algorithms that the NSA considers secure enough for most classified and unclassified information. It's more important for US developers than for us in Europe to support Suite B. We still gain from the US requirements, though.
Suite B introduces a new set of algorithms: elliptic curve encryption. Computers have become so powerful that traditional prime factor based algorithms such as RSA require increasingly longer keys to remain secure. Currently experts recommend using a key with at least 2048 bits.
Microsoft’s Crypto API doesn’t support elliptic encryption natively. While there are providers out there for purchase, Microsoft aims at supporting Suite B out of the box, because that’s a requirement to sell Windows PCs to certain public agencies in the United States. With Windows Vista and Windows Server 2008, Microsoft therefore introduced a new API that is compliant with NSA’s requirements.
Cryptography API: Next Generation
The “Cryptography API: Next Generation”, or CNG for short, has even more advantages then just compliance. Using the classic API required a lot of knowledge. Even a simple task like encrypting data forced developers to deal with contexts, providers, and the like. One of CNG’s goals was to simplify this interface, especially when dealing with the various ways of using encryption. The CNG supports:
- Generating random numbers
- Hashing
- Symmetric and asymmetric encryption
- Digital signature
- Key exchange
At this point writing code for the CNG is difficult for the lack of information. At the time of writing this article, Google would return less than five hits for some of the API functions, all of which point to the rather sparse MSDN documentation on that matter. I expect the situation to improve over time, though.
When using CNG you always start by opening an algorithm. An algorithm supports at least one operation from the list above, but might support any of them. The following program creates a SHA-256 hash value for any data passed in. As the code uses the CNG, it currently only runs on Vista.
*========================================================
* Creates a SHA 256 hash value.
*========================================================
Lparameters tcData
* Initialize API
Local llOK
llOK = .T.
Do CNG.prg
* Get a handle to the hashing algorithm provider
Local lnAlg
lnAlg = 0
If m.llOK
llOK = BCryptOpenAlgorithmProvider( ;
@lnAlg, Strconv("SHA256",5)+Chr(0), NULL, 0 ) == 0
EndIf
* Determine how many bytes we need to store hash object
Local lnSizeObj, lnData
If m.llOK
lnSizeObj = 0
lnData = 0
llOK = BCryptGetProperty( m.lnAlg, ;
Strconv("ObjectLength",5)+Chr(0), @lnSizeObj, ;
4, @lnData, 0 ) == 0
EndIf
* Determine length of the hash value
Local lnSizeHash
If m.llOK
lnSizeHash = 0
llOK = BCryptGetProperty( m.lnAlg, ;
Strconv("HashDigestLength",5)+Chr(0), ;
@lnSizeHash, 4, @lnData, 0 ) == 0
EndIf
* Create the hash object
Local lnHash, lcHashObj
lnHash = 0
If m.llOK
lcHashObj = Space(m.lnSizeObj)
llOK = BCryptCreateHash( m.lnAlg, @lnHash, ;
@lcHashObj, m.lnSizeObj, NULL, 0, 0 ) == 0
EndIf
* To create the hash value we add data to the hash
* object. You can repeat this step as needed
If m.llOK
llOK = BCryptHashData( ;
m.lnHash, m.tcData, Len(m.tcData), 0 ) == 0
EndIf
* Tell the hash object that we are done. The algorithm
* now calculates the hash value and returns it.
Local lcHash
If m.llOK
lcHash = Space(m.lnSizeHash)
llOK = BCryptFinishHash( ;
m.lnHash, @lcHash, m.lnSizeHash, 0 ) == 0
EndIf
* Cleanup
If m.lnAlg != 0
BCryptCloseAlgorithmProvider( m.lnAlg, 0 )
EndIf
If m.lnHash != 0
BCryptDestroyHash( m.lnHash )
EndIf
If not m.llOK
lcHash = ""
EndIf
Return m.lcHash
After opening the hash algorithm, the program creates a hash object with BCryptCreateHash, feeds it with data using BCryptHashData and finally obtains the hash by calling BCryptFinishHash. Half of the code just deals with error checking, but it’s still significantly easier to read than the same code written for the classic CryptoAPI.
PS: For VFP you also need the API declarations (which are included in CNG.PRG):
*========================================================
* API declarations for the CNG API.
*========================================================
Declare Long BCryptOpenAlgorithmProvider in BCrypt.DLL ;
Long @phAlgorithm, String pszAlgId, ;
String pszImplementation, Long dwFlags
Declare Long BCryptGetProperty in BCrypt.DLL ;
Long hObject, String pszProperty, Long @pbOutput, ;
Long cbOutput, Long @pcbResult, Long dwFlags
Declare Long BCryptCreateHash in BCrypt.DLL ;
Long hAlgorithm, Long @phHash, String @pbHashObject, ;
Long cbHashObject, String pbSecret, Long cbSecret, ;
Long dwFlags
Declare Long BCryptHashData in BCrypt.DLL ;
Long hHash, String pbInput, Long cbInput, Long dwFlags
Declare Long BCryptFinishHash in BCrypt.DLL ;
Long hHash, String @pbOutput, Long cbOutput, Long dwFlags
Declare Long BCryptDestroyHash in BCrypt.DLL ;
Long hHash
Declare Long BCryptCloseAlgorithmProvider in BCrypt.DLL;
Long hAlgorithm, Long dwFlags
Declare Long BCryptDestroyKey in BCrypt.DLL ;
Long hKey
Declare long BCryptFinalizeKeyPair in BCrypt.DLL ;
Long hKey, Long dwFlags
Declare Long BCryptGenerateKeyPair in BCrypt.DLL ;
long hAlgorithm, long @phKey, Long dwLength, ;
Long dwFlags
Declare Long BCryptExportKey in BCrypt.DLL ;
Long hKey, Long hExportKey, String pszBlobType, ;
String @pbOutput, Long cbOutput, Long @pcbResult, ;
Long dwFlags
Declare Long BCryptEncrypt in BCrypt.DLL ;
Long hKey, String pbInput, Long cbInput, ;
String pPaddingInfo, String @pbIV, Long cbIV, ;
String @pbOutput, Long cbOutput, Long @pcbResult, ;
Long dwFlags
Declare Long BCryptImportKeyPair in BCrypt.DLL ;
Long hAlgorithm, Long hImportKey, String pszBlobType, ;
Long @phKey, String pbInput, Long cbInput, ;
Long dwFlags
Declare Long BCryptDecrypt in BCrypt.DLL ;
Long hKey, String pbInput, Long cbInput, ;
String pPaddingInfo, String @pbIV, Long cbIV, ;
String @pbOutput, Long cbOutput, Long @pcbResult, ;
Long dwFlags
The CNG API doesn’t make heavy use of magic numbers in the form of constants; instead it uses readable Unicode strings
All CNG functions are located in BCRYPT.DLL. There are a few notable things about the new API. Unlike most previous APIs the CNG API doesn’t make heavy use of magic numbers in the form of constants. Instead of giving each property and each algorithm some number, the API function uses readable strings. The only drawback for us is that these strings are Unicode strings. The STRCONV function converts strings from ANSI to Unicode.
To encrypt data with asymmetric keys you first have to generate them. Here’s the code to generate RSA 2048 bit keys using the CNG:
*========================================================
* Generates a pair of public and private keys.
*========================================================
Lparameters rcPrivate, rcPublic
* Initialize API
Local llOK
llOK = .T.
Do CNG.prg
* Get a handle to the algorithm.
Local lnAlg
lnAlg = 0
If m.llOK
llOK = BCryptOpenAlgorithmProvider( @lnAlg, ;
Strconv("RSA",5)+Chr(0), NULL, 0 ) == 0
EndIf
* Generate the keys.
Local lnKey
lnKey = 0
If m.llOK
llOK = BCryptGenerateKeyPair( ;
m.lnAlg, @lnKey, 2048, 0 ) == 0
EndIf
If m.llOK
llOK = BCryptFinalizeKeyPair( lnKey, 0 ) == 0
EndIf
* Get the two keys
If m.llOK
rcPrivate = ExportKey( m.lnKey, "RSAPRIVATEBLOB" )
rcPublic = ExportKey( m.lnKey, "RSAPUBLICBLOB" )
if Empty(m.rcPrivate) or Empty(m.rcPublic)
llOK = .F.
EndIf
EndIf
* Cleanup
If m.lnAlg != 0
BCryptCloseAlgorithmProvider( m.lnAlg, 0 )
EndIf
If m.lnKey != 0
BCryptDestroyKey( m.lnKey )
EndIf
Return m.llOK
*========================================================
* Returns the key value
*========================================================
Procedure ExportKey( tnKey, tcKey)
Local llOK
llOK = .T.
* Determine the size of the key
Local lnSize
If m.llOK
lnSize = 0
llOK = BCryptExportKey( m.tnKey, 0, ;
Strconv(m.tcKey,5)+Chr(0), NULL, 0, @lnSize, ;
0 ) == 0
EndIf
* Request the key
Local lcKey
If m.llOK
lcKey = Space(m.lnSize)
llOK = BCryptExportKey( m.tnKey, 0, ;
Strconv(m.tcKey,5)+Chr(0), @lcKey, Len(m.lcKey), ;
@lnSize, 0 ) == 0
EndIf
* Return key if successfull
If not m.llOK
lcKey = ""
EndIf
Return m.lcKey
The keys are two strings that you can store away anywhere you want. Only one of them should be embedded into the application, the other should safely remain in your office. Encryption is a bit confusing. Basically, you have two keys. One of them is called private, the other one public. Which one is which is a matter of definition. You might be tempted to use the private key for encryption, and the public key for decryption.
Remember, how RSA is used in exchanging keys? One side sends out the public key. The receiving side encrypts a password with the public key which the sender decrypts with the private key. For this reason CNG uses the public key for encryption, and the private key for decryption. The following program encrypts data using the public key:
*========================================================
* Encrypts data using the RSA algorithm.
*========================================================
Lparameters tcData, tcPublicKey
* Initialize API
Local llOK
llOK = .T.
Do CNG.prg
* Get a handle to the RSA algorithm.
Local lnAlg
lnAlg = 0
If m.llOK
llOK = BCryptOpenAlgorithmProvider( @lnAlg, ;
Strconv("RSA",5)+Chr(0), NULL, 0 ) == 0
EndIf
* Import the public key
Local lnKey
lnKey = 0
If m.llOK
llOK = BCryptImportKeyPair( m.lnAlg, 0, ;
Strconv("RSAPUBLICBLOB",5)+Chr(0), @lnKey, ;
m.tcPublicKey, Len(m.tcPublicKey), 0 ) == 0
EndIf
* Determine the size of the encrypted data blob
Local lnSize
If m.llOK
lnSize = 0
llOK = BCryptEncrypt( m.lnKey, m.tcData, ;
Len(m.tcData), NULL, NULL, 0, NULL, 0, @lnSize, ;
0x00000002 ) == 0
EndIf
* Encrypt the data block
Local lcEncrypted
If m.llOK
lcEncrypted = Space(m.lnSize)
llOK = BCryptEncrypt( m.lnKey, m.tcData, ;
Len(m.tcData), NULL, NULL, 0, @lcEncrypted, ;
Len(m.lcEncrypted), @lnSize, 0x00000002 ) == 0
EndIf
* Cleanup
If m.lnAlg != 0
BCryptCloseAlgorithmProvider( m.lnAlg, 0 )
EndIf
If m.lnKey != 0
BCryptDestroyKey( m.lnKey )
EndIf
If not m.llOK
lcEncrypted = ""
EndIf
Return m.lcEncrypted
To decrypt data you need the private key.
*========================================================
* Decrypts RSA encrypted data using the private key.
*========================================================
Lparameters tcData, tcPrivateKey
* Initialize API
Local llOK
llOK = .T.
Do CNG.prg
* Get a handle to the RSA algorithm.
Local lnAlg
lnAlg = 0
If m.llOK
llOK = BCryptOpenAlgorithmProvider( @lnAlg, ;
Strconv("RSA",5)+Chr(0), NULL, 0 ) == 0
EndIf
* Import the private key
Local lnKey
lnKey = 0
If m.llOK
llOK = BCryptImportKeyPair( m.lnAlg, 0, ;
Strconv("RSAPRIVATEBLOB",5)+Chr(0), @lnKey, ;
m.tcPrivateKey, Len(m.tcPrivateKey), 0 ) == 0
EndIf
* Determine the size of the decrypted data block
Local lnSize
If m.llOK
lnSize = 0
llOK = BCryptDecrypt( m.lnKey, m.tcData, ;
Len(m.tcData), NULL, NULL, 0, NULL, 0, @lnSize, ;
0x00000002 ) == 0
EndIf
* Decrypt the data block
Local lcPlainText
If m.llOK
lcPlainText = Space(m.lnSize)
llOK = BCryptDecrypt( m.lnKey, m.tcData, ;
Len(m.tcData), NULL, NULL, 0, @lcPlainText, ;
Len(m.lcPlainText), @lnSize, 0x00000002 ) == 0
EndIf
* Cleanup
If m.lnAlg != 0
BCryptCloseAlgorithmProvider( m.lnAlg, 0 )
EndIf
If m.lnKey != 0
BCryptDestroyKey( m.lnKey )
EndIf
If not m.llOK
lcPlainText = ""
EndIf
Return m.lcPlainText
It’s important to know one more peculiarity: when you retrieve the public key, all you get is the public key. When retrieving the private key, you get a package that contains both keys, as the public key is known anyway. When using asymmetric encryption to exchange keys that’s not an issue, because you publish the public key, and keep the private key private.
This has impact on using encryption in your application. It is safe to use asymmetric encryption if you want to encrypt something on the users PC that only you can decrypt. However, you cannot use it to encrypt something on your machine and have your application decrypt it on the user's machine, such as license information.
Simple RSA encryption suffers from another problem. As a block based algorithm, you can only encrypt a block that is smaller than the key. The code above which uses a 2048 bit key can encrypt 253 bytes. If your file is larger, you have to manually split the file into blocks of 253 bytes. There is no automatic way to do this.
Conclusion
Despite all this, asymmetric encryption has usages in software development. For instance, error logs contain lots of information that are very valuable to hackers and developers, but less so to average users. By encrypting your error logs with your public key in the application, you can ensure that only you can access them to fix bugs. Instead of the entire log you could also create a random password and just encrypt the password with RSA. Then use the password for symmetric encryption.
Sources
De sources die bij dit artikel horen kun je downloaden via Wollenhaupt_CryptoApiNextGen_SRC.zip.