n l i t e d

:



Thread Links
next

Crypto 3: Salts and IV

📢 PUBLIC Page 1012:19/19 | edit | chip 2018-01-25 10:46:10
Tags: Crypto

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 BCryptSetProperty() to set KEY_LENGTH or KEY_STRENGTH does not work. The key length is determined solely by the input to BCryptGenerateSymmetricKey().
  • Using the same salt for different plaintext inputs will generate ciphers that have identical leading bytes. I guess this is to be expected, since encryption is a deterministic transform -- the same input must always generate the same output. But it seems that this exposes the value of my salt given enough cipher samples. This would be why it is a bad idea to use a fixed salt. The salt value should be randomized by some session value, but then this session value must be a shared secret or stored somewhere. This StackExchange talks about "salt" vis-a-vis "IV". They are essentially the same thing, just a terminology difference: "Salt" is used for hashes and "IV" is used for encryption. And yes, reusing the same IV does expose the data:
    "NEVER reuse a salt value for distinct passwords. Never."
  • The Microsoft Cryptography - "Next Generation" is now nearly 10 years old, yet I could find very few source code examples for it.

Salt / IV

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:

  1. Customer IV: The base64 encoded IV used to encrypt the Customer key.
  2. Customer key: The public/private ECC key. This was encrypted using the customer IV and is used to generate the internal AES key used to transcrypt the actual data.
  3. License file: The actual license data, encrypted using the customer IV and key.

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.

  1. Create a new customer account, including a password hash.
  2. Generate the "Customer Key" -- a secret random SHA256 hash and store it in the customer record. This is the customer's permanent IV and is never exposed outside the license server.
  3. Hash the Customer Key with the user's password to form the Customer IV. This can be stored, but both the Customer Key and password are already being stored so it is redundant.
  4. Use the Customer IV to generate the customer's private/public key pair.
  5. Store the customer's key pair in the license server database. This is also redundant info.
  6. Generate the plain-text license file as base64 text.
  7. Encrypt the license file using the customer's private key and the corporate public key. (PKI seems overkill at this point...)
  8. Deliver the customer's key pair to the customer.
    This is a secret file. It would be nice if it required the user to provide his password, but that would require providing the Customer IV to hash against.
  9. Deliver the encrypted license file to the customer.
  10. AutoBOM then uses the customer's private key (stored on the customer's computer) and the corporate public key (fetched from the license server or included in the key file and stored on the customer's computer) to decrypt the license file.

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:

  • The contents of the license file need to be unreadable and unalterable to anyone except the license server and the AutoBOM code. This means encrypting it, which also implicitly authenticates it.
  • The license file can only be used on one computer by an authorized customer. This means embedding the computer's CPUID and the customer's password in the license file. If the CPUID matches, the license file is accepted. If the CPUID does not match, the user must enter his password. If the password matches, AutoBOM must be able to contact the license server to update the CPUID. This catches the case where someone posts a license file along with his password; the license server will deny the CPUID update when it exceeds a threshold (five?).
  • The key used to decipher the license file needs to be protected while being transmitted to customer, especially via email. I could rely on a machine-to-machine connection between AutoBOM and the license server, in which case the key could be transmitted as plaintext. Otherwise, the key needs to be encrypted and encoded as base64 text using an encrpytion key that is commonly known to both the license server and AutoBOM without being transmitted, such as the user's password.
  • The key needs to be unusable to anyone other than AutoBOM (and the license server). This means encrypting it, but using a secret that is know to both AutoBOM and the license server without being transmitted. This could be the user's password. It must be cached to avoid requiring the (admin) password to be entered every time AutoBOM starts. Caching means storing the hashed password as the decryption key for the key.

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?

Epiphany!

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.

Solution

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".

  1. At some point, prompt the user for his admin password.
  2. HASH(password) to create PasswordHash1 and store it on the user's computer.
  3. Decrypt the key file using the embedded salt value.
  4. HASH(PasswordHash1,PrivateKeyData) to create the license key data.
  5. Use the license key data to decrypt the license file.

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.

  1. The user creates the admin password on his local computer.
  2. The local computer generates and stores PwHash1.
  3. The local computer contacts the license server.
  4. The local computer authenticates by providing its key file and having the user enter the previous password. If the previous password is unknown, the license server authenticates using email and/or text message to the user's phone.
  5. The local computer pushes the new PwHash1 to the license server.
  6. The license server stores PwHash1.
  7. The license server uses HASH(PwHash1,PrivateKeyData) to generate PwHash2.
  8. The license server generates a new license file and encrypts it using PwHash2.
  9. The license server pushes the new license file down to the local machine.
  10. The local machine stores the new license file.

Generating the Key Pairs

  1. Create a random ECC key pair: BCryptGenerateKeyPair(hECC,&hKey,384,0);
  2. BCryptFinalizeKeyPair()
  3. Extract the private key
    1. Create a symmetric (AES) key: BCryptGenerateSymmetricKey()
    2. Get the block length (bytes): BCryptGetProperty(hKey,BCRYPT_BLOCK_LENGTH)
    3. Allocate the IV buffer
    4. Fill the IV buffer with the salt value
    5. Export the key data: BCryptExportKey()
    6. Allocate a KeyData_s struct and populate it with the key info.
    7. Encrypt the KeyData_s using the AES key.
      EncryptBytes() will prefix the plaintext with 16 bytes of randomness.
    8. Encode the ciphertext to base64 text.
    9. Save the base64 ciphertext to a file.
  4. Extract the public key the same way.

Loading a key pair

  1. Import the private key
    1. Create a symmetric (AES) key using the private salt text.
    2. Decode the base64 text to binary ciphertext.
    3. Decrypt the ciphertext using the AES key. (Stripping the randomness)
    4. Validate the KeyData_s struct.
    5. Import the key data: BCryptImportKeyPair()
  2. Import the public key (same as the private key)
  3. Create a secret agreement: BCryptSecretAgreement()
  4. Extract the combined key data: BCryptDeriveKey()
    Keep the key data to use as the transcryption salt.
  5. Create the symmetric (AES) key: BCryptGenerateSymmetricKey()

Encrypting user data

  1. For the first block only:
    1. Copy the salt into the IV buffer.
    2. Prefix the first plaintext block with random bytes.
  2. Encrypt: BCryptEncrypt()
  3. Encode the cipher text to base64 text.

Decoding user data

  1. For the first block only, copy the salt into the IV buffer.
  2. Decode from base64 text to binary ciphertext
  3. Decrypt: BCryptDecrypt()
  4. For the first block only, drop the first random bytes.


close comments Comments are closed.

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.

  1. [] ok delete


Page rendered by tikope in 230.118ms