Search code examples
javaencryptioncryptographypassword-encryptionapr

Exact algorithm used by apr_crypto_passphrase


I need to write an apache module that encrypts data and I need to decrypt the data in java.

So far I managed to encrypt the data on the apache side, however I cannot find out the key that is effectively used for encryption because its hidden away in an apr_crypto_passphrase and stored in an apr_crypto_key_t, which is an incomplete type.

The documentation mentions that it uses by "default the PBKDF2 algorithm", but doesn't clarify which flavor, e.g. PBKDF2WithHmacSHA256, nor what "by default" means, nor can I see a way to change the algorithm.

I am using the OpenSSL driver and want to use AES 128 in CBC mode.

How do I get (or set) the key effectively used, or how can I compute this key in Java.


Solution

  • At least with the OpenSSL driver PBKDF2 with SHA1 is used (this is hard-coded).

    Below are two code pieces that produce the same output from given plain-text, password, salt, iteration count and IV.

    C / APR (functions referenced can be found in testcrypto.c):

    char *plain_text = apr_pstrdup(pool, "some value");
    char *passphrase = apr_pstrdup(pool, "some pass");
    
    const int salt_len = 9;
    const char salt_in[] = {1, 1, 1, 1, 1, 1, 1, 1, 1};
    
    const int iterations = 1000;
    
    // everything after the 16th byte is ignored for AES 128
    const int iv_len = 16;
    const char iv_in[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
    
    unsigned char *cipher_text = NULL;
    apr_size_t cipher_text_len = 0;
    apr_size_t block_size = 0;
    apr_size_t iv_size = 0;
    
    const apr_crypto_driver_t *driver = get_openssl_driver(r, pool);
    apr_crypto_t *f1 = factory(pool, driver);
    
    char *salt = apr_palloc(pool, salt_len);
    memcpy(salt, salt_in, salt_len);
    
    apr_crypto_key_t *crypto_key = NULL; // init to NULL is important, see docs!
    
    apr_crypto_passphrase(&crypto_key, &iv_size, passphrase, strlen(passphrase), salt, salt_len, APR_KEY_AES_128, APR_MODE_CBC, 1, iterations, f1, pool);
    
    unsigned char *iv = apr_palloc(pool, iv_len);
    memcpy(iv, iv_in, iv_len);
    
    encrypt_block(NULL, pool, driver, f1, crypto_key, plain_text, strlen(plain_text),
                               &cipher_text, &cipher_text_len, &iv, &block_size, "KEY_AES_128/MODE_CBC");
    
    // Note: b64_len includes spaces for '\0' terminator
    int b64_len = apr_base64_encode_len(cipher_text_len);
    char *b64 = apr_pcalloc(pool, b64_len);
    apr_base64_encode_binary(b64, cipher_text, cipher_text_len);
    

    Java:

    /* Derive the key, given passphrase and salt. */
    final String plainText = "some value";
    final String passphrase = "some pass";
    final byte[] salt = {1, 1, 1, 1, 1, 1, 1, 1, 1};
    final int iterations = 1000;
    byte[] iv = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
    
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    KeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(), salt, iterations, 128);
    SecretKey tmp = factory.generateSecret(keySpec);
    SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(iv));
    byte[] ciphertext = cipher.doFinal(plainText.getBytes("UTF-8"));
    
    System.out.println(DatatypeConverter.printBase64Binary(ciphertext));
    

    Reminder: do not use these values for productive code.