Browse Source

- added file integrity check (#17) - not yet visible to the user

Sebastian Stenzel 10 years ago
parent
commit
2e67910a60

+ 30 - 6
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java

@@ -41,6 +41,7 @@ import javax.security.auth.Destroyable;
 
 import org.apache.commons.io.Charsets;
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.output.NullOutputStream;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.bouncycastle.crypto.generators.SCrypt;
@@ -403,7 +404,30 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 
 	@Override
 	public boolean authenticateContent(SeekableByteChannel encryptedFile) throws IOException {
-		throw new UnsupportedOperationException("Not yet implemented.");
+		// read file size:
+		final Long fileSize = decryptedContentLength(encryptedFile);
+
+		// init mac:
+		final Mac mac = this.hmacSha256(hMacMasterKey);
+
+		// read stored mac:
+		encryptedFile.position(16);
+		final ByteBuffer macBuffer = ByteBuffer.allocate(mac.getMacLength());
+		final int numMacBytesRead = encryptedFile.read(macBuffer);
+
+		// check validity of header:
+		if (numMacBytesRead != mac.getMacLength() || fileSize == null) {
+			throw new IOException("Failed to read file header.");
+		}
+
+		// read all encrypted data and calculate mac:
+		encryptedFile.position(64);
+		final InputStream in = new SeekableByteChannelInputStream(encryptedFile);
+		final InputStream macIn = new MacInputStream(in, mac);
+		IOUtils.copyLarge(macIn, new NullOutputStream(), 0, fileSize);
+
+		// compare:
+		return Arrays.equals(macBuffer.array(), mac.doFinal());
 	}
 
 	@Override
@@ -517,18 +541,18 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 		final OutputStream cipheredOut = new CipherOutputStream(macOut, cipher);
 		final Long actualSize = IOUtils.copyLarge(plaintextFile, cipheredOut);
 
+		// copy MAC:
+		macBuffer.position(0);
+		macBuffer.put(mac.doFinal());
+
 		// append fake content:
-		final int randomContentLength = (int) Math.ceil(Math.random() * actualSize / 10.0);
+		final int randomContentLength = (int) Math.ceil((Math.random() + 1.0) * actualSize / 20.0);
 		final byte[] emptyBytes = new byte[AES_BLOCK_LENGTH];
 		for (int i = 0; i < randomContentLength; i += AES_BLOCK_LENGTH) {
 			cipheredOut.write(emptyBytes);
 		}
 		cipheredOut.flush();
 
-		// copy MAC:
-		macBuffer.position(0);
-		macBuffer.put(mac.doFinal());
-
 		// encrypt actualSize
 		try {
 			final ByteBuffer fileSizeBuffer = ByteBuffer.allocate(Long.BYTES);

+ 1 - 1
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/MacInputStream.java

@@ -32,7 +32,7 @@ class MacInputStream extends FilterInputStream {
 	@Override
 	public int read(byte[] b, int off, int len) throws IOException {
 		int read = in.read(b, off, len);
-		mac.update(b);
+		mac.update(b, off, len);
 		return read;
 	}
 

+ 16 - 5
main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java

@@ -25,7 +25,6 @@ import org.cryptomator.crypto.exceptions.DecryptFailedException;
 import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
 import org.cryptomator.crypto.exceptions.WrongPasswordException;
 import org.junit.Assert;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class Aes256CryptorTest {
@@ -73,7 +72,6 @@ public class Aes256CryptorTest {
 		}
 	}
 
-	@Ignore
 	@Test
 	public void testIntegrityAuthentication() throws IOException {
 		// our test plaintext data:
@@ -93,9 +91,22 @@ public class Aes256CryptorTest {
 		encryptedData.position(0);
 
 		// authenticate unmodified content:
-		final SeekableByteChannel encryptedIn = new ByteBufferBackedSeekableChannel(encryptedData);
-		final boolean unmodifiedContent = cryptor.authenticateContent(encryptedIn);
-		Assert.assertTrue(unmodifiedContent);
+		final SeekableByteChannel encryptedIn1 = new ByteBufferBackedSeekableChannel(encryptedData);
+		final boolean isContentUnmodified1 = cryptor.authenticateContent(encryptedIn1);
+		Assert.assertTrue(isContentUnmodified1);
+
+		// toggle one bit inf first content byte:
+		encryptedData.position(64);
+		final byte fifthByte = encryptedData.get();
+		encryptedData.position(64);
+		encryptedData.put((byte) (fifthByte ^ 0x01));
+
+		encryptedData.position(0);
+
+		// authenticate modified content:
+		final SeekableByteChannel encryptedIn2 = new ByteBufferBackedSeekableChannel(encryptedData);
+		final boolean isContentUnmodified2 = cryptor.authenticateContent(encryptedIn2);
+		Assert.assertFalse(isContentUnmodified2);
 	}
 
 	@Test