This is a continuation from my question yesterday:
Android Java AES Encryption
I am currently testing AES encryption on Android. In my previous question I am able to encrypt and decrypt a string using Cipher c = Cipher.getInstance("AES");
I was informed in the replies that I should specify the IV, encryption mode and padding to prevent any potential issues in the future since no specification means the program will use the system's default value. So I changed my code to c = Cipher.getInstance("AES/CBC/PKCS5Padding");
But now my code no longer works, and it will return a NullPointerException.
My code:
byte[] a = encryptFIN128AES("pls");
String b = decryptFIN128AES(a);
Log.e("AES_Test", "b = " + b);
/**
* Encrypts a string with AES (128 bit key)
* #param fin
* #return the AES encrypted string
*/
private byte[] encryptFIN128AES(String fin) {
SecretKeySpec sks = null;
try {
sks = new SecretKeySpec(generateKey(PASSPHRASE, SALT.getBytes(StandardCharsets.UTF_8)).getEncoded(), "AES");
} catch (Exception e) {
Log.e("encryptFIN128AES", "AES key generation error");
}
// Encode the original data with AES
byte[] encodedBytes = null;
try {
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, sks);
encodedBytes = c.doFinal(fin.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
Log.e("encryptFIN128AES", "AES encryption error");
}
return encodedBytes;
}
/**
* Decrypts a string with AES (128 bit key)
* #param encodedBytes
* #return the decrypted String
*/
private String decryptFIN128AES(byte[] encodedBytes) {
SecretKeySpec sks = null;
try {
sks = new SecretKeySpec(generateKey(PASSPHRASE, SALT.getBytes(StandardCharsets.UTF_8)).getEncoded(), "AES");
} catch (Exception e) {
Log.e("decryptFIN128AES", "AES key generation error");
}
// Decode the encoded data with AES
byte[] decodedBytes = null;
try {
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.DECRYPT_MODE, sks);
decodedBytes = c.doFinal(encodedBytes);
} catch (Exception e) {
Log.e("decryptFIN128AES", "AES decryption error");
}
//return Base64.encodeToString(decodedBytes, Base64.DEFAULT);
return new String(decodedBytes, StandardCharsets.UTF_8);
}
/**
* Build private key from a passpharase/PIN (incl. key derivation (Uses PBKDF2))
* #param passphraseOrPin
* #param salt
* #return The generated SecretKey (Used for AES-encryption, key size specified in outputKeyLength)
*/
public static SecretKey generateKey(char[] passphraseOrPin, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
// Number of PBKDF2 hardening rounds to use. Larger values increase
// computation time. You should select a value that causes computation
// to take >100ms.
final int iterations = 1000;
// Generate a 256-bit key
final int outputKeyLength = 128;
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec keySpec = new PBEKeySpec(passphraseOrPin, salt, iterations, outputKeyLength);
SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
return secretKey;
}
Output:
E/decryptFIN128AES: AES decryption error
E/AndroidRuntime: FATAL EXCEPTION: Thread-176
Process: testapp.ttyi.nfcapp, PID: 2920
java.lang.NullPointerException: Attempt to get length of null array
at java.lang.String.<init>(String.java:371)
at testapp.ttyi.nfcapp.DisplayQRActivity.decryptFIN128AES(DisplayQRActivity.java:254)
at testapp.ttyi.nfcapp.DisplayQRActivity.access$100(DisplayQRActivity.java:29)
at testapp.ttyi.nfcapp.DisplayQRActivity$1.run(DisplayQRActivity.java:77)
at java.lang.Thread.run(Thread.java:818)
testapp.ttyi.nfcapp.DisplayQRActivity.decryptFIN128AES(DisplayQRActivity.java:254) points to the last line of decryptFIN128AES, which is:
return new String(decodedBytes, StandardCharsets.UTF_8);
I understand that the NullPointerException occurs because something went wrong with the decryption process. Since it must have went into the catch case and thus decodedBytes remains as NULL and thus causes the error when I want to return decodedBytes. So now my question is: Why does this happen and how can I fix this?
Your help is much appreciated.
Many thanks to #Thomas W for your help. I changed the catch to throw and now I can see the actual error has something to do with BadPaddingException: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
After some googling I found the solution is the lack of IV argument in my c.init. Previously I was using "AES" in which Java defaults to "AES/ECB/PKCS5Padding" and that works fine without IV.
(Source: Android: Encrypt a string with AES 256bit Encryption with iv and secret key)
But once I changed to "AES/CBC/PKCS5Padding" Java will have issues without a declared IV. Therefore by changing c.init(Cipher.ENCRYPT_MODE, sks); and c.init(Cipher.DECRYPT_MODE, sks); to
c.init(Cipher.ENCRYPT_MODE, sks, new IvParameterSpec(new byte[16])); and
c.init(Cipher.DECRYPT_MODE, sks, new IvParameterSpec(new byte[16])); fixed the issue.
Now my program can encrypt and decrypt properly.
Related
I've following aes encryption code in Java which I want to write it in C#, but it is not giving same output.
Java Code
public String doEncryptString(String salt, String password,String token) throws CryptoException {
try {
Cipher cipher = Cipher.getInstance("AES");
SecretKeySpec secretKeySpec = generateKeySpec(salt,password);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] inputBytes = token.getBytes();
byte[] outputBytes = cipher.doFinal(inputBytes);
return Base64Utils.encodeToString(outputBytes);
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | BadPaddingException
| IllegalBlockSizeException ex) {
throw new CryptoException("Error encrypting password", ex);
}
}
private SecretKeySpec generateKeySpec(String salt,String password) throws CryptoException{
try {
String generatedkey=salt+password;
byte[] key = generatedkey.getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16); // use only first 128 bit
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
return secretKeySpec;
} catch (NoSuchAlgorithmException | IOException ex) {
throw new CryptoException("Error encrypting password", ex);
}
}
This is what I've tried in C#
public static string DoEncrypt(string salt, string password, string token)
{
var tdes = new AesManaged();
tdes.Key = GenerateKey(salt, password);
tdes.Mode = CipherMode.ECB;
tdes.Padding = PaddingMode.PKCS7;
ICryptoTransform crypt = tdes.CreateEncryptor();
byte[] plain = Encoding.UTF8.GetBytes(token);
byte[] cipher = crypt.TransformFinalBlock(plain, 0, plain.Length);
return Convert.ToBase64String(cipher);
}
private static byte[] GenerateKey(string salt, string password)
{
string generatedkey = $"{salt}{password}";
var key = Encoding.UTF8.GetBytes(generatedkey);
var sha1 = SHA1Managed.Create();
key = sha1.ComputeHash(key);
return key.Take(16).ToArray(); // use only first 128 bit
}
string/token to encrypt : ZHKRIWB310XVVWG315PI7UZZWU1V0YYL5WE9JL
Java output: eUjNH8kcgWtlEmuCFHMPwnCFWjy5Pye/gF+itrPs1g8AjtAEZQqlzW/v7kEt2haG
My C# code output: O8sKdJWH+XCOIbexZPEwN5NxWqpWRHC5b3ZsihT8cfBqpI1eVr3PEr9Eq39a5pMn
I don't know what I am doing wrong here. Any help would be appreciated. Thanks
Update
My apologies everyone. The code translated in C# in working fine. By mistake, I was passing different salt value. Thanks everyone.
What's in TRANSFORMATION from the Java code?
You need also to use the same mode and padding to get the same results, meaning ECB and PKCS7 in your case.
Java seems to offer only PKCS5 padding? But it seems to be compatible with PKCS7? I'm not a Java dev and can't provide details, but there is a discussion here: https://crypto.stackexchange.com/questions/9043/what-is-the-difference-between-pkcs5-padding-and-pkcs7-padding where they say:
Some cryptographic libraries such as the SUN provider in Java indicate
PKCS#5 where PKCS#7 should be used - "PKCS5Padding" should have been
"PKCS7Padding". This is - with high probability - a legacy from the
time that only 8 byte block ciphers such as (triple) DES symmetric
cipher were available.
And by the way: for production never use ECB mode as it's unsafe.
Please excuse the hackjob! Still new at coding and was utilizing a lot of sys outs to troubleshoot, and this is my first post at StackOverflow. Thank you for the help!
So I've been working on trying to encrypt String objects using the javax.crypto.Cipher API, and I have found some success, but only when it is using the same instance of the Cipher object. However, for the purposes of my project, I am encrypting text (String) to and decrypting text from a text file, and will not be accessing the same Cipher object every time.
I believe the issue is not from converting between the byte arrays and Strings, since the Base64 encoder seems to have taken care of that problem. The output of the byte arrays are identical pre-encoding and post-decoding, so that should isolate it as an issue during the decryption phase. What can be done so that my decryptPW method can use a different Cipher instance (with the same arguments passed) and not trigger a BadPaddingException?
private static String encryptPW(String pw){
byte[] pwBytes = pw.getBytes();
byte[] keyBytes = "0123456789abcdef".getBytes();
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
try {
Cipher ciph = Cipher.getInstance("AES/CBC/PKCS5Padding");
ciph.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encryptedBytes = ciph.doFinal(pwBytes);
pw = Base64.getEncoder().encodeToString(ciph.doFinal(pwBytes));
for (byte b : encryptedBytes){
System.out.print(b);
}
System.out.println();
} catch (Exception e){
e.printStackTrace();
}
return pw;
}
private static String decryptPW(String pw){
byte[] pwBytes = Base64.getDecoder().decode(pw.getBytes());
for (byte b : pwBytes){
System.out.print(b);
}
System.out.println();
byte[] keyBytes = "0123456789abcdef".getBytes();
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
try {
Cipher ciph = Cipher.getInstance("AES/CBC/PKCS5Padding");
ciph.init(Cipher.DECRYPT_MODE, keySpec, ciph.getParameters());
pw = new String(ciph.doFinal(pwBytes));
} catch (Exception e){
e.printStackTrace();
}
return pw;
}
Again, thank you!
As you use CBC mode, you need to save the random Initialization Vector (IV) from the encryption cipher and provide it to the decryption cipher.
You can get it from the encryption cipher after init like:
ciph.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] iv = ciph.getIV();
And provide it to the decrypt cipher with something like:
ciph.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
You need to come up with a method of saving the IV. A common way is to prefix the encrypted data with the IV, so it's available when you need to initialize your decrypt cipher. It don't need to be secret.
In this code, this line is causing an exception:
clearText = c.doFinal(Base64.decode(encryptedText, Base64.DEFAULT));
javax.crypto.BadPaddingException: pad block corrupted
I got the code from:
http://www.techrepublic.com/blog/software-engineer/attention-android-developers-keep-user-data-safe/
Any ideas?
private String decrypt (String encryptedText) {
byte[] clearText = null;
try {
SecretKeySpec ks = new SecretKeySpec(getKey(), "AES");
Cipher c = Cipher.getInstance("AES");
c.init(Cipher.DECRYPT_MODE, ks);
clearText = c.doFinal(Base64.decode(encryptedText, Base64.DEFAULT));
return new String(clearText, "UTF-8");
} catch (Exception e) {
return null;
}
}
Details: I am encrypting it on the android as well
owlstead's advice was helpful, but for this case when using the code in
Attention Android developers: Keep user data safe
http://www.techrepublic.com/blog/software-engineer/attention-android-developers-keep-user-data-safe/
I made some changes to the code that might be helpful for other people in the future. I completely deleted the getkey method.
private static String seed;
/**
* Encrypts the text.
* #param clearText The text you want to encrypt
* #return Encrypted data if successful, or null if unsucessful
*/
protected String encrypt(String clearText) {
byte[] encryptedText = null;
try {
byte[] keyData = seed.getBytes();
SecretKey ks = new SecretKeySpec(keyData, "AES");
Cipher c = Cipher.getInstance("AES");
c.init(Cipher.ENCRYPT_MODE, ks);
encryptedText = c.doFinal(clearText.getBytes("UTF-8"));
return Base64.encodeToString(encryptedText, Base64.DEFAULT);
} catch (Exception e) {
return null;
}
}
/**
* Decrypts the text
* #param encryptedText The text you want to encrypt
* #return Decrypted data if successful, or null if unsucessful
*/
protected String decrypt (String encryptedText) {
byte[] clearText = null;
try {
byte[] keyData = seed.getBytes();
SecretKey ks = new SecretKeySpec(keyData, "AES");
Cipher c = Cipher.getInstance("AES");
c.init(Cipher.DECRYPT_MODE, ks);
clearText = c.doFinal(Base64.decode(encryptedText, Base64.DEFAULT));
return new String(clearText, "UTF-8");
} catch (Exception e) {
return null;
}
}
Java + Android + Encryption + Exception means just one thing normally, somebody is using the SecureRandom class again as a key derivation function. This fails when the SecureRandom implementation of "SHA1PRNG" does not behave as the one in Sun's implementation in Java SE. Especially if the seed is added to the state of the random number generator instead of the seed being used as a starting point of the PRNG.
Basically, simply use SecretKey aesKey = new SecretKeySpec(byte[] keyData, "AES") instead, or - if you start off with a password - try and generate the key using PBKDF2.
For me, the problem is in getKey()
Make sure that two invocation of getKey() return the same value.
I used new SecureRandom(password.getBytes()) to generate key. It worked on Windows, but on Android, it returned different value for different call.
I Reffred From this : https://androidfreetutorial.wordpress.com/2017/03/14/android-encryptiondecryption-with-aes-algorithm/
Change to "AES" From "AES/ECB/PKCS7Padding";
I'm currently running into problems decrypting my data. The base64 of the encoded string is being stored in the database. So, I'm printing out the encoded string and then trying to run it back through with "DECRYPT" instead of "ENCRYPT". However, I never get a value that the Decrypter method likes, it always gives me an error about parameters or the value not being 16 bytes.
public class crypto {
public static void main(String [] args) {
String s = args[0];
String s1 = args[1];
String ivkey = "thisisasecretkey";
byte[] ivraw = ivkey.getBytes();
SecretKeySpec skeySpec = new SecretKeySpec(ivraw, "AES");
if (s.equalsIgnoreCase("ENCRYPT")) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(s1.getBytes());
System.out.println(new String(Base64.encodeBase64(encrypted)));
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(s1.getBytes());
System.out.println(new String(Base64.decodeBase64(encrypted)));
} catch (Exception e) {
e.printStackTrace();
}
}
return;
};
}
command:crypto "ENCRYPT" "password"
output: 5eQvSzPG1TE2AybgCmeV6A==
command:crytpo "DECRYPT" "5eQvSzPG1TE2AybgCmeV6A=="
output: java.security.InvalidKeyException: Parameters missing
I'm aware of the security flaws, that's not what I'm asking about and I would prefer answers/comments not get cluttered with best practices.
You should do base 64 decoding, and you should do that before decrypting.
You are not including the initialization vector (IV).
AES in CBC mode has both a 16 byte IV and the 16 byte symmetric key.
String IV = "AAAAAAAAAAAAAAAA"; // generate this randomly
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(IV.getBytes()));
byte[] encrypted = cipher.doFinal(s.getBytes());
Edit: as it turns out, encryption does not require a IV to be provided (as owlstead pointed out), but decryption does. The best bet would be to be explicit and use IV in both encryption and decryption. Change your decryption function to include the IV, and you will run into the other error in your code that owlstead pointed out.
I am doing encryption and decryption of data as follows but getting error
protected Cipher aes_Gen_with_Key(byte[] key)
{
Cipher cipher=null;
try
{
byte[] key_hash = (key).toString().getBytes("UTF-8");
key_hash = Arrays.copyOf(key_hash, 32); // use only first 256 bit
SecretKeySpec secretKeySpec = new SecretKeySpec(key_hash, "AES");
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
} catch (Exception e) {
System.out.println("Error Occured");
}
return cipher;
}
protected Cipher aes_Dec_with_Key(byte[] key,byte[] iv)
{
Cipher cipher=null;
try
{
byte[] key_hash = (key).toString().getBytes("UTF-8");
key_hash = Arrays.copyOf(key_hash, 32); // use only first 256 bit
SecretKeySpec secretKeySpec = new SecretKeySpec(key_hash, "AES");
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE,secretKeySpec,new IvParameterSpec(iv));
}
catch (Exception e) {
System.out.println(e);
}
return cipher;
}
With above 2 functions I am getting ciphers with which I am doing encryption and decryption, but getting javax.crypto.BadPaddingException: Given final block not properly padded as error. Length of decryption byte array is 752 and IV at decryption is 16 byte long. Can any one suggest?
Here are few more relevant code blocks.
Apologies for invalid use java naming conventions
// Key Class
import java.io.Serializable;
#SuppressWarnings("serial")
public class Key implements Serializable{
byte[] gsmodp_hash=null;
byte[] iv_pass=null;
byte[] Nonce_Enc=null;
byte[] iv_non=null;
public Key() {
}
public Key(byte[] gsmodp_hash,byte[] iv_pass,byte[] Nonce_Enc,byte[] iv_non) {
this.gsmodp_hash=gsmodp_hash;
this.iv_pass=iv_pass;
this.Nonce_Enc=Nonce_Enc;
this.iv_non=iv_non;
}
}
// Client side code
JSONObject auth_step_obj=new JSONObject();
try {
BigInteger gsmodp=get_modu_frm_server(receivePacket);
BigInteger R2=get_R2_frm_server(receivePacket);
BigInteger N2=get_N2_frm_server(receivePacket);
N2=crypto.dec_NonceG(N2);
BigInteger a=crypto.get_RandLong();
BigInteger gamodp= crypto.dh_GenerationG(a, crypto.g, crypto.p);
BigInteger key=crypto.dh_GenerationG(a, gsmodp, crypto.p);
// Got hash of g^abmodp
byte[] dhkey=crypto.sha256G(key.toString());
key=null;
//Mixing passwords
SecretKey secretkey=(SecretKey) userCredentials.get("password");
byte[] mixed_hash=crypto.passwordMixerG(R2, secretkey.getEncoded());
//Working Fine Till now
// Getting Cipher for encrypting gsmodp using password and nonce
Cipher cipher_password=crypto.aes_Gen_with_Key(mixed_hash);
Cipher cipher_key=crypto.aes_Gen_with_Key(dhkey);
// Generating quantities for JSON Object
byte[] gsmodp_hash=cipher_password.doFinal(gamodp.toString().getBytes());
byte[] gsmodp_hash_iv=cipher_password.getIV();
byte[] nonce_enc=cipher_key.doFinal(N2.toString().getBytes());
byte[] nonce_enc_iv=cipher_key.getIV();
Key authetication_parameters=new Key(gsmodp_hash,gsmodp_hash_iv,nonce_enc,nonce_enc_iv);
auth_step_obj.put("obj",authetication_parameters);
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("Hi:sec_DH_Step");
}
// This sends JSONObject to calling method which generates UDP packet and sends to server
return auth_step_obj;
}
// Server side code
// Once packet received on server following happens
ds.receive(receivePacket);
SecretKey userKey=cryptoObj.get_from_KeyStoreG(user_name);
// Adding R2 and userKey byte by byte
byte[] mixed_hash=cryptoObj.passwordMixerG(R2, userKey.getEncoded());
JSONObject authentication_nonce=new JSONObject();
authentication_nonce=cryptoObj.readRecievedPacket(receivePacket);
Key obj=(Key)authentication_nonce.get("obj");
Cipher cipher=cryptoObj.aes_Dec_with_Key(mixed_hash,obj.iv_pass);
// I am getting error on do final
System.out.println(new String(cipher.doFinal(obj.gsmodp_hash)));
The code you presented works just fine. The error must lie in the code you haven't shared with us (i.e. the code that actually uses the Cipher objects).
I wrote the code below to test your question code:
public static void main(String[] args) throws Exception {
Random random = new Random();
byte[] key = new byte[32];
random.nextBytes(key);
byte[] plaintext = new byte[100];
random.nextBytes(plaintext);
Cipher enc = aes_Gen_with_Key(key);
byte[] ciphertext = enc.doFinal(plaintext);
byte[] iv = enc.getIV();
Cipher dec = aes_Dec_with_Key(key, iv);
byte[] recoveredPlaintext = dec.doFinal(ciphertext);
System.out.println(Arrays.equals(plaintext, recoveredPlaintext));
}
Note, I made your two methods static. You should do the same, as they don't use any instance variables.