mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Implement KDBX V4 read support
This commit is contained in:
@@ -21,6 +21,7 @@ package com.keepassdroid.tests;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Calendar;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
@@ -191,4 +192,20 @@ public class TypesTest extends TestCase {
|
||||
|
||||
assertArrayEquals("UUID match failed", bUUID, eUUID);
|
||||
}
|
||||
|
||||
public void testULongMax() throws Exception {
|
||||
byte[] ulongBytes = new byte[8];
|
||||
for (int i = 0; i < ulongBytes.length; i++) {
|
||||
ulongBytes[i] = -1;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
LEDataOutputStream leos = new LEDataOutputStream(bos);
|
||||
leos.writeLong(Types.ULONG_MAX_VALUE);
|
||||
leos.close();
|
||||
|
||||
byte[] uLongMax = bos.toByteArray();
|
||||
|
||||
assertArrayEquals(ulongBytes, uLongMax);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 Brian Pellin.
|
||||
* Copyright 2013-2017 Brian Pellin.
|
||||
*
|
||||
* This file is part of KeePassDroid.
|
||||
*
|
||||
@@ -38,6 +38,8 @@ import javax.crypto.NoSuchPaddingException;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import com.keepassdroid.crypto.CipherFactory;
|
||||
import com.keepassdroid.crypto.engine.AesEngine;
|
||||
import com.keepassdroid.crypto.engine.CipherEngine;
|
||||
import com.keepassdroid.stream.BetterCipherInputStream;
|
||||
import com.keepassdroid.stream.LEDataInputStream;
|
||||
|
||||
@@ -54,9 +56,10 @@ public class CipherTest extends TestCase {
|
||||
rand.nextBytes(iv);
|
||||
rand.nextBytes(plaintext);
|
||||
|
||||
Cipher encrypt = CipherFactory.getInstance(CipherFactory.AES_CIPHER, Cipher.ENCRYPT_MODE, key, iv);
|
||||
Cipher decrypt = CipherFactory.getInstance(CipherFactory.AES_CIPHER, Cipher.DECRYPT_MODE, key, iv);
|
||||
|
||||
CipherEngine aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID);
|
||||
Cipher encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv);
|
||||
Cipher decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv);
|
||||
|
||||
byte[] secrettext = encrypt.doFinal(plaintext);
|
||||
byte[] decrypttext = decrypt.doFinal(secrettext);
|
||||
|
||||
@@ -74,10 +77,11 @@ public class CipherTest extends TestCase {
|
||||
rand.nextBytes(key);
|
||||
rand.nextBytes(iv);
|
||||
rand.nextBytes(plaintext);
|
||||
|
||||
Cipher encrypt = CipherFactory.getInstance(CipherFactory.AES_CIPHER, Cipher.ENCRYPT_MODE, key, iv);
|
||||
Cipher decrypt = CipherFactory.getInstance(CipherFactory.AES_CIPHER, Cipher.DECRYPT_MODE, key, iv);
|
||||
|
||||
|
||||
CipherEngine aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID);
|
||||
Cipher encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv);
|
||||
Cipher decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv);
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
CipherOutputStream cos = new CipherOutputStream(bos, encrypt);
|
||||
cos.write(plaintext);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2016 Brian Pellin.
|
||||
* Copyright 2010-2017 Brian Pellin.
|
||||
*
|
||||
* This file is part of KeePassDroid.
|
||||
*
|
||||
@@ -26,6 +26,7 @@ import android.content.res.AssetManager;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import com.keepassdroid.crypto.CipherFactory;
|
||||
import com.keepassdroid.crypto.engine.AesEngine;
|
||||
import com.keepassdroid.database.PwDatabaseV4;
|
||||
import com.keepassdroid.database.load.ImporterV4;
|
||||
|
||||
@@ -42,7 +43,7 @@ public class Kdb4Header extends AndroidTestCase {
|
||||
|
||||
assertEquals(6000, db.numKeyEncRounds);
|
||||
|
||||
assertTrue(db.dataCipher.equals(CipherFactory.AES_CIPHER));
|
||||
assertTrue(db.dataCipher.equals(AesEngine.CIPHER_UUID));
|
||||
|
||||
is.close();
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import com.keepassdroid.crypto.engine.AesEngine;
|
||||
import com.keepassdroid.crypto.engine.CipherEngine;
|
||||
import com.keepassdroid.crypto.engine.TwofishEngine;
|
||||
import com.keepassdroid.utils.Types;
|
||||
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
@@ -70,17 +73,8 @@ public class CipherFactory {
|
||||
private static boolean hasNativeImplementation(String transformation) {
|
||||
return transformation.equals("AES/CBC/PKCS5Padding");
|
||||
}
|
||||
|
||||
|
||||
public static final UUID AES_CIPHER = Types.bytestoUUID(
|
||||
new byte[]{(byte)0x31, (byte)0xC1, (byte)0xF2, (byte)0xE6, (byte)0xBF, (byte)0x71, (byte)0x43, (byte)0x50,
|
||||
(byte)0xBE, (byte)0x58, (byte)0x05, (byte)0x21, (byte)0x6A, (byte)0xFC, 0x5A, (byte)0xFF
|
||||
});
|
||||
public static final UUID TWOFISH_CIPHER = Types.bytestoUUID(
|
||||
new byte[]{(byte)0xAD, (byte)0x68, (byte)0xF2, (byte)0x9F, (byte)0x57, (byte)0x6F, (byte)0x4B, (byte)0xB9,
|
||||
(byte)0xA3, (byte)0x6A, (byte)0xD4, (byte)0x7A, (byte)0xF9, (byte)0x65, (byte)0x34, (byte)0x6C
|
||||
});
|
||||
|
||||
|
||||
|
||||
/** Generate appropriate cipher based on KeePass 2.x UUID's
|
||||
* @param uuid
|
||||
* @return
|
||||
@@ -89,28 +83,11 @@ public class CipherFactory {
|
||||
* @throws InvalidAlgorithmParameterException
|
||||
* @throws InvalidKeyException
|
||||
*/
|
||||
public static Cipher getInstance(UUID uuid, int opmode, byte[] key, byte[] IV) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
||||
return getInstance(uuid, opmode, key, IV, false);
|
||||
}
|
||||
|
||||
public static Cipher getInstance(UUID uuid, int opmode, byte[] key, byte[] IV, boolean androidOverride) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
||||
if ( uuid.equals(AES_CIPHER) ) {
|
||||
Cipher cipher = CipherFactory.getInstance("AES/CBC/PKCS5Padding", androidOverride);
|
||||
|
||||
cipher.init(opmode, new SecretKeySpec(key, "AES"), new IvParameterSpec(IV));
|
||||
|
||||
return cipher;
|
||||
} else if ( uuid.equals(TWOFISH_CIPHER) ) {
|
||||
Cipher cipher;
|
||||
if (opmode == Cipher.ENCRYPT_MODE) {
|
||||
cipher = CipherFactory.getInstance("Twofish/CBC/ZeroBytePadding", androidOverride);
|
||||
} else {
|
||||
cipher = CipherFactory.getInstance("Twofish/CBC/NoPadding", androidOverride);
|
||||
}
|
||||
|
||||
cipher.init(opmode, new SecretKeySpec(key, "AES"), new IvParameterSpec(IV));
|
||||
|
||||
return cipher;
|
||||
public static CipherEngine getInstance(UUID uuid) throws NoSuchAlgorithmException {
|
||||
if ( uuid.equals(AesEngine.CIPHER_UUID) ) {
|
||||
return new AesEngine();
|
||||
} else if ( uuid.equals(TwofishEngine.CIPHER_UUID) ) {
|
||||
return new TwofishEngine();
|
||||
}
|
||||
|
||||
throw new NoSuchAlgorithmException("UUID unrecognized.");
|
||||
|
||||
109
app/src/main/java/com/keepassdroid/crypto/CryptoUtil.java
Normal file
109
app/src/main/java/com/keepassdroid/crypto/CryptoUtil.java
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2017 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.crypto;
|
||||
|
||||
import com.keepassdroid.stream.LEDataOutputStream;
|
||||
import com.keepassdroid.stream.NullOutputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.DigestOutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
|
||||
public class CryptoUtil {
|
||||
public static byte[] resizeKey(byte[] in, int inOffset, int cbIn, int cbOut) {
|
||||
if (cbOut == 0) return new byte[0];
|
||||
|
||||
byte[] hash;
|
||||
if (cbOut <= 32) { hash = hashSha256(in, inOffset, cbIn); }
|
||||
else { hash = hashSha512(in, inOffset, cbIn); }
|
||||
|
||||
if (cbOut == hash.length) { return hash; }
|
||||
|
||||
byte[] ret = new byte[cbOut];
|
||||
if (cbOut < hash.length) {
|
||||
System.arraycopy(hash, 0, ret, 0, cbOut);
|
||||
}
|
||||
else {
|
||||
int pos = 0;
|
||||
long r = 0;
|
||||
while (pos < cbOut) {
|
||||
Mac hmac;
|
||||
try {
|
||||
hmac = Mac.getInstance("HmacSHA256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
byte[] pbR = LEDataOutputStream.writeLongBuf(r);
|
||||
byte[] part = hmac.doFinal(pbR);
|
||||
|
||||
int copy = Math.min(cbOut - pos, part.length);
|
||||
assert(copy > 0);
|
||||
|
||||
System.arraycopy(part, 0, ret, pos, copy);
|
||||
pos += copy;
|
||||
r++;
|
||||
|
||||
Arrays.fill(part, (byte)0);
|
||||
}
|
||||
assert(pos == cbOut);
|
||||
}
|
||||
|
||||
Arrays.fill(hash, (byte)0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static byte[] hashSha256(byte[] data) {
|
||||
return hashSha256(data, 0, data.length);
|
||||
}
|
||||
|
||||
public static byte[] hashSha256(byte[] data, int offset, int count) {
|
||||
return hashGen("SHA-256", data, offset, count);
|
||||
}
|
||||
|
||||
public static byte[] hashSha512(byte[] data, int offset, int count) {
|
||||
return hashGen("SHA-512", data, offset, count);
|
||||
}
|
||||
|
||||
public static byte[] hashGen(String transform, byte[] data, int offset, int count) {
|
||||
MessageDigest hash;
|
||||
try {
|
||||
hash = MessageDigest.getInstance(transform);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
NullOutputStream nos = new NullOutputStream();
|
||||
DigestOutputStream dos = new DigestOutputStream(nos, hash);
|
||||
|
||||
try {
|
||||
dos.write(data, offset, count);
|
||||
dos.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return hash.digest();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2017 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.crypto.engine;
|
||||
|
||||
|
||||
import com.keepassdroid.crypto.CipherFactory;
|
||||
import com.keepassdroid.utils.Types;
|
||||
|
||||
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;
|
||||
|
||||
public class AesEngine extends CipherEngine {
|
||||
public static final UUID CIPHER_UUID = Types.bytestoUUID(
|
||||
new byte[]{(byte) 0x31, (byte) 0xC1, (byte) 0xF2, (byte) 0xE6, (byte) 0xBF, (byte) 0x71, (byte) 0x43, (byte) 0x50,
|
||||
(byte) 0xBE, (byte) 0x58, (byte) 0x05, (byte) 0x21, (byte) 0x6A, (byte) 0xFC, 0x5A, (byte) 0xFF
|
||||
});
|
||||
|
||||
@Override
|
||||
public Cipher getCipher(int opmode, byte[] key, byte[] IV, boolean androidOverride) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
||||
Cipher cipher = CipherFactory.getInstance("AES/CBC/PKCS5Padding", androidOverride);
|
||||
|
||||
cipher.init(opmode, new SecretKeySpec(key, "AES"), new IvParameterSpec(IV));
|
||||
|
||||
return cipher;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2017 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.crypto.engine;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class ChaCha20Engine extends CipherEngine {
|
||||
@Override
|
||||
public int ivLength() {
|
||||
return 12;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cipher getCipher(int opmode, byte[] key, byte[] IV, boolean androidOverride) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
||||
Cipher cipher = Cipher.getInstance("Chacha");
|
||||
cipher.init(opmode, new SecretKeySpec(key, "ChaCha"), new IvParameterSpec(IV));
|
||||
return cipher;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,44 @@
|
||||
/*
|
||||
* Copyright 2017 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.crypto.engine;
|
||||
|
||||
/**
|
||||
* Created by bpellin on 1/29/17.
|
||||
*/
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
|
||||
public abstract class CipherEngine {
|
||||
public int keyLength() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
public int ivLength() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
public abstract Cipher getCipher(int opmode, byte[] key, byte[] IV, boolean androidOverride) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException;
|
||||
|
||||
public Cipher getCipher(int opmode, byte[] key, byte[] IV) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
||||
return getCipher(opmode, key, IV, false);
|
||||
}
|
||||
|
||||
public class CipherEngine {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2017 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.crypto.engine;
|
||||
|
||||
import com.keepassdroid.crypto.CipherFactory;
|
||||
import com.keepassdroid.utils.Types;
|
||||
|
||||
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;
|
||||
|
||||
public class TwofishEngine extends CipherEngine {
|
||||
public static final UUID CIPHER_UUID = Types.bytestoUUID(
|
||||
new byte[]{(byte)0xAD, (byte)0x68, (byte)0xF2, (byte)0x9F, (byte)0x57, (byte)0x6F, (byte)0x4B, (byte)0xB9,
|
||||
(byte)0xA3, (byte)0x6A, (byte)0xD4, (byte)0x7A, (byte)0xF9, (byte)0x65, (byte)0x34, (byte)0x6C
|
||||
});
|
||||
@Override
|
||||
public Cipher getCipher(int opmode, byte[] key, byte[] IV, boolean androidOverride) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
||||
Cipher cipher;
|
||||
if (opmode == Cipher.ENCRYPT_MODE) {
|
||||
cipher = CipherFactory.getInstance("Twofish/CBC/ZeroBytePadding", androidOverride);
|
||||
} else {
|
||||
cipher = CipherFactory.getInstance("Twofish/CBC/NoPadding", androidOverride);
|
||||
}
|
||||
|
||||
cipher.init(opmode, new SecretKeySpec(key, "AES"), new IvParameterSpec(IV));
|
||||
|
||||
return cipher;
|
||||
}
|
||||
}
|
||||
@@ -92,11 +92,12 @@ public abstract class PwDatabase {
|
||||
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
|
||||
protected static byte[] transformMasterKey( byte[] pKeySeed, byte[] pKey, int rounds ) throws IOException
|
||||
{
|
||||
FinalKey key = FinalKeyFactory.createFinalKey();
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -34,6 +35,7 @@ import java.util.UUID;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
import org.spongycastle.crypto.engines.AESEngine;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
@@ -44,6 +46,10 @@ import android.webkit.URLUtil;
|
||||
import biz.source_code.base64Coder.Base64Coder;
|
||||
|
||||
import com.keepassdroid.crypto.CipherFactory;
|
||||
import com.keepassdroid.crypto.CryptoUtil;
|
||||
import com.keepassdroid.crypto.PwStreamCipherFactory;
|
||||
import com.keepassdroid.crypto.engine.AesEngine;
|
||||
import com.keepassdroid.crypto.engine.CipherEngine;
|
||||
import com.keepassdroid.database.exception.InvalidKeyFileException;
|
||||
import com.keepassdroid.utils.EmptyUtils;
|
||||
|
||||
@@ -55,8 +61,10 @@ public class PwDatabaseV4 extends PwDatabase {
|
||||
private static final int DEFAULT_HISTORY_MAX_ITEMS = 10; // -1 unlimited
|
||||
private static final long DEFAULT_HISTORY_MAX_SIZE = 6 * 1024 * 1024; // -1 unlimited
|
||||
private static final String RECYCLEBIN_NAME = "RecycleBin";
|
||||
|
||||
public UUID dataCipher = CipherFactory.AES_CIPHER;
|
||||
|
||||
public byte[] hmacKey;
|
||||
public UUID dataCipher = AesEngine.CIPHER_UUID;
|
||||
public CipherEngine dataEngine = new AesEngine();
|
||||
public PwCompressionAlgorithm compressionAlgorithm = PwCompressionAlgorithm.Gzip;
|
||||
public long numKeyEncRounds = 6000;
|
||||
public Date nameChanged = DEFAULT_NOW;
|
||||
@@ -134,6 +142,29 @@ public class PwDatabaseV4 extends PwDatabase {
|
||||
return md.digest(fKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void makeFinalKey(byte[] masterSeed, byte[] masterSeed2, int numRounds) throws IOException {
|
||||
|
||||
byte[] transformedMasterKey = transformMasterKey(masterSeed2, masterKey, numRounds);
|
||||
|
||||
|
||||
byte[] cmpKey = new byte[65];
|
||||
System.arraycopy(masterSeed, 0, cmpKey, 0, 32);
|
||||
System.arraycopy(transformedMasterKey, 0, cmpKey, 32, 32);
|
||||
finalKey = CryptoUtil.resizeKey(cmpKey, 0, 64, dataEngine.keyLength());
|
||||
|
||||
MessageDigest md;
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-512");
|
||||
cmpKey[64] = 1;
|
||||
hmacKey = md.digest(cmpKey);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IOException("No SHA-512 implementation");
|
||||
} finally {
|
||||
Arrays.fill(cmpKey, (byte)0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPasswordEncoding() {
|
||||
return "UTF-8";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2012 Brian Pellin.
|
||||
* Copyright 2010-2017 Brian Pellin.
|
||||
*
|
||||
* This file is part of KeePassDroid.
|
||||
*
|
||||
@@ -19,23 +19,32 @@
|
||||
*/
|
||||
package com.keepassdroid.database;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.DigestInputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import com.keepassdroid.database.exception.InvalidAlgorithmException;
|
||||
import com.keepassdroid.database.exception.InvalidDBException;
|
||||
import com.keepassdroid.database.exception.InvalidDBVersionException;
|
||||
import com.keepassdroid.stream.CopyInputStream;
|
||||
import com.keepassdroid.stream.HmacBlockStream;
|
||||
import com.keepassdroid.stream.LEDataInputStream;
|
||||
import com.keepassdroid.utils.Types;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
|
||||
public class PwDbHeaderV4 extends PwDbHeader {
|
||||
public static final int DBSIG_PRE2 = 0xB54BFB66;
|
||||
public static final int DBSIG_2 = 0xB54BFB67;
|
||||
|
||||
private static final int FILE_VERSION_CRITICAL_MASK = 0xFFFF0000;
|
||||
public static final int FILE_VERSION_32 = 0x00030001;
|
||||
|
||||
public static final int FILE_VERSION_32_3 = 0x00030001;
|
||||
public static final int FILE_VERSION_32_4 = 0x00040001;
|
||||
public static final int FILE_VERSION_32 = FILE_VERSION_32_4;
|
||||
|
||||
public class PwDbHeaderV4Fields {
|
||||
public static final byte EndOfHeader = 0;
|
||||
public static final byte Comment = 1;
|
||||
@@ -50,11 +59,22 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
public static final byte InnerRandomStreamID = 10;
|
||||
|
||||
}
|
||||
|
||||
public class HeaderAndHash {
|
||||
public byte[] header;
|
||||
public byte[] hash;
|
||||
|
||||
public HeaderAndHash (byte[] header, byte[] hash) {
|
||||
this.header = header;
|
||||
this.hash = hash;
|
||||
}
|
||||
}
|
||||
|
||||
private PwDatabaseV4 db;
|
||||
public byte[] protectedStreamKey = new byte[32];
|
||||
public byte[] streamStartBytes = new byte[32];
|
||||
public CrsAlgorithm innerRandomStream;
|
||||
public long version;
|
||||
|
||||
public PwDbHeaderV4(PwDatabaseV4 d) {
|
||||
db = d;
|
||||
@@ -67,7 +87,7 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
* @throws IOException
|
||||
* @throws InvalidDBVersionException
|
||||
*/
|
||||
public byte[] loadFromFile(InputStream is) throws IOException, InvalidDBVersionException {
|
||||
public HeaderAndHash loadFromFile(InputStream is) throws IOException, InvalidDBVersionException {
|
||||
MessageDigest md;
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256");
|
||||
@@ -75,7 +95,9 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
throw new IOException("No SHA-256 implementation");
|
||||
}
|
||||
|
||||
DigestInputStream dis = new DigestInputStream(is, md);
|
||||
ByteArrayOutputStream headerBOS = new ByteArrayOutputStream();
|
||||
CopyInputStream cis = new CopyInputStream(is, headerBOS);
|
||||
DigestInputStream dis = new DigestInputStream(cis, md);
|
||||
LEDataInputStream lis = new LEDataInputStream(dis);
|
||||
|
||||
int sig1 = lis.readInt();
|
||||
@@ -85,7 +107,7 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
throw new InvalidDBVersionException();
|
||||
}
|
||||
|
||||
long version = lis.readUInt();
|
||||
version = lis.readUInt();
|
||||
if ( ! validVersion(version) ) {
|
||||
throw new InvalidDBVersionException();
|
||||
}
|
||||
@@ -94,8 +116,9 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
while ( ! done ) {
|
||||
done = readHeaderField(lis);
|
||||
}
|
||||
|
||||
return md.digest();
|
||||
|
||||
byte[] hash = md.digest();
|
||||
return new HeaderAndHash(headerBOS.toByteArray(), hash);
|
||||
}
|
||||
|
||||
private boolean readHeaderField(LEDataInputStream dis) throws IOException {
|
||||
@@ -221,12 +244,26 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
||||
*/
|
||||
private boolean validVersion(long version) {
|
||||
|
||||
return ! ((version & FILE_VERSION_CRITICAL_MASK) > (FILE_VERSION_32 & FILE_VERSION_CRITICAL_MASK));
|
||||
return ! ((version & FILE_VERSION_CRITICAL_MASK) > (FILE_VERSION_32_3 & FILE_VERSION_CRITICAL_MASK));
|
||||
|
||||
}
|
||||
|
||||
public static boolean matchesHeader(int sig1, int sig2) {
|
||||
return (sig1 == PWM_DBSIG_1) && ( (sig2 == DBSIG_2) || (sig2 == DBSIG_2) );
|
||||
}
|
||||
|
||||
public static byte[] computeHeaderHmac(byte[] header, byte[] key) throws IOException{
|
||||
byte[] headerHmac;
|
||||
byte[] blockKey = HmacBlockStream.GetHmacKey64(key, Types.ULONG_MAX_VALUE);
|
||||
|
||||
Mac hmac;
|
||||
try {
|
||||
hmac = Mac.getInstance("HmacSHA256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IOException("No HmacAlogirthm");
|
||||
}
|
||||
|
||||
return hmac.doFinal(header);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import biz.source_code.base64Coder.Base64Coder;
|
||||
import com.keepassdroid.UpdateStatus;
|
||||
import com.keepassdroid.crypto.CipherFactory;
|
||||
import com.keepassdroid.crypto.PwStreamCipherFactory;
|
||||
import com.keepassdroid.crypto.engine.CipherEngine;
|
||||
import com.keepassdroid.database.BinaryPool;
|
||||
import com.keepassdroid.database.ITimeLogger;
|
||||
import com.keepassdroid.database.PwCompressionAlgorithm;
|
||||
@@ -64,6 +65,7 @@ import com.keepassdroid.database.security.ProtectedBinary;
|
||||
import com.keepassdroid.database.security.ProtectedString;
|
||||
import com.keepassdroid.stream.BetterCipherInputStream;
|
||||
import com.keepassdroid.stream.HashedBlockInputStream;
|
||||
import com.keepassdroid.stream.HmacBlockInputStream;
|
||||
import com.keepassdroid.stream.LEDataInputStream;
|
||||
import com.keepassdroid.utils.EmptyUtils;
|
||||
import com.keepassdroid.utils.MemUtil;
|
||||
@@ -75,7 +77,8 @@ public class ImporterV4 extends Importer {
|
||||
private PwDatabaseV4 db;
|
||||
private BinaryPool binPool = new BinaryPool();
|
||||
|
||||
private byte[] hashOfHeader = null;
|
||||
private byte[] hashOfHeader = null;
|
||||
private byte[] pbHeader = null;
|
||||
|
||||
protected PwDatabaseV4 createDB() {
|
||||
return new PwDatabaseV4();
|
||||
@@ -97,49 +100,82 @@ public class ImporterV4 extends Importer {
|
||||
db = createDB();
|
||||
|
||||
PwDbHeaderV4 header = new PwDbHeaderV4(db);
|
||||
|
||||
hashOfHeader = header.loadFromFile(inStream);
|
||||
|
||||
PwDbHeaderV4.HeaderAndHash hh = header.loadFromFile(inStream);
|
||||
|
||||
hashOfHeader = hh.hash;
|
||||
pbHeader = hh.header;
|
||||
|
||||
db.setMasterKey(password, keyInputStream);
|
||||
db.makeFinalKey(header.masterSeed, header.transformSeed, (int)db.numKeyEncRounds);
|
||||
|
||||
// Attach decryptor
|
||||
Cipher cipher;
|
||||
try {
|
||||
cipher = CipherFactory.getInstance(db.dataCipher, Cipher.DECRYPT_MODE, 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 BetterCipherInputStream(inStream, cipher, 50 * 1024);
|
||||
LEDataInputStream dataDecrypted = new LEDataInputStream(decrypted);
|
||||
byte[] storedStartBytes = null;
|
||||
try {
|
||||
storedStartBytes = dataDecrypted.readBytes(32);
|
||||
if ( storedStartBytes == null || storedStartBytes.length != 32 ) {
|
||||
|
||||
InputStream isPlain;
|
||||
if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) {
|
||||
|
||||
// Attach decryptor
|
||||
CipherEngine engine;
|
||||
Cipher cipher;
|
||||
try {
|
||||
engine = CipherFactory.getInstance(db.dataCipher);
|
||||
db.dataEngine = engine;
|
||||
cipher = engine.getCipher(Cipher.DECRYPT_MODE, 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 BetterCipherInputStream(inStream, cipher, 50 * 1024);
|
||||
LEDataInputStream dataDecrypted = new LEDataInputStream(decrypted);
|
||||
byte[] storedStartBytes = null;
|
||||
try {
|
||||
storedStartBytes = dataDecrypted.readBytes(32);
|
||||
if (storedStartBytes == null || storedStartBytes.length != 32) {
|
||||
throw new InvalidPasswordException();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new InvalidPasswordException();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new InvalidPasswordException();
|
||||
|
||||
if (!Arrays.equals(storedStartBytes, header.streamStartBytes)) {
|
||||
throw new InvalidPasswordException();
|
||||
}
|
||||
|
||||
isPlain = new HashedBlockInputStream(dataDecrypted);
|
||||
}
|
||||
|
||||
if ( ! Arrays.equals(storedStartBytes, header.streamStartBytes) ) {
|
||||
throw new InvalidPasswordException();
|
||||
else { // KDBX 4
|
||||
LEDataInputStream isData = new LEDataInputStream(inStream);
|
||||
byte[] storedHash = isData.readBytes(32);
|
||||
if (!Arrays.equals(storedHash,hashOfHeader)) {
|
||||
throw new InvalidDBException();
|
||||
}
|
||||
|
||||
byte[] hmacKey = db.hmacKey;
|
||||
byte[] headerHmac = PwDbHeaderV4.computeHeaderHmac(pbHeader, hmacKey);
|
||||
byte[] storedHmac = isData.readBytes(32);
|
||||
if (storedHmac == null || storedHmac.length != 32) {
|
||||
throw new InvalidDBException();
|
||||
}
|
||||
// Mac doesn't match
|
||||
if (! Arrays.equals(headerHmac, storedHmac)) {
|
||||
throw new InvalidDBException();
|
||||
}
|
||||
|
||||
HmacBlockInputStream hmIs = new HmacBlockInputStream(isData, true, hmacKey);
|
||||
|
||||
isPlain = null;
|
||||
|
||||
}
|
||||
|
||||
HashedBlockInputStream hashed = new HashedBlockInputStream(dataDecrypted);
|
||||
|
||||
InputStream decompressed;
|
||||
InputStream isXml;
|
||||
if ( db.compressionAlgorithm == PwCompressionAlgorithm.Gzip ) {
|
||||
decompressed = new GZIPInputStream(hashed);
|
||||
isXml = new GZIPInputStream(isPlain);
|
||||
} else {
|
||||
decompressed = hashed;
|
||||
isXml = isPlain;
|
||||
}
|
||||
|
||||
if ( header.protectedStreamKey == null ) {
|
||||
@@ -153,7 +189,7 @@ public class ImporterV4 extends Importer {
|
||||
throw new ArcFourException();
|
||||
}
|
||||
|
||||
ReadXmlStreamed(decompressed);
|
||||
ReadXmlStreamed(isXml);
|
||||
|
||||
return db;
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ public class PwDbHeaderOutputV4 extends PwDbHeaderOutput {
|
||||
public void output() throws IOException {
|
||||
los.writeUInt(PwDbHeader.PWM_DBSIG_1);
|
||||
los.writeUInt(PwDbHeaderV4.DBSIG_2);
|
||||
los.writeUInt(PwDbHeaderV4.FILE_VERSION_32);
|
||||
los.writeUInt(PwDbHeaderV4.FILE_VERSION_32_3);
|
||||
|
||||
writeHeaderField(PwDbHeaderV4Fields.CipherID, Types.UUIDtoBytes(db.dataCipher));
|
||||
writeHeaderField(PwDbHeaderV4Fields.CompressionFlags, LEDataOutputStream.writeIntBuf(db.compressionAlgorithm.id));
|
||||
|
||||
@@ -43,6 +43,7 @@ import biz.source_code.base64Coder.Base64Coder;
|
||||
|
||||
import com.keepassdroid.crypto.CipherFactory;
|
||||
import com.keepassdroid.crypto.PwStreamCipherFactory;
|
||||
import com.keepassdroid.crypto.engine.CipherEngine;
|
||||
import com.keepassdroid.database.BinaryPool;
|
||||
import com.keepassdroid.database.CrsAlgorithm;
|
||||
import com.keepassdroid.database.EntryHandler;
|
||||
@@ -245,9 +246,12 @@ public class PwDbV4Output extends PwDbOutput {
|
||||
|
||||
private CipherOutputStream attachStreamEncryptor(PwDbHeaderV4 header, OutputStream os) throws PwDbOutputException {
|
||||
Cipher cipher;
|
||||
CipherEngine engine;
|
||||
try {
|
||||
mPM.makeFinalKey(header.masterSeed, header.transformSeed, (int)mPM.numKeyEncRounds);
|
||||
cipher = CipherFactory.getInstance(mPM.dataCipher, Cipher.ENCRYPT_MODE, mPM.finalKey, header.encryptionIV);
|
||||
|
||||
engine = CipherFactory.getInstance(mPM.dataCipher);
|
||||
cipher = engine.getCipher(Cipher.ENCRYPT_MODE, mPM.finalKey, header.encryptionIV);
|
||||
} catch (Exception e) {
|
||||
throw new PwDbOutputException("Invalid algorithm.", e);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2017 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 com.keepassdroid.utils.Types;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
|
||||
public class HmacBlockInputStream extends InputStream {
|
||||
private LEDataInputStream baseStream;
|
||||
private boolean verify;
|
||||
private byte[] key;
|
||||
private byte[] buffer;
|
||||
private int bufferPos = 0;
|
||||
private long blockIndex = 0;
|
||||
private boolean endOfStream = false;
|
||||
|
||||
public HmacBlockInputStream(InputStream baseStream, boolean verify, byte[] key) {
|
||||
super();
|
||||
|
||||
this.baseStream = new LEDataInputStream(baseStream);
|
||||
this.verify = verify;
|
||||
this.key = key;
|
||||
buffer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (endOfStream) return -1;
|
||||
|
||||
if (bufferPos == buffer.length) {
|
||||
if (!readSafeBlock()) return -1;
|
||||
}
|
||||
|
||||
int output = Types.readUByte(buffer, bufferPos);
|
||||
bufferPos++;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] outBuffer, int byteOffset, int byteCount) throws IOException {
|
||||
int remaining = byteCount;
|
||||
while (remaining > 0) {
|
||||
if (bufferPos == buffer.length) {
|
||||
if (!readSafeBlock()) {
|
||||
return byteCount - remaining;
|
||||
}
|
||||
}
|
||||
|
||||
int copy = Math.min(buffer.length - bufferPos, remaining);
|
||||
assert(copy > 0);
|
||||
|
||||
System.arraycopy(buffer, bufferPos, outBuffer, byteOffset, copy);
|
||||
byteOffset += copy;
|
||||
bufferPos += copy;
|
||||
|
||||
remaining -= copy;
|
||||
}
|
||||
|
||||
return byteCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] outBuffer) throws IOException {
|
||||
return read(outBuffer, 0, outBuffer.length);
|
||||
}
|
||||
|
||||
private boolean readSafeBlock() throws IOException {
|
||||
if (endOfStream) return false;
|
||||
|
||||
byte[] storedHmac = baseStream.readBytes(32);
|
||||
if (storedHmac == null || storedHmac.length != 32) {
|
||||
throw new IOException("File corrupted");
|
||||
}
|
||||
|
||||
byte[] pbBlockIndex = LEDataOutputStream.writeLongBuf(blockIndex);
|
||||
byte[] pbBlockSize = baseStream.readBytes(4);
|
||||
if (pbBlockSize == null || pbBlockSize.length != 4) {
|
||||
throw new IOException("File corrupted");
|
||||
}
|
||||
int blockSize = LEDataInputStream.readInt(pbBlockSize, 0);
|
||||
bufferPos = 0;
|
||||
|
||||
buffer = baseStream.readBytes(blockSize);
|
||||
|
||||
if (verify) {
|
||||
byte[] cmpHmac;
|
||||
byte[] pbBlockKey = HmacBlockStream.GetHmacKey64(key, blockIndex);
|
||||
Mac hmac;
|
||||
try {
|
||||
hmac = Mac.getInstance("HmacSHA256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IOException("Invalid Hmac");
|
||||
}
|
||||
|
||||
hmac.update(pbBlockIndex);
|
||||
hmac.update(pbBlockSize);
|
||||
|
||||
if (buffer.length > 0) {
|
||||
hmac.update(buffer);
|
||||
}
|
||||
|
||||
cmpHmac = hmac.doFinal();
|
||||
Arrays.fill(pbBlockKey, (byte)0);
|
||||
|
||||
if (!Arrays.equals(cmpHmac, storedHmac)) {
|
||||
throw new IOException("Invalid Hmac");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
blockIndex++;
|
||||
|
||||
if (blockSize == 0) {
|
||||
endOfStream = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
baseStream.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long byteCount) throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return buffer.length - bufferPos;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2009-2011 Brian Pellin.
|
||||
* Copyright 2009-2017 Brian Pellin.
|
||||
*
|
||||
* This file is part of KeePassDroid.
|
||||
*
|
||||
@@ -56,7 +56,9 @@ import com.keepassdroid.stream.LEDataOutputStream;
|
||||
* @author Bill Zwicky <wrzwicky@pobox.com>
|
||||
*/
|
||||
public class Types {
|
||||
|
||||
|
||||
public static long ULONG_MAX_VALUE = -1;
|
||||
|
||||
/** Read an unsigned byte */
|
||||
public static int readUByte( byte[] buf, int offset ) {
|
||||
return ((int)buf[offset] & 0xFF);
|
||||
|
||||
Reference in New Issue
Block a user