Files
KeePassDX/src/com/keepassdroid/database/PwDatabase.java

219 lines
6.2 KiB
Java

/*
* Copyright 2009 Brian Pellin.
*
* This file is part of KeePassDroid.
*
* KeePassDroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* KeePassDroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDroid. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.keepassdroid.database;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Vector;
import com.keepassdroid.crypto.finalkey.FinalKey;
import com.keepassdroid.crypto.finalkey.FinalKeyFactory;
import com.keepassdroid.database.exception.InvalidKeyFileException;
import com.keepassdroid.stream.NullOutputStream;
public abstract class PwDatabase {
public byte masterKey[] = new byte[32];
public byte[] finalKey;
public String name = "KeePass database";
public void makeFinalKey(byte[] masterSeed, byte[] masterSeed2, int numRounds) throws IOException {
// Write checksum Checksum
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new IOException("SHA-256 not implemented here.");
}
NullOutputStream nos = new NullOutputStream();
DigestOutputStream dos = new DigestOutputStream(nos, md);
byte[] transformedMasterKey = transformMasterKey(masterSeed2, masterKey, numRounds);
dos.write(masterSeed);
dos.write(transformedMasterKey);
finalKey = md.digest();
}
/**
* Encrypt the master key a few times to make brute-force key-search harder
* @throws IOException
*/
private static byte[] transformMasterKey( byte[] pKeySeed, byte[] pKey, int rounds ) throws IOException
{
FinalKey key = FinalKeyFactory.createFinalKey();
return key.transformMasterKey(pKeySeed, pKey, rounds);
}
public abstract byte[] getMasterKey(String key, String keyFileName) throws InvalidKeyFileException, IOException;
// TODO: This needs to work differently for KDB4. It always produces a composite key.
// So even if there is just a password it gets SHA-256'ed twice.
public void setMasterKey(String key, String keyFileName)
throws InvalidKeyFileException, IOException {
assert( key != null && keyFileName != null );
masterKey = getMasterKey(key, keyFileName);
}
protected byte[] getCompositeKey(String key, String keyFileName)
throws InvalidKeyFileException, IOException {
assert(key != null && keyFileName != null);
byte[] fileKey = getFileKey(keyFileName);
byte[] passwordKey = getPasswordKey(key);
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new IOException("SHA-256 not supported");
}
md.update(passwordKey);
return md.digest(fileKey);
}
protected static byte[] getFileKey(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 ) {
byte[] outputKey = new byte[32];
if ( fis.read(outputKey, 0, 32) != 32 ) {
throw new IOException("Error reading key.");
}
return outputKey;
} else if ( fileSize == 64 ) {
byte[] hex = new byte[64];
if ( fis.read(hex, 0, 64) != 64 ) {
throw new IOException("Error reading key.");
}
return hexStringToByteArray(new String(hex));
}
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new IOException("SHA-256 not supported");
}
//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());
}
return md.digest();
}
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;
}
protected byte[] getPasswordKey(String key, String encoding) throws IOException {
assert(key!=null);
if ( key.length() == 0 )
throw new IllegalArgumentException( "Key cannot be empty." );
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new IOException("SHA-256 not supported");
}
byte[] bKey;
try {
// TODO: Need to make this UTF-8 in kdb4
bKey = key.getBytes(encoding);
} catch (UnsupportedEncodingException e) {
assert false;
bKey = key.getBytes();
}
md.update(bKey, 0, bKey.length );
return md.digest();
}
public abstract byte[] getPasswordKey(String key) throws IOException;
public abstract Vector<PwGroup> getGrpRoots();
public abstract Vector<PwGroup> getGroups();
public abstract Vector<PwEntry> getEntries();
public abstract long getNumRounds();
public abstract void setNumRonuds(long rounds) throws NumberFormatException;
}