n l i t e d
May 19 2017
The AES algorithm defaults to 128bit keys. I should be able to bump this up to 256 bits, which seems to be the strongest symmetrical encryption available. This is tricky to test; because the encryption keys are themselves convoluted using AES, changing the algorithm results in incompatible keys. So I will need to regenerate new keys and new ciphers each time I tweak the algorithm.
The docs tell me AES supports only a limited set of key lengths: 128, 192, and 256. Calling BCryptGetProperty(hAES,BCRYPT_KEY_LENGTHS) returns a minimum of 128 and maximum of 256. I set my SymKeySz= KeyLengths.dwMaxLength/8 and pass that into BCryptGenerateSymmetricKey().
This seems to generate valid keys and cipher output, but the data
decryption fails in BCryptDecrypt() with error
0xc000003e : {Data Error} An error in reading or writing data occurred.
This is especially perplexing because the first call to
BCryptDecrypt() to determine the output size succeeds, then the second
call fails. The error code (which could be lying to me) seems to point
to the pDst and DstSz parameters, but they look fine.
I spent several hours working on this yesterday and another hour today. I do not understand why 256 bits fails. It does work with 128 and 192 bits. There may be no benefit to AES-256 anyway: StackExchange
I am tabling this problem for now.
UPDATE 20180121: 0xC000003E can be caused when IV data is provided and length does not match the block size.
Set BCRYPT_BLOCK_PADDING only if the source data is an odd size.
if((StreamFlags & BLOCK_LAST) && (szSrc % BlockLen)>0) BFlags|= BCRYPT_BLOCK_PADDING;
Things I have learned along the way:
Using constant IV values is a fundamental mistake that compromises the cipher. I need to use a random IV for every encryption instead. Does this mean I need to store every random IV somewhere? OR, does it mean I simply need to ignore the first decrypted cipher block?
Ignoring the first block doesn't work. This makes sense as the decryption is an accumulative process, the output from the current cipher block depends on the state from the previous block. To decode a cipher stream, I must know the IV used to encrypt it.
So now I am back to a key management problem: How do I store the encryption IV if it is random? The standard answer to this problem is to rely on the password, which is hashed to form the IV -- essentially relying on the user to remember and provide the IV.
I could do a similar thing. I already need to store and remember a customer password (in the form of a hash) for website access. I could reuse this as the IV for the cipher key. However, this becomes a nightmare when the user changes his login password. All existing keys would continue to require the old password until the keys were regenerated. (The new keys using the new IV would still generate the same internal key, so the new key would should still be able to decrypt old ciphers.)
Or I could use an internally well-known secret for cipher key, such as a hash of the customer ID. This should never change, but would be a unique IV for each customer. I would need a method to expand the customer ID out to a 128bit hash. I could start with the a constant salt string, overwrite part of it with the customer ID and the date and time when the account was created, then run this through SHA256. The result would be stored in my customer database as base64 text and used as the IV for all cipher keys generated for that customer. This text would need to be copied to the customer's computer so it would be available for AutoBOM to use it to decrypt the license file.
So the user would need to deal with 3 separate texts:
Since the customer IV and customer key are both secrets, they could be combined into the same text when delivered to the customer. However, I need to maintain them as two distinct items since the key can change.
This is not quite right... What I want is for the actual cipher key to never change after I generate it, but the cipher text of the key changes when the user changes his password.
At no point did the user enter his password to decrypt the license file, so why bother using it to encrypt the key pair?
If neither the license file nor key pair depend on the user's password, why not just generate them once using a random IV that is stored on the license server? The secret key pair contains all the information needed, except the IV used to encrypt the key pairs.
Maybe I am over-thinking the whole thing from the start... What is the point of encrypting the key pair if it must always be considered a secret? Encrypting the key pair simply kicks the can one step further down the road without solving anything. I should just leave the key pair as binary plaintext and treat it as a secret.
Taking a step back, the fundament problem I am trying to solve is protecting the license file and blocking unauthorized use of it:
So it appears that I have been pursuing a red herring for the past two days. The extra step of encrypting the private/public key pair introduces extra complexity without really providing any extra security. These are the hard lessons of cryptography.
Why bother with the key at all? Is it a problem if AutoBOM can decrypt any license file for any customer? This would require a generic license file key to be embedded in AutoBOM that could decrypt all license files. If this key were to be extracted from an AutoBOM executable, someone could then set up their own license server that would be accepted by all existing AutoBOM binaries with a local DNS redirect. But couldn't a hacker accomplish the same thing given any key that could decrypt any license file?
I was thinking about something else when it occurred to me that encryption is symmetric, which means there is no real difference between (random IV and constant plaintext) and (constant IV and random plaintext) with regard to the randomness of the ciphertext. If I can ignore the random plaintext in the decrypted stream....
I suddenly realized that the answer is simple and easy: Prefix the plaintext with random data before encrypting, then skip over the random data after decrypting. The random data at the beginning of the stream makes the first byte different and unpredictable, and it ripple through the entire cipher stream. Now I can reuse the same IV without anyone knowing. The more random data the better, up to a maximum of one cipher block (16 bytes).
With this change, encrypting the keys make sense again because it has the effect of rippling the randomness through the entire ciphertext. The problem of sharing the IV's goes away since I can now use hard-coded (secret) text.
An alternative is to use the raw bytes of the encryption key itself as the IV. This information is known to both parties and are the sancrosanct secret so they can be trusted. They are convoluted through the encryption of the plaintext stream, so the only risk is that someone might know the first block of the plaintext and be able to reverse the encryption to derive the IV, then know that the IV is the key. I can plug this hole by running the key bytes through a one-way hash that generates a 16 byte output.
An alternate alternative is to inject and skip the random data inside my EncodeBytes() and DecodeBytes() functions. I just need a flag that is set for the first block of the stream. This is an awkward (but doable) solution, since it would require allocating another temporary buffer to hold the first block plus the extra random data. This also makes the cipher streams non-standard, no other decryptor will know to skip the random header.
The code is a bit awkward, but making the random bytes completely invisible makes everything better.
The solution to protecting the salt was to randomize the input. This makes sure that every cipher stream will be unique even when encrypting identical data with the same keys and salts.
The risk is that a hacker will be able to extract the salt values from an executable file, know that the salt can be used to decipher the keys, decipher the key file using the salt, understand that the first 16 bytes should be ignored, reverse engineer the format of the deciphered key file, extract the key data, then use the salt to generate his own key file and license file.
The weak link is using only the salt encrypt the key file. This exposes (to a small degree) the format of the key file and the ability to generate new key files. However, the license file is encrypted using a hash of the private key (which I am assuming was compromised) and the user's password. The password is stored with the license file in (a different) hashed form. The hacker would also need to have the password hash and know how it is combined with the key to form the final key that is used to decrypt the license file. I can make this more difficult by hashing together the password hash and the private key to generate the key for the license file. Only the first-order hash of the password is stored on the user's computer, the hacker would need to know the code that is used to derive the second-order hash.
If someone is able to do all this, I can only assume he has complete access to the computer, is hooking all the BCrypt functions, and works for the NSA. At that point I don't care because I am already a millionaire since hacking AutoBOM is worth so much effort.
The best option for securing the salt value is to obfuscate it through some rudimentary math operation that does not require keys (something like a basic XOR and rotate) so that it is not obvious in a hex dump. Also change the linker names to something like "x1" instead of "PrivateSalt".
The downside to this approach is that it requires the license server to know PasswordHash1 to encrypt the license file. This requires the user to change his admin password on the license server, not the local computer. Then the local computer needs to fetch PasswordHash1 from the license server.
OR, I could do the inverse.
Comments are moderated. Anonymous comments are not visible to other users until approved. The content of comments remains the intellectual property of the poster. Comments may be removed or reused (but not modified) by this site at any time without notice.