mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Dynamic creation of KdfEngine, reorganise code
This commit is contained in:
@@ -21,28 +21,28 @@ package com.kunzisoft.keepass.crypto.keyDerivation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class KdfFactory {
|
||||
|
||||
public static AesKdf aesKdf = new AesKdf();
|
||||
public static Argon2Kdf argon2Kdf = new Argon2Kdf();
|
||||
|
||||
public static List<KdfEngine> kdfListV3 = new ArrayList<>();
|
||||
public static List<KdfEngine> kdfList = new ArrayList<>();
|
||||
|
||||
static {
|
||||
kdfList.add(new AesKdf());
|
||||
kdfList.add(new Argon2Kdf());
|
||||
kdfListV3.add(aesKdf);
|
||||
|
||||
kdfList.add(aesKdf);
|
||||
kdfList.add(argon2Kdf);
|
||||
}
|
||||
|
||||
public static KdfParameters getDefaultParameters() {
|
||||
return kdfList.get(0).getDefaultParameters();
|
||||
}
|
||||
|
||||
public static KdfEngine get(UUID uuid) {
|
||||
public static KdfEngine get(KdfParameters kdfParameters) {
|
||||
for (KdfEngine engine: kdfList) {
|
||||
if (engine.uuid.equals(uuid)) {
|
||||
if (engine.uuid.equals(kdfParameters.kdfUUID)) {
|
||||
return engine;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public class KdfParameters extends VariantDictionary {
|
||||
|
||||
private static final String ParamUUID = "$UUID";
|
||||
|
||||
public KdfParameters(UUID uuid) {
|
||||
KdfParameters(UUID uuid) {
|
||||
kdfUUID = uuid;
|
||||
}
|
||||
|
||||
@@ -45,20 +45,14 @@ public class KdfParameters extends VariantDictionary {
|
||||
|
||||
VariantDictionary d = VariantDictionary.deserialize(lis);
|
||||
if (d == null) {
|
||||
assert(false);
|
||||
return null;
|
||||
}
|
||||
|
||||
UUID uuid = Types.bytestoUUID(d.getByteArray(ParamUUID));
|
||||
if (uuid == null) {
|
||||
assert(false);
|
||||
return null;
|
||||
}
|
||||
|
||||
KdfParameters kdfP = new KdfParameters(uuid);
|
||||
kdfP.copyTo(d);
|
||||
return kdfP;
|
||||
|
||||
}
|
||||
|
||||
public static byte[] serialize(KdfParameters kdf) throws IOException {
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.util.Log;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine;
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory;
|
||||
import com.kunzisoft.keepass.database.exception.ContentFileNotFoundException;
|
||||
import com.kunzisoft.keepass.database.exception.InvalidDBException;
|
||||
import com.kunzisoft.keepass.database.exception.InvalidPasswordException;
|
||||
@@ -376,9 +377,9 @@ public class Database {
|
||||
public List<KdfEngine> getAvailableKdfEngines() {
|
||||
switch (getPwDatabase().getVersion()) {
|
||||
case V4:
|
||||
return ((PwDatabaseV4) getPwDatabase()).getAvailableKdfEngines();
|
||||
return KdfFactory.kdfList;
|
||||
case V3:
|
||||
return ((PwDatabaseV3) getPwDatabase()).getAvailableKdfEngines();
|
||||
return KdfFactory.kdfListV3;
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
@@ -391,14 +392,18 @@ public class Database {
|
||||
switch (getPwDatabase().getVersion()) {
|
||||
case V4:
|
||||
PwDatabaseV4 db = ((PwDatabaseV4) getPwDatabase());
|
||||
db.setKdfEngine(kdfEngine);
|
||||
db.setKdfParameters(kdfEngine.getDefaultParameters());
|
||||
db.setNumberKeyEncryptionRounds(kdfEngine.getDefaultKeyRounds());
|
||||
setNumberKeyEncryptionRounds(kdfEngine.getDefaultKeyRounds());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public String getKeyDerivationName(Resources resources) {
|
||||
return getPwDatabase().getKeyDerivationName(resources);
|
||||
KdfEngine kdfEngine = getPwDatabase().getKdfEngine();
|
||||
if (kdfEngine != null) {
|
||||
return kdfEngine.getName(resources);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getNumberKeyEncryptionRoundsAsString() {
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database;
|
||||
|
||||
import android.content.res.Resources;
|
||||
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine;
|
||||
import com.kunzisoft.keepass.database.exception.InvalidKeyFileException;
|
||||
import com.kunzisoft.keepass.database.exception.KeyFileEmptyException;
|
||||
@@ -251,12 +249,8 @@ public abstract class PwDatabase<PwGroupDB extends PwGroup<PwGroupDB, PwGroupDB,
|
||||
|
||||
public abstract List<PwEncryptionAlgorithm> getAvailableEncryptionAlgorithms();
|
||||
|
||||
public abstract List<KdfEngine> getAvailableKdfEngines();
|
||||
|
||||
public abstract KdfEngine getKdfEngine();
|
||||
|
||||
public abstract String getKeyDerivationName(Resources resources);
|
||||
|
||||
public abstract List<PwGroupDB> getGrpRoots();
|
||||
|
||||
public abstract List<PwGroupDB> getGroups();
|
||||
|
||||
@@ -45,12 +45,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
package com.kunzisoft.keepass.database;
|
||||
|
||||
import android.content.res.Resources;
|
||||
|
||||
import com.kunzisoft.keepass.crypto.finalkey.FinalKey;
|
||||
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory;
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.AesKdf;
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine;
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory;
|
||||
import com.kunzisoft.keepass.database.exception.InvalidKeyFileException;
|
||||
import com.kunzisoft.keepass.stream.NullOutputStream;
|
||||
|
||||
@@ -71,7 +69,6 @@ import java.util.Random;
|
||||
public class PwDatabaseV3 extends PwDatabase<PwGroupV3, PwEntryV3> {
|
||||
|
||||
private static final int DEFAULT_ENCRYPTION_ROUNDS = 300;
|
||||
private KdfEngine kdfEngine = new AesKdf(); // Always the same
|
||||
|
||||
// all entries
|
||||
private List<PwEntryV3> entries = new ArrayList<>();
|
||||
@@ -107,19 +104,7 @@ public class PwDatabaseV3 extends PwDatabase<PwGroupV3, PwEntryV3> {
|
||||
|
||||
@Override
|
||||
public KdfEngine getKdfEngine() {
|
||||
return kdfEngine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KdfEngine> getAvailableKdfEngines() {
|
||||
List<KdfEngine> list = new ArrayList<>();
|
||||
list.add(kdfEngine);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKeyDerivationName(Resources resources) {
|
||||
return kdfEngine.getName(resources);
|
||||
return KdfFactory.aesKdf;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -346,7 +331,6 @@ public class PwDatabaseV3 extends PwDatabase<PwGroupV3, PwEntryV3> {
|
||||
|
||||
// Add tree to root groups
|
||||
groups.add(newGroup);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.webkit.URLUtil;
|
||||
|
||||
import com.kunzisoft.keepass.collections.VariantDictionary;
|
||||
@@ -67,9 +66,10 @@ public class PwDatabaseV4 extends PwDatabase<PwGroupV4, PwEntryV4> {
|
||||
private UUID dataCipher = AesEngine.CIPHER_UUID;
|
||||
private CipherEngine dataEngine = new AesEngine();
|
||||
private PwCompressionAlgorithm compressionAlgorithm = PwCompressionAlgorithm.Gzip;
|
||||
private KdfEngine kdfEngine;
|
||||
private KdfParameters kdfParameters;
|
||||
private long numKeyEncRounds;
|
||||
private VariantDictionary publicCustomData = new VariantDictionary();
|
||||
|
||||
private long numKeyEncRounds = AesKdf.DEFAULT_ROUNDS; // By default take the AES rounds
|
||||
protected String name = "KeePass DX database";
|
||||
private PwDate nameChanged = new PwDate();
|
||||
private PwDate settingsChanged = new PwDate();
|
||||
@@ -99,8 +99,6 @@ public class PwDatabaseV4 extends PwDatabase<PwGroupV4, PwEntryV4> {
|
||||
private List<PwIconCustom> customIcons = new ArrayList<>();
|
||||
private Map<String, String> customData = new HashMap<>();
|
||||
|
||||
private KdfParameters kdfParameters = KdfFactory.getDefaultParameters();
|
||||
private VariantDictionary publicCustomData = new VariantDictionary();
|
||||
private BinaryPool binPool = new BinaryPool();
|
||||
|
||||
public String localizedAppName = "KeePassDX"; // TODO resource
|
||||
@@ -153,24 +151,7 @@ public class PwDatabaseV4 extends PwDatabase<PwGroupV4, PwEntryV4> {
|
||||
|
||||
@Override
|
||||
public KdfEngine getKdfEngine() {
|
||||
return kdfEngine;
|
||||
}
|
||||
|
||||
public void setKdfEngine(KdfEngine kdfEngine) {
|
||||
this.kdfEngine = kdfEngine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKeyDerivationName(Resources resources) {
|
||||
if (kdfEngine!=null)
|
||||
return kdfEngine.getName(resources);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KdfEngine> getAvailableKdfEngines() {
|
||||
return KdfFactory.kdfList;
|
||||
return KdfFactory.get(getKdfParameters());
|
||||
}
|
||||
|
||||
public KdfParameters getKdfParameters() {
|
||||
@@ -389,25 +370,25 @@ public class PwDatabaseV4 extends PwDatabase<PwGroupV4, PwEntryV4> {
|
||||
return md.digest(fKey);
|
||||
}
|
||||
|
||||
public void makeFinalKey(byte[] masterSeed, KdfParameters kdfP) throws IOException {
|
||||
makeFinalKey(masterSeed, kdfP, 0);
|
||||
public void makeFinalKey(byte[] masterSeed) throws IOException {
|
||||
makeFinalKey(masterSeed, 0);
|
||||
}
|
||||
|
||||
public void makeFinalKey(byte[] masterSeed, KdfParameters kdfP, long roundsFix)
|
||||
public void makeFinalKey(byte[] masterSeed, long roundsFix)
|
||||
throws IOException {
|
||||
|
||||
kdfEngine = KdfFactory.get(kdfP.kdfUUID);
|
||||
KdfEngine kdfEngine = KdfFactory.get(kdfParameters);
|
||||
if (kdfEngine == null) {
|
||||
throw new IOException("Unknown key derivation function");
|
||||
}
|
||||
|
||||
// Set to 6000 rounds to open corrupted database
|
||||
if (roundsFix > 0 && kdfP.kdfUUID.equals(AesKdf.CIPHER_UUID)) {
|
||||
kdfP.setUInt32(AesKdf.ParamRounds, roundsFix);
|
||||
if (roundsFix > 0 && kdfParameters.kdfUUID.equals(AesKdf.CIPHER_UUID)) {
|
||||
kdfParameters.setUInt32(AesKdf.ParamRounds, roundsFix);
|
||||
numKeyEncRounds = roundsFix;
|
||||
}
|
||||
|
||||
byte[] transformedMasterKey = kdfEngine.transform(masterKey, kdfP);
|
||||
byte[] transformedMasterKey = kdfEngine.transform(masterKey, kdfParameters);
|
||||
if (transformedMasterKey.length != 32) {
|
||||
transformedMasterKey = CryptoUtil.hashSha256(transformedMasterKey);
|
||||
}
|
||||
@@ -795,7 +776,7 @@ public class PwDatabaseV4 extends PwDatabase<PwGroupV4, PwEntryV4> {
|
||||
}
|
||||
|
||||
public int getMinKdbxVersion() {
|
||||
if (!AesKdf.CIPHER_UUID.equals(kdfParameters.kdfUUID)) {
|
||||
if (kdfParameters != null && !AesKdf.CIPHER_UUID.equals(kdfParameters.kdfUUID)) {
|
||||
return PwDbHeaderV4.FILE_VERSION_32;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package com.kunzisoft.keepass.database;
|
||||
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.AesKdf;
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory;
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters;
|
||||
import com.kunzisoft.keepass.database.exception.InvalidDBVersionException;
|
||||
import com.kunzisoft.keepass.stream.CopyInputStream;
|
||||
@@ -92,9 +93,9 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
public long version;
|
||||
|
||||
public PwDbHeaderV4(PwDatabaseV4 d) {
|
||||
db = d;
|
||||
version = d.getMinKdbxVersion();
|
||||
masterSeed = new byte[32];
|
||||
this.db = d;
|
||||
this.version = d.getMinKdbxVersion();
|
||||
this.masterSeed = new byte[32];
|
||||
}
|
||||
|
||||
/** Assumes the input stream is at the beginning of the .kdbx file
|
||||
@@ -173,24 +174,13 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
break;
|
||||
|
||||
case PwDbHeaderV4Fields.TransformSeed:
|
||||
assert(version < PwDbHeaderV4.FILE_VERSION_32_4); // TODO file > FILEVERSION
|
||||
AesKdf kdfS = new AesKdf();
|
||||
if (!db.getKdfParameters().kdfUUID.equals(kdfS.uuid)) {
|
||||
db.setKdfParameters(kdfS.getDefaultParameters());
|
||||
}
|
||||
|
||||
db.getKdfParameters().setByteArray(AesKdf.ParamSeed, fieldData);
|
||||
if(version < PwDbHeaderV4.FILE_VERSION_32_4)
|
||||
setTransformSeed(fieldData);
|
||||
break;
|
||||
|
||||
case PwDbHeaderV4Fields.TransformRounds:
|
||||
assert(version < PwDbHeaderV4.FILE_VERSION_32_4);
|
||||
AesKdf kdfR = new AesKdf();
|
||||
if (!db.getKdfParameters().kdfUUID.equals(kdfR.uuid)) {
|
||||
db.setKdfParameters(kdfR.getDefaultParameters());
|
||||
}
|
||||
long rounds = LEDataInputStream.readLong(fieldData, 0);
|
||||
db.getKdfParameters().setUInt64(AesKdf.ParamRounds, rounds);
|
||||
db.setNumberKeyEncryptionRounds(rounds);
|
||||
if(version < PwDbHeaderV4.FILE_VERSION_32_4)
|
||||
setTransformRound(fieldData);
|
||||
break;
|
||||
|
||||
case PwDbHeaderV4Fields.EncryptionIV:
|
||||
@@ -198,7 +188,7 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
break;
|
||||
|
||||
case PwDbHeaderV4Fields.InnerRandomstreamKey:
|
||||
assert(version < PwDbHeaderV4.FILE_VERSION_32_4);
|
||||
if(version < PwDbHeaderV4.FILE_VERSION_32_4)
|
||||
innerRandomStreamKey = fieldData;
|
||||
break;
|
||||
|
||||
@@ -207,7 +197,7 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
break;
|
||||
|
||||
case PwDbHeaderV4Fields.InnerRandomStreamID:
|
||||
assert(version < PwDbHeaderV4.FILE_VERSION_32_4);
|
||||
if(version < PwDbHeaderV4.FILE_VERSION_32_4)
|
||||
setRandomStreamID(fieldData);
|
||||
break;
|
||||
|
||||
@@ -225,6 +215,12 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void assignAesKdfEngineIfNotExists() {
|
||||
if (db.getKdfParameters() == null || !db.getKdfParameters().kdfUUID.equals(KdfFactory.aesKdf.uuid)) {
|
||||
db.setKdfParameters(KdfFactory.aesKdf.getDefaultParameters());
|
||||
}
|
||||
}
|
||||
|
||||
private void setCipher(byte[] pbId) throws IOException {
|
||||
if ( pbId == null || pbId.length != 16 ) {
|
||||
throw new IOException("Invalid cipher ID.");
|
||||
@@ -233,6 +229,18 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
db.setDataCipher(Types.bytestoUUID(pbId));
|
||||
}
|
||||
|
||||
private void setTransformSeed(byte[] seed) {
|
||||
assignAesKdfEngineIfNotExists();
|
||||
db.getKdfParameters().setByteArray(AesKdf.ParamSeed, seed);
|
||||
}
|
||||
|
||||
private void setTransformRound(byte[] roundsByte) {
|
||||
assignAesKdfEngineIfNotExists();
|
||||
long rounds = LEDataInputStream.readLong(roundsByte, 0);
|
||||
db.getKdfParameters().setUInt64(AesKdf.ParamRounds, rounds);
|
||||
db.setNumberKeyEncryptionRounds(rounds);
|
||||
}
|
||||
|
||||
private void setCompressionFlags(byte[] pbFlags) throws IOException {
|
||||
if ( pbFlags == null || pbFlags.length != 4 ) {
|
||||
throw new IOException("Invalid compression flags.");
|
||||
@@ -244,23 +252,6 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
}
|
||||
|
||||
db.setCompressionAlgorithm(PwCompressionAlgorithm.fromId(flag));
|
||||
|
||||
}
|
||||
|
||||
private void setTransformRounds(byte[] rounds) throws IOException {
|
||||
if ( rounds == null || rounds.length != 8 ) {
|
||||
throw new IOException("Invalid rounds.");
|
||||
}
|
||||
|
||||
long rnd = LEDataInputStream.readLong(rounds, 0);
|
||||
|
||||
if ( rnd < 0 || rnd > Integer.MAX_VALUE ) {
|
||||
//TODO: Actually support really large numbers
|
||||
throw new IOException("Rounds higher than " + Integer.MAX_VALUE + " are not currently supported.");
|
||||
}
|
||||
|
||||
db.setNumberKeyEncryptionRounds(rnd);
|
||||
|
||||
}
|
||||
|
||||
public void setRandomStreamID(byte[] streamID) throws IOException {
|
||||
@@ -276,25 +267,23 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
innerRandomStream = CrsAlgorithm.fromId(id);
|
||||
}
|
||||
|
||||
/** Determines if this is a supported version.
|
||||
/**
|
||||
* Determines if this is a supported version.
|
||||
*
|
||||
* A long is needed here to represent the unsigned int since we perform
|
||||
* arithmetic on it.
|
||||
* @param version
|
||||
* @return
|
||||
* A long is needed here to represent the unsigned int since we perform arithmetic on it.
|
||||
* @param version Database version
|
||||
* @return true if it's a supported version
|
||||
*/
|
||||
private boolean validVersion(long version) {
|
||||
|
||||
return ! ((version & FILE_VERSION_CRITICAL_MASK) > (FILE_VERSION_32 & FILE_VERSION_CRITICAL_MASK));
|
||||
|
||||
}
|
||||
|
||||
public static boolean matchesHeader(int sig1, int sig2) {
|
||||
return (sig1 == PWM_DBSIG_1) && ( (sig2 == DBSIG_2) || (sig2 == DBSIG_2) );
|
||||
return (sig1 == PWM_DBSIG_1) && ( (sig2 == DBSIG_2) );
|
||||
//return (sig1 == PWM_DBSIG_1) && ( (sig2 == DBSIG_PRE2) || (sig2 == DBSIG_2) ); // TODO verify add DBSIG_PRE2
|
||||
}
|
||||
|
||||
public static byte[] computeHeaderHmac(byte[] header, byte[] key) throws IOException{
|
||||
byte[] headerHmac;
|
||||
byte[] blockKey = HmacBlockStream.GetHmacKey64(key, Types.ULONG_MAX_VALUE);
|
||||
|
||||
Mac hmac;
|
||||
@@ -312,8 +301,7 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
}
|
||||
|
||||
public byte[] getTransformSeed() {
|
||||
assert(version < FILE_VERSION_32_4);
|
||||
|
||||
// version < FILE_VERSION_32_4)
|
||||
return db.getKdfParameters().getByteArray(AesKdf.ParamSeed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,24 +27,21 @@ import com.kunzisoft.keepass.database.exception.PwDbOutputException;
|
||||
import java.io.IOException;
|
||||
|
||||
public class SaveDBRunnable extends RunnableOnFinish {
|
||||
|
||||
private Context mCtx;
|
||||
private Database mDb;
|
||||
private boolean mDontSave;
|
||||
private Context mCtx;
|
||||
|
||||
public SaveDBRunnable(Context ctx, Database db, OnFinishRunnable finish, boolean dontSave) {
|
||||
super(finish);
|
||||
|
||||
mDb = db;
|
||||
mDontSave = dontSave;
|
||||
mCtx = ctx;
|
||||
this.mDb = db;
|
||||
this.mDontSave = dontSave;
|
||||
this.mCtx = ctx;
|
||||
}
|
||||
|
||||
public SaveDBRunnable(Context ctx, Database db, OnFinishRunnable finish) {
|
||||
super(finish);
|
||||
|
||||
mDb = db;
|
||||
mDontSave = false;
|
||||
mCtx = ctx;
|
||||
this(ctx, db, finish, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -63,7 +63,6 @@ import com.kunzisoft.keepass.database.exception.InvalidDBVersionException;
|
||||
import com.kunzisoft.keepass.database.exception.InvalidKeyFileException;
|
||||
import com.kunzisoft.keepass.database.exception.InvalidPasswordException;
|
||||
import com.kunzisoft.keepass.stream.LEDataInputStream;
|
||||
import com.kunzisoft.keepass.stream.LEDataOutputStream;
|
||||
import com.kunzisoft.keepass.stream.NullOutputStream;
|
||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater;
|
||||
import com.kunzisoft.keepass.utils.Types;
|
||||
@@ -133,13 +132,13 @@ public class ImporterV3 extends Importer {
|
||||
@Override
|
||||
public PwDatabaseV3 openDatabase(InputStream inStream, String password, InputStream kfIs, ProgressTaskUpdater progressTaskUpdater, long roundsFix)
|
||||
throws IOException, InvalidDBException {
|
||||
PwDatabaseV3 newManager;
|
||||
|
||||
PwDatabaseV3 databaseToOpen;
|
||||
|
||||
// Load entire file, most of it's encrypted.
|
||||
int fileSize = inStream.available();
|
||||
byte[] filebuf = new byte[fileSize + 16]; // Pad with a blocksize (Twofish uses 128 bits), since Android 4.3 tries to write more to the buffer
|
||||
inStream.read(filebuf, 0, fileSize);
|
||||
inStream.read(filebuf, 0, fileSize); // TODO remove
|
||||
inStream.close();
|
||||
|
||||
// Parse header (unencrypted)
|
||||
@@ -158,34 +157,34 @@ public class ImporterV3 extends Importer {
|
||||
|
||||
if (progressTaskUpdater != null)
|
||||
progressTaskUpdater.updateMessage(R.string.creating_db_key);
|
||||
newManager = createDB();
|
||||
newManager.retrieveMasterKey(password, kfIs);
|
||||
databaseToOpen = createDB();
|
||||
databaseToOpen.retrieveMasterKey(password, kfIs);
|
||||
|
||||
// Select algorithm
|
||||
if( (hdr.flags & PwDbHeaderV3.FLAG_RIJNDAEL) != 0 ) {
|
||||
newManager.setEncryptionAlgorithm(PwEncryptionAlgorithm.AES_Rijndael);
|
||||
databaseToOpen.setEncryptionAlgorithm(PwEncryptionAlgorithm.AES_Rijndael);
|
||||
} else if( (hdr.flags & PwDbHeaderV3.FLAG_TWOFISH) != 0 ) {
|
||||
newManager.setEncryptionAlgorithm(PwEncryptionAlgorithm.Twofish);
|
||||
databaseToOpen.setEncryptionAlgorithm(PwEncryptionAlgorithm.Twofish);
|
||||
} else {
|
||||
throw new InvalidAlgorithmException();
|
||||
}
|
||||
|
||||
// Copy for testing
|
||||
newManager.copyHeader(hdr);
|
||||
databaseToOpen.copyHeader(hdr);
|
||||
|
||||
newManager.setNumberKeyEncryptionRounds(hdr.numKeyEncRounds);
|
||||
databaseToOpen.setNumberKeyEncryptionRounds(hdr.numKeyEncRounds);
|
||||
|
||||
// Generate transformedMasterKey from masterKey
|
||||
newManager.makeFinalKey(hdr.masterSeed, hdr.transformSeed, newManager.getNumberKeyEncryptionRounds());
|
||||
databaseToOpen.makeFinalKey(hdr.masterSeed, hdr.transformSeed, databaseToOpen.getNumberKeyEncryptionRounds());
|
||||
|
||||
if (progressTaskUpdater != null)
|
||||
progressTaskUpdater.updateMessage(R.string.decrypting_db);
|
||||
// Initialize Rijndael algorithm
|
||||
Cipher cipher;
|
||||
try {
|
||||
if ( newManager.getEncryptionAlgorithm() == PwEncryptionAlgorithm.AES_Rijndael) {
|
||||
if ( databaseToOpen.getEncryptionAlgorithm() == PwEncryptionAlgorithm.AES_Rijndael) {
|
||||
cipher = CipherFactory.getInstance("AES/CBC/PKCS5Padding");
|
||||
} else if ( newManager.getEncryptionAlgorithm() == PwEncryptionAlgorithm.Twofish ) {
|
||||
} else if ( databaseToOpen.getEncryptionAlgorithm() == PwEncryptionAlgorithm.Twofish ) {
|
||||
cipher = CipherFactory.getInstance("Twofish/CBC/PKCS7PADDING");
|
||||
} else {
|
||||
throw new IOException( "Encryption algorithm is not supported" );
|
||||
@@ -198,7 +197,7 @@ public class ImporterV3 extends Importer {
|
||||
}
|
||||
|
||||
try {
|
||||
cipher.init( Cipher.DECRYPT_MODE, new SecretKeySpec( newManager.getFinalKey(), "AES" ), new IvParameterSpec( hdr.encryptionIV ) );
|
||||
cipher.init( Cipher.DECRYPT_MODE, new SecretKeySpec( databaseToOpen.getFinalKey(), "AES" ), new IvParameterSpec( hdr.encryptionIV ) );
|
||||
} catch (InvalidKeyException e1) {
|
||||
throw new IOException("Invalid key");
|
||||
} catch (InvalidAlgorithmParameterException e1) {
|
||||
@@ -218,7 +217,7 @@ public class ImporterV3 extends Importer {
|
||||
}
|
||||
|
||||
// Copy decrypted data for testing
|
||||
newManager.copyEncrypted(filebuf, PwDbHeaderV3.BUF_SIZE, encryptedPartSize);
|
||||
databaseToOpen.copyEncrypted(filebuf, PwDbHeaderV3.BUF_SIZE, encryptedPartSize);
|
||||
|
||||
MessageDigest md = null;
|
||||
try {
|
||||
@@ -251,13 +250,13 @@ public class ImporterV3 extends Importer {
|
||||
if( fieldType == 0xFFFF ) {
|
||||
|
||||
// End-Group record. Save group and count it.
|
||||
newGrp.populateBlankFields(newManager);
|
||||
newManager.addGroup(newGrp);
|
||||
newGrp.populateBlankFields(databaseToOpen);
|
||||
databaseToOpen.addGroup(newGrp);
|
||||
newGrp = new PwGroupV3();
|
||||
i++;
|
||||
}
|
||||
else {
|
||||
readGroupField(newManager, newGrp, fieldType, filebuf, pos);
|
||||
readGroupField(databaseToOpen, newGrp, fieldType, filebuf, pos);
|
||||
}
|
||||
pos += fieldSize;
|
||||
}
|
||||
@@ -270,65 +269,22 @@ public class ImporterV3 extends Importer {
|
||||
|
||||
if( fieldType == 0xFFFF ) {
|
||||
// End-Group record. Save group and count it.
|
||||
newEnt.populateBlankFields(newManager);
|
||||
newManager.addEntry(newEnt);
|
||||
newEnt.populateBlankFields(databaseToOpen);
|
||||
databaseToOpen.addEntry(newEnt);
|
||||
newEnt = new PwEntryV3();
|
||||
i++;
|
||||
}
|
||||
else {
|
||||
readEntryField(newManager, newEnt, filebuf, pos);
|
||||
readEntryField(databaseToOpen, newEnt, filebuf, pos);
|
||||
}
|
||||
pos += 2 + 4 + fieldSize;
|
||||
}
|
||||
|
||||
newManager.constructTree(null);
|
||||
databaseToOpen.constructTree(null);
|
||||
|
||||
return newManager;
|
||||
return databaseToOpen;
|
||||
}
|
||||
|
||||
/**
|
||||
* KeePass's custom pad style.
|
||||
*
|
||||
* @param data buffer to pad.
|
||||
* @return addtional bytes to append to data[] to make
|
||||
* a properly padded array.
|
||||
*/
|
||||
public static byte[] makePad( byte[] data ) {
|
||||
//custom pad method
|
||||
|
||||
// append 0x80 plus zeros to a multiple of 4 bytes
|
||||
int thisblk = 32 - data.length % 32; // bytes needed to finish blk
|
||||
int nextblk = 0; // 32 if we need another block
|
||||
// need 9 bytes; add new block if no room
|
||||
if( thisblk < 9 ) {
|
||||
nextblk = 32;
|
||||
}
|
||||
|
||||
// all bytes are zeroed for free
|
||||
byte[] pad = new byte[ thisblk + nextblk ];
|
||||
pad[0] = (byte)0x80;
|
||||
|
||||
// write length*8 to end of final block
|
||||
int ix = thisblk + nextblk - 8;
|
||||
LEDataOutputStream.writeInt( data.length>>29, pad, ix );
|
||||
bsw32( pad, ix );
|
||||
ix += 4;
|
||||
LEDataOutputStream.writeInt( data.length<<3, pad, ix );
|
||||
bsw32( pad, ix );
|
||||
|
||||
return pad;
|
||||
}
|
||||
|
||||
public static void bsw32( byte[] ary, int offset ) {
|
||||
byte t = ary[offset];
|
||||
ary[offset] = ary[offset+3];
|
||||
ary[offset+3] = t;
|
||||
t = ary[offset+1];
|
||||
ary[offset+1] = ary[offset+2];
|
||||
ary[offset+2] = t;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse and save one record from binary file.
|
||||
* @param buf
|
||||
@@ -336,7 +292,7 @@ public class ImporterV3 extends Importer {
|
||||
* @return If >0,
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
void readGroupField(PwDatabaseV3 db, PwGroupV3 grp, int fieldType, byte[] buf, int offset) throws UnsupportedEncodingException {
|
||||
private void readGroupField(PwDatabaseV3 db, PwGroupV3 grp, int fieldType, byte[] buf, int offset) throws UnsupportedEncodingException {
|
||||
switch( fieldType ) {
|
||||
case 0x0000 :
|
||||
// Ignore field
|
||||
@@ -373,9 +329,7 @@ public class ImporterV3 extends Importer {
|
||||
|
||||
|
||||
|
||||
void readEntryField(PwDatabaseV3 db, PwEntryV3 ent, byte[] buf, int offset)
|
||||
throws UnsupportedEncodingException
|
||||
{
|
||||
private void readEntryField(PwDatabaseV3 db, PwEntryV3 ent, byte[] buf, int offset) throws UnsupportedEncodingException {
|
||||
int fieldType = LEDataInputStream.readUShort(buf, offset);
|
||||
offset += 2;
|
||||
int fieldSize = LEDataInputStream.readInt(buf, offset);
|
||||
|
||||
@@ -82,18 +82,12 @@ public class ImporterV4 extends Importer {
|
||||
private byte[] hashOfHeader = null;
|
||||
private byte[] pbHeader = null;
|
||||
private long version;
|
||||
private int binNum = 0;
|
||||
Calendar utcCal;
|
||||
|
||||
public ImporterV4() {
|
||||
utcCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
protected PwDatabaseV4 createDB() {
|
||||
return new PwDatabaseV4();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PwDatabaseV4 openDatabase(InputStream inStream, String password,
|
||||
InputStream keyInputStream) throws IOException, InvalidDBException {
|
||||
@@ -108,7 +102,7 @@ public class ImporterV4 extends Importer {
|
||||
|
||||
if (progressTaskUpdater != null)
|
||||
progressTaskUpdater.updateMessage(R.string.creating_db_key);
|
||||
db = createDB();
|
||||
db = new PwDatabaseV4();
|
||||
|
||||
PwDbHeaderV4 header = new PwDbHeaderV4(db);
|
||||
db.getBinPool().clear();
|
||||
@@ -120,7 +114,7 @@ public class ImporterV4 extends Importer {
|
||||
pbHeader = hh.header;
|
||||
|
||||
db.retrieveMasterKey(password, keyInputStream);
|
||||
db.makeFinalKey(header.masterSeed, db.getKdfParameters(), roundsFix);
|
||||
db.makeFinalKey(header.masterSeed, roundsFix);
|
||||
|
||||
if (progressTaskUpdater != null)
|
||||
progressTaskUpdater.updateMessage(R.string.decrypting_db);
|
||||
@@ -192,7 +186,7 @@ public class ImporterV4 extends Importer {
|
||||
isXml = isPlain;
|
||||
}
|
||||
|
||||
if (version >= header.FILE_VERSION_32_4) {
|
||||
if (version >= PwDbHeaderV4.FILE_VERSION_32_4) {
|
||||
LoadInnerHeader(isXml, header);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ public class PwDbHeaderOutputV4 extends PwDbHeaderOutput {
|
||||
db = d;
|
||||
header = h;
|
||||
|
||||
MessageDigest md = null;
|
||||
MessageDigest md;
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
@@ -63,7 +63,7 @@ public class PwDbHeaderOutputV4 extends PwDbHeaderOutput {
|
||||
}
|
||||
|
||||
try {
|
||||
d.makeFinalKey(header.masterSeed, d.getKdfParameters());
|
||||
d.makeFinalKey(header.masterSeed);
|
||||
} catch (IOException e) {
|
||||
throw new PwDbOutputException(e);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.io.OutputStream;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public abstract class PwDbOutput {
|
||||
public abstract class PwDbOutput<Header extends PwDbHeader> {
|
||||
|
||||
protected OutputStream mOS;
|
||||
|
||||
@@ -47,7 +47,7 @@ public abstract class PwDbOutput {
|
||||
mOS = os;
|
||||
}
|
||||
|
||||
protected SecureRandom setIVs(PwDbHeader header) throws PwDbOutputException {
|
||||
protected SecureRandom setIVs(Header header) throws PwDbOutputException {
|
||||
SecureRandom random;
|
||||
try {
|
||||
random = SecureRandom.getInstance("SHA1PRNG");
|
||||
@@ -62,6 +62,6 @@ public abstract class PwDbOutput {
|
||||
|
||||
public abstract void output() throws PwDbOutputException;
|
||||
|
||||
public abstract PwDbHeader outputHeader(OutputStream os) throws PwDbOutputException;
|
||||
public abstract Header outputHeader(OutputStream os) throws PwDbOutputException;
|
||||
|
||||
}
|
||||
@@ -48,7 +48,7 @@ import javax.crypto.CipherOutputStream;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class PwDbV3Output extends PwDbOutput {
|
||||
public class PwDbV3Output extends PwDbOutput<PwDbHeaderV3> {
|
||||
private PwDatabaseV3 mPM;
|
||||
private byte[] headerHashBlock;
|
||||
|
||||
@@ -111,15 +111,13 @@ public class PwDbV3Output extends PwDbOutput {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SecureRandom setIVs(PwDbHeader header) throws PwDbOutputException {
|
||||
protected SecureRandom setIVs(PwDbHeaderV3 header) throws PwDbOutputException {
|
||||
SecureRandom random = super.setIVs(header);
|
||||
|
||||
PwDbHeaderV3 h3 = (PwDbHeaderV3) header;
|
||||
random.nextBytes(h3.transformSeed);
|
||||
|
||||
random.nextBytes(header.transformSeed);
|
||||
return random;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PwDbHeaderV3 outputHeader(OutputStream os) throws PwDbOutputException {
|
||||
// Build header
|
||||
PwDbHeaderV3 header = new PwDbHeaderV3();
|
||||
|
||||
@@ -21,7 +21,6 @@ package com.kunzisoft.keepass.database.save;
|
||||
|
||||
import com.kunzisoft.keepass.database.PwDatabaseV3;
|
||||
import com.kunzisoft.keepass.database.PwDatabaseV3Debug;
|
||||
import com.kunzisoft.keepass.database.PwDbHeader;
|
||||
import com.kunzisoft.keepass.database.PwDbHeaderV3;
|
||||
import com.kunzisoft.keepass.database.exception.PwDbOutputException;
|
||||
|
||||
@@ -32,10 +31,6 @@ public class PwDbV3OutputDebug extends PwDbV3Output {
|
||||
PwDatabaseV3Debug debugDb;
|
||||
private boolean noHeaderHash;
|
||||
|
||||
public PwDbV3OutputDebug(PwDatabaseV3 pm, OutputStream os) {
|
||||
this(pm, os, false);
|
||||
}
|
||||
|
||||
public PwDbV3OutputDebug(PwDatabaseV3 pm, OutputStream os, boolean noHeaderHash) {
|
||||
super(pm, os);
|
||||
debugDb = (PwDatabaseV3Debug) pm;
|
||||
@@ -43,10 +38,7 @@ public class PwDbV3OutputDebug extends PwDbV3Output {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SecureRandom setIVs(PwDbHeader h) throws PwDbOutputException {
|
||||
PwDbHeaderV3 header = (PwDbHeaderV3) h;
|
||||
|
||||
|
||||
protected SecureRandom setIVs(PwDbHeaderV3 header) throws PwDbOutputException {
|
||||
// Reuse random values to test equivalence in debug mode
|
||||
PwDbHeaderV3 origHeader = debugDb.getDbHeader();
|
||||
System.arraycopy(origHeader.encryptionIV, 0, header.encryptionIV, 0, origHeader.encryptionIV.length);
|
||||
|
||||
@@ -35,7 +35,6 @@ import com.kunzisoft.keepass.database.MemoryProtectionConfig;
|
||||
import com.kunzisoft.keepass.database.PwCompressionAlgorithm;
|
||||
import com.kunzisoft.keepass.database.PwDatabaseV4;
|
||||
import com.kunzisoft.keepass.database.PwDatabaseV4XML;
|
||||
import com.kunzisoft.keepass.database.PwDbHeader;
|
||||
import com.kunzisoft.keepass.database.PwDbHeaderV4;
|
||||
import com.kunzisoft.keepass.database.PwDefsV4;
|
||||
import com.kunzisoft.keepass.database.PwDeletedObject;
|
||||
@@ -74,9 +73,9 @@ import javax.crypto.CipherOutputStream;
|
||||
|
||||
import biz.source_code.base64Coder.Base64Coder;
|
||||
|
||||
public class PwDbV4Output extends PwDbOutput {
|
||||
public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
|
||||
|
||||
PwDatabaseV4 mPM;
|
||||
private PwDatabaseV4 mPM;
|
||||
private StreamCipher randomStream;
|
||||
private XmlSerializer xml;
|
||||
private PwDbHeaderV4 header;
|
||||
@@ -86,8 +85,7 @@ public class PwDbV4Output extends PwDbOutput {
|
||||
|
||||
protected PwDbV4Output(PwDatabaseV4 pm, OutputStream os) {
|
||||
super(os);
|
||||
|
||||
mPM = pm;
|
||||
this.mPM = pm;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -100,15 +98,14 @@ public class PwDbV4Output extends PwDbOutput {
|
||||
throw new PwDbOutputException("No such cipher", e);
|
||||
}
|
||||
|
||||
header = (PwDbHeaderV4) outputHeader(mOS);
|
||||
header = outputHeader(mOS);
|
||||
|
||||
OutputStream osPlain;
|
||||
if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) {
|
||||
CipherOutputStream cos = attachStreamEncryptor(header, mOS);
|
||||
cos.write(header.streamStartBytes);
|
||||
|
||||
HashedBlockOutputStream hashed = new HashedBlockOutputStream(cos);
|
||||
osPlain = hashed;
|
||||
osPlain = new HashedBlockOutputStream(cos);
|
||||
} else {
|
||||
mOS.write(hashOfHeader);
|
||||
mOS.write(headerHmac);
|
||||
@@ -286,45 +283,43 @@ public class PwDbV4Output extends PwDbOutput {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SecureRandom setIVs(PwDbHeader header) throws PwDbOutputException {
|
||||
protected SecureRandom setIVs(PwDbHeaderV4 header) throws PwDbOutputException {
|
||||
SecureRandom random = super.setIVs(header);
|
||||
|
||||
PwDbHeaderV4 h = (PwDbHeaderV4) header;
|
||||
random.nextBytes(h.masterSeed);
|
||||
random.nextBytes(header.masterSeed);
|
||||
|
||||
int ivLength = engine.ivLength();
|
||||
if (ivLength != h.encryptionIV.length) {
|
||||
h.encryptionIV = new byte[ivLength];
|
||||
if (ivLength != header.encryptionIV.length) {
|
||||
header.encryptionIV = new byte[ivLength];
|
||||
}
|
||||
random.nextBytes(h.encryptionIV);
|
||||
random.nextBytes(header.encryptionIV);
|
||||
|
||||
UUID kdfUUID = mPM.getKdfParameters().kdfUUID;
|
||||
KdfEngine kdf = KdfFactory.get(kdfUUID);
|
||||
KdfEngine kdf = KdfFactory.get(mPM.getKdfParameters());
|
||||
kdf.randomize(mPM.getKdfParameters());
|
||||
|
||||
if (h.version < PwDbHeaderV4.FILE_VERSION_32_4) {
|
||||
h.innerRandomStream = CrsAlgorithm.Salsa20;
|
||||
h.innerRandomStreamKey = new byte[32];
|
||||
if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) {
|
||||
header.innerRandomStream = CrsAlgorithm.Salsa20;
|
||||
header.innerRandomStreamKey = new byte[32];
|
||||
} else {
|
||||
h.innerRandomStream = CrsAlgorithm.ChaCha20;
|
||||
h.innerRandomStreamKey = new byte[64];
|
||||
header.innerRandomStream = CrsAlgorithm.ChaCha20;
|
||||
header.innerRandomStreamKey = new byte[64];
|
||||
}
|
||||
random.nextBytes(h.innerRandomStreamKey);
|
||||
random.nextBytes(header.innerRandomStreamKey);
|
||||
|
||||
randomStream = PwStreamCipherFactory.getInstance(h.innerRandomStream, h.innerRandomStreamKey);
|
||||
randomStream = PwStreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey);
|
||||
if (randomStream == null) {
|
||||
throw new PwDbOutputException("Invalid random cipher");
|
||||
}
|
||||
|
||||
if ( h.version < PwDbHeaderV4.FILE_VERSION_32_4) {
|
||||
random.nextBytes(h.streamStartBytes);
|
||||
if ( header.version < PwDbHeaderV4.FILE_VERSION_32_4) {
|
||||
random.nextBytes(header.streamStartBytes);
|
||||
}
|
||||
|
||||
return random;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PwDbHeader outputHeader(OutputStream os) throws PwDbOutputException {
|
||||
public PwDbHeaderV4 outputHeader(OutputStream os) throws PwDbOutputException {
|
||||
|
||||
PwDbHeaderV4 header = new PwDbHeaderV4(mPM);
|
||||
setIVs(header);
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ public abstract class DatabaseSavePreferenceDialogFragmentCompat extends InputP
|
||||
protected void onBindDialogView(View view) {
|
||||
super.onBindDialogView(view);
|
||||
|
||||
database = App.getDB();
|
||||
this.database = App.getDB();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,7 +47,9 @@ public abstract class DatabaseSavePreferenceDialogFragmentCompat extends InputP
|
||||
assert getActivity() != null;
|
||||
|
||||
if (database != null && afterSaveDatabase != null) {
|
||||
SaveDBRunnable saveDBRunnable = new SaveDBRunnable(getContext(), database, afterSaveDatabase);
|
||||
SaveDBRunnable saveDBRunnable = new SaveDBRunnable(getContext(),
|
||||
database,
|
||||
afterSaveDatabase);
|
||||
saveDBRunnable.setUpdateProgressTaskStatus(
|
||||
new UpdateProgressTaskStatus(getContext(),
|
||||
SaveDatabaseProgressTaskDialogFragment.start(
|
||||
|
||||
Reference in New Issue
Block a user