aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsteckbrief <steckbrief@chefmail.de>2016-07-16 15:11:36 +0200
committersteckbrief <steckbrief@chefmail.de>2016-07-16 15:11:36 +0200
commit97100834a5bcb08f2fdf2eb6c580d3ceeb8b6b2f (patch)
tree7dc5897dc229b39a123484fb3a20bb989ee3c1fb
parent10e607ac51dcc42fa1b54bacb698beed43750de7 (diff)
Implements FS#227: Store password encrypted in internal database
-rw-r--r--build.gradle14
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/entities/Account.java8
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java16
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/SimpleCryptoUtil.java110
-rw-r--r--src/test/java/de/thedevstack/conversationsplus/utils/SimpleCryptoUtilTest.java136
5 files changed, 280 insertions, 4 deletions
diff --git a/build.gradle b/build.gradle
index de4d418e..f58ac6df 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,7 +3,6 @@
buildscript {
repositories {
jcenter()
- mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.0.0'
@@ -21,6 +20,9 @@ repositories {
flatDir {
dirs 'libs/3rdParty/jcenter', 'libs/3rdParty/maven'
}
+ maven {
+ url "http://snippets.thedevstack.de/mvn"
+ }
}
dependencies {
@@ -55,6 +57,10 @@ dependencies {
// Android dependencies
compile 'com.android.support:support-v13:23.2.0'
+
+ testCompile 'junit:junit:4.12'
+ // Optional -- Mockito framework
+ testCompile 'org.mockito:mockito-core:1.10.19'
}
ext {
@@ -90,6 +96,12 @@ android {
playstore
free
}
+ testOptions {
+ unitTests.all {
+ // All the usual Gradle options.
+ jvmArgs '-XX:MaxPermSize=256m'
+ }
+ }
if (project.hasProperty('mStoreFile') &&
project.hasProperty('mStorePassword') &&
project.hasProperty('mKeyAlias') &&
diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Account.java b/src/main/java/de/thedevstack/conversationsplus/entities/Account.java
index bc956364..2ee76504 100644
--- a/src/main/java/de/thedevstack/conversationsplus/entities/Account.java
+++ b/src/main/java/de/thedevstack/conversationsplus/entities/Account.java
@@ -28,6 +28,7 @@ import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService;
import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlServiceImpl;
import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlServiceStub;
import de.thedevstack.conversationsplus.services.XmppConnectionService;
+import de.thedevstack.conversationsplus.utils.SimpleCryptoUtil;
import de.thedevstack.conversationsplus.xmpp.XmppConnection;
import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException;
import de.thedevstack.conversationsplus.xmpp.jid.Jid;
@@ -172,6 +173,8 @@ public class Account extends AbstractEntity {
private List<Bookmark> bookmarks = new CopyOnWriteArrayList<>();
private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>();
+ public static final String PW_SEED = "sadjgdiahsdkhashp3zt98edAFSFIOKZUIUOz23ejj12ezhez2398iehz";
+
public Account() {
this.uuid = "0";
}
@@ -210,9 +213,10 @@ public class Account extends AbstractEntity {
cursor.getString(cursor.getColumnIndex(SERVER)), "mobile");
} catch (final InvalidJidException ignored) {
}
+ String password = SimpleCryptoUtil.decrypt(PW_SEED, cursor.getString(cursor.getColumnIndex(PASSWORD)));
return new Account(cursor.getString(cursor.getColumnIndex(UUID)),
jid,
- cursor.getString(cursor.getColumnIndex(PASSWORD)),
+ password,
cursor.getInt(cursor.getColumnIndex(OPTIONS)),
cursor.getString(cursor.getColumnIndex(ROSTERVERSION)),
cursor.getString(cursor.getColumnIndex(KEYS)),
@@ -342,7 +346,7 @@ public class Account extends AbstractEntity {
values.put(UUID, uuid);
values.put(USERNAME, jid.getLocalpart());
values.put(SERVER, jid.getDomainpart());
- values.put(PASSWORD, password);
+ values.put(PASSWORD, SimpleCryptoUtil.encrypt(PW_SEED, password));
values.put(OPTIONS, options);
values.put(KEYS, this.keys.toString());
values.put(ROSTERVERSION, rosterVersion);
diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java b/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java
index 5a5746d5..6442e909 100644
--- a/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java
+++ b/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java
@@ -44,6 +44,7 @@ import de.thedevstack.conversationsplus.entities.Conversation;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.entities.Roster;
import de.thedevstack.conversationsplus.entities.ServiceDiscoveryResult;
+import de.thedevstack.conversationsplus.utils.SimpleCryptoUtil;
import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException;
import de.thedevstack.conversationsplus.xmpp.jid.Jid;
@@ -54,7 +55,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "history";
private static final int DATABASE_VERSION = 25;
private static final int C_TO_CPLUS_VERSION_OFFSET = 1000;
- private static final int CPLUS_DATABASE_VERSION = 1;
+ private static final int CPLUS_DATABASE_VERSION = 2;
private static final int CPLUS_DATABASE_VERSION_MULTIPLIER = 100;
private static final int PHYSICAL_DATABASE_VERSION = DATABASE_VERSION + C_TO_CPLUS_VERSION_OFFSET + (CPLUS_DATABASE_VERSION * CPLUS_DATABASE_VERSION_MULTIPLIER);
@@ -203,6 +204,19 @@ public class DatabaseBackend extends SQLiteOpenHelper {
db.execSQL("INSERT INTO " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + "(" + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + ") "
+ " SELECT " + Message.UUID + " FROM " + Message.TABLENAME);
}
+ if (newVersion == 2) {
+ Logging.d("db.upgrade.cplus", "Encrypt all passwords for the first time");
+ Cursor cursor = db.rawQuery("SELECT " + Account.UUID + ", " + Account.PASSWORD + " FROM " + Account.TABLENAME, new String[0]);
+ while (cursor.moveToNext()) {
+ String uuid = CursorHelper.getString(cursor, Account.UUID);
+ String password = CursorHelper.getString(cursor, Account.PASSWORD);
+ String encryptedPassword = SimpleCryptoUtil.encrypt(Account.PW_SEED, password);
+ ContentValues values = new ContentValues();
+ values.put(Account.PASSWORD, encryptedPassword);
+ db.update(Account.TABLENAME, values, Account.UUID + "=?", new String[] {uuid});
+ }
+ cursor.close();
+ }
}
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/SimpleCryptoUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/SimpleCryptoUtil.java
new file mode 100644
index 00000000..0a8c80d1
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/SimpleCryptoUtil.java
@@ -0,0 +1,110 @@
+package de.thedevstack.conversationsplus.utils;
+
+
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ *
+ */
+public final class SimpleCryptoUtil {
+
+ public static String encrypt(String seed, String cleartext) {
+ String result = null;
+ if (null != seed && null != cleartext) {
+ try {
+ byte[] rawKey = getRawKey(seed.getBytes());
+ byte[] encryptedBytes = encrypt(rawKey, cleartext.getBytes());
+ result = toHex(encryptedBytes);
+ } catch (NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException e) {
+ // FIXME
+ }
+ }
+
+ return result;
+ }
+
+ public static String decrypt(String seed, String encrypted) {
+ String result = null;
+ if (null != seed && null != encrypted) {
+ try {
+ byte[] rawKey = getRawKey(seed.getBytes());
+ byte[] enc = toByte(encrypted);
+ byte[] decryptedBytes = decrypt(rawKey, enc);
+ result = new String(decryptedBytes);
+ } catch (NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException e) {
+ // FIXME
+ }
+ }
+
+ return result;
+ }
+
+ private static byte[] encrypt(byte[] raw, byte[] clear) throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
+ byte[] encrypted = doCipherOperation(raw, clear, Cipher.ENCRYPT_MODE);
+ return encrypted;
+ }
+
+ private static byte[] decrypt(byte[] raw, byte[] encrypted) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+ byte[] decrypted = doCipherOperation(raw, encrypted, Cipher.DECRYPT_MODE);
+ return decrypted;
+ }
+
+ private static byte[] doCipherOperation(byte[] raw, byte[] input, int mode) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+ SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
+ Cipher cipher = Cipher.getInstance("AES");
+ cipher.init(mode, skeySpec);
+
+ return cipher.doFinal(input);
+ }
+
+ private static byte[] getRawKey(byte[] seed) throws NoSuchAlgorithmException {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ byte[] md5Bytes = md.digest(seed); // 128 Bit = 16 byte
+ SecretKey skey = new SecretKeySpec(md5Bytes, "AES");
+ byte[] raw = skey.getEncoded();
+ return raw;
+ }
+
+ public static byte[] toByte(String hexString) {
+ int len = hexString.length() / 2;
+ byte[] result = new byte[len];
+ for (int i = 0; i < len; i++) {
+ result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).byteValue();
+ }
+ return result;
+ }
+
+ public static String toHex(byte[] buf) {
+ if (buf == null) {
+ return null;
+ }
+ StringBuffer result = new StringBuffer(2 * buf.length);
+ for (int i = 0; i < buf.length; i++) {
+ appendHex(result, buf[i]);
+ }
+ return result.toString();
+ }
+
+ private final static String HEX = "0123456789ABCDEF";
+
+ private static void appendHex(StringBuffer sb, byte b) {
+ sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
+ }
+
+ /**
+ * private constructor to avoid instantiation
+ */
+ private SimpleCryptoUtil() {
+ // private constructor to avoid instantiation
+ }
+}
+
diff --git a/src/test/java/de/thedevstack/conversationsplus/utils/SimpleCryptoUtilTest.java b/src/test/java/de/thedevstack/conversationsplus/utils/SimpleCryptoUtilTest.java
new file mode 100644
index 00000000..7e71a3bf
--- /dev/null
+++ b/src/test/java/de/thedevstack/conversationsplus/utils/SimpleCryptoUtilTest.java
@@ -0,0 +1,136 @@
+package de.thedevstack.conversationsplus.utils;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Created by steckbrief on 10.07.2016.
+ */
+public class SimpleCryptoUtilTest {
+ @Test
+ public void testEncryptWithNullSeed() {
+ String seed = null;
+ String cleartext = "Test123";
+ String expectedResult = null;
+ String result = SimpleCryptoUtil.encrypt(seed, cleartext);
+ assertEquals(expectedResult, result);
+ }
+
+ @Test
+ public void testEncryptWithWhitespaceSeed() {
+ String seed = " ";
+ String cleartext = "Test123";
+ String expectedResult = "3E7721EF6F8D9E47403AC03F45341E5C";
+ String result = SimpleCryptoUtil.encrypt(seed, cleartext);
+ assertEquals(expectedResult, result);
+ }
+
+ @Test
+ public void testEncryptWithZeroLengthSeed() {
+ String seed = "";
+ String cleartext = "Test123";
+ String expectedResult = "BEFF4A6800B91AE505672CA238D7C72F";
+ String result = SimpleCryptoUtil.encrypt(seed, cleartext);
+ assertEquals(expectedResult, result);
+ }
+
+ @Test
+ public void testEncryptWithNullCleartext() {
+ String seed = "asdasdadasd";
+ String cleartext = null;
+ String expectedResult = null;
+ String result = SimpleCryptoUtil.encrypt(seed, cleartext);
+ assertEquals(expectedResult, result);
+ }
+
+ @Test
+ public void testEncryptWithWhitespaceCleartext() {
+ String seed = "asdasdadasd";
+ String cleartext = " ";
+ String expectedResult = "1C476E5E4E66809AB6C91EEF9C93A32F";
+ String result = SimpleCryptoUtil.encrypt(seed, cleartext);
+ assertEquals(expectedResult, result);
+ }
+
+ @Test
+ public void testEncryptWithZeroLengthCleartext() {
+ String seed = "asdasdadasd";
+ String cleartext = "";
+ String expectedResult = "22BD1DA9232CA6594A466B419B2AFB8C";
+ String result = SimpleCryptoUtil.encrypt(seed, cleartext);
+ assertEquals(expectedResult, result);
+ }
+
+ @Test
+ public void testEncrypt() {
+ String seed = "asdasdadasd";
+ String cleartext = "Test123";
+ String expectedResult = "E1D9C96AAF09434D915A2F0C147CBF64";
+ String result = SimpleCryptoUtil.encrypt(seed, cleartext);
+ assertEquals(expectedResult, result);
+ }
+
+ @Test
+ public void testDecryptWithNullSeed() {
+ String seed = null;
+ String encryptedText = "E1D9C96AAF09434D915A2F0C147CBF64";
+ String expectedResult = null;
+ String result = SimpleCryptoUtil.decrypt(seed, encryptedText);
+ assertEquals(expectedResult, result);
+ }
+
+ @Test
+ public void testDecryptWithWhitespaceSeed() {
+ String seed = " ";
+ String encryptedText = "3E7721EF6F8D9E47403AC03F45341E5C";
+ String expectedResult = "Test123";
+ String result = SimpleCryptoUtil.decrypt(seed, encryptedText);
+ assertEquals(expectedResult, result);
+ }
+
+ @Test
+ public void testDecryptWithZeroLengthSeed() {
+ String seed = "";
+ String encryptedText = "BEFF4A6800B91AE505672CA238D7C72F";
+ String expectedResult = "Test123";
+ String result = SimpleCryptoUtil.decrypt(seed, encryptedText);
+ assertEquals(expectedResult, result);
+ }
+
+ @Test
+ public void testDecryptWithNullEncryptedText() {
+ String seed = "asdasdadasd";
+ String encryptedText = null;
+ String expectedResult = null;
+ String result = SimpleCryptoUtil.decrypt(seed, encryptedText);
+ assertEquals(expectedResult, result);
+ }
+
+ @Test
+ public void testDecryptWithWhitespaceEncryptedText() {
+ String seed = "asdasdadasd";
+ String encryptedText = " ";
+ String expectedResult = "";
+ String result = SimpleCryptoUtil.decrypt(seed, encryptedText);
+ assertEquals(expectedResult, result);
+ }
+
+ @Test
+ public void testDecryptWithZeroLengthEncryptedText() {
+ String seed = "asdasdadasd";
+ String encryptedText = "";
+ String expectedResult = "";
+ String result = SimpleCryptoUtil.decrypt(seed, encryptedText);
+ assertEquals(expectedResult, result);
+ }
+
+ @Test
+ public void testDecrypt() {
+ String seed = "asdasdadasd";
+ String encryptedText = "E1D9C96AAF09434D915A2F0C147CBF64";
+ String expectedResult = "Test123";
+ String result = SimpleCryptoUtil.decrypt(seed, encryptedText);
+ assertEquals(expectedResult, result);
+ }
+}