diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 000000000..5a41f262d
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/default.properties b/default.properties
new file mode 100644
index 000000000..19c96655d
--- /dev/null
+++ b/default.properties
@@ -0,0 +1,13 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Indicates whether an apk should be generated for each density.
+split.density=false
+# Project target.
+target=android-4
diff --git a/res/drawable-finger/btn_circle.xml b/res/drawable-finger/btn_circle.xml
new file mode 100644
index 000000000..6c3c7fc1a
--- /dev/null
+++ b/res/drawable-finger/btn_circle.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable-hdpi-finger/btn_circle_disable.png b/res/drawable-hdpi-finger/btn_circle_disable.png
new file mode 100644
index 000000000..ae063b545
Binary files /dev/null and b/res/drawable-hdpi-finger/btn_circle_disable.png differ
diff --git a/res/drawable-hdpi-finger/btn_circle_disable_focused.png b/res/drawable-hdpi-finger/btn_circle_disable_focused.png
new file mode 100644
index 000000000..7a5d4fe4e
Binary files /dev/null and b/res/drawable-hdpi-finger/btn_circle_disable_focused.png differ
diff --git a/res/drawable-hdpi-finger/btn_circle_normal.png b/res/drawable-hdpi-finger/btn_circle_normal.png
new file mode 100644
index 000000000..5eda66883
Binary files /dev/null and b/res/drawable-hdpi-finger/btn_circle_normal.png differ
diff --git a/res/drawable-hdpi-finger/btn_circle_pressed.png b/res/drawable-hdpi-finger/btn_circle_pressed.png
new file mode 100644
index 000000000..88848bac2
Binary files /dev/null and b/res/drawable-hdpi-finger/btn_circle_pressed.png differ
diff --git a/res/drawable-hdpi-finger/btn_circle_selected.png b/res/drawable-hdpi-finger/btn_circle_selected.png
new file mode 100644
index 000000000..74690705f
Binary files /dev/null and b/res/drawable-hdpi-finger/btn_circle_selected.png differ
diff --git a/res/drawable-hdpi-finger/ic_btn_round_minus.png b/res/drawable-hdpi-finger/ic_btn_round_minus.png
new file mode 100644
index 000000000..27af3faf4
Binary files /dev/null and b/res/drawable-hdpi-finger/ic_btn_round_minus.png differ
diff --git a/res/drawable-hdpi-finger/ic_btn_round_plus.png b/res/drawable-hdpi-finger/ic_btn_round_plus.png
new file mode 100644
index 000000000..b24168c32
Binary files /dev/null and b/res/drawable-hdpi-finger/ic_btn_round_plus.png differ
diff --git a/res/drawable-hdpi/encrypted.png b/res/drawable-hdpi/encrypted.png
new file mode 100644
index 000000000..6d7c616a4
Binary files /dev/null and b/res/drawable-hdpi/encrypted.png differ
diff --git a/res/drawable-hdpi/encrypted_large.png b/res/drawable-hdpi/encrypted_large.png
new file mode 100644
index 000000000..dc7466e45
Binary files /dev/null and b/res/drawable-hdpi/encrypted_large.png differ
diff --git a/res/drawable-hdpi/encrypted_small.png b/res/drawable-hdpi/encrypted_small.png
new file mode 100644
index 000000000..5ed9fe4b8
Binary files /dev/null and b/res/drawable-hdpi/encrypted_small.png differ
diff --git a/res/drawable-hdpi/icon.png b/res/drawable-hdpi/icon.png
new file mode 100644
index 000000000..9e2e7c0e4
Binary files /dev/null and b/res/drawable-hdpi/icon.png differ
diff --git a/res/drawable-hdpi/key.png b/res/drawable-hdpi/key.png
new file mode 100644
index 000000000..6f18c0240
Binary files /dev/null and b/res/drawable-hdpi/key.png differ
diff --git a/res/drawable-hdpi/key_large.png b/res/drawable-hdpi/key_large.png
new file mode 100644
index 000000000..81816835d
Binary files /dev/null and b/res/drawable-hdpi/key_large.png differ
diff --git a/res/drawable-hdpi/key_small.png b/res/drawable-hdpi/key_small.png
new file mode 100644
index 000000000..3f42a0d9b
Binary files /dev/null and b/res/drawable-hdpi/key_small.png differ
diff --git a/res/drawable-hdpi/overlay_error.png b/res/drawable-hdpi/overlay_error.png
new file mode 100644
index 000000000..db6a08329
Binary files /dev/null and b/res/drawable-hdpi/overlay_error.png differ
diff --git a/res/drawable-hdpi/overlay_ok.png b/res/drawable-hdpi/overlay_ok.png
new file mode 100644
index 000000000..33dc08094
Binary files /dev/null and b/res/drawable-hdpi/overlay_ok.png differ
diff --git a/res/drawable-hdpi/signed.png b/res/drawable-hdpi/signed.png
new file mode 100644
index 000000000..92e64dc51
Binary files /dev/null and b/res/drawable-hdpi/signed.png differ
diff --git a/res/drawable-hdpi/signed_large.png b/res/drawable-hdpi/signed_large.png
new file mode 100644
index 000000000..53d8ac991
Binary files /dev/null and b/res/drawable-hdpi/signed_large.png differ
diff --git a/res/drawable-hdpi/signed_small.png b/res/drawable-hdpi/signed_small.png
new file mode 100644
index 000000000..d7f147f05
Binary files /dev/null and b/res/drawable-hdpi/signed_small.png differ
diff --git a/res/drawable-ldpi/encrypted.png b/res/drawable-ldpi/encrypted.png
new file mode 100644
index 000000000..7f4ab803f
Binary files /dev/null and b/res/drawable-ldpi/encrypted.png differ
diff --git a/res/drawable-ldpi/encrypted_large.png b/res/drawable-ldpi/encrypted_large.png
new file mode 100644
index 000000000..2783804bc
Binary files /dev/null and b/res/drawable-ldpi/encrypted_large.png differ
diff --git a/res/drawable-ldpi/encrypted_small.png b/res/drawable-ldpi/encrypted_small.png
new file mode 100644
index 000000000..0ffedf2dd
Binary files /dev/null and b/res/drawable-ldpi/encrypted_small.png differ
diff --git a/res/drawable-ldpi/icon.png b/res/drawable-ldpi/icon.png
new file mode 100644
index 000000000..9d44341f1
Binary files /dev/null and b/res/drawable-ldpi/icon.png differ
diff --git a/res/drawable-ldpi/key.png b/res/drawable-ldpi/key.png
new file mode 100644
index 000000000..121803508
Binary files /dev/null and b/res/drawable-ldpi/key.png differ
diff --git a/res/drawable-ldpi/key_large.png b/res/drawable-ldpi/key_large.png
new file mode 100644
index 000000000..de7e72524
Binary files /dev/null and b/res/drawable-ldpi/key_large.png differ
diff --git a/res/drawable-ldpi/key_small.png b/res/drawable-ldpi/key_small.png
new file mode 100644
index 000000000..1763c4256
Binary files /dev/null and b/res/drawable-ldpi/key_small.png differ
diff --git a/res/drawable-ldpi/overlay_error.png b/res/drawable-ldpi/overlay_error.png
new file mode 100644
index 000000000..568f2b1ee
Binary files /dev/null and b/res/drawable-ldpi/overlay_error.png differ
diff --git a/res/drawable-ldpi/overlay_ok.png b/res/drawable-ldpi/overlay_ok.png
new file mode 100644
index 000000000..db415a846
Binary files /dev/null and b/res/drawable-ldpi/overlay_ok.png differ
diff --git a/res/drawable-ldpi/signed.png b/res/drawable-ldpi/signed.png
new file mode 100644
index 000000000..590220281
Binary files /dev/null and b/res/drawable-ldpi/signed.png differ
diff --git a/res/drawable-ldpi/signed_large.png b/res/drawable-ldpi/signed_large.png
new file mode 100644
index 000000000..490e94fbd
Binary files /dev/null and b/res/drawable-ldpi/signed_large.png differ
diff --git a/res/drawable-ldpi/signed_small.png b/res/drawable-ldpi/signed_small.png
new file mode 100644
index 000000000..ca33fc1f7
Binary files /dev/null and b/res/drawable-ldpi/signed_small.png differ
diff --git a/res/drawable-mdpi-finger/btn_circle_disable.png b/res/drawable-mdpi-finger/btn_circle_disable.png
new file mode 100644
index 000000000..33b74a66c
Binary files /dev/null and b/res/drawable-mdpi-finger/btn_circle_disable.png differ
diff --git a/res/drawable-mdpi-finger/btn_circle_disable_focused.png b/res/drawable-mdpi-finger/btn_circle_disable_focused.png
new file mode 100644
index 000000000..005ad8dca
Binary files /dev/null and b/res/drawable-mdpi-finger/btn_circle_disable_focused.png differ
diff --git a/res/drawable-mdpi-finger/btn_circle_normal.png b/res/drawable-mdpi-finger/btn_circle_normal.png
new file mode 100644
index 000000000..fc5af1c9f
Binary files /dev/null and b/res/drawable-mdpi-finger/btn_circle_normal.png differ
diff --git a/res/drawable-mdpi-finger/btn_circle_pressed.png b/res/drawable-mdpi-finger/btn_circle_pressed.png
new file mode 100644
index 000000000..8f40afdfc
Binary files /dev/null and b/res/drawable-mdpi-finger/btn_circle_pressed.png differ
diff --git a/res/drawable-mdpi-finger/btn_circle_selected.png b/res/drawable-mdpi-finger/btn_circle_selected.png
new file mode 100644
index 000000000..c74fac227
Binary files /dev/null and b/res/drawable-mdpi-finger/btn_circle_selected.png differ
diff --git a/res/drawable-mdpi-finger/ic_btn_round_minus.png b/res/drawable-mdpi-finger/ic_btn_round_minus.png
new file mode 100644
index 000000000..96dbb17d2
Binary files /dev/null and b/res/drawable-mdpi-finger/ic_btn_round_minus.png differ
diff --git a/res/drawable-mdpi-finger/ic_btn_round_plus.png b/res/drawable-mdpi-finger/ic_btn_round_plus.png
new file mode 100644
index 000000000..1ec8a956a
Binary files /dev/null and b/res/drawable-mdpi-finger/ic_btn_round_plus.png differ
diff --git a/res/drawable-mdpi/encrypted.png b/res/drawable-mdpi/encrypted.png
new file mode 100644
index 000000000..2783804bc
Binary files /dev/null and b/res/drawable-mdpi/encrypted.png differ
diff --git a/res/drawable-mdpi/encrypted_large.png b/res/drawable-mdpi/encrypted_large.png
new file mode 100644
index 000000000..6d7c616a4
Binary files /dev/null and b/res/drawable-mdpi/encrypted_large.png differ
diff --git a/res/drawable-mdpi/encrypted_small.png b/res/drawable-mdpi/encrypted_small.png
new file mode 100644
index 000000000..7f4ab803f
Binary files /dev/null and b/res/drawable-mdpi/encrypted_small.png differ
diff --git a/res/drawable-mdpi/icon.png b/res/drawable-mdpi/icon.png
new file mode 100644
index 000000000..b4e4db40e
Binary files /dev/null and b/res/drawable-mdpi/icon.png differ
diff --git a/res/drawable-mdpi/key.png b/res/drawable-mdpi/key.png
new file mode 100644
index 000000000..de7e72524
Binary files /dev/null and b/res/drawable-mdpi/key.png differ
diff --git a/res/drawable-mdpi/key_large.png b/res/drawable-mdpi/key_large.png
new file mode 100644
index 000000000..6f18c0240
Binary files /dev/null and b/res/drawable-mdpi/key_large.png differ
diff --git a/res/drawable-mdpi/key_small.png b/res/drawable-mdpi/key_small.png
new file mode 100644
index 000000000..121803508
Binary files /dev/null and b/res/drawable-mdpi/key_small.png differ
diff --git a/res/drawable-mdpi/overlay_error.png b/res/drawable-mdpi/overlay_error.png
new file mode 100644
index 000000000..2372de59e
Binary files /dev/null and b/res/drawable-mdpi/overlay_error.png differ
diff --git a/res/drawable-mdpi/overlay_ok.png b/res/drawable-mdpi/overlay_ok.png
new file mode 100644
index 000000000..2f0005898
Binary files /dev/null and b/res/drawable-mdpi/overlay_ok.png differ
diff --git a/res/drawable-mdpi/signed.png b/res/drawable-mdpi/signed.png
new file mode 100644
index 000000000..490e94fbd
Binary files /dev/null and b/res/drawable-mdpi/signed.png differ
diff --git a/res/drawable-mdpi/signed_large.png b/res/drawable-mdpi/signed_large.png
new file mode 100644
index 000000000..92e64dc51
Binary files /dev/null and b/res/drawable-mdpi/signed_large.png differ
diff --git a/res/drawable-mdpi/signed_small.png b/res/drawable-mdpi/signed_small.png
new file mode 100644
index 000000000..590220281
Binary files /dev/null and b/res/drawable-mdpi/signed_small.png differ
diff --git a/res/layout/account_item.xml b/res/layout/account_item.xml
new file mode 100644
index 000000000..e37000ff0
--- /dev/null
+++ b/res/layout/account_item.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
diff --git a/res/layout/create_key.xml b/res/layout/create_key.xml
new file mode 100644
index 000000000..569b703f5
--- /dev/null
+++ b/res/layout/create_key.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/decrypt_message.xml b/res/layout/decrypt_message.xml
new file mode 100644
index 000000000..2a0aa153d
--- /dev/null
+++ b/res/layout/decrypt_message.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/edit_key.xml b/res/layout/edit_key.xml
new file mode 100644
index 000000000..2fceeb5a3
--- /dev/null
+++ b/res/layout/edit_key.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/edit_key_key_item.xml b/res/layout/edit_key_key_item.xml
new file mode 100644
index 000000000..46de4a977
--- /dev/null
+++ b/res/layout/edit_key_key_item.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/edit_key_section.xml b/res/layout/edit_key_section.xml
new file mode 100644
index 000000000..b3a48f87e
--- /dev/null
+++ b/res/layout/edit_key_section.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/edit_key_user_id_item.xml b/res/layout/edit_key_user_id_item.xml
new file mode 100644
index 000000000..998c436cb
--- /dev/null
+++ b/res/layout/edit_key_user_id_item.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/encrypt_message.xml b/res/layout/encrypt_message.xml
new file mode 100644
index 000000000..254552e03
--- /dev/null
+++ b/res/layout/encrypt_message.xml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/key_list_child_item_master_key.xml b/res/layout/key_list_child_item_master_key.xml
new file mode 100644
index 000000000..47eba65b5
--- /dev/null
+++ b/res/layout/key_list_child_item_master_key.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/key_list_child_item_sub_key.xml b/res/layout/key_list_child_item_sub_key.xml
new file mode 100644
index 000000000..085d78f05
--- /dev/null
+++ b/res/layout/key_list_child_item_sub_key.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/key_list_child_item_user_id.xml b/res/layout/key_list_child_item_user_id.xml
new file mode 100644
index 000000000..80cdd2867
--- /dev/null
+++ b/res/layout/key_list_child_item_user_id.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
diff --git a/res/layout/key_list_group_item.xml b/res/layout/key_list_group_item.xml
new file mode 100644
index 000000000..aaada82e3
--- /dev/null
+++ b/res/layout/key_list_group_item.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/mailbox_message_item.xml b/res/layout/mailbox_message_item.xml
new file mode 100644
index 000000000..b2b5e91d4
--- /dev/null
+++ b/res/layout/mailbox_message_item.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/main.xml b/res/layout/main.xml
new file mode 100644
index 000000000..81c5f224b
--- /dev/null
+++ b/res/layout/main.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/select_public_key.xml b/res/layout/select_public_key.xml
new file mode 100644
index 000000000..9a2d9f578
--- /dev/null
+++ b/res/layout/select_public_key.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/select_public_key_item.xml b/res/layout/select_public_key_item.xml
new file mode 100644
index 000000000..aba0c09b9
--- /dev/null
+++ b/res/layout/select_public_key_item.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/select_secret_key.xml b/res/layout/select_secret_key.xml
new file mode 100644
index 000000000..64967ace6
--- /dev/null
+++ b/res/layout/select_secret_key.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/select_secret_key_item.xml b/res/layout/select_secret_key_item.xml
new file mode 100644
index 000000000..0b0475c37
--- /dev/null
+++ b/res/layout/select_secret_key_item.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 000000000..b1fa76915
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,68 @@
+
+
+
+
+ APG
+ Mail Inbox
+ Manage Public Keys
+ Manage Secret Keys
+ Select Recipients
+ Select Signature
+ Encrypt Message
+ Decrypt Message
+ Authentification
+ Create Key
+ Edit Key
+
+ User IDs
+ Keys
+
+ Send via Email
+ Decrypt
+ Select Recipients
+ Reply
+ Encrypt Message
+ Decrypt Message
+ Save
+ Cancel
+
+ About
+ Add GMail Account
+ Manage Public Keys
+ Manage Secret Keys
+
+ Sign
+ Sign as
+ Select Recipients
+ 1 Recipient
+ Recipients
+ <unknown>
+ <none>
+
+ Sign only
+ Encrypt only
+ Sign and Encrypt
+
+ DSA
+ ElGamal
+ RSA
+
+ Wrong pass phrase.
+ Using clipboard content.
+ Key saved.
+ Set a pass phrase via the option menu first.
+
+
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 000000000..720f7aedb
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
diff --git a/src/org/thialfihar/android/apg/Apg.java b/src/org/thialfihar/android/apg/Apg.java
new file mode 100644
index 000000000..68c4f8877
--- /dev/null
+++ b/src/org/thialfihar/android/apg/Apg.java
@@ -0,0 +1,1496 @@
+/*
+ * Copyright (C) 2010 Thialfihar
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thialfihar.android.apg;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.SignatureException;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Vector;
+import java.util.regex.Pattern;
+
+import org.bouncycastle2.bcpg.ArmoredOutputStream;
+import org.bouncycastle2.bcpg.BCPGOutputStream;
+import org.bouncycastle2.bcpg.CompressionAlgorithmTags;
+import org.bouncycastle2.bcpg.HashAlgorithmTags;
+import org.bouncycastle2.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle2.bcpg.sig.KeyFlags;
+import org.bouncycastle2.jce.provider.BouncyCastleProvider;
+import org.bouncycastle2.jce.spec.ElGamalParameterSpec;
+import org.bouncycastle2.openpgp.PGPCompressedData;
+import org.bouncycastle2.openpgp.PGPCompressedDataGenerator;
+import org.bouncycastle2.openpgp.PGPEncryptedData;
+import org.bouncycastle2.openpgp.PGPEncryptedDataGenerator;
+import org.bouncycastle2.openpgp.PGPEncryptedDataList;
+import org.bouncycastle2.openpgp.PGPException;
+import org.bouncycastle2.openpgp.PGPKeyPair;
+import org.bouncycastle2.openpgp.PGPKeyRingGenerator;
+import org.bouncycastle2.openpgp.PGPLiteralData;
+import org.bouncycastle2.openpgp.PGPLiteralDataGenerator;
+import org.bouncycastle2.openpgp.PGPObjectFactory;
+import org.bouncycastle2.openpgp.PGPOnePassSignature;
+import org.bouncycastle2.openpgp.PGPOnePassSignatureList;
+import org.bouncycastle2.openpgp.PGPPrivateKey;
+import org.bouncycastle2.openpgp.PGPPublicKey;
+import org.bouncycastle2.openpgp.PGPPublicKeyEncryptedData;
+import org.bouncycastle2.openpgp.PGPPublicKeyRing;
+import org.bouncycastle2.openpgp.PGPSecretKey;
+import org.bouncycastle2.openpgp.PGPSecretKeyRing;
+import org.bouncycastle2.openpgp.PGPSignature;
+import org.bouncycastle2.openpgp.PGPSignatureGenerator;
+import org.bouncycastle2.openpgp.PGPSignatureList;
+import org.bouncycastle2.openpgp.PGPSignatureSubpacketGenerator;
+import org.bouncycastle2.openpgp.PGPSignatureSubpacketVector;
+import org.bouncycastle2.openpgp.PGPUtil;
+import org.thialfihar.android.apg.provider.PublicKeys;
+import org.thialfihar.android.apg.provider.SecretKeys;
+import org.thialfihar.android.apg.ui.widget.KeyEditor;
+import org.thialfihar.android.apg.ui.widget.SectionView;
+import org.thialfihar.android.apg.ui.widget.UserIdEditor;
+import org.thialfihar.android.apg.utils.IterableIterator;
+
+import android.app.Activity;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.view.ViewGroup;
+
+public class Apg {
+ public static class Intent {
+ public static final String DECRYPT = "org.thialfihar.android.apg.intent.DECRYPT";
+ public static final String ENCRYPT = "org.thialfihar.android.apg.intent.ENCRYPT";
+ }
+
+ public static String VERSION = "0.8.0";
+ public static String FULL_VERSION = "APG v" + VERSION;
+
+ private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS =
+ new int[] {
+ SymmetricKeyAlgorithmTags.AES_256,
+ SymmetricKeyAlgorithmTags.AES_192,
+ SymmetricKeyAlgorithmTags.AES_128,
+ SymmetricKeyAlgorithmTags.CAST5,
+ SymmetricKeyAlgorithmTags.TRIPLE_DES };
+ private static final int[] PREFERRED_HASH_ALGORITHMS =
+ new int[] {
+ HashAlgorithmTags.SHA1,
+ HashAlgorithmTags.SHA256,
+ HashAlgorithmTags.RIPEMD160 };
+ private static final int[] PREFERRED_COMPRESSION_ALGORITHMS =
+ new int[] {
+ CompressionAlgorithmTags.ZLIB,
+ CompressionAlgorithmTags.BZIP2,
+ CompressionAlgorithmTags.ZIP };
+
+ protected static Vector mPublicKeyRings;
+ protected static Vector mSecretKeyRings;
+
+ public static Pattern PGP_MESSAGE =
+ Pattern.compile(".*?(-----BEGIN PGP MESSAGE-----\n.*?-----END PGP MESSAGE-----).*",
+ Pattern.DOTALL);
+
+ protected static boolean mInitialized = false;
+
+ protected static final int RETURN_NO_MASTER_KEY = -2;
+ protected static final int RETURN_ERROR = -1;
+ protected static final int RETURN_OK = 0;
+ protected static final int RETURN_UPDATED = 1;
+
+ protected static final int TYPE_PUBLIC = 0;
+ protected static final int TYPE_SECRET = 1;
+
+ protected static HashMap mSecretKeyIdToIdMap;
+ protected static HashMap mSecretKeyIdToKeyRingMap;
+ protected static HashMap mPublicKeyIdToIdMap;
+ protected static HashMap mPublicKeyIdToKeyRingMap;
+
+ public static final String PUBLIC_KEY_PROJECTION[] =
+ new String[] {
+ PublicKeys._ID,
+ PublicKeys.KEY_ID,
+ PublicKeys.KEY_DATA,
+ PublicKeys.WHO_ID, };
+ public static final String SECRET_KEY_PROJECTION[] =
+ new String[] {
+ PublicKeys._ID,
+ PublicKeys.KEY_ID,
+ PublicKeys.KEY_DATA,
+ PublicKeys.WHO_ID, };
+
+ private static String mPassPhrase = null;
+
+ public static class GeneralException extends Exception {
+ static final long serialVersionUID = 0xf812773342L;
+
+ public GeneralException(String message) {
+ super(message);
+ }
+ }
+
+ static {
+ mPublicKeyRings = new Vector();
+ mSecretKeyRings = new Vector();
+ mSecretKeyIdToIdMap = new HashMap();
+ mSecretKeyIdToKeyRingMap = new HashMap();
+ mPublicKeyIdToIdMap = new HashMap();
+ mPublicKeyIdToKeyRingMap = new HashMap();
+ }
+
+ public static void initialize(Activity context) {
+ setPassPhrase(null);
+ if (mInitialized) {
+ return;
+ }
+
+ loadKeyRings(context, TYPE_PUBLIC);
+ loadKeyRings(context, TYPE_SECRET);
+
+ mInitialized = true;
+ }
+
+ public static class PublicKeySorter implements Comparator {
+ @Override
+ public int compare(PGPPublicKeyRing object1, PGPPublicKeyRing object2) {
+ PGPPublicKey key1 = getMasterKey(object1);
+ PGPPublicKey key2 = getMasterKey(object2);
+ if (key1 == null && key2 == null) {
+ return 0;
+ }
+
+ if (key1 == null) {
+ return -1;
+ }
+
+ if (key2 == null) {
+ return 1;
+ }
+
+ String uid1 = getMainUserId(key1);
+ String uid2 = getMainUserId(key2);
+ if (uid1 == null && uid2 == null) {
+ return 0;
+ }
+
+ if (uid1 == null) {
+ return -1;
+ }
+
+ if (uid2 == null) {
+ return 1;
+ }
+
+ return uid1.compareTo(uid2);
+ }
+ }
+
+ public static class SecretKeySorter implements Comparator {
+ @Override
+ public int compare(PGPSecretKeyRing object1, PGPSecretKeyRing object2) {
+ PGPSecretKey key1 = getMasterKey(object1);
+ PGPSecretKey key2 = getMasterKey(object2);
+ if (key1 == null && key2 == null) {
+ return 0;
+ }
+
+ if (key1 == null) {
+ return -1;
+ }
+
+ if (key2 == null) {
+ return 1;
+ }
+
+ String uid1 = getMainUserId(key1);
+ String uid2 = getMainUserId(key2);
+ if (uid1 == null && uid2 == null) {
+ return 0;
+ }
+
+ if (uid1 == null) {
+ return -1;
+ }
+
+ if (uid2 == null) {
+ return 1;
+ }
+
+ return uid1.compareTo(uid2);
+ }
+ }
+
+ public static void setPassPhrase(String passPhrase) {
+ mPassPhrase = passPhrase;
+ }
+
+ public static String getPassPhrase() {
+ return mPassPhrase;
+ }
+
+ public static PGPSecretKey createKey(KeyEditor.AlgorithmChoice algorithmChoice, int keySize,
+ String passPhrase)
+ throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
+ GeneralException, InvalidAlgorithmParameterException {
+
+ if (algorithmChoice == null) {
+ throw new GeneralException("unknown algorithm choice");
+ }
+ if (keySize < 512) {
+ throw new GeneralException("key size must be at least 512bit");
+ }
+
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (passPhrase == null) {
+ passPhrase = "";
+ }
+
+ int algorithm = 0;
+ KeyPairGenerator keyGen = null;
+
+ switch (algorithmChoice.getId()) {
+ case KeyEditor.AlgorithmChoice.DSA: {
+ keyGen = KeyPairGenerator.getInstance("DSA", new BouncyCastleProvider());
+ keyGen.initialize(keySize, new SecureRandom());
+ algorithm = PGPPublicKey.DSA;
+ break;
+ }
+
+ case KeyEditor.AlgorithmChoice.ELGAMAL: {
+ if (keySize != 2048) {
+ throw new GeneralException("ElGamal currently requires 2048bit");
+ }
+ keyGen = KeyPairGenerator.getInstance("ELGAMAL", new BouncyCastleProvider());
+ BigInteger p = new BigInteger(
+ "36F0255DDE973DCB3B399D747F23E32ED6FDB1F77598338BFDF44159C4EC64DDAEB5F78671CBFB22" +
+ "106AE64C32C5BCE4CFD4F5920DA0EBC8B01ECA9292AE3DBA1B7A4A899DA181390BB3BD1659C81294" +
+ "F400A3490BF9481211C79404A576605A5160DBEE83B4E019B6D799AE131BA4C23DFF83475E9C40FA" +
+ "6725B7C9E3AA2C6596E9C05702DB30A07C9AA2DC235C5269E39D0CA9DF7AAD44612AD6F88F696992" +
+ "98F3CAB1B54367FB0E8B93F735E7DE83CD6FA1B9D1C931C41C6188D3E7F179FC64D87C5D13F85D70" +
+ "4A3AA20F90B3AD3621D434096AA7E8E7C66AB683156A951AEA2DD9E76705FAEFEA8D71A575535597" +
+ "0000000000000001", 16);
+ ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, new BigInteger("2"));
+ keyGen.initialize(elParams);
+ algorithm = PGPPublicKey.ELGAMAL_GENERAL;
+ break;
+ }
+
+ case KeyEditor.AlgorithmChoice.RSA: {
+ keyGen = KeyPairGenerator.getInstance("RSA", new BouncyCastleProvider());
+ keyGen.initialize(keySize, new SecureRandom());
+
+ algorithm = PGPPublicKey.RSA_GENERAL;
+ break;
+ }
+
+ default: {
+ throw new GeneralException("unknown algorithm choice");
+ }
+ }
+
+ PGPKeyPair keyPair = new PGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date());
+
+ // enough for now, as we assemble the key again later anyway
+ PGPSecretKey secretKey =
+ new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, keyPair, "",
+ PGPEncryptedData.CAST5, passPhrase.toCharArray(), null, null,
+ new SecureRandom(), new BouncyCastleProvider().getName());
+
+ return secretKey;
+ }
+
+ private static long getNumDatesBetween(GregorianCalendar first, GregorianCalendar second) {
+ GregorianCalendar tmp = new GregorianCalendar();
+ tmp.setTime(first.getTime());
+ long numDays = (second.getTimeInMillis() - first.getTimeInMillis()) / 1000 / 86400;
+ tmp.add(Calendar.DAY_OF_MONTH, (int)numDays);
+ while (tmp.before(second)) {
+ tmp.add(Calendar.DAY_OF_MONTH, 1);
+ ++numDays;
+ }
+ return numDays;
+ }
+
+ public static void buildSecretKey(Activity context,
+ SectionView userIdsView, SectionView keysView,
+ String oldPassPhrase, String newPassPhrase,
+ ProgressDialogUpdater progress)
+ throws Apg.GeneralException, NoSuchProviderException, PGPException,
+ NoSuchAlgorithmException, SignatureException {
+
+ progress.setProgress("building key...", 0, 100);
+
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (oldPassPhrase == null || oldPassPhrase.equals("")) {
+ oldPassPhrase = "";
+ }
+
+ if (newPassPhrase == null || newPassPhrase.equals("")) {
+ newPassPhrase = "";
+ }
+
+ Vector userIds = new Vector();
+ Vector keys = new Vector();
+
+ ViewGroup userIdEditors = userIdsView.getEditors();
+ ViewGroup keyEditors = keysView.getEditors();
+
+ boolean gotMainUserId = false;
+ for (int i = 0; i < userIdEditors.getChildCount(); ++i) {
+ UserIdEditor editor = (UserIdEditor)userIdEditors.getChildAt(i);
+ String userId = null;
+ try {
+ userId = editor.getValue();
+ } catch (UserIdEditor.NoNameException e) {
+ throw new Apg.GeneralException("you need to specify a name");
+ } catch (UserIdEditor.NoEmailException e) {
+ throw new Apg.GeneralException("you need to specify an email");
+ } catch (UserIdEditor.InvalidEmailException e) {
+ throw new Apg.GeneralException(e.getMessage());
+ }
+
+ if (userId.equals("")) {
+ continue;
+ }
+
+ if (editor.isMainUserId()) {
+ userIds.insertElementAt(userId, 0);
+ gotMainUserId = true;
+ } else {
+ userIds.add(userId);
+ }
+ }
+
+ if (userIds.size() == 0) {
+ throw new Apg.GeneralException("need at least one user id");
+ }
+
+ if (!gotMainUserId) {
+ throw new Apg.GeneralException("main user id can't be empty");
+ }
+
+ if (keyEditors.getChildCount() == 0) {
+ throw new Apg.GeneralException("need at least a main key");
+ }
+
+ for (int i = 0; i < keyEditors.getChildCount(); ++i) {
+ KeyEditor editor = (KeyEditor)keyEditors.getChildAt(i);
+ keys.add(editor.getValue());
+ }
+
+ progress.setProgress("preparing master key...", 10, 100);
+ KeyEditor keyEditor = (KeyEditor) keyEditors.getChildAt(0);
+ int usageId = keyEditor.getUsage().getId();
+ boolean canSign = (usageId == KeyEditor.UsageChoice.SIGN_ONLY ||
+ usageId == KeyEditor.UsageChoice.SIGN_AND_ENCRYPT);
+ boolean canEncrypt = (usageId == KeyEditor.UsageChoice.ENCRYPT_ONLY ||
+ usageId == KeyEditor.UsageChoice.SIGN_AND_ENCRYPT);
+
+ String mainUserId = userIds.get(0);
+
+ PGPSecretKey masterKey = keys.get(0);
+ PGPPublicKey tmpKey = masterKey.getPublicKey();
+ PGPPublicKey masterPublicKey =
+ new PGPPublicKey(tmpKey.getAlgorithm(),
+ tmpKey.getKey(new BouncyCastleProvider()),
+ tmpKey.getCreationTime());
+ PGPPrivateKey masterPrivateKey =
+ masterKey.extractPrivateKey(oldPassPhrase.toCharArray(),
+ new BouncyCastleProvider());
+
+ progress.setProgress("certifying master key...", 20, 100);
+ for (int i = 0; i < userIds.size(); ++i) {
+ String userId = userIds.get(i);
+
+ PGPSignatureGenerator sGen =
+ new PGPSignatureGenerator(masterPublicKey.getAlgorithm(),
+ HashAlgorithmTags.SHA1, new BouncyCastleProvider());
+
+ sGen.initSign(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
+
+ PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
+
+ masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification);
+ }
+
+ // TODO: cross-certify the master key with every sub key
+
+ PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey);
+
+ PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
+ PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
+
+ int keyFlags = KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA;
+ if (canEncrypt) {
+ keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
+ }
+ hashedPacketsGen.setKeyFlags(true, keyFlags);
+
+ hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS);
+ hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
+ hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
+
+ if (keyEditor.getExpiryDate() != null) {
+ GregorianCalendar creationDate = new GregorianCalendar();
+ creationDate.setTime(getCreationDate(masterKey));
+ GregorianCalendar expiryDate = keyEditor.getExpiryDate();
+ long numDays = getNumDatesBetween(creationDate, expiryDate);
+ if (numDays <= 0) {
+ throw new GeneralException("expiry date must be later than creation date");
+ }
+ hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400);
+ }
+
+ progress.setProgress("building master key ring...", 30, 100);
+ PGPKeyRingGenerator keyGen =
+ new PGPKeyRingGenerator(PGPSignature.DEFAULT_CERTIFICATION,
+ masterKeyPair, mainUserId,
+ PGPEncryptedData.CAST5, newPassPhrase.toCharArray(),
+ hashedPacketsGen.generate(), unhashedPacketsGen.generate(),
+ new SecureRandom(), new BouncyCastleProvider().getName());
+
+ progress.setProgress("adding sub keys...", 40, 100);
+ for (int i = 1; i < keys.size(); ++i) {
+ progress.setProgress(40 + 50 * (i - 1)/ (keys.size() - 1), 100);
+ PGPSecretKey subKey = keys.get(i);
+ keyEditor = (KeyEditor) keyEditors.getChildAt(i);
+ PGPPublicKey subPublicKey = subKey.getPublicKey();
+ PGPPrivateKey subPrivateKey =
+ subKey.extractPrivateKey(oldPassPhrase.toCharArray(),
+ new BouncyCastleProvider());
+ PGPKeyPair subKeyPair =
+ new PGPKeyPair(subPublicKey.getAlgorithm(),
+ subPublicKey.getKey(new BouncyCastleProvider()),
+ subPrivateKey.getKey(),
+ subPublicKey.getCreationTime());
+
+ hashedPacketsGen = new PGPSignatureSubpacketGenerator();
+ unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
+
+ keyFlags = 0;
+ usageId = keyEditor.getUsage().getId();
+ canSign = (usageId == KeyEditor.UsageChoice.SIGN_ONLY ||
+ usageId == KeyEditor.UsageChoice.SIGN_AND_ENCRYPT);
+ canEncrypt = (usageId == KeyEditor.UsageChoice.ENCRYPT_ONLY ||
+ usageId == KeyEditor.UsageChoice.SIGN_AND_ENCRYPT);
+ if (canSign) {
+ keyFlags |= KeyFlags.SIGN_DATA;
+ }
+ if (canEncrypt) {
+ keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
+ }
+ hashedPacketsGen.setKeyFlags(true, keyFlags);
+
+ if (keyEditor.getExpiryDate() != null) {
+ GregorianCalendar creationDate = new GregorianCalendar();
+ creationDate.setTime(getCreationDate(masterKey));
+ GregorianCalendar expiryDate = keyEditor.getExpiryDate();
+ long numDays = getNumDatesBetween(creationDate, expiryDate);
+ if (numDays <= 0) {
+ throw new GeneralException("expiry date must be later than creation date");
+ }
+ hashedPacketsGen.setKeyExpirationTime(true, numDays * 86400);
+ }
+
+ keyGen.addSubKey(subKeyPair,
+ hashedPacketsGen.generate(), unhashedPacketsGen.generate());
+ }
+
+ PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing();
+ PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing();
+
+ progress.setProgress("saving key ring...", 90, 100);
+ saveKeyRing(context, secretKeyRing);
+ saveKeyRing(context, publicKeyRing);
+
+ loadKeyRings(context, TYPE_PUBLIC);
+ loadKeyRings(context, TYPE_SECRET);
+ progress.setProgress("done.", 100, 100);
+ }
+
+ private static int saveKeyRing(Activity context, PGPPublicKeyRing keyRing) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ContentValues values = new ContentValues();
+
+ PGPPublicKey masterKey = getMasterKey(keyRing);
+ if (masterKey == null) {
+ return RETURN_NO_MASTER_KEY;
+ }
+
+ try {
+ keyRing.encode(out);
+ out.close();
+ } catch (IOException e) {
+ return RETURN_ERROR;
+ }
+
+ values.put(PublicKeys.KEY_ID, masterKey.getKeyID());
+ values.put(PublicKeys.KEY_DATA, out.toByteArray());
+
+ Uri uri = Uri.withAppendedPath(PublicKeys.CONTENT_URI_BY_KEY_ID, "" + masterKey.getKeyID());
+ Cursor cursor = context.managedQuery(uri, PUBLIC_KEY_PROJECTION, null, null, null);
+ if (cursor != null && cursor.getCount() > 0) {
+ context.getContentResolver().update(uri, values, null, null);
+ return RETURN_UPDATED;
+ } else {
+ context.getContentResolver().insert(PublicKeys.CONTENT_URI, values);
+ return RETURN_OK;
+ }
+ }
+
+ private static int saveKeyRing(Activity context, PGPSecretKeyRing keyRing) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ContentValues values = new ContentValues();
+
+ PGPSecretKey masterKey = getMasterKey(keyRing);
+ if (masterKey == null) {
+ return RETURN_NO_MASTER_KEY;
+ }
+
+ try {
+ keyRing.encode(out);
+ out.close();
+ } catch (IOException e) {
+ return RETURN_ERROR;
+ }
+
+ values.put(SecretKeys.KEY_ID, masterKey.getKeyID());
+ values.put(SecretKeys.KEY_DATA, out.toByteArray());
+
+ Uri uri = Uri.withAppendedPath(SecretKeys.CONTENT_URI_BY_KEY_ID, "" + masterKey.getKeyID());
+ Cursor cursor = context.managedQuery(uri, SECRET_KEY_PROJECTION, null, null, null);
+ if (cursor != null && cursor.getCount() > 0) {
+ context.getContentResolver().update(uri, values, null, null);
+ return RETURN_UPDATED;
+ } else {
+ context.getContentResolver().insert(SecretKeys.CONTENT_URI, values);
+ return RETURN_OK;
+ }
+ }
+
+ public static Bundle importKeyRings(Activity context, int type, String filename,
+ ProgressDialogUpdater progress)
+ throws GeneralException, FileNotFoundException, PGPException, IOException {
+ Bundle returnData = new Bundle();
+ PGPObjectFactory objectFactors = null;
+
+ if (type == TYPE_SECRET) {
+ progress.setProgress("importing secret keys...", 0, 100);
+ } else {
+ progress.setProgress("importing public keys...", 0, 100);
+ }
+
+ if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ throw new GeneralException("external storage not ready");
+ }
+
+ FileInputStream fileIn = new FileInputStream(filename);
+ InputStream in = PGPUtil.getDecoderStream(fileIn);
+ objectFactors = new PGPObjectFactory(in);
+
+ Vector