Android encryption and decryption of text fails

281 views Asked by At

I try to encrypt some text (here it is named code) and decrypt it again. For this i use a 4 digit Pin which is salted.

After this the text is encrypted, also again some Base64 decoding, so i can safely output the String again.

As i understand i have to base64.decode it and then again decrypt it.

But i don't get my encrypted text correctly decrypted back. Just some different text. ( something like this[B@3ceB...)

It is just a small project nothing serious. Also it is my second android app, so some good examples would be great.

String pinstr = new String();
pinstr = "5555";
try {
    EncryptDecrypt encryptor = new EncryptDecrypt(pinstr);
    //encryptor.encrypt(code);
    String encrypted = new String();
    encrypted = encryptor.encrypt(code);

    String decrypted = new String();
    decrypted = encryptor.decrypt(encrypted);

    Toast.makeText(MainActivity.this, decrypted, Toast.LENGTH_SHORT).show();
} catch (InvalidKeyException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (UnsupportedEncodingException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (NoSuchPaddingException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (IllegalBlockSizeException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (BadPaddingException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
public class EncryptDecrypt {
    private SecretKeySpec skeySpec;
    private Cipher cipher, cipher2;


    EncryptDecrypt(String password) throws NoSuchAlgorithmException,
    UnsupportedEncodingException, NoSuchPaddingException,
    IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
        MessageDigest sha = MessageDigest.getInstance("SHA-1");
        byte[] key = Arrays.copyOf(sha.digest(("ThisisMySalt1234" + password).getBytes("UTF-8")),
        16);
        skeySpec = new SecretKeySpec(key, "AES");
        cipher = Cipher.getInstance("AES");
        cipher2 = Cipher.getInstance("AES");
    }

    String encrypt(String clear) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
        String encrypted = new String();

        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);

        byte[] encryptedBytes = null;
        encryptedBytes = cipher.doFinal(clear.getBytes());

        encrypted = Base64.encodeToString(encryptedBytes, Base64.DEFAULT);

        return encrypted;
    }

    // fehlerhaft
    String decrypt(String encryptedBase64) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        String decrypted = new String();
        cipher2.init(Cipher.DECRYPT_MODE, skeySpec);
        byte[] decodedBytes = null;


        decodedBytes = Base64.decode(encryptedBase64, Base64.DEFAULT);

        decrypted = cipher2.doFinal(decodedBytes).toString();

        return decrypted;

    }
}
2

There are 2 answers

0
Artjom B. On BEST ANSWER

The output type of Cipher#doFinal(byte[]) is byte[], but Arrays don't have a default way in which their contents are printed. By calling byte[].toString() on an array, you're simply printing its type and hash code. (More on this here)

What you want is

decrypted = new String(cipher2.doFinal(decodedBytes), "UTF-8");

which tells the String constructor that the given byte array contains characters that are encoded in UTF-8.

When you do that, then you also need to get the byte array out of the string in a specific encoding:

clear.getBytes("UTF-8")

If you omit the encoding, then the system default is used which might make your plaintexts unrecoverable when you send the ciphertexts across systems which have different system encoding defaults.

Btw, you don't need both Cipher instances. Only one suffices since you're initiating it always during encryption and decryption.


Other security problems:

  • Always use a fully qualified cipher string. So change Cipher.getInstance("AES") to Cipher.getInstance("AES/CBC/PKCS5Padding").
  • Never use ECB mode which is the default when you use "AES" cipher string. It is not semantically secure. Use at least CBC mode with a random IV:

    SecureRandom r = new SecureRandom();
    byte[] iv = new byte[16];
    r.nextBytes(iv);
    ...
    cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));
    

    The IV is not supposed to be secret, so you can simply prepend it to the ciphertext and splice it off before decryption.

  • Authenticate your ciphertext so that you're not vulnerable to a padding oracle attack and can always check your ciphertexts for integrity. This can be easily done with an encrypt-then-MAC scheme with a strong MAC algorithm like HMAC-SHA256. You can also use an authenticated mode of encryption like GCM or EAX.

0
Robert On

Your code works, but you are throwing away the result.

Change the line

decrypted = cipher2.doFinal(decodedBytes).toString();

to

 decrypted = new String(cipher2.doFinal(decodedBytes),"UTF-8");

Then you are getting the decoded String instead of the ID of the byte array (calling toString() does give you the byte array as String).

BTW: You don't need to initialize variables in Java. String encrypted = new String(); is useless. Just write String encrypted;.