杰瑞科技汇

android aes java

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

android aes java-图1
(图片来源网络,侵删)

This guide will cover:

  1. Why AES? - The importance of using strong, standard encryption.
  2. The Critical Concept: Key Management - The hardest part of encryption.
  3. Android's Recommended Approach: Cipher and KeyGenerator - The standard Java way.
  4. A Complete, Runnable Android Example - An Activity with encryption and decryption.
  5. Best Practices & Security Considerations - How to do it right.
  6. 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.

android aes java-图2
(图片来源网络,侵删)

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

  1. Never Hardcode Keys: As mentioned, this is the cardinal sin. If you must store a key outside the Keystore, use Android's EncryptedSharedPreferences or MasterKey API, which manage the key for you securely.
  2. 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.
  3. 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.
  4. 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.
  5. 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 for SharedPreferences that automatically encrypts keys and values.
  • EncryptedFile: A wrapper for java.io.File that 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.

分享:
扫描分享到社交APP
上一篇
下一篇