Share the password
(a char[]
) and salt
(a byte[]
—8 bytes selected by a SecureRandom
makes a good salt—which doesn't need to be kept secret) with the recipient out-of-band. Then to derive a good key from this information:
/* Derive the key, given password and salt. */
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
The magic numbers (which could be defined as constants somewhere) 65536 and 256 are the key derivation iteration count and the key size, respectively.
The key derivation function is iterated to require significant computational effort, and that prevents attackers from quickly trying many different passwords. The iteration count can be changed depending on the computing resources available.
The key size can be reduced to 128 bits, which is still considered "strong" encryption, but it doesn't give much of a safety margin if attacks are discovered that weaken AES.
Used with a proper block-chaining mode, the same derived key can be used to encrypt many messages. In Cipher Block Chaining (CBC), a random initialization vector (IV) is generated for each message, yielding different cipher text even if the plain text is identical. CBC may not be the most secure mode available to you (see AEAD below); there are many other modes with different security properties, but they all use a similar random input. In any case, the outputs of each encryption operation are the cipher text and the initialization vector:
/* Encrypt the message. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] ciphertext = cipher.doFinal("Hello, World!".getBytes(StandardCharsets.UTF_8));
Store the ciphertext
and the iv
. On decryption, the SecretKey
is regenerated in exactly the same way, using using the password with the same salt and iteration parameters. Initialize the cipher with this key and the initialization vector stored with the message:
/* Decrypt the message, given derived key and initialization vector. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
String plaintext = new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
System.out.println(plaintext);
Java 7 included API support for AEAD cipher modes, and the "SunJCE" provider included with OpenJDK and Oracle distributions implements these beginning with Java 8. One of these modes is strongly recommended in place of CBC; it will protect the integrity of the data as well as their privacy.
A java.security.InvalidKeyException
with the message "Illegal key size or default parameters" means that the cryptography strength is limited; the unlimited strength jurisdiction policy files are not in the correct location. In a JDK, they should be placed under ${jdk}/jre/lib/security
Based on the problem description, it sounds like the policy files are not correctly installed. Systems can easily have multiple Java runtimes; double-check to make sure that the correct location is being used.
As Alex already mentioned, Password-Based Encryption is the way to go for you. AES keys need to be exactly 128, 192 or 256 bits long. So passwords of arbitrary length won't work in your situation right away, but using a "normal" password of the right size is also wrong, because these kinds of passwords do not contain enough entropy and will allow attackers to brute-force them more easily because they are not random enough.
With that said, let's take a look at your implementation. Instead of taking a String as a password in PHP what you should probably do is generate 16 bytes (128 bit) with Java's `SecureRandom' class or anything equivalent in PHP (don't know if such a thing is available, remember it needs to be a cryptographically secure random number). Encode that using Base64 so that you obtain a String representation of the key to be used both in Java and PHP.
Do the same for an IV that is exactly 16 bytes long (always the same as AES block size), encode it to Base64 again. You do this not for the reason that it has to be secret or complicated, but for the encoding issues that your approach is almost surely to cause. As far as I know PHP's default encoding is not UTF-8, so your approach is doomed to fail.
Using these two values (Base64-decode them before usage!) you're ready to go on the PHP side. In Java, obtain your Cipher using
Cipher c = Cipher.getInstance("AES/CBC/PKCS5PAdding");
and initialize it with the same IV and key used in PHP (again remember to Base64-decode first).
Final note: I do not recommend what I just described because it means that your keys will be hardcoded into your source files. That is actually not really secure and generally frowned upon. If you want to do symmetric encryption with passwords, then you should use PBE and not store the passwords anywhere, at the very least not in the client code. Another possibility is involving asymmetric public/private key cryptography, e.g. using a Diffie-Hellman key exchange.
Best Answer
The key you generated is 128 bytes, not 128 bits. "Key Length" should be 16.