I am attempting to implement a basic example of AES encryption across two devices in Java. However using the same password(128 bit) on both devices to generate an AES key results in different keys on both devices, each time we run the application. So we are unable to decrypt the text that we send between devices.
The methods I'm using are below, they are slightly modified versions of code I've found elsewhere:
public String encryptMessage(String message, String password) throws Exception {
// Creating key and cipher
SecretKeySpec aesKey = new SecretKeySpec(password.getBytes("UTF-8"), "AES");
byte[] iv = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
IvParameterSpec ivspec = new IvParameterSpec(iv);
//AES cipher
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
// encrypt the text
cipher.init(Cipher.ENCRYPT_MODE, aesKey, ivspec);
byte[] encrypted;
encrypted = cipher.doFinal(message.getBytes());
return new String(encrypted, "UTF-8");
}
public String decryptMessage(String encryptedMessage, String password) throws Exception {
// Creating key and cipher
byte[] passwordBytes = password.getBytes("UTF-8");
SecretKeySpec aesKey = new SecretKeySpec(passwordBytes, "AES");
byte[] iv = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
IvParameterSpec ivspec = new IvParameterSpec(iv);
//AES cipher
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
// decrypting the text
cipher.init(Cipher.DECRYPT_MODE, aesKey, ivspec);
String decrypted = new String(cipher.doFinal(encryptedMessage.getBytes(Charset.forName("UTF-8"))));
//returning decrypted text
return decrypted;
}
Every time I run this code and print out aesKey, it is different.
My understanding of AES and symmetric encryption is that given the same password it should generate the same key, otherwise how is it able to decrypt the artefact?
Have I got the wrong end of the stick wrt AES or can someone suggest what might be going on?
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
Related
I am currently developing an Android app that requests specific properties from my webserver, which look like this:
<properties>
<property name="Approved-IP" value="SomeIPAddresses"/>
</properties>
The app saves the site as a temporary XML file and tries to check these IPs.
I want to let the server encrypt the properties using AES and IF the app doesn't have an encryption password set use a default key like "test123".
The app then decrypts this information via the EasyCrypt library and uses it.
The problem is that I don't really know how to do it server-side, so that the information won't be passed in plain text.
So I found a solution for my program (beware, this is just for exercise, so I wouldn't call it safe in ANY way)
I am using this method to encrypt the String on my WebServer:
private static byte[] encrypt(String plain, String key) throws Exception {
byte[] clean = plain.getBytes();
int ivSize = 16;
byte[] iv = new byte[ivSize];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(key.getBytes("UTF-8"));
byte[] keyBytes = new byte[16];
System.arraycopy(digest.digest(), 0, keyBytes, 0, keyBytes.length);
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] encrypted = cipher.doFinal(clean);
byte[] encryptedIVAndText = new byte[ivSize + encrypted.length];
System.arraycopy(iv, 0, encryptedIVAndText, 0, ivSize);
System.arraycopy(encrypted, 0, encryptedIVAndText, ivSize, encrypted.length);
return encryptedIVAndText;
}
Then I am encoding the generated String with Base64
Base64.getEncoder().encodeToString(encryptedString)
And return the Base64 String.
My App decodes the returned String using the getDecoder function and decrypts it with this method:
public static String decrypt(byte[] encryptedIvTextBytes, String key) throws Exception {
int ivSize = 16;
int keySize = 16;
byte[] iv = new byte[ivSize];
System.arraycopy(encryptedIvTextBytes, 0, iv, 0, iv.length);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
int encryptedSize = encryptedIvTextBytes.length - ivSize;
byte[] encryptedBytes = new byte[encryptedSize];
System.arraycopy(encryptedIvTextBytes, ivSize, encryptedBytes, 0, encryptedSize);
byte[] keyBytes = new byte[keySize];
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(key.getBytes());
System.arraycopy(md.digest(), 0, keyBytes, 0, keyBytes.length);
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipherDecrypt = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipherDecrypt.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] decrypted = cipherDecrypt.doFinal(encryptedBytes);
return new String(decrypted);
}
And the result is the normal properties file.
I'm a fresh IOS developer, and facing a problem in encryption and decryption.
decrypt code(java)
byte[] keyPass = pass.getBytes("ASCII");
byte[] aesIV = new byte[16];
byte[] Decryptdata = Base64.decode(encodedString, Base64.NO_WRAP);
System.arraycopy(Decryptdata, 0, aesIV, 0, 16);
byte[] data = new byte[Decryptdata.length - 16];
System.arraycopy(Decryptdata, 16, data, 0, dataToDecrypt.length - 16);
Key aesKey = new SecretKeySpec(keyPass, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(aesIV);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.DECRYPT_MODE, aesKey, ivSpec);
Text = new String(cipher.doFinal(data), "UTF-8");
encrypt code(java)
byte[] keyPass = pass.getBytes("ASCII");
final Key key = new SecretKeySpec(keyPass, "AES");
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
byte[] byteMessage = text.getBytes("UTF-8");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] cipherText = cipher.doFinal(byteMessage);
byte[] ivByte = cipher.getIV();
byte[] bytesTotal = new byte[ivByte.length+cipherText.length];
System.arraycopy(ivByte, 0, bytesTotal, 0, ivByte.length);
System.arraycopy(cipherText, 0, bytesTotal, ivByte.length, cipherText.length);
encyoted = Base64.encodeToString(bytesTotal, Base64.NO_WRAP);
How can I create encrypt and decrypt in objective-c?
"how can i create function from java to objective-c"
For iOS use Common Crypto.
Study, write code and debug. Repeat as necessary.
In relation to this question; Java (Android) Decrypting msg with IV attached
My message decrypts fine but with the unwanted IV byte data.
I try to remove the attached IV, yet it does not delete all the characters and some character always remain behind. I'm not sure how I should be calculating the length of the encoded IV to remove the non-required characters.
public String decrypt(String cipherText, byte[] encryptionKey) throws Exception {
SecretKeySpec key = new SecretKeySpec(encryptionKey, "AES");
cipher.init(Cipher.DECRYPT_MODE, key, iV);
String decrypt = new String(cipher.doFinal( Base64.decode(cipherText, Base64.DEFAULT)));
byte[] decryptData = new byte[decrypt.getBytes().length - iV.getIV().length];
System.arraycopy(decrypt.getBytes(), iV.getIV().length, decryptData, 0, decrypt.getBytes().length - iV.getIV().length);
Log.d("decrypt = ", decrypt);
decrypt = new String(decryptData, "UTF-8");
return decrypt;
}
You need to remove the IV before decryption not after it, because it is a parameter of the decryption. Since the IV is prepended to the ciphertext there is no need to save it somewhere else (no need for your iV reference).
byte[] ciphertextBytes = Base64.decode(cipherText, Base64.DEFAULT);
IvParameterSpec iv = new IvParameterSpec(ciphertextBytes, 0, 16);
ciphertextBytes = Arrays.copyOfRange(ciphertextBytes, 16, ciphertextBytes.length);
SecretKeySpec key = new SecretKeySpec(encryptionKey, "AES");
cipher.init(Cipher.DECRYPT_MODE, key, iv);
String decrypt = new String(cipher.doFinal(ciphertextBytes), "UTF-8");
I'm in need of a simple AES cryptosystem in ECB. I have one working at the moment in the sense that given the same key twice in a row, it will correctly encrypt and decrypt a message.
However, if I use two different keys for encrypting/decrypting, the program throws a javax.crypto.BadPaddingException: Given final block not properly padded. I need the program to provide an incorrect decryption, presumably something that looks like some encrypted string. Here's my code:
public static byte[] encrypt(byte[] plaintext, String key) throws Exception {
char[] password = key.toCharArray();
byte[] salt = "12345678".getBytes();
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] ciphertext = cipher.doFinal(plaintext);
return ciphertext;
}
public static byte[] decrypt(byte[] ciphertext, String key) throws Exception {
char[] password = key.toCharArray();
byte[] salt = "12345678".getBytes();
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret);
byte[] plaintext = cipher.doFinal(ciphertext);
return plaintext;
}
(Note: I'm aware of the disadvantages of using ECB, salt = "12345678", etc., but it's not my concern at the moment.) Thanks for any and all help.
PKCS#5 padding has a very specific structure, so you cannot continue using it if you want decryption with the wrong key to complete without error.
A good way to achieve your goal might be to use a stream mode of operation, rather than a block-mode. In a stream mode, the input key is used to produce a never-ending stream of seemingly random data, which is XORed with the ciphertext to produce plaintext (and vice versa). If you use the wrong key, you get nonsense data out which is the same size as the original plaintext.
Here's a simple example, based on your original code. I use an IV of all zeroes, but you may wish to improve that to be a random value in due course (note: you'll need to store this value with the ciphertext).
public static void main(String[] args) throws Exception {
byte[] plaintext = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
byte[] ciphertext = encrypt(plaintext, "foo");
byte[] goodDecryption = decrypt(ciphertext, "foo");
byte[] badDecryption = decrypt(ciphertext, "bar");
System.out.println(DatatypeConverter.printHexBinary(goodDecryption));
System.out.println(DatatypeConverter.printHexBinary(badDecryption));
}
public static SecretKey makeKey(String key) throws GeneralSecurityException {
char[] password = key.toCharArray();
byte[] salt = "12345678".getBytes();
SecretKeyFactory factory =
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
SecretKey tmp = factory.generateSecret(spec);
return new SecretKeySpec(tmp.getEncoded(), "AES");
}
public static byte[] encrypt(byte[] plaintext, String key) throws Exception {
SecretKey secret = makeKey(key);
Cipher cipher = Cipher.getInstance("AES/OFB8/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(new byte[16]));
return cipher.doFinal(plaintext);
}
public static byte[] decrypt(byte[] ciphertext, String key) throws Exception {
SecretKey secret = makeKey(key);
Cipher cipher = Cipher.getInstance("AES/OFB8/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(new byte[16]));
return cipher.doFinal(ciphertext);
}
Output:
00010203040506070809
5F524D4A8D977593D34C
I wrote my SecretKey to a file using the following code. Similarly, I have to write my ivParameterSpec to another file. How can I do this?
SecretKey key = KeyGenerator.getInstance("AES").generateKey();
ObjectOutputStream secretkeyOS = new ObjectOutputStream(new FileOutputStream("publicKeyFile"));
secretkeyOS.writeObject(key);
secretkeyOS.close();
AlgorithmParameterSpec paramSpec1 = new IvParameterSpec(iv);
session.setAttribute("secParam", paramSpec1);
ObjectOutputStream paramOS = new ObjectOutputStream(new FileOutputStream("paramFile"));
paramOS.writeObject(paramSpec1);
paramOS.close();
Don't try to store the IvParameterSpec object. It is not serializable, because it is not intended to be stored.
The IV is the important part. Store this and create a new IvSpec from the IV. I have changed example code from here for AES encryption to store the IV and use the loaded IV to decrypt the ciphertext so you can see a possible workflow.
Please be aware that this is a minimal example. In a real usecase you would store and load the key as well and exception handling should also be reconsidered :-D
public class Test {
public static void main(String[] args) throws Exception {
String message = "This string contains a secret message.";
// generate a key
KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(128);
byte[] key = keygen.generateKey().getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
byte[] iv = { 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8 };
IvParameterSpec ivspec = new IvParameterSpec(iv);
// initialize the cipher for encrypt mode
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivspec);
// encrypt the message
byte[] encrypted = cipher.doFinal(message.getBytes());
System.out.println("Ciphertext: " + hexEncode(encrypted) + "\n");
// Write IV
FileOutputStream fs = new FileOutputStream(new File("paramFile"));
BufferedOutputStream bos = new BufferedOutputStream(fs);
bos.write(iv);
bos.close();
// Read IV
byte[] fileData = new byte[16];
DataInputStream dis = null;
dis = new DataInputStream(new FileInputStream(new File("paramFile")));
dis.readFully(fileData);
if (dis != null) {
dis.close();
}
// reinitialize the cipher for decryption
cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(fileData));
// decrypt the message
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Plaintext: " + new String(decrypted) + "\n");
}
[...]
}