netnix.org
Networking and Unix


Updated Encryption Routines in TemplateFx v2.54

 August 12th, 2016Aug 12th, 2016        0

In the spirit of public disclosure as all encryption/decryption routines should be publicly available for scrutiny, the following article includes the updated routines which are within TemplateFx v2.54. Previously TemplateFx was using AES-128 for encryption and HMAC-SHA256 for authentication. The keys were being derived using PBKDF2 with HMAC-SHA1 using 100,000 iterations. However, I wasn’t completely happy with the following bits:

  1. I was using PBKDF2 with HMAC-SHA1 as the PRF (pseudo-random function) which didn’t provide enough output for my HMAC-SHA256 key (160 bit output instead of 256 bit). This also meant I had to run PBKDF2 twice, which was a bit messy – once for my AES-128 key and once (with a different salt) for my HMAC-SHA256 key.
  2. My HMAC wasn’t across all the data – I was just including the ciphertext, which means someone could have changed my encryption salt to manipulate my decrypted ciphertext while still passing the HMAC authentication check.
  3. There was no support for AES-256 if the user had decided to install the “Java Cryptography Extension Unlimited Strength Jurisdiction Policy Files”.

The updated routines below address all the above issues by using the following technique:

  1. We generate a 64 byte pseudo-random SALT which we pass to PBKDF2 (along with the password) which uses HMAC-SHA512 as the PRF with 50,000 iterations. This generates 512 bits of output which we split in half – the first 128 or 256 bits is used for AES (depending on key length) and the second 256 bits is used as our HMAC-SHA256 key.
  2. We generate a 16 byte pseudo-random IV – this isn’t technically necessary as the routines never use the same key twice, so there is no risk of creating duplicate ciphertext. However, it adds extra protection for anyone who thinks it is a clever idea of re-using the SALT.
  3. We then encrypt the plaintext using AES in CTR mode using either a 128 or 256 bit key.
  4. We then use HMAC-SHA256 to generate a HMAC across “kLEN || SALT || IV || CIPHERTEXT”. It is critical that you include all fields which could change the decrypted ciphertext in the HMAC. The output is then constructed using “kLEN || SALT || IV || CIPHERTEXT || HMAC” before encoding using Base64.
  // This value should be reviewed regularly and increased as technology improves.
  private final int PBKDF2Iterations = 50000;

  public String encrypt(String plaintext, String password, int keyLength) throws Exception {
    SecureRandom r = SecureRandom.getInstance("SHA1PRNG");

    // Template will validate that keyLength is either 128, 192 or 256 before being passed to this function.
    // It also checks we are able to use AES-192 and AES-256 as well, but the below check has
    // been included for completeness.
    if (keyLength <= Cipher.getMaxAllowedKeyLength("AES")) {
      // Generate a 64 byte random salt for PBKDF2 (HKDF RFC states salt size should match hashLen).
      byte[] salt = new byte[64]; r.nextBytes(salt);
      // Perform PBKDF2 using HMAC-SHA512 - native implementation available in Java 8
      byte[] dkey = PBKDF2WithHmacSHA512(password, salt, PBKDF2Iterations, 512);
      // Split PBKDF2 output in half for AES and HMAC keys
      byte[] kaes = Arrays.copyOfRange(dkey, 0, (keyLength / 8));
      byte[] khmac = Arrays.copyOfRange(dkey, 32, 64);
      // Generate 16 byte random IV for AES
      byte[] iv = new byte[16]; r.nextBytes(iv);

      // Perform encryption of ciphertext
      Cipher c = Cipher.getInstance("AES/CTR/NoPadding");
      c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(kaes, "AES"), new IvParameterSpec(iv));
      byte[] ciphertext = c.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));

      // Construct output - kLEN || SALT || IV || CIPHERTEXT
      byte[] os = new byte[1 + 64 + 16 + ciphertext.length + 32];
      os[0] = (byte)(keyLength / 8); // keyLength in bytes
      System.arraycopy(salt, 0, os, 1, 64);
      System.arraycopy(iv, 0, os, 65, 16);
      System.arraycopy(ciphertext, 0, os, 81, ciphertext.length);

      Mac m = Mac.getInstance("HmacSHA256");
      m.init(new SecretKeySpec(khmac, "HmacSHA256"));
      // Perform HMAC-SHA256 across kLEN || SALT || IV || CIPHERTEXT
      byte[] hmac = m.doFinal(Arrays.copyOfRange(os, 0, os.length - 32));
      // Add the calculated HMAC to the end of the output string
      System.arraycopy(hmac, 0, os, 81 + ciphertext.length, 32);
      // Return Base64 encoded string
      return new String(encodeBase64(os));
    }
    else {
      throw new Exception(keyLength + " bit encryption requires Java Cryptography Extension Unlimited Strength Jurisdiction Policy Files");
    }
  }

The decryption is the opposite but we don’t do anything unless the HMAC is valid:

  1. We decode the string using Base64, confirm the string meets the minimum length requirements, extract the SALT and HMAC which we then use to derive the key using the provided password using PBKDF2 with the same parameters as before (using the last 256 bits as the HMAC key).
  2. We then use HMAC-SHA256 to generate a HMAC across the decoded string (everything except the HMAC) using the key derived from PBKDF2. We then use a time constant comparison function (to mitigate the risk of timing attacks) to verify our generated HMAC matches the HMAC at the end of the encoded string.
  3. We then retrieve the used key length from the beginning of the string, the IV and the ciphertext and decrypt it using AES using the derived AES key from PBKDF2 (first 128 or 256 bits, depending on key length).
  public String decrypt(String eos, String password) throws Exception {
    // Base64 decode the provided string
    byte[] os = decodeBase64(eos.toCharArray());

    // Check we have a minimum length > length(kLEN || SALT || IV || HMAC)
    if (os.length > 113) {
      // Recover 64 byte salt
      byte[] salt = Arrays.copyOfRange(os, 1, 65);
      // Derive 512 bits of output from PBKDF2 using HMAC-SHA512
      byte[] dkey = PBKDF2WithHmacSHA512(password, salt, PBKDF2Iterations, 512);
      // Take the last 256 bits as our HMAC key
      byte[] khmac = Arrays.copyOfRange(dkey, 32, 64);

      Mac m = Mac.getInstance("HmacSHA256");
      m.init(new SecretKeySpec(khmac, "HmacSHA256"));
      // Generate a HMAC of all the data excluding the provided HMAC
      byte[] chmac = m.doFinal(Arrays.copyOfRange(os, 0, os.length - 32));
      byte[] hmac = Arrays.copyOfRange(os, os.length - 32, os.length);

      // This is a time constant comparison function
      if (MessageDigest.isEqual(hmac, chmac)) {
        // Retrieve the key length (we know this hasn't been tampered with as it is included as part of the HMAC)
        int keyLength = os[0] * 8;
        // Check the recipient is allowed to decrypt the specific key length
        if (keyLength <= Cipher.getMaxAllowedKeyLength("AES")) {
          // Extract the AES Key, IV and ciphertext from the encoded string
          byte[] kaes = Arrays.copyOfRange(dkey, 0, (keyLength / 8));
          byte[] iv = Arrays.copyOfRange(os, 65, 81);
          byte[] ciphertext = Arrays.copyOfRange(os, 81, os.length - 32);

          // Decrypt the ciphertext using AES and the specified key length
          Cipher c = Cipher.getInstance("AES/CTR/NoPadding");
          c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(kaes, "AES"), new IvParameterSpec(iv));
          byte[] plaintext = c.doFinal(ciphertext);
          // Return the decrypted string
          return new String(plaintext, StandardCharsets.UTF_8);
        }
        else {
          throw new Exception(keyLength + " bit encryption requires Java Cryptography Extension Unlimited Strength Jurisdiction Policy Files");
        }
      }
      else {
        throw new Exception("failed HMAC validation - maybe incorrect password?");
      }
    }
    throw new Exception("unexpected encrypted string length - too short");
  }

Java 8 provides a native implementation of PBKDF2 using HMAC-SHA512 and Base64 routines but unfortunately Java 7 doesn’t. I have included the above routines in a TemplateFx_AES class which contains the necessary helper routines for Java 7 that are used in TemplateFx.

General


Leave a Reply

Comment:

Name:

e-mail Address: