Java 256-bit AES Password-Based Encryption
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.
AES-256 Password Based Encryption/Decryption in Java
I think that you are making two mistakes :)
I've corrected your sample code to make it work :
import java.security.AlgorithmParameters;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
public class EncryptionDecryption {
private static String salt;
private static int iterations = 65536 ;
private static int keySize = 256;
private static byte[] ivBytes;
private static SecretKey secretKey;
public static void main(String []args) throws Exception {
salt = getSalt();
char[] message = "PasswordToEncrypt".toCharArray();
System.out.println("Message: " + String.valueOf(message));
System.out.println("Encrypted: " + encrypt(message));
System.out.println("Decrypted: " + decrypt(encrypt(message).toCharArray()));
}
public static String encrypt(char[] plaintext) throws Exception {
byte[] saltBytes = salt.getBytes();
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(plaintext, saltBytes, iterations, keySize);
secretKey = skf.generateSecret(spec);
SecretKeySpec secretSpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretSpec);
AlgorithmParameters params = cipher.getParameters();
ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] encryptedTextBytes = cipher.doFinal(String.valueOf(plaintext).getBytes("UTF-8"));
return DatatypeConverter.printBase64Binary(encryptedTextBytes);
}
public static String decrypt(char[] encryptedText) throws Exception {
System.out.println(encryptedText);
byte[] encryptedTextBytes = DatatypeConverter.parseBase64Binary(new String(encryptedText));
SecretKeySpec secretSpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretSpec, new IvParameterSpec(ivBytes));
byte[] decryptedTextBytes = null;
try {
decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return new String(decryptedTextBytes);
}
public static String getSalt() throws Exception {
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
byte[] salt = new byte[20];
sr.nextBytes(salt);
return new String(salt);
}
}
The first mistake is that you generate 2 different salts (when using the encrypt method), so encrypted/decrypted logs were differents (logical, but the decryption would still work because you are calling the decryption directly after encryption).
The second mistake was for the secret key. You need to generate a secret key when you are encrypting, but not decrypting. To put it more simply, it is as if i was encrypting with the password "encrypt" and that you are trying to decrypt it with the password "decrypt".
I would advise you to generate every random stuff (such as private key, salt etc on startup). But beware that when you'll stop your app, you won't be able to decrypt old stuff unless getting the exact same random stuff.
Hope I helped :)
Regards,
AES 256 bit encryption
By default Java supports only 128-bit encryption. If you want to exceed more than that you need the unlimited strength file installed. To do that download the jars and extract the jar files from the zip and save them in ${java.home}/jre/lib/security/.
For more see here: https://stackoverflow.com/a/6481658/1008278
Java AES 256 encryption
Yes, it will as 64 characters are 32 bytes and 256 bits and any sequence of 256 bits can be used as an AES-256 key.
I suggest you to use DatatypeConverter.parseHexBinary (or similar utility from library of your choice) to convert hexadecimal strings into byte arrays.
Same password results in different AES keys in Java
Your understanding is correct and the key in your code is the same.
You can't "print" the aesKey
since SecretKeySpec
has no toString()
method. So the built-in Object.toString()
will be called, which just prints the address of the object in memory
javax.crypto.spec.SecretKeySpec@14c7f // <--- useless info //
There are just a few issues with your code:
Don't convert encrypted bytes to a UTF-8 string. There can be combinations which are invalid in UTF-8, as well as 00 bytes. Use Base64 or Hex encoding for printing encrypted data.
You shouldn't use ASCII bytes as a key, that greatly reduces security of the key. Derive a key from the password, at least with SHA-256, but preferably PBKDF2 or scrypt.
Use a high entropy random IV and store it with the ciphertext.
Here's an updated version demonstrating that it's working:
public static String encryptMessageGH(String message, String password) throws Exception {
MessageDigest sha = MessageDigest.getInstance("SHA-256");
byte[] key = sha.digest(password.getBytes("UTF-8"));
SecretKeySpec aesKey = new SecretKeySpec(key, "AES");
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, aesKey, new IvParameterSpec(iv));
byte[] ciphertext = cipher.doFinal(message.getBytes());
byte[] encrypted = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, encrypted, 0, iv.length);
System.arraycopy(ciphertext, 0, encrypted, iv.length, ciphertext.length);
return Base64.getEncoder().encodeToString(encrypted);
}
public static String decryptMessageGH(String encryptedMessage, String password) throws Exception {
MessageDigest sha = MessageDigest.getInstance("SHA-256");
byte[] key = sha.digest(password.getBytes("UTF-8"));
SecretKeySpec aesKey = new SecretKeySpec(key, "AES");
byte[] encrypted = Base64.getDecoder().decode(encryptedMessage);
byte[] iv = new byte[16];
System.arraycopy(encrypted, 0, iv, 0, iv.length);
byte[] ciphertext = new byte[encrypted.length - iv.length];
System.arraycopy(encrypted, iv.length, ciphertext, 0, ciphertext.length);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, aesKey, new IvParameterSpec(iv));
return new String(cipher.doFinal(ciphertext), "UTF-8");
}
public static void main(String[] args) throws Exception {
String orig = "Test message";
String enc = encryptMessageGH(orig, "abcdef123");
System.out.println("Encrypted: " + enc);
String dec = decryptMessageGH(enc, "abcdef123");
System.out.println("Decrypted: " + dec);
}
Output:
Encrypted: lcqcd9UZpjLSY9SsQ/N7kV/cpdzL3c7HQcCSiIs6p/k=
Decrypted: Test message
Does this use 256-bit AES encryption?
Your example appears to use a 32-byte key and a 256 bit version of the AES cryptosystem. So, technically yes it is 256-bit AES encryption. The actual size of the message determines the resulting output but it should be larger then the original message. Also, you should be able to decrypt it and get the original message. Finally, using a constant iv is not recommended and may well render your system insecure in and of itself.
Related Topics
Running Code in Main Thread from Another Thread
Concurrent Modification Exception: Adding to an Arraylist
Why Does My Function That Calls an API or Launches a Coroutine Return an Empty or Null Value
How to Redirect Multiple Types of Users to Their Respective Activities
Nullpointerexception Accessing Views in Oncreate()
How to Read a Text File in Android
Android - Get Real Path of a .Txt File Selected from the File Explorer
Can't Create Directory in Android 10
What's the Best Way to Share Data Between Activities
Why Doesn't "System.Out.Println" Work in Android
Why Doesn't Recyclerview Have Onitemclicklistener()
One Time Login in App - Firebaseauth
Firebase Ui Authentication With Google Fails With Message (Code:10 Message:10)
How to Request Location Permission At Runtime
Why Does Gson Fromjson Throw a Jsonsyntaxexception: Expected Begin_Object But Was Begin_Array
How to Properly Stop the Thread in Java
Java: Splitting a Comma-Separated String But Ignoring Commas in Quotes