diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java index 59ff7b74b8..52cf540e67 100644 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java +++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java @@ -41,6 +41,7 @@ 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 @@ private void storeMetadata(CryptorIOSupport ioSupport, String metadataFile, Long @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 Long encryptFile(InputStream plaintextFile, SeekableByteChannel encrypted 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); diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/MacInputStream.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/MacInputStream.java index b59ae49c7f..464fc4eac5 100644 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/MacInputStream.java +++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/MacInputStream.java @@ -32,7 +32,7 @@ public int read() throws IOException { @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; } diff --git a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java b/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java index 51e675358f..b0e2ed354a 100644 --- a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java +++ b/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java @@ -25,7 +25,6 @@ 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 void testWrongPassword() throws IOException, DecryptFailedException, Wron } } - @Ignore @Test public void testIntegrityAuthentication() throws IOException { // our test plaintext data: @@ -93,9 +91,22 @@ public void testIntegrityAuthentication() throws IOException { 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