前回の[Java]SQLChipherを導入してAndroidアプリのSQLiteデータを暗号化するに続いて、今回はSharedPreferencesのデータを暗号化する内容です。

 

SharedPreferencesはアプリ内で保存しておきたいようなデータがある時に利用するもので、文字列などは平文で保存されています。

Context.MODE_PROVATE設定にしておけば、そのアプリ以外からは参照できないような仕組みとなっているため、基本的には外部からデータは見られません。

 

ただし、端末がroot化されていたりすると簡単に参照できてしまいます。

そこで、SharedPreferencesのデータも暗号化されていればデータが参照されても文字列の内容は分からないため、もしもの時も安全性が高まります。

 

今回は、パスワードデータを暗号化して保存するサンプルを書きます。

 

1.暗号化/復号化のユーティリティクラスを作成

java.crypto.Cipherクラスを使って暗号化/復号化するクラスです。

import android.util.Base64;

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * 暗号化/復号化のユーティリティ
 */
public class CryptUtil {
    // アルゴリズム
    private static final String ALGORITHM = "Blowfish";
    private static final String MODE = "Blowfish/CBC/PKCS5Padding";
    private static final String IV = "abcdefgh"; // Blowfishの場合は64ビット(8バイト)なので

    /**
     * 引数の文字列を暗号化する(Base64対応)
     *
     * @param value     暗号化対象文字列
     * @param secretKey 暗号化キー
     * @return String 暗号化済み文字列
     */
    public String encrypt(String value, String secretKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);
        Cipher cipher = Cipher.getInstance(MODE);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(IV.getBytes()));
        byte[] values = cipher.doFinal(value.getBytes());
        return Base64.encodeToString(values, Base64.DEFAULT);
    }

    /**
     * 引数のBase64された文字列を復号化する
     *
     * @param value     復号化対象文字列
     * @param secretKey 復号化キー
     * @return String 復号化済み文字列
     */
    public String decrypt(String value, String secretKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        byte[] values = Base64.decode(value, Base64.DEFAULT);
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);
        Cipher cipher = Cipher.getInstance(MODE);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(IV.getBytes()));
        return new String(cipher.doFinal(values));
    }
}

2.文字列を渡すと暗号化してSharedPreferencesに保存するクラスを作成

保存時には、データの名前と実際のパスワードの文字列を渡すと保存できる

取得時には、データの名前を渡すと取得できる

というインターフェースとなるメソッドを用意しました。

import android.content.SharedPreferences;

public class MasterPasswordUtil {

    SharedPreferences sharedPref;
    // 暗号化/復号キー
    private static final String secretKey = "暗号化するキー文字列";

    MasterPasswordUtil(SharedPreferences pref) {
        sharedPref = pref;
    }

    // 暗号化してパスワードを保存
    public void saveMasterPasswordString(String key, String value) throws Exception {
        if (key == null || key.length() == 0) {
            throw new Exception("キーが空です。");
        }
        if (value == null) {
            throw new Exception("値が空です。");
        }
        // 暗号化
        CryptUtil cryptUtil = new CryptUtil();
        String encValue = cryptUtil.encrypt(value, secretKey);
        if (encValue == null) {
            throw new Exception("暗号化に失敗しました。");
        }

        // 保存
        SharedPreferences.Editor editor = sharedPref.edit();
        editor.putString(key, encValue).commit();
    }

    // 複合化してパスワードを読み込み
    public String getMasterPasswordString(String key) throws Exception {
        // 値取得
        if (key == null || key.length() == 0) {
            throw new Exception("キーが空です。");
        }
        String value = sharedPref.getString(key, null);
        if (value == null) {
            return null;
        }

        // 復号
        CryptUtil cryptUtil = new CryptUtil();
        String decValue = cryptUtil.decrypt(value, secretKey);
        if (decValue == null) {
            throw new Exception("復号に失敗しました。");
        }
        return decValue;
    }
}

3.上記2.のクラスを利用してパスワードデータを保存/取得する

SharedPreferencesのインスタンスを渡してMasterPasswordUtilをインスタンス化して利用します。

import android.content.SharedPreferences;

public class LoginActivity extends AppCompatActivity {
    /* 保存するファイル名を定義
    public static final String PREF_FILE_NAME ="LoginPasswordFile";

    public void SavePassword(String loginPassword) {
        try {
            SharedPreferences sharedPref = getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE);
            MasterPasswordUtil passUtil = new MasterPasswordUtil(sharedPref);
            passUtil.saveMasterPasswordString("LoginPassword", loginPassword);
        } catch (Exception e) {
            Log.e(TAG, e.getMessage(), e);
        }
    }

    public String GetPassword() {
        String loginPassword;

        try {
            SharedPreferences sharedPref = getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE);
            MasterPasswordUtil passUtil = new MasterPasswordUtil(sharedPref);
            loginPassword = passUtil.getMasterPasswordString("LoginPassword");
        } catch (Exception e) {
            Log.e(TAG, e.getMessage(), e);
        }

        return loginPassword;
    }

この記事を書くに当たって、SharedPreferencesに保存する文字列を暗号化

を参考にさせてもらいました!

 

SQLiteのデータの暗号化については、

[Java]SQLChipherを導入してAndroidアプリのSQLiteデータを暗号化する

で書いてます。

 

最後に・・・上記の仕組みを利用した、

パスワード管理アプリ「パスワードメモ」のAndroidアプリをリリースしています。

ぜひ利用して見てください!