Search code examples
javaandroidencryptionandroid-keystore

Android String Encryption/Decryption


I want to encrypt and decrypt a String from an EditText using AndroidKeyStore. My problem is that at the decrypt process is get a BadPaddingException.

Key generator code:

        KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");

        KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT).
                setBlockModes(KeyProperties.BLOCK_MODE_GCM).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE).build();

        keyGenerator.init(keyGenParameterSpec);
        keyGenerator.generateKey();

Encryption code:

            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null);

            KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore.getEntry(ALIAS, null);
            SecretKey secretKey = secretKeyEntry.getSecretKey();

            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);

            cipherIV = cipher.getIV();

            plainText.setText(new String(cipher.doFinal(plainText.getText().toString().getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8));

Decryption code:

            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null);

            final KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore.getEntry(ALIAS, null);
            final SecretKey secretKey = secretKeyEntry.getSecretKey();

            final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            final GCMParameterSpec spec = new GCMParameterSpec(128, cipherIV);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);

            byte[] decrypted = cipher.doFinal(plainText.getText().toString().getBytes(StandardCharsets.UTF_8));
            plainText.setText(new String(decrypted, StandardCharsets.UTF_8));

Solution

  • This line is in error:

    plainText.setText(new String(cipher.doFinal(plainText.getText().toString().getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8));
    

    If we break it apart, we have something like

    byte [] cipherBytes = cipher.doFinal(plainText.getText().toString().getBytes(StandardCharsets.UTF_8));
    plainText.setText(new String(cipherBytes, StandardCharsets.UTF_8);
    

    The problem is that cipherBytes is a sequence of arbitrary bytes rather than the characters of a string. The String constructor will silently replace invalid characters with something else, a process which corrupts the data.

    If you want to display the cipher bytes or otherwise send it to a character oriented channel you must encode it. Typically encodings are base64 or hex. To decrypt the String you must then decode it to bytes first and then decrypt it.

    Example:

    byte [] cipherBytes = cipher.doFinal(plainText.getText().toString().getBytes(StandardCharsets.UTF_8));
    plainText.setText(Base64.encodeToString(cipherBytes, Base64.DEFAULT));
    

    And on decrypt:

    byte[] cipherBytes = Base64.decode(plainText.getText().toString(), Base64.DEFAULT);
    byte[] decrypted = cipher.doFinal(cipherBytes);