Working HashedBlock implementation.

This commit is contained in:
Brian Pellin
2010-05-06 00:09:41 -05:00
parent df30f09c2d
commit 2ebeacdf35
21 changed files with 501 additions and 150 deletions

View File

@@ -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.");

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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[];
}

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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));
}

View File

@@ -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

View 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;
}
}

View 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);
}
}

View 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();
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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);

View 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);
}
}