Android で System.exit() を使ってはいけない理由と、終了方法のまとめ

Androidではアプリを終了させる場合、System.exit(0) でもアプリを終了することができますが、この方法ではVMごと強制終了させるため、アクティビティのライフサイクルを無視することになる上メモリなどのリソース解放に失敗するおそれがあり、安全ではありません。
適切な終了方法はどれか、ということでアプリケーションを終了させる方法をまとめてみました。

■Activity#finish()
アクティビティを閉じる際の最良の終了方法です。
現在のアクティビティを閉じて前のアクティビティに戻ります。
アプリケーションの起動アクティビティで呼び出した場合はアプリケーションを終了します。
バックボタンを押した時と同じ動きで、実行すると onPause(), onDestroy()が順番に呼ばれます。
復帰時は新規生成となり onCreate(), onStart(), onResume() の順でイベントが発生します。
 
■Activity#moveTaskToBack (boolean nonRoot)
アプリケーション全体を終了する際の推奨される終了方法です。
アプリケーションを中断状態にします。
引数に false を指定した場合、アプリケーションの開始アクティビティ以外の呼び出しでは何もしなくなるため、通常は引数にtrueを指定して呼び出します。
ホームボタンを押した時の動作と同じ動きで、実行するとonPause()イベントが発生します。
次の再開までバックグラウンドで待機状態になり、メモリ不足になった時はOSにより自動でonDestroy()イベントが発生してアプリケーションの全てのタスクが終了します。
onDestroy()が呼ばれる前に復帰した時は最後のアクティビティから再開し、onRestart(), onStart(), onResume() の順でイベントが発生します。
 
例:

this.moveTaskToBack(true);

ドキュメントより引用:

public boolean moveTaskToBack (boolean nonRoot)
 
Since: API Level 1
Move the task containing this activity to the back of the activity stack.
The activity’s order within the task is unchanged.
If the task was moved (or it was already at the back) true is returned, else false.
 
訳:このアクティビティを含むタスク全体のアクティビティスタックの順番を後ろに移動させます。
タスク内のアクティビティの順番は変更されません。
 
パラメータ
nonRoot If false then this only works if the activity is the root of a task; if true it will work for any activity in a task.
  訳:falseの場合は現在のアクティビティがタスクのルートアクティビティ(アプリケーションの開始アクティビティ)の時だけ動作します。
     trueの場合はタスク内のどのアクティビティであっても動作します。

 
■android.os.Process.killProcess(int pid)
Process.myPid ()を引数に与えると現在のプロセスを強制終了します。
アクティビティが実行中のプロセスを対象に呼び出すと、finish()と同じくそのアクティビティを終了します。
ただし、onDestroy()などActivityのライフサイクルを全て無視して強制終了するため、通常は Activity.finish() が推奨されます。
例:

android.os.Process.killProcess(Process.myPid());

ドキュメントより引用:

public static final void killProcess (int pid)
 
Since: API Level 1
Kill the process with the given PID.
Note that, though this API allows us to request to kill any process based on its PID,
the kernel will still impose standard restrictions on which PIDs you are actually able to kill.
Typically this means only the process running the caller’s packages/application and any additional
processes created by that app; packages sharing a common UID will also be able to kill each other’s processes.
訳:指定したIDのプロセスを終了します。
このAPIでは指定のPIDに基づいてどんなプロセスでも終了することが許されていますが、
実際終了できるかどうかはカーネルの標準的な制限で決まる点に注意してください。
つまり、典型的には呼び出し元のパッケージやアプリケーションが実行しているプロセスと、そのアプリが作成した追加のプロセスに限られるという意味です。
UIDを共有しているパッケージもまたお互いのプロセスを終了させる事ができます。

 
■System.exit(int)
System.exit(RESULT_OK)のように実行することで実行中のプロセスを強制終了します。
android.os.Process.killProcess(Process.myPid()) と同じような動きになりますが、
安全ではないので使うべきではありません。
ドキュメントには、

public static void exit (int code)
 
Since: API Level 1
Causes the virtual machine to stop running and the program to exit.
If runFinalizersOnExit(boolean) has been previously invoked with a true argument, then all objects will be properly garbage-collected and finalized first.
訳:バーチャルマシンの実行を停止し、プログラムを終了させます。
runFinalizersOnExit(boolean) を事前に引数にtrueを指定して呼び出してれば、全てのオブジェクトは最初に適切にガベージコレクトされてファイナライズされます。

のように一見、runFinalizersOnExit(true)を設定しておけば問題ないかのように書かれていますが、
実際にはrunFinalizersOnExit(boolean)の説明に

This method is deprecated. this method is unsafe.

と、どちらにしても安全ではない事が明確に書かれているためです。
 
通常は finish() または moveTaskToBack(true)を使い、どうしてもプロセスを強制終了したい場合は、Process.killProcess(Process.myPid ()) を使いましょう。
 
■おまけ…
アプリ内の全てのタスク(アクティビティ,プロセス)を一度に即終了したい場合, moveTaskToBack(true) を使う以外の方法として次のようなものがあります。
 
・AndroidManifest.xml で <activity android:noHistory=”true”> オプションを指定して finish() する。
 noHistory=”true”を指定しておくとアクティビティのスタックがなくなる事を利用する方法です。
 
参考:
Activity(developer.android.com)
 アクティビティのライフサイクルについてなど。
アクティビティとタスクデザイン (ソフトウェア技術ドキュメントを勝手に翻訳)
Application Fundamentals(developer.android.com)
android close application
 Androidの強制終了の仕方についての議論。

携帯のかんたんログイン問題

– まだまだ他でも破綻しているケータイID認証
http://takagi-hiromitsu.jp/diary/20100427.html#p01
– ここまで破綻しているケータイID認証(簡単ログイン)
http://takagi-hiromitsu.jp/diary/20100425.html#p01
– SoftBank Mobileの携帯用GatewayをPCで通る方法のメモ
http://d.hatena.ne.jp/hideden/20090801/1249142985

Sandboxie – Windows 用サンドボックス実行環境

Sandboxie – http://www.sandboxie.com/
 
プログラムのハードディスクへの書き込みを仮想化してくれる簡易サンドボックス。
Windows XP/2003/Vista 対応。
 
使い方は公式サイトや参考サイトを参照してください。
 
Sandoboxie を使ってサンドボックス化して実行したプログラムはファイル/レジストリの読み込みは仮想領域内参照+なければ実際の情報参照で行い、書き込みは仮想領域内にしかできなくなります。追加書き込みにしても実環境からコピー後に仮想環境に追加になるのでファイルの改ざん対策として抜群の効果があります。また、実行したプログラムがどのファイルを作成/変更するかを調べるのにも便利です。
 
ちなみに仮想領域は c:\Sandbox\(Windowsユーザアカウント名)\(DefaultBoxなどサンドボックス名\ 内に作られます。drive が C: ドライブ、 D: ドライブなどハードディスク構成を仮想化した領域で、user が Application Data や Local Settings, デスクトップなどユーザーのホームディレクトリ内の情報として利用されます。
 
なお、 Sandboxie ではネットワーク通信はそのまま流れるため情報流出は防げませんので、サンドボックスだからといって Winny など P2P を実行しても大丈夫なんだ、とは決して思わないように。
サンドボックスフォルダを削除するだけで確実に駆除できるため、事後対応は楽ですが…。
 
参考:
ヤバげなファイルは「Sandboxie」の仮想領域で実行せよ(教えて君.net)

CAcert.org のセキュリティ上の問題点

いまさらネタですが、最近になってようやく CAcert.org を使ってみました。
 
しかし少し触ってみた結果、CAcert.org をルート証明書に入れるように人に勧めることはとてもできないという結論に至りました。
気をつけるべき箇所を把握した上でルート証明に加えるなら問題はないのですが。。
 
大事な点は、身分証明や本人確認を
「電子メール証明なら対象メールアドレス、サーバ証明なら対象ドメインのwhois の Administrative Contact に書かれたメールアドレスへのメールを読めるかどうか?」
だけで判断しているため、少なからず第三者がサーバ証明書を作る危険性がある点(ただし既に CAcert.org で誰かに登録されたメールアドレス/ドメインは不可)と、
「誰でも無料で登録、即時証明書発行ができる」
という点。
CAcert.org では第三者に保証人になってもらいポイントを増やすことで信頼を重ねたりもできますが、実際のところはそんなものがなくても、(CAcert.org がルート証明機関に入ったブラウザであれば) https 通信で警告が出ないサーバ証明書を作ることができます。
 
CAcert.org に限りませんが、たとえば g00gle.com みたいな、ターゲットドメインに類似した名前のドメインを正規にとり、そのサーバ証明書を作ってフィッシングサイトを作る、なんてこともありえます。
 
クライアントが証明書を適切にチェックするようになればこういった問題を考える必要はないのですが、現実にはそうもいきません。
今日日数十ドルで証明書を発行してくれるルート証明機関もいますが、CAcert.org のような無料で証明書を発行できる組織が、そういうユーザが使うブラウザのルート証明機関に入るのはリスクの増大に他ならないかなという結論に至ったわけです。
 
参考:
CAcert.org は Windows のルート証明書に含まれるようになるのか?(yamk 日記)

Google を使った MD5 逆変換

http://labs.cybozu.co.jp/blog/nishio/2007/12/googlemd5.html
より。
 

http://md5-db.com/hashes/

から始まる URL をハッシュと文字列の対照表にして Google に読ませてMD5検索から実体を得ようという試みらしいです。

http://md5-db.com/hashes/foobar

とか。
このサイトのMD5値は 2008-01-28 現在数桁分しか Google でヒットしませんでしたが、一般的な英単語、短い英数字文字列などの MD5 値は他のサイトで結構ヒットするようで。。
 
d35cfefa43f4ad9ef9d5b962d487a12c を Google で検索

PHP で OpenSSL

PHP で OpenSSL 関数を使ってみたので、定番のハローワールドを置いておきます。


OpenSSL 関数で暗号化、復号化を行うには、
暗号化、復号化に必要な鍵のペアを PEM 形式で生成する必要があります。
1. パスワード付き秘密鍵 private_password.pem を生成

$ openssl genrsa private_password.pem -des3 1024

2. (1)からパスワードのない秘密鍵 private.pem を生成

$ openssl rsa -in private_password.pem -out private.pem

3. (1)を削除

$ rm private_password.pem

4. (2)から公開鍵 public.pem を生成

$ openssl rsa -in private.pem -pubout -out public.pem

 


openssl_public_encrypt(), openssl_private
…公開鍵で暗号化し、秘密鍵で復号化します。
つまり、誰からでも暗号化して送れるが、内容を読めるのは秘密鍵を持っている人だけになるため、通信経路での傍受が不可能になり、内容が外部に洩れる心配がないので機密性が高くなります。
ただし誰でも暗号化できるので全く別の内容へのかいざんは可能であり、必ずしも信頼性はありません。
 

// 暗号化に使う公開鍵
$public_key = file_get_contents(“public.pem”);
 
// 暗号化する文字列
$message = “Hello, world!!”;
 
// 暗号化データの結果格納先
$encrypted = “”;
 
// 暗号化実行
if(!openssl_public_encrypt($message, $encrypted, $public_key)){
    echo “暗号化に失敗\n”;
    die;
}
 
// 暗号化結果を表示(そのままでは表示できないのでBASE64エンコードする)
echo “暗号化データ: \n “.base64_encode($encrypted) . “\n”;
 
$message = null;
// ———————
// 復号化
// ———————
// 暗号化に使った公開鍵に対応する秘密鍵
$private_key = file_get_contents(“private.pem”);
 
// 復号化結果格納先
$result = “”;
 
// 復号化実行
if(!openssl_private_decrypt($encrypted, $result, $private_key)){
    echo “復号化に失敗\n”;
    die;
}
 
// 結果出力
echo “復号化結果: \n ” . $result . “\n”
     “BASE64:\n” . base64_encode($result).”\n”; // 比較用

結果:

暗号化データ:
FmPaJ9jOmjl/mK249NqUPzYjPUHsmFkSuNyp0SMlgW1c9RRzmKXBjcRhXH/5fwTxItAcE8/
AlZkjYo27XvGR7a3hdXxgaXbk4VORdRRk8LJXKB+xlVzF48OlHJ5lKUrXUdfr2BFtx53/IH
431mBlYWDhf+KyW608eKAA0T34qRY=
復号化結果:
Hello, world!!
BASE64:
SGVsbG8sIHdvcmxkZmc=

 


openssl_private_encrypt(), openssl_public_decrypt() の例:
…秘密鍵で暗号化し、公開鍵で復号化します。
つまり、鍵の所有者しか暗号化できないので中身のかいざんも、経路での差し替えも不可能になり、内容についての信頼性が高くなります。
ただし誰でも読めるため、内容の機密性はありません。
 
秘密鍵、公開鍵の使い方を逆にする以外は上の例と全く同じです。

// 暗号化に使う秘密鍵
$private_key = file_get_contents(“private.pem”);
 
// 暗号化する文字列
$message = “Hello, world!!”;
 
// 暗号化データの結果格納先
$encrypted = “”;
 
// 暗号化実行
if(!openssl_private_encrypt($message, $encrypted, $private_key)){
    echo “暗号化に失敗\n”;
    die;
}
 
// 暗号化結果を表示(そのままでは表示できないのでBASE64エンコードする)
echo “暗号化データ: \n “.base64_encode($encrypted) . “\n”;
 
$message = null;
// ———————
// 復号化
// ———————
// 暗号化に使った秘密鍵から作られた公開鍵
$public_key = file_get_contents(“public.pem”);
 
// 復号化結果格納先
$result = “”;
 
// 復号化実行
if(!openssl_public_decrypt($encrypted, $result, $public_key)){
    echo “復号化に失敗\n”;
    die;
}
 
// 結果出力
echo “復号化結果: \n ” . $result . “\n”
     “BASE64:\n” . base64_encode($result).”\n”; // 比較用

結果:

暗号化データ:
CII4EW6LQBq0faVJxshUfk9VCdFsE66DnQPVdsLbvfq7tNCMzAIZbZA3GBxHu4cG2TqvOnn
xN9C9S9IgBIbulfj4c90IkrYtHQGt3RHAJBYiAgfmzCKbMr2cPJhZwkMUkjczXeaOD1htFJ
TL9e2RKsrWBVFIaiG1JgFk2Jyd928=
復号化結果:
Hello, world!!
BASE64:
SGVsbG8sIHdvcmxkZmc=


openssl_open(), openssl_seal() の例:
…同報メールのように、別々の公開鍵を持つ複数の相手に対して同じデータを暗号化して送る場合に使います
 
– 送信側(送信先1の公開鍵 user1_public.pem と送信先2の公開鍵 user2_public.pem を持っているとする. 自分の鍵は必要としない)

// 送り先の公開鍵一覧。
$pubkeys = array(
        file_get_contents(“user1_public.pem”),
        file_get_contents(“user2_public.pem”),
   );
 
// 送る内容
$real_message = “Hello, world!!”;
 
// 暗号化データの結果格納先
$encrypted = “”;
// 復号化に必要なエンベロープキーの格納先。$pubkeys の数だけ格納される。
$ekeys = array();
 
if(!openssl_seal($real_message, $encrypted, $ekeys, $pubkeys)){
    echo “暗号化に失敗しました\n”;
    die;
}
 
// 出力. エンベロープキーと暗号化データについてはバイトコードなので、表示のために BASE64 エンコードしています。
for($i = 0; $i < count($pubkeys); $i++){
    echo “送り先番号: $i\n” .
         “使った公開鍵: \n” .
         $pubkeys[$i] .”\n” .
         “対応するエンベロープキー: \n” .
         base64_encode($ekeys[$i]) .”\n” .
         “暗号化されたデータ: \n” .
         base64_decode($encrypted);
}

 
受信側(送信側からエンベロープキー $ekey と暗号化データ $encrypted を受け取っているとする):

// 送信側が暗号化に使った公開鍵(user1_public.pem)に対応する秘密鍵
$private_key = file_get_contents(“user1_private.pem”);
 
// 結果格納先。復号化した文字列が入る
$message = “”;
 
if(!openssl_open($encrypted, $message, $ekey, $private_key)){
    echo “復号化に失敗しました\n”;
    die;
}
 
// 結果表示
echo $message . “\n”;

 
送信側結果は次のようになります。


送り先番号: 0
使った公開鍵:


-BEGIN PUBLIC KEY—–
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCzZFcKoXbuWzGvlv++7Y05/jBh
nYC+PM67v3H2I5pYV8tUz/6GqsG5afFgLJYoWPEd3KYXHb46B0GxEOydOTuTLEcv
+gVr6v4khgOgjxOcM1hlv+EvAThm1lIY1Y5lbTv+MEUyrGm7SDk8iUaHF6OBcQp3
NzkO6j3+YrsC8znXPwIDAQAB


-END PUBLIC KEY—–
対応するエンベロープキー:
lu4ON1xW2hrrS1jXfav6mjWD32+Iuz1Jk20vhDRmSsfEp3vXuij7Z0R/ClhpgE9MxDJADiU
LS/CCUPuiZ3wUDErccfUFGGgcBKFsoXOgcX/vSfhi8yCYu3BOD+h5fOMmg/ZVe6NMjCVq50
D8yce5gPoypg2LHijse+0R1bPil8o=
暗号化されたデータ:
0qf1eWjjaHkiqRFKijam


送り先番号: 1
使った公開鍵:


-BEGIN PUBLIC KEY—–
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCzZFcKoXbuWzGvlv++7Y05/jBh
nYC+PM67v3H2I5pYV8tUz/6GqsG5afFgLJYoWPEd3KYXHb46B0GxEOydOTuTLEcv
+gVr6v4khgOgjxOcM1hlv+EvAThm1lIY1Y5lbTv+MEUyrGm7SDk8iUaHF6OBcQp3
NzkO6j3+YrsC8znXPwIDAQAB


-END PUBLIC KEY—–
対応するエンベロープキー:
Hpv4sE4M4RGnbEAPMqFKxOOPDJG0hpWBoIXCe8xUaIzx/dskUQ/a+T4jYyw+RRGh11wlzZt
CJOjzkP3pbafc0ymElAzS8RFAne7bVuCJh44UF55thAUo0nZVo1/2Wz9HuTDL23AYwc+jyh
U/k24bpGh81VJl5I7IkItoDeiS70Y=
暗号化されたデータ:
0qf1eWjjaHkiqRFKijam

受信側結果:

Hello, world!!

 
参考:
OpenSSL 関数(php.net)
 PHP マニュアルの OpenSSL 関数についての項目。
OpenSSL 公式サイトの日本語訳サイト
更新は不定期のようですが、openssl のマニュアルが参考になります。
公式はこちらです。

Webサービス脆弱性検査ツール

Acunetix Web Vulnerability Scanner という有償製品が比較的高機能で使いやすそうです。
Hotscriptsから、フォーム入力なしでトライアル版がダウンロードできます。
 
トライアルは検査対象サイトが、Acunetix のテスト用サイトに限定されてしまうため、性能については試せませんが、使い勝手等は体感できます。