mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Support key files
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
KeePassDroid (0.1.4)
|
||||
|
||||
* Add key file support
|
||||
|
||||
KeePassDroid (0.1.3)
|
||||
|
||||
* Hide Meta-Info entries
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user