Support key files

This commit is contained in:
Brian Pellin
2009-02-03 21:42:21 -06:00
parent e7abac9faf
commit 29c7659d39
9 changed files with 205 additions and 55 deletions

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.keepass"
android:versionName="0.1.3" android:versionCode="4">
android:versionCode="5" android:versionName="0.1.4">
<application android:label="@string/app_name" android:icon="@drawable/keepass_icon">
<activity android:name=".KeePass"
android:label="@string/app_name">

View File

@@ -1,3 +1,7 @@
KeePassDroid (0.1.4)
* Add key file support
KeePassDroid (0.1.3)
* Hide Meta-Info entries

View File

@@ -13,6 +13,11 @@
android:layout_weight="1"
android:singleLine="true"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:text="@string/entry_and_or" />
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/entry_password" />
@@ -23,9 +28,18 @@
android:password="true"
android:singleLine="true"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/entry_keyfile" />
<EditText android:id="@+id/pass_keyfile"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true"/>
<Button android:id="@+id/pass_ok"
android:text="@string/pass_ok"
android:layout_width="wrap_content"
android:layout_width="100sp"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@@ -4,7 +4,7 @@
<string name="app_name">KeePassDroid</string>
<string name="no_keys">No keys in database.</string>
<string name="FileNotFound">/sdcard/keepass/keepass.kdb not found</string>
<string name="InvalidPassword">Invalid password.</string>
<string name="InvalidPassword">Invalid password or key file.</string>
<string name="pass_ok">Ok</string>
<string name="entry_title">Name: </string>
<string name="entry_user_name">User Name: </string>
@@ -22,5 +22,7 @@
<string name="menu_homepage">Go to Homepage</string>
<string name="homepage">http://www.keepassdroid.com</string>
<string name="menu_lock">Lock Database</string>
<string name="error_nopass">Password is required.</string>
<string name="error_nopass">A password or a keyfile is requried.</string>
<string name="entry_and_or">Enter a password and/or a key file to unlock your database:</string>
<string name="entry_keyfile">Key file (optional):</string>
</resources>

View File

@@ -32,36 +32,25 @@ import org.phoneid.keepassj2me.PwEntry;
import org.phoneid.keepassj2me.PwGroup;
import org.phoneid.keepassj2me.PwManager;
import com.android.keepass.keepasslib.InvalidKeyFileException;
public class Database {
public static HashMap<Integer, WeakReference<PwGroup>> gGroups = new HashMap<Integer, WeakReference<PwGroup>>();
public static HashMap<UUID, WeakReference<PwEntry>> gEntries = new HashMap<UUID, WeakReference<PwEntry>>();
public static PwGroup gRoot;
private static PwManager mPM;
public static int LoadData(String filename, String password) {
public static void LoadData(String filename, String password, String keyfile) throws InvalidCipherTextException, IOException, InvalidKeyFileException, FileNotFoundException {
FileInputStream fis;
try {
fis = new FileInputStream(filename);
} catch (FileNotFoundException e) {
return R.string.FileNotFound;
}
fis = new FileInputStream(filename);
ImporterV3 Importer = new ImporterV3();
try {
mPM = Importer.openDatabase(fis, password);
if ( mPM != null ) {
mPM.constructTree(null);
populateGlobals(null);
}
} catch (InvalidCipherTextException e) {
return R.string.InvalidPassword;
} catch (IOException e) {
return -1;
mPM = Importer.openDatabase(fis, password, keyfile);
if ( mPM != null ) {
mPM.constructTree(null);
populateGlobals(null);
}
return 0;
}
private static void populateGlobals(PwGroup currentGroup) {

View File

@@ -19,6 +19,13 @@
*/
package com.android.keepass;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.bouncycastle1.crypto.InvalidCipherTextException;
import com.android.keepass.keepasslib.InvalidKeyFileException;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
@@ -34,6 +41,9 @@ public class KeePass extends Activity {
public static final int EXIT_NORMAL = 0;
public static final int EXIT_LOCK = 1;
public static final String LAST_FILENAME = "lastFile";
public static final String LAST_KEYFILE = "lastKey";
private static final int MENU_HOMEPAGE = Menu.FIRST;
@Override
@@ -65,19 +75,22 @@ public class KeePass extends Activity {
private void loadDefaultPrefs() {
SharedPreferences settings = getPreferences(MODE_PRIVATE);
String lastfn = settings.getString("lastFile", "");
String lastFile = settings.getString(LAST_FILENAME, "");
String lastKey = settings.getString(LAST_KEYFILE,"");
if (lastfn == "") {
lastfn = "/sdcard/keepass/keepass.kdb";
if (lastFile == "") {
lastFile = "/sdcard/keepass/keepass.kdb";
}
setEditText(R.id.pass_filename, lastfn);
setEditText(R.id.pass_filename, lastFile);
setEditText(R.id.pass_keyfile, lastKey);
}
private void saveDefaultPrefs() {
SharedPreferences settings = getPreferences(MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
editor.putString("lastFile", getEditText(R.id.pass_filename));
editor.putString(LAST_FILENAME, getEditText(R.id.pass_filename));
editor.putString(LAST_KEYFILE, getEditText(R.id.pass_keyfile));
editor.commit();
}
@@ -101,26 +114,28 @@ public class KeePass extends Activity {
public void onClick(View view) {
String pass = getEditText(R.id.pass_password);
if ( pass.length() == 0 ) {
String key = getEditText(R.id.pass_keyfile);
if ( pass.length() == 0 && key.length() == 0 ) {
errorMessage(R.string.error_nopass);
return;
}
int result = Database.LoadData(getEditText(R.id.pass_filename),pass);
switch (result) {
case 0:
try {
Database.LoadData(getEditText(R.id.pass_filename), pass, key);
saveDefaultPrefs();
GroupActivity.Launch(mAct, null);
break;
case -1:
} catch (InvalidCipherTextException e) {
errorMessage(R.string.InvalidPassword);
} catch (FileNotFoundException e) {
errorMessage(R.string.FileNotFound);
} catch (IOException e) {
errorMessage("Unknown error.");
break;
default:
errorMessage(result);
break;
} catch (InvalidKeyFileException e) {
errorMessage(e.getMessage());
}
}
}
}

View File

@@ -0,0 +1,12 @@
package com.android.keepass.keepasslib;
public class InvalidKeyFileException extends Exception {
/**
*
*/
private static final long serialVersionUID = 5540694419562294464L;
public InvalidKeyFileException(String str) {
super(str);
}
}

View File

@@ -46,6 +46,8 @@ import org.bouncycastle1.crypto.params.KeyParameter;
import org.bouncycastle1.crypto.params.ParametersWithIV;
import org.phoneid.PhoneIDUtil;
import com.android.keepass.keepasslib.InvalidKeyFileException;
/**
* Load a v3 database file.
*
@@ -68,6 +70,7 @@ public class ImporterV3 {
* @return new PwManager container.
*
* @throws IOException on any file error.
* @throws InvalidKeyFileException
* @throws InvalidKeyException on a decryption error, or possible internal bug.
* @throws IllegalBlockSizeException on a decryption error, or possible internal bug.
* @throws BadPaddingException on a decryption error, or possible internal bug.
@@ -76,8 +79,8 @@ public class ImporterV3 {
* @throws InvalidAlgorithmParameterException if error decrypting main file body.
* @throws ShortBufferException if error decrypting main file body.
*/
public PwManager openDatabase( InputStream inStream, String password )
throws IOException, InvalidCipherTextException
public PwManager openDatabase( InputStream inStream, String password, String keyfile )
throws IOException, InvalidCipherTextException, InvalidKeyFileException
{
PwManager newManager;
SHA256Digest md;
@@ -108,7 +111,7 @@ public class ImporterV3 {
}
newManager = new PwManager();
newManager.setMasterKey( password );
newManager.setMasterKey( password, keyfile );
// Select algorithm
if( (hdr.flags & PwManager.PWM_FLAG_RIJNDAEL) != 0 ) {
@@ -137,6 +140,7 @@ public class ImporterV3 {
transformedMasterKey = transformMasterKey( hdr.masterSeed2,
newManager.masterKey,
newManager.numKeyEncRounds );
// Hash the master password with the salt in the file
md = new SHA256Digest();
md.update( hdr.masterSeed, 0, hdr.masterSeed.length );

View File

@@ -26,10 +26,15 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
package org.phoneid.keepassj2me;
// Java
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Vector;
// Bouncy Castle
import org.bouncycastle1.crypto.digests.*;
import org.bouncycastle1.crypto.digests.SHA256Digest;
import com.android.keepass.keepasslib.InvalidKeyFileException;
/**
* @author Naomaru Itoi <nao@phoneid.org>
@@ -80,16 +85,121 @@ public class PwManager {
// root group
PwGroup rootGroup;
public void setMasterKey( String key ) {
if( key == null || key.length() == 0 )
throw new IllegalArgumentException( "Key cannot be empty." );
SHA256Digest md = new SHA256Digest();
md.update( key.getBytes(), 0, key.getBytes().length );
masterKey = new byte[md.getDigestSize()];
md.doFinal(masterKey, 0);
public void setMasterKey( String key, String keyFileName ) throws InvalidKeyFileException, IOException {
assert( key != null && keyFileName != null );
if ( key.length() > 0 && keyFileName.length() > 0 ) {
setCompositeKey(key, keyFileName);
} else if ( key.length() > 0 ) {
setPasswordKey(key);
} else if ( keyFileName.length() > 0 ) {
setFileKey(keyFileName);
} else {
throw new IllegalArgumentException( "Key cannot be empty." );
}
}
/*
private void setCompositeKey( String key, String keyFileName) throws InvalidKeyFileException, IOException {
assert(key != null && keyFileName != null);
byte[] fileKey = new byte[32];
setFileKey(keyFileName);
System.arraycopy(masterKey, 0, fileKey, 0, 32);
byte[] passwordKey = new byte[32];
setPasswordKey(key);
System.arraycopy(masterKey, 0, passwordKey, 0, 32);
SHA256Digest md = new SHA256Digest();
md.update(passwordKey, 0, 32);
md.update(fileKey, 0, 32);
masterKey = new byte[md.getDigestSize()];
md.doFinal(masterKey, 0);
}
private void setFileKey(String fileName) throws InvalidKeyFileException, IOException {
assert(fileName != null);
File keyfile = new File(fileName);
long fileSize = keyfile.length();
if ( ! keyfile.exists() ) {
throw new InvalidKeyFileException("Key file does not exist.");
}
FileInputStream fis;
try {
fis = new FileInputStream(keyfile);
} catch (FileNotFoundException e) {
throw new InvalidKeyFileException("Key file does not exist.");
}
if ( fileSize == 0 ) {
throw new InvalidKeyFileException("Key file is empty.");
} else if ( fileSize == 32 ) {
masterKey = new byte[32];
if ( fis.read(masterKey, 0, 32) != 32 ) {
throw new IOException("Error reading key.");
}
return;
} else if ( fileSize == 64 ) {
byte[] hex = new byte[64];
if ( fis.read(hex, 0, 64) != 64 ) {
throw new IOException("Error reading key.");
}
masterKey = hexStringToByteArray(new String(hex));
return;
}
SHA256Digest md = new SHA256Digest();
byte[] buffer = new byte[2048];
int offset = 0;
try {
while (true) {
int bytesRead = fis.read(buffer, 0, 2048);
if ( bytesRead == -1 ) break; // End of file
md.update(buffer, 0, bytesRead);
offset += bytesRead;
}
} catch (Exception e) {
System.out.println(e.toString());
}
masterKey = new byte[md.getDigestSize()];
md.doFinal(masterKey, 0);
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
private void setPasswordKey(String key ) {
assert(key!=null);
if ( key.length() == 0 )
throw new IllegalArgumentException( "Key cannot be empty." );
SHA256Digest md = new SHA256Digest();
md.update( key.getBytes(), 0, key.getBytes().length );
masterKey = new byte[md.getDigestSize()];
md.doFinal(masterKey, 0);
}
/*
//
// Erase all members and buffers, then null pointers.
// Ensures no memory (that we control) contains leftover keys.