1

I am currently making an Android app that includes encrypting a String with AES. But for some reason my app does not decrypt properly. I tried to change the Base64 format but it does not fix it. The code is similar to the example on Android Encryption with the Android Cryptography API

Does anyone know where did I go wrong with my functions? Since it does not decode to the same string as my encoded string ("pls").

Your help is much appreciated.

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 byte[]
 */
private byte[] encryptFIN128AES(String fin) {

    SecretKeySpec sks = null;

    try {
        sks = new SecretKeySpec(generateKey("Test1".toCharArray(), "Test2".getBytes()).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");
        c.init(Cipher.ENCRYPT_MODE, sks);
        encodedBytes = c.doFinal(fin.getBytes());
    } 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("Test1".toCharArray(), "Test2".getBytes()).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");
        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);
}


public static SecretKey generateKey(char[] passphraseOrPin, byte[] salt)
        throws NoSuchAlgorithmException, InvalidKeySpecException {

    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/AES_Test: b = cGxz

**

[EDIT] Modified my code but now there is a NullPointerException

**

/**
     * 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);
    }

// generateKey(char[] passphraseOrPin, byte[] salt) remains the same

Error:

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)

**

[EDIT2] Resolved (But no Padding/Encryption Mode allowed)

**

I managed to resolve the issue. (Decodes to "pls") using Codo's solution ofreturn new String(decodedBytes, StandardCharsets.UTF_8);

Though it only works when the algorithm used is: Cipher c = Cipher.getInstance("AES");

When I put Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); The "NullPointerException" as seen above will happen. My observation shows that during decryption:

 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");
            }

something will fail and it will always print out:

E/decryptFIN128AES: AES decryption error

And thus the NullPointerException will occur as decodedBytes is always initiated to NULL.

Ryo
  • 15
  • 2
  • 8

3 Answers3

2

Your process is not balanced. For encryption you do:

  1. Encode string using default charset (fin.getBytes()) to get binary data
  2. Encrypt binary data to get encrypted data (doFinal)

For the decryption, you do:

  1. Decrypt encrypted data to get unencrypted binary data (doFinal)
  2. Encode binary data as a Base64 string

Instead of Base64 encoding, the last step should be the reverse of step 1 in the encryption, i.e. you should decode the binary data into a string:

return String(decodedBytes);

It strongly recommend, you do not use the default charset for encoding and decoding as it depends on the system's setting. So it could be different between the system where you encrypt and decyrpt.

So use:

fin.getBytes(StandardCharsets.UTF_8);

and:

return String(decodedBytes, StandardCharsets.UTF_8);

The same applies for the salt.

Also note that you should specify the padding and chaining mode. If you don't, provider-specific default values apply. See @Ryan's answer for more details.

Codo
  • 75,595
  • 17
  • 168
  • 206
  • Hi thanks for your reply. I tried to put in your solution but now it gives me a NullPointerException at the final line of of decryptFIN128AES: `return new String(decodedBytes, StandardCharsets.UTF_8);` – Ryo Jan 17 '17 at 01:45
1

You should research more on how to use AES correctly as you are missing some basic fundamentals of AES security: no IV (assuming using CBC), no mode specified (such as CBC), and no padding specified (such as PKCS5).

Ryan
  • 1,863
  • 13
  • 20
0

Looks like char encoding issue. With minor modifications it works.

in encryptFIN128AES:

encodedBytes = c.doFinal(Base64.getEncoder().encode(fin.getBytes()));

in decryptFIN128AES:

return new String(Base64.getDecoder().decode(decodedBytes));
Andrey Lebedenko
  • 1,850
  • 17
  • 24
  • The Base64 encoding and decoding adds nothing to the process here. It only increases the length of the encrypted data. It can simply be skipped. – Codo Jan 16 '17 at 11:41