Of course! Here is a comprehensive guide on how to use AES (Advanced Encryption Standard) in Java for Android applications.

This guide will cover:
- Why AES? - The importance of using strong, standard encryption.
- The Critical Concept: Key Management - The hardest part of encryption.
- Android's Recommended Approach:
CipherandKeyGenerator- The standard Java way. - A Complete, Runnable Android Example - An Activity with encryption and decryption.
- Best Practices & Security Considerations - How to do it right.
- Modern Android Recommendation: Jetpack Security - The easier, safer way for new apps.
Why AES?
AES is a symmetric encryption algorithm, meaning the same key is used for both encryption and decryption. It is the current standard for government and military organizations worldwide and is considered very secure when implemented correctly.
In Android, you should always use AES (or another strong algorithm like ChaCha20) instead of rolling your own crypto or using older, broken algorithms like DES or RC4.
The Critical Concept: Key Management
This is the most important rule in cryptography: A secure algorithm is useless if your key is not secure.

You cannot hardcode a secret key in your app's source code (String secretKey = "mySuperSecretKey123";). This is easily discoverable by anyone with decompiler access (which is everyone).
So, where do you store the key? On a mobile device, the best place is in the Android Keystore. The Keystore is a secure container provided by the Android OS that is hardware-backed (using the device's Trusted Execution Environment or TEE). It allows you to:
- Generate keys and never let them leave the secure hardware.
- Use keys for encryption/decryption without exposing the key material in your app's memory.
- Restrict keys to only be used by your app or only when the device is unlocked.
For this guide, we'll start with a simpler approach using a KeyGenerator and store the key in memory. This is for demonstration purposes only. The final section will show you the correct, production-ready way using the Keystore.
The Standard Java Approach: Cipher and KeyGenerator
We will use two main Java classes:
java.security.KeyGenerator: To generate a new secret key.javax.crypto.Cipher: To perform the actual encryption and decryption.
We'll use AES/GCM/NoPadding. This is a highly recommended mode because:
- GCM (Galois/Counter Mode) provides both confidentiality (encryption) and authenticity (integrity checking). It protects against tampering with the encrypted data.
- NoPadding is used because GCM handles padding internally.
Key Generation
import java.security.Key;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.NoSuchAlgorithmException;
// Generate a 256-bit AES key
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(256); // Key size
SecretKey secretKey = keyGenerator.generateKey();
Encryption
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.Base64;
// You MUST store this and use it for decryption!
byte[] iv = new byte[12]; // GCM recommended IV size is 12 bytes
new SecureRandom().nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); // 128-bit auth tag
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
// Combine IV and cipherText for storage/transmission
// IV is not a secret, so it can be stored with the ciphertext.
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + iv.length + cipherText.length);
byteBuffer.putInt(iv.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] encryptedMessage = byteBuffer.array();
Decryption
To decrypt, you must have the original key and the IV that was used for encryption.
// Assume 'encryptedMessage' is the byte array you stored.
ByteBuffer byteBuffer = ByteBuffer.wrap(encryptedMessage);
int ivLength = byteBuffer.getInt();
byte[] iv = new byte[ivLength];
byteBuffer.get(iv);
byte[] cipherText = new byte[byteBuffer.remaining()];
byteBuffer.get(cipherText);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
byte[] decryptedText = cipher.doFinal(cipherText);
String originalMessage = new String(decryptedText, StandardCharsets.UTF_8);
Complete Android Example Activity
Here is a full Activity that demonstrates encrypting and decrypting a String. It generates a new key each time the app starts, which is why it's only for demonstration.
MainActivity.java
import android.os.Bundle;
import android.util.Base64;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;
public class MainActivity extends AppCompatActivity {
private EditText etPlainText;
private TextView tvEncryptedText;
private TextView tvDecryptedText;
private Button btnEncrypt;
private Button btnDecrypt;
// In a real app, you would NOT generate this key every time.
// It would be generated once and stored securely (e.g., in Android Keystore).
private SecretKey secretKey;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
etPlainText = findViewById(R.id.etPlainText);
tvEncryptedText = findViewById(R.id.tvEncryptedText);
tvDecryptedText = findViewById(R.id.tvDecryptedText);
btnEncrypt = findViewById(R.id.btnEncrypt);
btnDecrypt = findViewById(R.id.btnDecrypt);
try {
// Generate a 256-bit AES key for this demo
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(256);
secretKey = keyGenerator.generateKey();
} catch (NoSuchAlgorithmException e) {
Toast.makeText(this, "Error generating key", Toast.LENGTH_SHORT).show;
e.printStackTrace();
}
btnEncrypt.setOnClickListener(v -> {
String plainText = etPlainText.getText().toString();
if (!plainText.isEmpty()) {
String encrypted = encrypt(plainText, secretKey);
tvEncryptedText.setText("Encrypted: " + encrypted);
}
});
btnDecrypt.setOnClickListener(v -> {
String encryptedText = tvEncryptedText.getText().toString().replace("Encrypted: ", "");
if (!encryptedText.isEmpty()) {
String decrypted = decrypt(encryptedText, secretKey);
tvDecryptedText.setText("Decrypted: " + decrypted);
}
});
}
public String encrypt(String plaintext, SecretKey key) {
try {
// 1. Generate a random IV (Initialization Vector)
byte[] iv = new byte[12]; // 12 bytes is recommended for GCM
new SecureRandom().nextBytes(iv);
// 2. Create and initialize the Cipher
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); // 128-bit auth tag
cipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec);
// 3. Encrypt the plaintext
byte[] cipherText = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// 4. Combine IV and ciphertext for storage/transmission
// Format: [IV Length: 4 bytes][IV][Ciphertext]
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + iv.length + cipherText.length);
byteBuffer.putInt(iv.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] encryptedMessage = byteBuffer.array();
// 5. Return as a Base64 string for easy storage
return Base64.encodeToString(encryptedMessage, Base64.NO_WRAP);
} catch (Exception e) {
e.printStackTrace();
return "Encryption Error";
}
}
public String decrypt(String base64Encrypted, SecretKey key) {
try {
// 1. Decode the Base64 string
byte[] encryptedMessage = Base64.decode(base64Encrypted, Base64.NO_WRAP);
// 2. Extract the IV and ciphertext from the message
ByteBuffer byteBuffer = ByteBuffer.wrap(encryptedMessage);
int ivLength = byteBuffer.getInt();
byte[] iv = new byte[ivLength];
byteBuffer.get(iv);
byte[] cipherText = new byte[byteBuffer.remaining()];
byteBuffer.get(cipherText);
// 3. Create and initialize the Cipher
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, key, gcmSpec);
// 4. Decrypt the ciphertext
byte[] decryptedText = cipher.doFinal(cipherText);
// 5. Return the original plaintext string
return new String(decryptedText, StandardCharsets.UTF_8);
} catch (Exception e) {
// This often happens if the key is wrong or the data is tampered with
e.printStackTrace();
return "Decryption Error";
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<EditText
android:id="@+id/etPlainText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter text to encrypt"
android:inputType="textMultiLine"
android:minHeight="100dp"/>
<Button
android:id="@+id/btnEncrypt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Encrypt"
android:layout_marginTop="16dp"/>
<TextView
android:id="@+id/tvEncryptedText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Encrypted text will appear here..."
android:background="#EEEEEE"
android:padding="8dp"/>
<Button
android:id="@+id/btnDecrypt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Decrypt"
android:layout_marginTop="16dp"/>
<TextView
android:id="@+id/tvDecryptedText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Decrypted text will appear here..."
android:background="#DDDDDD"
android:padding="8dp"/>
</LinearLayout>
Best Practices & Security Considerations
- Never Hardcode Keys: As mentioned, this is the cardinal sin. If you must store a key outside the Keystore, use Android's
EncryptedSharedPreferencesorMasterKeyAPI, which manage the key for you securely. - Use the Android Keystore: For any real application, you must use the Android Keystore. It's the only way to ensure your keys are not exposed. The process is more involved but is the industry standard.
- Always Use an IV: Each time you encrypt, you must use a new, random IV. Reusing an IV with the same key completely breaks the security of GCM mode.
- Handle Exceptions Gracefully: Decryption can fail for many reasons (wrong key, corrupted data, wrong IV). Catch exceptions and provide meaningful feedback without leaking sensitive information.
- Authenticate Your Data: Using a mode like GCM (which provides an Authentication Tag) is crucial. It prevents an attacker from changing your encrypted data in a way that might cause a crash or vulnerability when decrypted. If you used a mode like CBC, you would need to add an HMAC (Hash-based Message Authentication Code) separately to ensure integrity.
Modern Android Recommendation: Jetpack Security
For new Android apps, Google provides the Jetpack Security library. It simplifies the process of using the Android Keystore significantly by providing high-level abstractions.
It includes:
EncryptedSharedPreferences: Drop-in replacement forSharedPreferencesthat automatically encrypts keys and values.EncryptedFile: A wrapper forjava.io.Filethat transparently encrypts and decrypts file contents.MasterKey: A central helper class to generate and manage the master key used by the other components.
Example using EncryptedSharedPreferences:
// In your app-level build.gradle
dependencies {
implementation "androidx.security:security-crypto:1.1.0-alpha06" // Or latest version
}
import androidx.security.crypto.EncryptedSharedPreferences;
import androidx.security.crypto.MasterKeys;
import android.content.SharedPreferences;
public class SecurePrefsHelper {
private SharedPreferences encryptedPrefs;
public SecurePrefsHelper(Context context) throws Exception {
// Create a master key to encrypt the shared preferences
String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);
// Create an EncryptedSharedPreferences instance
encryptedPrefs = (EncryptedSharedPreferences) EncryptedSharedPreferences.create(
"secret_shared_prefs",
masterKeyAlias,
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
}
public void saveSecret(String key, String value) {
encryptedPrefs.edit().putString(key, value).apply();
}
public String getSecret(String key) {
return encryptedPrefs.getString(key, null);
}
}
This library is the strongly recommended approach for most common encryption tasks on Android, as it handles the complexities of the Keystore for you.
