mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Working HashedBlock implementation.
This commit is contained in:
@@ -19,11 +19,15 @@
|
||||
*/
|
||||
package com.keepassdroid.crypto;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import com.keepassdroid.utils.Types;
|
||||
|
||||
@@ -59,10 +63,16 @@ public class CipherFactory {
|
||||
* @return
|
||||
* @throws NoSuchPaddingException
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws InvalidAlgorithmParameterException
|
||||
* @throws InvalidKeyException
|
||||
*/
|
||||
public static Cipher getInstance(UUID uuid) throws NoSuchAlgorithmException, NoSuchPaddingException {
|
||||
public static Cipher getInstance(UUID uuid, byte[] key, byte[] IV) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
||||
if ( uuid.equals(AES_CIPHER) ) {
|
||||
return CipherFactory.getInstance("AES/CBC/PKCS5Padding");
|
||||
Cipher cipher = CipherFactory.getInstance("AES/CBC/PKCS5Padding");
|
||||
|
||||
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(IV));
|
||||
|
||||
return cipher;
|
||||
}
|
||||
|
||||
throw new NoSuchAlgorithmException("UUID unrecognized.");
|
||||
|
||||
@@ -19,13 +19,29 @@
|
||||
*/
|
||||
package com.keepassdroid.database;
|
||||
|
||||
public class PwCompressionAlgorithm {
|
||||
public enum PwCompressionAlgorithm {
|
||||
|
||||
None(0),
|
||||
Gzip(1);
|
||||
|
||||
// Note: We can get away with using int's to store unsigned 32-bit ints
|
||||
// since we won't do arithmetic on these values (also unlikely to
|
||||
// reach
|
||||
// reach
|
||||
private final int id;
|
||||
public static final int count = 2;
|
||||
|
||||
public static final int None = 0;
|
||||
public static final int Gzip = 1;
|
||||
private PwCompressionAlgorithm(int num) {
|
||||
id = num;
|
||||
}
|
||||
|
||||
public static PwCompressionAlgorithm fromId(int num) {
|
||||
for ( PwCompressionAlgorithm e : PwCompressionAlgorithm.values() ) {
|
||||
if ( e.id == num ) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static final int Count = 2;
|
||||
}
|
||||
|
||||
@@ -24,14 +24,50 @@ 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 com.keepassdroid.crypto.finalkey.FinalKey;
|
||||
import com.keepassdroid.crypto.finalkey.FinalKeyFactory;
|
||||
import com.keepassdroid.database.exception.InvalidKeyFileException;
|
||||
import com.keepassdroid.stream.NullOutputStream;
|
||||
|
||||
public class PwDatabase {
|
||||
|
||||
public byte masterKey[] = new byte[32];
|
||||
public byte[] finalKey;
|
||||
|
||||
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 static byte[] getMasterKey(String key, String keyFileName)
|
||||
throws InvalidKeyFileException, IOException {
|
||||
@@ -74,20 +110,7 @@ public class PwDatabase {
|
||||
md.update(passwordKey);
|
||||
|
||||
return md.digest(fileKey);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
SHA256Digest md = new SHA256Digest();
|
||||
md.update(passwordKey, 0, 32);
|
||||
md.update(fileKey, 0, 32);
|
||||
|
||||
byte[] outputKey = new byte[md.getDigestSize()];
|
||||
md.doFinal(outputKey, 0);
|
||||
|
||||
return outputKey;
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] getFileKey(String fileName)
|
||||
throws InvalidKeyFileException, IOException {
|
||||
@@ -175,7 +198,7 @@ public class PwDatabase {
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IOException("SHA-256 not supported");
|
||||
}
|
||||
//SHA256Digest md = new SHA256Digest();
|
||||
|
||||
byte[] bKey;
|
||||
try {
|
||||
bKey = key.getBytes("ISO-8859-1");
|
||||
@@ -184,7 +207,7 @@ public class PwDatabase {
|
||||
bKey = key.getBytes();
|
||||
}
|
||||
md.update(bKey, 0, bKey.length );
|
||||
//byte[] outputKey = new byte[md.getDigestSize()];
|
||||
|
||||
return md.digest();
|
||||
}
|
||||
|
||||
|
||||
@@ -61,9 +61,6 @@ public class PwDatabaseV3 extends PwDatabase {
|
||||
|
||||
// Debugging entries
|
||||
public PwDbHeaderV3 dbHeader;
|
||||
//public long paddingBytes;
|
||||
public byte[] finalKey;
|
||||
|
||||
// root group
|
||||
public PwGroupV3 rootGroup;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import java.util.UUID;
|
||||
public class PwDatabaseV4 extends PwDatabase {
|
||||
|
||||
public UUID dataCipher;
|
||||
public int compressionAlgorithm;
|
||||
public PwCompressionAlgorithm compressionAlgorithm;
|
||||
public long numKeyEncRounds;
|
||||
|
||||
}
|
||||
|
||||
@@ -24,12 +24,12 @@ public abstract class PwDbHeader {
|
||||
public static final int PWM_DBSIG_1 = 0x9AA2D903;
|
||||
|
||||
/** Seed that gets hashed with the userkey to form the final key */
|
||||
public byte mMasterSeed[];
|
||||
public byte masterSeed[];
|
||||
|
||||
/** Used for the dwKeyEncRounds AES transformations */
|
||||
public byte mTransformSeed[];
|
||||
public byte transformSeed[];
|
||||
|
||||
/** IV used for content encryption */
|
||||
public byte mEncryptionIV[];
|
||||
public byte encryptionIV[];
|
||||
|
||||
}
|
||||
|
||||
@@ -74,15 +74,15 @@ public class PwDbHeaderV3 extends PwDbHeader {
|
||||
flags = Types.readInt( buf, offset + 8 );
|
||||
version = Types.readInt( buf, offset + 12 );
|
||||
|
||||
System.arraycopy( buf, offset + 16, mMasterSeed, 0, 16 );
|
||||
System.arraycopy( buf, offset + 32, mEncryptionIV, 0, 16 );
|
||||
System.arraycopy( buf, offset + 16, masterSeed, 0, 16 );
|
||||
System.arraycopy( buf, offset + 32, encryptionIV, 0, 16 );
|
||||
|
||||
numGroups = Types.readInt( buf, offset + 48 );
|
||||
numEntries = Types.readInt( buf, offset + 52 );
|
||||
|
||||
System.arraycopy( buf, offset + 56, contentsHash, 0, 32 );
|
||||
|
||||
System.arraycopy( buf, offset + 88, mTransformSeed, 0, 32 );
|
||||
System.arraycopy( buf, offset + 88, transformSeed, 0, 32 );
|
||||
numKeyEncRounds = Types.readInt( buf, offset + 120 );
|
||||
if ( numKeyEncRounds < 0 ) {
|
||||
// TODO: Really treat this like an unsigned integer
|
||||
@@ -91,9 +91,9 @@ public class PwDbHeaderV3 extends PwDbHeader {
|
||||
}
|
||||
|
||||
public PwDbHeaderV3() {
|
||||
mMasterSeed = new byte[16];
|
||||
mTransformSeed = new byte[32];
|
||||
mEncryptionIV = new byte[16];
|
||||
masterSeed = new byte[16];
|
||||
transformSeed = new byte[32];
|
||||
encryptionIV = new byte[16];
|
||||
}
|
||||
|
||||
public static boolean matchesHeader(int sig1, int sig2) {
|
||||
|
||||
@@ -34,7 +34,8 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
|
||||
private class PwDbHeaderV4Fields {
|
||||
public static final byte EndOfHeader = 0;
|
||||
public static final byte Comment = 1;
|
||||
@SuppressWarnings("unused")
|
||||
public static final byte Comment = 1;
|
||||
public static final byte CipherID = 2;
|
||||
public static final byte CompressionFlags = 3;
|
||||
public static final byte MasterSeed = 4;
|
||||
@@ -109,11 +110,11 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
break;
|
||||
|
||||
case PwDbHeaderV4Fields.MasterSeed:
|
||||
mMasterSeed = fieldData;
|
||||
masterSeed = fieldData;
|
||||
break;
|
||||
|
||||
case PwDbHeaderV4Fields.TransformSeed:
|
||||
mTransformSeed = fieldData;
|
||||
transformSeed = fieldData;
|
||||
break;
|
||||
|
||||
case PwDbHeaderV4Fields.TransformRounds:
|
||||
@@ -121,7 +122,7 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
break;
|
||||
|
||||
case PwDbHeaderV4Fields.EncryptionIV:
|
||||
mEncryptionIV = fieldData;
|
||||
encryptionIV = fieldData;
|
||||
break;
|
||||
|
||||
case PwDbHeaderV4Fields.ProtectedStreamKey:
|
||||
@@ -158,11 +159,11 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
}
|
||||
|
||||
int flag = Types.readInt(pbFlags, 0);
|
||||
if ( flag < 0 || flag >= PwCompressionAlgorithm.Count ) {
|
||||
if ( flag < 0 || flag >= PwCompressionAlgorithm.count ) {
|
||||
throw new IOException("Unrecognized compression flag.");
|
||||
}
|
||||
|
||||
db.compressionAlgorithm = flag;
|
||||
db.compressionAlgorithm = PwCompressionAlgorithm.fromId(flag);
|
||||
|
||||
}
|
||||
|
||||
@@ -173,9 +174,9 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
|
||||
long rnd = Types.readLong(rounds, 0);
|
||||
|
||||
if ( rnd < 0 ) {
|
||||
if ( rnd < 0 || rnd > Integer.MAX_VALUE ) {
|
||||
//TODO: Actually support really large numbers
|
||||
throw new IOException("Rounds higher than " + Long.MAX_VALUE + " are not currently supported.");
|
||||
throw new IOException("Rounds higher than " + Integer.MAX_VALUE + " are not currently supported.");
|
||||
}
|
||||
|
||||
db.numKeyEncRounds = rnd;
|
||||
|
||||
@@ -47,8 +47,6 @@ import android.util.Log;
|
||||
import com.android.keepass.R;
|
||||
import com.keepassdroid.UpdateStatus;
|
||||
import com.keepassdroid.crypto.CipherFactory;
|
||||
import com.keepassdroid.crypto.finalkey.FinalKey;
|
||||
import com.keepassdroid.crypto.finalkey.FinalKeyFactory;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwDatabaseV3;
|
||||
import com.keepassdroid.database.PwDate;
|
||||
@@ -60,7 +58,7 @@ import com.keepassdroid.database.exception.InvalidDBSignatureException;
|
||||
import com.keepassdroid.database.exception.InvalidDBVersionException;
|
||||
import com.keepassdroid.database.exception.InvalidKeyFileException;
|
||||
import com.keepassdroid.database.exception.InvalidPasswordException;
|
||||
import com.keepassdroid.database.save.NullOutputStream;
|
||||
import com.keepassdroid.stream.NullOutputStream;
|
||||
import com.keepassdroid.utils.Types;
|
||||
|
||||
/**
|
||||
@@ -111,7 +109,6 @@ public class ImporterV3 extends Importer {
|
||||
throws IOException, InvalidKeyFileException, InvalidPasswordException, InvalidDBSignatureException, InvalidDBVersionException
|
||||
{
|
||||
PwDatabaseV3 newManager;
|
||||
byte[] finalKey;
|
||||
|
||||
|
||||
// Load entire file, most of it's encrypted.
|
||||
@@ -158,9 +155,7 @@ public class ImporterV3 extends Importer {
|
||||
newManager.name = "KeePass Password Manager";
|
||||
|
||||
// Generate transformedMasterKey from masterKey
|
||||
finalKey = makeFinalKey(hdr.mMasterSeed, hdr.mTransformSeed, newManager.masterKey, newManager.numKeyEncRounds);
|
||||
newManager.finalKey = new byte[finalKey.length];
|
||||
System.arraycopy(finalKey, 0, newManager.finalKey, 0, finalKey.length);
|
||||
newManager.makeFinalKey(hdr.masterSeed, hdr.transformSeed, newManager.numKeyEncRounds);
|
||||
|
||||
status.updateMessage(R.string.decrypting_db);
|
||||
// Initialize Rijndael algorithm
|
||||
@@ -176,7 +171,7 @@ public class ImporterV3 extends Importer {
|
||||
//BufferedBlockCipher cipher = new BufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
|
||||
|
||||
try {
|
||||
cipher.init( Cipher.DECRYPT_MODE, new SecretKeySpec( finalKey, "AES" ), new IvParameterSpec( hdr.mEncryptionIV ) );
|
||||
cipher.init( Cipher.DECRYPT_MODE, new SecretKeySpec( newManager.finalKey, "AES" ), new IvParameterSpec( hdr.encryptionIV ) );
|
||||
} catch (InvalidKeyException e1) {
|
||||
throw new IOException("Invalid key");
|
||||
} catch (InvalidAlgorithmParameterException e1) {
|
||||
@@ -224,9 +219,9 @@ public class ImporterV3 extends Importer {
|
||||
DigestOutputStream dos = new DigestOutputStream(nos, md);
|
||||
dos.write(filebuf, PwDbHeaderV3.BUF_SIZE, encryptedPartSize);
|
||||
dos.close();
|
||||
finalKey = md.digest();
|
||||
byte[] hash = md.digest();
|
||||
|
||||
if( ! Arrays.equals(finalKey, hdr.contentsHash) ) {
|
||||
if( ! Arrays.equals(hash, hdr.contentsHash) ) {
|
||||
|
||||
Log.w("KeePassDroid","Database file did not decrypt correctly. (checksum code is broken)");
|
||||
throw new InvalidPasswordException();
|
||||
@@ -276,25 +271,6 @@ public class ImporterV3 extends Importer {
|
||||
return newManager;
|
||||
}
|
||||
|
||||
public static byte[] makeFinalKey(byte[] masterSeed, byte[] masterSeed2, byte[] masterKey, 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 = ImporterV3.transformMasterKey(masterSeed2, masterKey, numRounds);
|
||||
dos.write(masterSeed);
|
||||
dos.write(transformedMasterKey);
|
||||
|
||||
return md.digest();
|
||||
}
|
||||
|
||||
/**
|
||||
* KeePass's custom pad style.
|
||||
*
|
||||
@@ -338,17 +314,6 @@ public class ImporterV3 extends Importer {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encrypt the master key a few times to make brute-force key-search harder
|
||||
* @throws IOException
|
||||
*/
|
||||
public static byte[] transformMasterKey( byte[] pKeySeed, byte[] pKey, int rounds ) throws IOException
|
||||
{
|
||||
FinalKey key = FinalKeyFactory.createFinalKey();
|
||||
|
||||
return key.transformMasterKey(pKeySeed, pKey, rounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and save one record from binary file.
|
||||
* @param buf
|
||||
|
||||
@@ -19,10 +19,23 @@
|
||||
*/
|
||||
package com.keepassdroid.database.load;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.DigestInputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
|
||||
import com.keepassdroid.UpdateStatus;
|
||||
import com.keepassdroid.crypto.CipherFactory;
|
||||
import com.keepassdroid.database.PwCompressionAlgorithm;
|
||||
import com.keepassdroid.database.PwDatabaseV4;
|
||||
import com.keepassdroid.database.PwDbHeaderV4;
|
||||
import com.keepassdroid.database.exception.InvalidDBSignatureException;
|
||||
@@ -52,9 +65,51 @@ public class ImporterV4 extends Importer {
|
||||
PwDbHeaderV4 header = new PwDbHeaderV4(db);
|
||||
|
||||
header.loadFromFile(inStream);
|
||||
|
||||
db.setMasterKey(password, keyfile);
|
||||
db.makeFinalKey(header.masterSeed, header.transformSeed, (int)db.numKeyEncRounds);
|
||||
|
||||
// Attach decryptor
|
||||
//if ( db.compressionAlgorithm == )
|
||||
Cipher cipher;
|
||||
try {
|
||||
cipher = CipherFactory.getInstance(db.dataCipher, db.finalKey, header.encryptionIV);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IOException("Invalid algorithm.");
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new IOException("Invalid algorithm.");
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IOException("Invalid algorithm.");
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new IOException("Invalid algorithm.");
|
||||
}
|
||||
|
||||
InputStream decrypted = new CipherInputStream(inStream, cipher);
|
||||
|
||||
MessageDigest md = null;
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IOException("SHA-256 not implemented here.");
|
||||
}
|
||||
|
||||
InputStream hashed = new DigestInputStream(decrypted, md);
|
||||
|
||||
InputStream decompressed;
|
||||
if ( db.compressionAlgorithm == PwCompressionAlgorithm.Gzip ) {
|
||||
decompressed = new GZIPInputStream(hashed);
|
||||
} else {
|
||||
decompressed = hashed;
|
||||
}
|
||||
|
||||
FileOutputStream fos = new FileOutputStream("output.xml");
|
||||
|
||||
byte[] buf = new byte[1024];
|
||||
int byteReads;
|
||||
while (( byteReads = decompressed.read(buf)) != -1 ) {
|
||||
fos.write(buf);
|
||||
}
|
||||
|
||||
fos.close();
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
@@ -40,12 +40,12 @@ public class PwDbHeaderOutput {
|
||||
mOS.write(Types.writeInt(mHeader.signature2));
|
||||
mOS.write(Types.writeInt(mHeader.flags));
|
||||
mOS.write(Types.writeInt(mHeader.version));
|
||||
mOS.write(mHeader.mMasterSeed);
|
||||
mOS.write(mHeader.mEncryptionIV);
|
||||
mOS.write(mHeader.masterSeed);
|
||||
mOS.write(mHeader.encryptionIV);
|
||||
mOS.write(Types.writeInt(mHeader.numGroups));
|
||||
mOS.write(Types.writeInt(mHeader.numEntries));
|
||||
mOS.write(mHeader.contentsHash);
|
||||
mOS.write(mHeader.mTransformSeed);
|
||||
mOS.write(mHeader.transformSeed);
|
||||
mOS.write(Types.writeInt(mHeader.numKeyEncRounds));
|
||||
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ import com.keepassdroid.database.PwDbHeaderV3;
|
||||
import com.keepassdroid.database.PwEntryV3;
|
||||
import com.keepassdroid.database.PwGroupV3;
|
||||
import com.keepassdroid.database.exception.PwDbOutputException;
|
||||
import com.keepassdroid.database.load.ImporterV3;
|
||||
import com.keepassdroid.stream.NullOutputStream;
|
||||
|
||||
public class PwDbV3Output {
|
||||
private PwDatabaseV3 mPM;
|
||||
@@ -64,15 +64,8 @@ public class PwDbV3Output {
|
||||
|
||||
public byte[] getFinalKey(PwDbHeader header) throws PwDbOutputException {
|
||||
try {
|
||||
return ImporterV3.makeFinalKey(header.mMasterSeed, header.mTransformSeed, mPM.masterKey, mPM.numKeyEncRounds);
|
||||
} catch (IOException e) {
|
||||
throw new PwDbOutputException("Key creation failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getFinalKey2(PwDbHeader header) throws PwDbOutputException {
|
||||
try {
|
||||
return ImporterV3.makeFinalKey(header.mMasterSeed, header.mTransformSeed, mPM.masterKey, mPM.numKeyEncRounds);
|
||||
mPM.makeFinalKey(header.masterSeed, header.transformSeed, mPM.numKeyEncRounds);
|
||||
return mPM.finalKey;
|
||||
} catch (IOException e) {
|
||||
throw new PwDbOutputException("Key creation failed: " + e.getMessage());
|
||||
}
|
||||
@@ -95,7 +88,7 @@ public class PwDbV3Output {
|
||||
}
|
||||
|
||||
try {
|
||||
cipher.init( Cipher.ENCRYPT_MODE, new SecretKeySpec(finalKey, "AES" ), new IvParameterSpec(header.mEncryptionIV) );
|
||||
cipher.init( Cipher.ENCRYPT_MODE, new SecretKeySpec(finalKey, "AES" ), new IvParameterSpec(header.encryptionIV) );
|
||||
CipherOutputStream cos = new CipherOutputStream(mOS, cipher);
|
||||
BufferedOutputStream bos = new BufferedOutputStream(cos);
|
||||
outputPlanGroupAndEntries(bos);
|
||||
@@ -134,9 +127,9 @@ public class PwDbV3Output {
|
||||
|
||||
// Reuse random values to test equivalence in debug mode
|
||||
if ( mDebug ) {
|
||||
System.arraycopy(mPM.dbHeader.mEncryptionIV, 0, header.mEncryptionIV, 0, mPM.dbHeader.mEncryptionIV.length);
|
||||
System.arraycopy(mPM.dbHeader.mMasterSeed, 0, header.mMasterSeed, 0, mPM.dbHeader.mMasterSeed.length);
|
||||
System.arraycopy(mPM.dbHeader.mTransformSeed, 0, header.mTransformSeed, 0, mPM.dbHeader.mTransformSeed.length);
|
||||
System.arraycopy(mPM.dbHeader.encryptionIV, 0, header.encryptionIV, 0, mPM.dbHeader.encryptionIV.length);
|
||||
System.arraycopy(mPM.dbHeader.masterSeed, 0, header.masterSeed, 0, mPM.dbHeader.masterSeed.length);
|
||||
System.arraycopy(mPM.dbHeader.transformSeed, 0, header.transformSeed, 0, mPM.dbHeader.transformSeed.length);
|
||||
} else {
|
||||
SecureRandom random;
|
||||
try {
|
||||
@@ -144,9 +137,9 @@ public class PwDbV3Output {
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new PwDbOutputException("Does not support secure random number generation.");
|
||||
}
|
||||
random.nextBytes(header.mEncryptionIV);
|
||||
random.nextBytes(header.mMasterSeed);
|
||||
random.nextBytes(header.mTransformSeed);
|
||||
random.nextBytes(header.encryptionIV);
|
||||
random.nextBytes(header.masterSeed);
|
||||
random.nextBytes(header.transformSeed);
|
||||
}
|
||||
|
||||
// Write checksum Checksum
|
||||
|
||||
64
src/com/keepassdroid/stream/BetterDataInputStream.java
Normal file
64
src/com/keepassdroid/stream/BetterDataInputStream.java
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2010 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.stream;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class BetterDataInputStream extends DataInputStream {
|
||||
|
||||
public static final long INT_TO_LONG_MASK = 0xffffffffL;
|
||||
|
||||
public BetterDataInputStream(InputStream in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
/** Read a 32-bit value and return it as a long, so that it can
|
||||
* be interpreted as an unsigned integer.
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public long readUInt() throws IOException {
|
||||
return (readInt() & INT_TO_LONG_MASK);
|
||||
}
|
||||
|
||||
public byte[] readBytes(int length) throws IOException {
|
||||
byte[] buf = new byte[length];
|
||||
|
||||
int count = 0;
|
||||
while ( count < length ) {
|
||||
int read = read(buf, count, length - count);
|
||||
|
||||
// Reached end
|
||||
if ( read == -1 ) {
|
||||
// Stop early
|
||||
byte[] early = new byte[count];
|
||||
System.arraycopy(buf, 0, early, 0, count);
|
||||
return early;
|
||||
}
|
||||
|
||||
count += read;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
}
|
||||
36
src/com/keepassdroid/stream/BetterDataOutputStream.java
Normal file
36
src/com/keepassdroid/stream/BetterDataOutputStream.java
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2010 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.stream;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class BetterDataOutputStream extends DataOutputStream {
|
||||
|
||||
public BetterDataOutputStream(OutputStream out) {
|
||||
super(out);
|
||||
}
|
||||
|
||||
public void writeUInt(long uint) throws IOException {
|
||||
writeInt((int)uint);
|
||||
}
|
||||
|
||||
}
|
||||
154
src/com/keepassdroid/stream/HashedBlockInputStream.java
Normal file
154
src/com/keepassdroid/stream/HashedBlockInputStream.java
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 2010 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.stream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class HashedBlockInputStream extends InputStream {
|
||||
|
||||
// private final static int BUFFER_SIZE = 1024 * 1024; // I think this is only needed in the writer
|
||||
private final static int HASH_SIZE = 32;
|
||||
|
||||
private BetterDataInputStream baseStream;
|
||||
private int bufferPos = 0;
|
||||
private byte[] buffer = new byte[0];
|
||||
private long bufferIndex = 0;
|
||||
private boolean atEnd = false;
|
||||
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
public HashedBlockInputStream(InputStream is) {
|
||||
baseStream = new BetterDataInputStream(is);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int offset, int length) throws IOException {
|
||||
if ( atEnd ) return -1;
|
||||
|
||||
int remaining = length;
|
||||
|
||||
while ( remaining > 0 ) {
|
||||
if ( bufferPos == buffer.length ) {
|
||||
// Get more from the source into the buffer
|
||||
if ( ! ReadHashedBlock() ) {
|
||||
return length - remaining;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Copy from buffer out
|
||||
int copyLen = Math.min(buffer.length - bufferPos, remaining);
|
||||
|
||||
System.arraycopy(buffer, bufferPos, b, offset, copyLen);
|
||||
|
||||
offset += copyLen;
|
||||
bufferPos += copyLen;
|
||||
|
||||
remaining -= copyLen;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false, when the end of the source stream is reached
|
||||
* @throws IOException
|
||||
*/
|
||||
private boolean ReadHashedBlock() throws IOException {
|
||||
if ( atEnd ) return false;
|
||||
|
||||
bufferPos = 0;
|
||||
|
||||
long index = baseStream.readUInt();
|
||||
if ( index != bufferIndex ) {
|
||||
throw new IOException("Invalid data format");
|
||||
}
|
||||
bufferIndex++;
|
||||
|
||||
byte[] storedHash = baseStream.readBytes(HASH_SIZE);
|
||||
if ( storedHash == null || storedHash.length != HASH_SIZE) {
|
||||
throw new IOException("Invalid data format");
|
||||
}
|
||||
|
||||
int bufferSize = baseStream.readInt();
|
||||
if ( bufferSize < 0 ) {
|
||||
throw new IOException("Invalid data format");
|
||||
}
|
||||
|
||||
if ( bufferSize == 0 ) {
|
||||
for (int hash = 0; hash < HASH_SIZE; hash++) {
|
||||
if ( storedHash[hash] != 0 ) {
|
||||
throw new IOException("Invalid data format");
|
||||
}
|
||||
}
|
||||
|
||||
atEnd = true;
|
||||
buffer = new byte[0];
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer = baseStream.readBytes(bufferSize);
|
||||
if ( buffer == null || buffer.length != bufferSize ) {
|
||||
throw new IOException("Invalid data format");
|
||||
}
|
||||
|
||||
MessageDigest md = null;
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IOException("SHA-256 not implemented here.");
|
||||
}
|
||||
|
||||
byte[] computedHash = md.digest(buffer);
|
||||
if ( computedHash == null || computedHash.length != HASH_SIZE ) {
|
||||
throw new IOException("Hash wrong size");
|
||||
}
|
||||
|
||||
if ( ! Arrays.equals(storedHash, computedHash) ) {
|
||||
throw new IOException("Hashes didn't match.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
throw new IOException("Not implemented.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
baseStream.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePassDroid. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.database.save;
|
||||
package com.keepassdroid.stream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePassDroid. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.database.save;
|
||||
package com.keepassdroid.stream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@@ -30,6 +30,8 @@ import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.keepassdroid.stream.BetterDataInputStream;
|
||||
|
||||
/**
|
||||
* Tools for slicing and dicing Java and KeePass data types.
|
||||
*
|
||||
@@ -37,41 +39,6 @@ import java.util.UUID;
|
||||
*/
|
||||
public class Types {
|
||||
|
||||
private static final long INT_TO_LONG_MASK = 0xffffffffL;
|
||||
|
||||
/*
|
||||
public static long readUInt(byte buf[], int offset) {
|
||||
int firstByte = 0;
|
||||
int secondByte = 0;
|
||||
int thirdByte = 0;
|
||||
int fourthByte = 0;
|
||||
|
||||
firstByte = (0x000000FF & ((int)buf[offset]));
|
||||
secondByte = (0x000000FF & ((int)buf[offset+1]));
|
||||
thirdByte = (0x000000FF & ((int)buf[offset+2]));
|
||||
fourthByte = (0x000000FF & ((int)buf[offset+3]));
|
||||
|
||||
return ((long) (firstByte << 24
|
||||
| secondByte << 16
|
||||
| thirdByte << 8
|
||||
| fourthByte))
|
||||
& 0xFFFFFFFFL;
|
||||
|
||||
}
|
||||
|
||||
public static byte[] writeUInt(long val) {
|
||||
byte[] buf = new byte[4];
|
||||
|
||||
buf[0] = (byte) ((val & 0xFF000000L) >> 24);
|
||||
buf[1] = (byte) ((val & 0x00FF0000L) >> 16);
|
||||
buf[2] = (byte) ((val & 0x0000FF00L) >> 8);
|
||||
buf[3] = (byte) (val & 0x000000FFL);
|
||||
|
||||
return buf;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
public static long readLong( byte buf[], int offset ) {
|
||||
return ((long)buf[offset + 0] & 0xFF) + (((long)buf[offset + 1] & 0xFF) << 8)
|
||||
+ (((long)buf[offset + 2] & 0xFF) << 16) + (((long)buf[offset + 3] & 0xFF) << 24)
|
||||
@@ -103,7 +70,7 @@ public class Types {
|
||||
}
|
||||
|
||||
public static long readUInt( byte buf[], int offset ) {
|
||||
return (readInt(buf, offset) & INT_TO_LONG_MASK);
|
||||
return (readInt(buf, offset) & BetterDataInputStream.INT_TO_LONG_MASK);
|
||||
}
|
||||
|
||||
public static int readInt(InputStream is) throws IOException {
|
||||
@@ -115,7 +82,7 @@ public class Types {
|
||||
}
|
||||
|
||||
public static long readUInt(InputStream is) throws IOException {
|
||||
return (readInt(is) & INT_TO_LONG_MASK);
|
||||
return (readInt(is) & BetterDataInputStream.INT_TO_LONG_MASK);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -38,11 +38,13 @@ public class Kdb4Header extends AndroidTestCase {
|
||||
|
||||
ImporterV4 importer = new ImporterV4();
|
||||
|
||||
PwDatabaseV4 db = importer.openDatabase(is, null, null);
|
||||
PwDatabaseV4 db = importer.openDatabase(is, "12345", "");
|
||||
|
||||
assertEquals(6000, db.numKeyEncRounds);
|
||||
|
||||
assertTrue(db.dataCipher.equals(CipherFactory.AES_CIPHER));
|
||||
|
||||
am.close();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,9 +37,9 @@ import com.keepassdroid.database.PwDatabaseV3;
|
||||
import com.keepassdroid.database.PwDbHeader;
|
||||
import com.keepassdroid.database.PwDbHeaderV3;
|
||||
import com.keepassdroid.database.exception.PwDbOutputException;
|
||||
import com.keepassdroid.database.save.NullOutputStream;
|
||||
import com.keepassdroid.database.save.PwDbHeaderOutput;
|
||||
import com.keepassdroid.database.save.PwDbV3Output;
|
||||
import com.keepassdroid.stream.NullOutputStream;
|
||||
import com.keepassdroid.tests.database.TestData;
|
||||
|
||||
public class PwManagerOutputTest extends AndroidTestCase {
|
||||
@@ -88,9 +88,9 @@ public class PwManagerOutputTest extends AndroidTestCase {
|
||||
assertEquals("Signature2 unequal", expected.signature2, actual.signature2);
|
||||
assertEquals("Version unequal", expected.version, actual.version);
|
||||
assertArrayEquals("Hash unequal", expected.contentsHash, actual.contentsHash);
|
||||
assertArrayEquals("IV unequal", expected.mEncryptionIV, actual.mEncryptionIV);
|
||||
assertArrayEquals("Seed unequal", expected.mMasterSeed, actual.mMasterSeed);
|
||||
assertArrayEquals("Seed2 unequal", expected.mTransformSeed, actual.mTransformSeed);
|
||||
assertArrayEquals("IV unequal", expected.encryptionIV, actual.encryptionIV);
|
||||
assertArrayEquals("Seed unequal", expected.masterSeed, actual.masterSeed);
|
||||
assertArrayEquals("Seed2 unequal", expected.transformSeed, actual.transformSeed);
|
||||
}
|
||||
|
||||
public void testHeader() throws PwDbOutputException, IOException {
|
||||
@@ -111,7 +111,7 @@ public class PwManagerOutputTest extends AndroidTestCase {
|
||||
ByteArrayOutputStream bActual = new ByteArrayOutputStream();
|
||||
PwDbV3Output pActual = new PwDbV3Output(mPM, bActual, PwDbV3Output.DEBUG);
|
||||
PwDbHeader hActual = pActual.outputHeader(bActual);
|
||||
byte[] finalKey = pActual.getFinalKey2(hActual);
|
||||
byte[] finalKey = pActual.getFinalKey(hActual);
|
||||
|
||||
assertArrayEquals("Keys mismatched", mPM.finalKey, finalKey);
|
||||
|
||||
|
||||
68
tests/src/com/keepassdroid/tests/stream/HashedBlock.java
Normal file
68
tests/src/com/keepassdroid/tests/stream/HashedBlock.java
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2010 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 3 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.tests.stream;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import com.keepassdroid.stream.HashedBlockInputStream;
|
||||
import com.keepassdroid.stream.HashedBlockOutputStream;
|
||||
|
||||
public class HashedBlock extends TestCase {
|
||||
public void testBlockAligned() throws IOException {
|
||||
final int blockSize = 1024;
|
||||
byte[] orig = new byte[blockSize];
|
||||
|
||||
Random rnd = new Random();
|
||||
rnd.nextBytes(orig);
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
HashedBlockOutputStream output = new HashedBlockOutputStream(bos, blockSize);
|
||||
output.write(orig);
|
||||
output.close();
|
||||
|
||||
byte[] encoded = bos.toByteArray();
|
||||
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
|
||||
HashedBlockInputStream input = new HashedBlockInputStream(bis);
|
||||
|
||||
ByteArrayOutputStream decoded = new ByteArrayOutputStream();
|
||||
while ( true ) {
|
||||
byte[] buf = new byte[1024];
|
||||
int read = input.read(buf);
|
||||
if ( read == -1 ) {
|
||||
break;
|
||||
}
|
||||
|
||||
decoded.write(buf, 0, read);
|
||||
}
|
||||
|
||||
byte[] out = decoded.toByteArray();
|
||||
|
||||
assertArrayEquals(orig, out);
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user