Browse Source

- always check HMAC before decryption
- separating AES and CMAC key during SIV mode

Sebastian Stenzel 10 năm trước cách đây
mục cha
commit
b68cf71494

+ 3 - 4
main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java

@@ -86,7 +86,7 @@ public final class WebDavServer {
 	 *            _ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
 	 * @return servlet
 	 */
-	public ServletLifeCycleAdapter createServlet(final Path workDir, final boolean checkFileIntegrity, final Cryptor cryptor, String name) {
+	public ServletLifeCycleAdapter createServlet(final Path workDir, final Cryptor cryptor, String name) {
 		try {
 			if (StringUtils.isEmpty(name)) {
 				throw new IllegalArgumentException("name empty");
@@ -97,7 +97,7 @@ public final class WebDavServer {
 			final URI uri = new URI(null, null, localConnector.getHost(), localConnector.getLocalPort(), "/" + UUID.randomUUID().toString() + "/" + name, null, null);
 
 			final ServletContextHandler servletContext = new ServletContextHandler(servletCollection, uri.getRawPath(), ServletContextHandler.SESSIONS);
-			final ServletHolder servlet = getWebDavServletHolder(workDir.toString(), checkFileIntegrity, cryptor);
+			final ServletHolder servlet = getWebDavServletHolder(workDir.toString(), cryptor);
 			servletContext.addServlet(servlet, "/*");
 
 			servletCollection.mapContexts();
@@ -109,10 +109,9 @@ public final class WebDavServer {
 		}
 	}
 
-	private ServletHolder getWebDavServletHolder(final String workDir, final boolean checkFileIntegrity, final Cryptor cryptor) {
+	private ServletHolder getWebDavServletHolder(final String workDir, final Cryptor cryptor) {
 		final ServletHolder result = new ServletHolder("Cryptomator-WebDAV-Servlet", new WebDavServlet(cryptor));
 		result.setInitParameter(WebDavServlet.CFG_FS_ROOT, workDir);
-		result.setInitParameter(WebDavServlet.CFG_CHECK_FILE_INTEGRITY, Boolean.toString(checkFileIntegrity));
 		return result;
 	}
 

+ 3 - 5
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavResourceFactoryImpl.java

@@ -34,11 +34,9 @@ class DavResourceFactoryImpl implements DavResourceFactory {
 
 	private final LockManager lockManager = new SimpleLockManager();
 	private final Cryptor cryptor;
-	private final boolean checkFileIntegrity;
 
-	DavResourceFactoryImpl(Cryptor cryptor, boolean checkFileIntegrity) {
+	DavResourceFactoryImpl(Cryptor cryptor) {
 		this.cryptor = cryptor;
-		this.checkFileIntegrity = checkFileIntegrity;
 	}
 
 	@Override
@@ -72,11 +70,11 @@ class DavResourceFactoryImpl implements DavResourceFactory {
 	}
 
 	private EncryptedFile createFilePart(DavResourceLocator locator, DavSession session, DavServletRequest request) {
-		return new EncryptedFilePart(this, locator, session, request, lockManager, cryptor, checkFileIntegrity);
+		return new EncryptedFilePart(this, locator, session, request, lockManager, cryptor);
 	}
 
 	private EncryptedFile createFile(DavResourceLocator locator, DavSession session) {
-		return new EncryptedFile(this, locator, session, lockManager, cryptor, checkFileIntegrity);
+		return new EncryptedFile(this, locator, session, lockManager, cryptor);
 	}
 
 	private EncryptedDir createDirectory(DavResourceLocator locator, DavSession session) {

+ 1 - 3
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java

@@ -23,7 +23,6 @@ public class WebDavServlet extends AbstractWebdavServlet {
 
 	private static final long serialVersionUID = 7965170007048673022L;
 	public static final String CFG_FS_ROOT = "cfg.fs.root";
-	public static final String CFG_CHECK_FILE_INTEGRITY = "cfg.checkFileIntegrity";
 	private DavSessionProvider davSessionProvider;
 	private DavLocatorFactory davLocatorFactory;
 	private DavResourceFactory davResourceFactory;
@@ -41,10 +40,9 @@ public class WebDavServlet extends AbstractWebdavServlet {
 		davSessionProvider = new DavSessionProviderImpl();
 
 		final String fsRoot = config.getInitParameter(CFG_FS_ROOT);
-		final boolean checkFileIntegrity = Boolean.parseBoolean(config.getInitParameter(CFG_CHECK_FILE_INTEGRITY));
 		this.davLocatorFactory = new DavLocatorFactoryImpl(fsRoot, cryptor);
 
-		this.davResourceFactory = new DavResourceFactoryImpl(cryptor, checkFileIntegrity);
+		this.davResourceFactory = new DavResourceFactoryImpl(cryptor);
 	}
 
 	@Override

+ 1 - 7
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFile.java

@@ -40,11 +40,8 @@ public class EncryptedFile extends AbstractEncryptedNode {
 
 	private static final Logger LOG = LoggerFactory.getLogger(EncryptedFile.class);
 
-	protected final boolean checkIntegrity;
-
-	public EncryptedFile(DavResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, boolean checkIntegrity) {
+	public EncryptedFile(DavResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) {
 		super(factory, locator, session, lockManager, cryptor);
-		this.checkIntegrity = checkIntegrity;
 	}
 
 	@Override
@@ -76,9 +73,6 @@ public class EncryptedFile extends AbstractEncryptedNode {
 			SeekableByteChannel channel = null;
 			try {
 				channel = Files.newByteChannel(path, StandardOpenOption.READ);
-				if (checkIntegrity && !cryptor.authenticateContent(channel)) {
-					throw new DecryptFailedException("File content compromised: " + path.toString());
-				}
 				outputContext.setContentLength(cryptor.decryptedContentLength(channel));
 				if (outputContext.hasStream()) {
 					cryptor.decryptedFile(channel, outputContext.getOutputStream());

+ 2 - 5
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFilePart.java

@@ -50,8 +50,8 @@ public class EncryptedFilePart extends EncryptedFile {
 
 	private final Set<Pair<Long, Long>> requestedContentRanges = new HashSet<Pair<Long, Long>>();
 
-	public EncryptedFilePart(DavResourceFactory factory, DavResourceLocator locator, DavSession session, DavServletRequest request, LockManager lockManager, Cryptor cryptor, boolean checkIntegrity) {
-		super(factory, locator, session, lockManager, cryptor, checkIntegrity);
+	public EncryptedFilePart(DavResourceFactory factory, DavResourceLocator locator, DavSession session, DavServletRequest request, LockManager lockManager, Cryptor cryptor) {
+		super(factory, locator, session, lockManager, cryptor);
 		final String rangeHeader = request.getHeader(HttpHeader.RANGE.asString());
 		if (rangeHeader == null) {
 			throw new IllegalArgumentException("HTTP request doesn't contain a range header");
@@ -116,9 +116,6 @@ public class EncryptedFilePart extends EncryptedFile {
 			SeekableByteChannel channel = null;
 			try {
 				channel = Files.newByteChannel(path, StandardOpenOption.READ);
-				if (checkIntegrity && !cryptor.authenticateContent(channel)) {
-					throw new DecryptFailedException("File content compromised: " + path.toString());
-				}
 				final Long fileSize = cryptor.decryptedContentLength(channel);
 				final Pair<Long, Long> range = getUnionRange(fileSize);
 				final Long rangeLength = range.getRight() - range.getLeft() + 1;

+ 20 - 11
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java

@@ -293,7 +293,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 			final String[] cleartextPathComps = StringUtils.split(cleartextPath, cleartextPathSep);
 			final List<String> encryptedPathComps = new ArrayList<>(cleartextPathComps.length);
 			for (final String cleartext : cleartextPathComps) {
-				final String encrypted = encryptPathComponent(cleartext, primaryMasterKey, ioSupport);
+				final String encrypted = encryptPathComponent(cleartext, primaryMasterKey, hMacMasterKey, ioSupport);
 				encryptedPathComps.add(encrypted);
 			}
 			return StringUtils.join(encryptedPathComps, encryptedPathSep);
@@ -317,11 +317,11 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 	 * These alternative names consist of the checksum, a unique id and a special file extension defined in
 	 * {@link FileNamingConventions#LONG_NAME_FILE_EXT}.
 	 */
-	private String encryptPathComponent(final String cleartext, final SecretKey key, CryptorIOSupport ioSupport) throws IOException, InvalidKeyException {
+	private String encryptPathComponent(final String cleartext, final SecretKey aesKey, final SecretKey macKey, CryptorIOSupport ioSupport) throws IOException, InvalidKeyException {
 		final byte[] cleartextBytes = cleartext.getBytes(StandardCharsets.UTF_8);
 
 		// encrypt:
-		final byte[] encryptedBytes = AesSivCipherUtil.sivEncrypt(key.getEncoded(), cleartextBytes);
+		final byte[] encryptedBytes = AesSivCipherUtil.sivEncrypt(aesKey, macKey, cleartextBytes);
 		final String ivAndCiphertext = ENCRYPTED_FILENAME_CODEC.encodeAsString(encryptedBytes);
 
 		if (ivAndCiphertext.length() + BASIC_FILE_EXT.length() > ENCRYPTED_FILENAME_LENGTH_LIMIT) {
@@ -342,7 +342,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 			final String[] encryptedPathComps = StringUtils.split(encryptedPath, encryptedPathSep);
 			final List<String> cleartextPathComps = new ArrayList<>(encryptedPathComps.length);
 			for (final String encrypted : encryptedPathComps) {
-				final String cleartext = decryptPathComponent(encrypted, primaryMasterKey, ioSupport);
+				final String cleartext = decryptPathComponent(encrypted, primaryMasterKey, hMacMasterKey, ioSupport);
 				cleartextPathComps.add(new String(cleartext));
 			}
 			return StringUtils.join(cleartextPathComps, cleartextPathSep);
@@ -354,7 +354,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 	/**
 	 * @see #encryptPathComponent(String, SecretKey, CryptorIOSupport)
 	 */
-	private String decryptPathComponent(final String encrypted, final SecretKey key, CryptorIOSupport ioSupport) throws IOException, InvalidKeyException, DecryptFailedException {
+	private String decryptPathComponent(final String encrypted, final SecretKey aesKey, final SecretKey macKey, CryptorIOSupport ioSupport) throws IOException, InvalidKeyException, DecryptFailedException {
 		final String ciphertext;
 		if (encrypted.endsWith(LONG_NAME_FILE_EXT)) {
 			final String basename = StringUtils.removeEnd(encrypted, LONG_NAME_FILE_EXT);
@@ -371,7 +371,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 
 		// decrypt:
 		final byte[] encryptedBytes = ENCRYPTED_FILENAME_CODEC.decode(ciphertext);
-		final byte[] cleartextBytes = AesSivCipherUtil.sivDecrypt(key.getEncoded(), encryptedBytes);
+		final byte[] cleartextBytes = AesSivCipherUtil.sivDecrypt(aesKey, macKey, encryptedBytes);
 
 		return new String(cleartextBytes, StandardCharsets.UTF_8);
 	}
@@ -389,8 +389,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 		ioSupport.writePathSpecificMetadata(metadataFile, objectMapper.writeValueAsBytes(metadata));
 	}
 
-	@Override
-	public boolean authenticateContent(SeekableByteChannel encryptedFile) throws IOException {
+	private void authenticateContent(SeekableByteChannel encryptedFile) throws IOException, DecryptFailedException {
 		// init mac:
 		final Mac calculatedMac = this.hmacSha256(hMacMasterKey);
 
@@ -411,7 +410,11 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 		IOUtils.copyLarge(macIn, new NullOutputStream());
 
 		// compare (in constant time):
-		return MessageDigest.isEqual(storedMac.array(), calculatedMac.doFinal());
+		boolean macMatches = MessageDigest.isEqual(storedMac.array(), calculatedMac.doFinal());
+
+		if (!macMatches) {
+			throw new DecryptFailedException("MAC authentication failed.");
+		}
 	}
 
 	@Override
@@ -440,7 +443,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 	}
 
 	@Override
-	public Long decryptedFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException {
+	public Long decryptedFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException, DecryptFailedException {
 		// read iv:
 		encryptedFile.position(0);
 		final ByteBuffer countingIv = ByteBuffer.allocate(AES_BLOCK_LENGTH);
@@ -454,6 +457,9 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 			throw new IOException("Failed to read file header.");
 		}
 
+		// check MAC:
+		this.authenticateContent(encryptedFile);
+
 		// go to begin of content:
 		encryptedFile.position(64);
 
@@ -467,7 +473,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 	}
 
 	@Override
-	public Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plaintextFile, long pos, long length) throws IOException {
+	public Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plaintextFile, long pos, long length) throws IOException, DecryptFailedException {
 		// read iv:
 		encryptedFile.position(0);
 		final ByteBuffer countingIv = ByteBuffer.allocate(AES_BLOCK_LENGTH);
@@ -478,6 +484,9 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 			throw new IOException("Failed to read file header.");
 		}
 
+		// check MAC:
+		this.authenticateContent(encryptedFile);
+
 		// seek relevant position and update iv:
 		long firstRelevantBlock = pos / AES_BLOCK_LENGTH; // cut of fraction!
 		long beginOfFirstRelevantBlock = firstRelevantBlock * AES_BLOCK_LENGTH;

+ 40 - 16
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesSivCipherUtil.java

@@ -13,6 +13,8 @@ import java.security.InvalidKeyException;
 import java.security.MessageDigest;
 import java.util.Arrays;
 
+import javax.crypto.SecretKey;
+
 import org.apache.commons.lang3.ArrayUtils;
 import org.bouncycastle.crypto.BlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
@@ -31,15 +33,26 @@ final class AesSivCipherUtil {
 	private static final byte[] BYTES_ZERO = new byte[16];
 	private static final byte DOUBLING_CONST = (byte) 0x87;
 
-	static byte[] sivEncrypt(byte[] key, byte[] plaintext, byte[]... additionalData) throws InvalidKeyException {
-		if (key.length != 32 && key.length != 48 && key.length != 64) {
-			throw new InvalidKeyException("Invalid key length " + key.length);
+	static byte[] sivEncrypt(SecretKey aesKey, SecretKey macKey, byte[] plaintext, byte[]... additionalData) throws InvalidKeyException {
+		final byte[] aesKeyBytes = aesKey.getEncoded();
+		final byte[] macKeyBytes = macKey.getEncoded();
+		if (aesKeyBytes == null || macKeyBytes == null) {
+			throw new IllegalArgumentException("Can't get bytes of given key.");
+		}
+		try {
+			return sivEncrypt(aesKeyBytes, macKeyBytes, plaintext, additionalData);
+		} finally {
+			Arrays.fill(aesKeyBytes, (byte) 0);
+			Arrays.fill(macKeyBytes, (byte) 0);
 		}
+	}
 
-		final byte[] k1 = Arrays.copyOf(key, key.length / 2);
-		final byte[] k2 = Arrays.copyOfRange(key, k1.length, key.length);
+	static byte[] sivEncrypt(byte[] aesKey, byte[] macKey, byte[] plaintext, byte[]... additionalData) throws InvalidKeyException {
+		if (aesKey.length != 16 && aesKey.length != 24 && aesKey.length != 32) {
+			throw new InvalidKeyException("Invalid aesKey length " + aesKey.length);
+		}
 
-		final byte[] iv = s2v(k1, plaintext, additionalData);
+		final byte[] iv = s2v(macKey, plaintext, additionalData);
 
 		final int numBlocks = (plaintext.length + 15) / 16;
 
@@ -52,7 +65,7 @@ final class AesSivCipherUtil {
 
 		final byte[] x = new byte[numBlocks * 16];
 		final BlockCipher aes = new AESFastEngine();
-		aes.init(true, new KeyParameter(k2));
+		aes.init(true, new KeyParameter(aesKey));
 		for (int i = 0; i < numBlocks; i++) {
 			final long ctrVal = initialCtrVal + i;
 			ctrBuf.putLong(8, ctrVal);
@@ -65,13 +78,24 @@ final class AesSivCipherUtil {
 		return ArrayUtils.addAll(iv, ciphertext);
 	}
 
-	static byte[] sivDecrypt(byte[] key, byte[] ciphertext, byte[]... additionalData) throws DecryptFailedException, InvalidKeyException {
-		if (key.length != 32 && key.length != 48 && key.length != 64) {
-			throw new InvalidKeyException("Invalid key length " + key.length);
+	static byte[] sivDecrypt(SecretKey aesKey, SecretKey macKey, byte[] plaintext, byte[]... additionalData) throws InvalidKeyException, DecryptFailedException {
+		final byte[] aesKeyBytes = aesKey.getEncoded();
+		final byte[] macKeyBytes = macKey.getEncoded();
+		if (aesKeyBytes == null || macKeyBytes == null) {
+			throw new IllegalArgumentException("Can't get bytes of given key.");
 		}
+		try {
+			return sivDecrypt(aesKeyBytes, macKeyBytes, plaintext, additionalData);
+		} finally {
+			Arrays.fill(aesKeyBytes, (byte) 0);
+			Arrays.fill(macKeyBytes, (byte) 0);
+		}
+	}
 
-		final byte[] k1 = Arrays.copyOf(key, key.length / 2);
-		final byte[] k2 = Arrays.copyOfRange(key, k1.length, key.length);
+	static byte[] sivDecrypt(byte[] aesKey, byte[] macKey, byte[] ciphertext, byte[]... additionalData) throws DecryptFailedException, InvalidKeyException {
+		if (aesKey.length != 16 && aesKey.length != 24 && aesKey.length != 32) {
+			throw new InvalidKeyException("Invalid aesKey length " + aesKey.length);
+		}
 
 		final byte[] iv = Arrays.copyOf(ciphertext, 16);
 
@@ -87,7 +111,7 @@ final class AesSivCipherUtil {
 
 		final byte[] x = new byte[numBlocks * 16];
 		final BlockCipher aes = new AESFastEngine();
-		aes.init(true, new KeyParameter(k2));
+		aes.init(true, new KeyParameter(aesKey));
 		for (int i = 0; i < numBlocks; i++) {
 			final long ctrVal = initialCtrVal + i;
 			ctrBuf.putLong(8, ctrVal);
@@ -97,7 +121,7 @@ final class AesSivCipherUtil {
 
 		final byte[] plaintext = xor(actualCiphertext, x);
 
-		final byte[] control = s2v(k1, plaintext, additionalData);
+		final byte[] control = s2v(macKey, plaintext, additionalData);
 
 		if (MessageDigest.isEqual(control, iv)) {
 			return plaintext;
@@ -106,8 +130,8 @@ final class AesSivCipherUtil {
 		}
 	}
 
-	static byte[] s2v(byte[] key, byte[] plaintext, byte[]... additionalData) {
-		final CipherParameters params = new KeyParameter(key);
+	static byte[] s2v(byte[] macKey, byte[] plaintext, byte[]... additionalData) {
+		final CipherParameters params = new KeyParameter(macKey);
 		final BlockCipher aes = new AESFastEngine();
 		final CMac mac = new CMac(aes);
 		mac.init(params);

+ 6 - 45
main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java

@@ -14,26 +14,12 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.nio.channels.SeekableByteChannel;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.Security;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Random;
 
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
 import org.apache.commons.io.IOUtils;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.cryptomator.crypto.CryptorIOSupport;
 import org.cryptomator.crypto.exceptions.DecryptFailedException;
 import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
@@ -86,8 +72,8 @@ public class Aes256CryptorTest {
 		}
 	}
 
-	@Test
-	public void testIntegrityAuthentication() throws IOException {
+	@Test(expected = DecryptFailedException.class)
+	public void testIntegrityAuthentication() throws IOException, DecryptFailedException {
 		// our test plaintext data:
 		final byte[] plaintextData = "Hello World".getBytes();
 		final InputStream plaintextIn = new ByteArrayInputStream(plaintextData);
@@ -104,11 +90,6 @@ public class Aes256CryptorTest {
 
 		encryptedData.position(0);
 
-		// authenticate unmodified content:
-		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();
@@ -117,30 +98,10 @@ public class Aes256CryptorTest {
 
 		encryptedData.position(0);
 
-		// authenticate modified content:
-		final SeekableByteChannel encryptedIn2 = new ByteBufferBackedSeekableChannel(encryptedData);
-		final boolean isContentUnmodified2 = cryptor.authenticateContent(encryptedIn2);
-		Assert.assertFalse(isContentUnmodified2);
-	}
-
-	@Test
-	public void foo() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException {
-		Security.addProvider(new BouncyCastleProvider());
-
-		final byte[] iv = new byte[16];
-		final byte[] keyBytes = new byte[16];
-		final SecretKey key = new SecretKeySpec(keyBytes, "AES");
-		final Cipher pkcs5PaddedCipher = Cipher.getInstance("AES/CTR/PKCS5Padding", BouncyCastleProvider.PROVIDER_NAME);
-		pkcs5PaddedCipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
-		final Cipher unpaddedCipher = Cipher.getInstance("AES/CTR/NoPadding");
-		unpaddedCipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
-
-		// test data:
-		final byte[] plaintextData = "Hello World".getBytes();
-		final byte[] pkcs5PaddedCiphertext = pkcs5PaddedCipher.doFinal(plaintextData);
-		final byte[] unpaddedCiphertext = unpaddedCipher.doFinal(plaintextData);
-
-		Assert.assertFalse(Arrays.equals(pkcs5PaddedCiphertext, unpaddedCiphertext));
+		// decrypt modified content (should fail with DecryptFailedException):
+		final SeekableByteChannel encryptedIn = new ByteBufferBackedSeekableChannel(encryptedData);
+		final ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream();
+		cryptor.decryptedFile(encryptedIn, plaintextOut);
 	}
 
 	@Test

+ 22 - 19
main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/AesSivCipherUtilTest.java

@@ -14,7 +14,7 @@ public class AesSivCipherUtilTest {
 
 	@Test
 	public void testS2v() throws DecoderException {
-		final byte[] key = {(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, //
+		final byte[] macKey = {(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, //
 				(byte) 0xfb, (byte) 0xfa, (byte) 0xf9, (byte) 0xf8, //
 				(byte) 0xf7, (byte) 0xf6, (byte) 0xf5, (byte) 0xf4, //
 				(byte) 0xf3, (byte) 0xf2, (byte) 0xf1, (byte) 0xf0};
@@ -36,17 +36,18 @@ public class AesSivCipherUtilTest {
 				(byte) 0x95, (byte) 0x0a, (byte) 0xcd, (byte) 0x32, //
 				(byte) 0x0a, (byte) 0x2e, (byte) 0xcc, (byte) 0x93};
 
-		final byte[] result = AesSivCipherUtil.s2v(key, plaintext, ad);
+		final byte[] result = AesSivCipherUtil.s2v(macKey, plaintext, ad);
 		Assert.assertArrayEquals(expected, result);
 	}
 
 	@Test
 	public void testSivEncrypt() throws InvalidKeyException {
-		final byte[] key = {(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, //
+		final byte[] macKey = {(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, //
 				(byte) 0xfb, (byte) 0xfa, (byte) 0xf9, (byte) 0xf8, //
 				(byte) 0xf7, (byte) 0xf6, (byte) 0xf5, (byte) 0xf4, //
-				(byte) 0xf3, (byte) 0xf2, (byte) 0xf1, (byte) 0xf0, //
-				(byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, //
+				(byte) 0xf3, (byte) 0xf2, (byte) 0xf1, (byte) 0xf0};
+
+		final byte[] aesKey = {(byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, //
 				(byte) 0xf4, (byte) 0xf5, (byte) 0xf6, (byte) 0xf7, //
 				(byte) 0xf8, (byte) 0xf9, (byte) 0xfa, (byte) 0xfb, //
 				(byte) 0xfc, (byte) 0xfd, (byte) 0xfe, (byte) 0xff};
@@ -72,17 +73,18 @@ public class AesSivCipherUtilTest {
 				(byte) 0xda, (byte) 0xef, (byte) 0x7f, (byte) 0x6a, //
 				(byte) 0xfe, (byte) 0x5c};
 
-		final byte[] result = AesSivCipherUtil.sivEncrypt(key, plaintext, ad);
+		final byte[] result = AesSivCipherUtil.sivEncrypt(aesKey, macKey, plaintext, ad);
 		Assert.assertArrayEquals(expected, result);
 	}
 
 	@Test
 	public void testSivDecrypt() throws DecryptFailedException, InvalidKeyException {
-		final byte[] key = {(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, //
+		final byte[] macKey = {(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, //
 				(byte) 0xfb, (byte) 0xfa, (byte) 0xf9, (byte) 0xf8, //
 				(byte) 0xf7, (byte) 0xf6, (byte) 0xf5, (byte) 0xf4, //
-				(byte) 0xf3, (byte) 0xf2, (byte) 0xf1, (byte) 0xf0, //
-				(byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, //
+				(byte) 0xf3, (byte) 0xf2, (byte) 0xf1, (byte) 0xf0};
+
+		final byte[] aesKey = {(byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, //
 				(byte) 0xf4, (byte) 0xf5, (byte) 0xf6, (byte) 0xf7, //
 				(byte) 0xf8, (byte) 0xf9, (byte) 0xfa, (byte) 0xfb, //
 				(byte) 0xfc, (byte) 0xfd, (byte) 0xfe, (byte) 0xff};
@@ -108,17 +110,18 @@ public class AesSivCipherUtilTest {
 				(byte) 0x99, (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, //
 				(byte) 0xdd, (byte) 0xee};
 
-		final byte[] result = AesSivCipherUtil.sivDecrypt(key, ciphertext, ad);
+		final byte[] result = AesSivCipherUtil.sivDecrypt(aesKey, macKey, ciphertext, ad);
 		Assert.assertArrayEquals(expected, result);
 	}
 
 	@Test(expected = DecryptFailedException.class)
 	public void testSivDecryptWithInvalidKey() throws DecryptFailedException, InvalidKeyException {
-		final byte[] key = {(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, //
+		final byte[] macKey = {(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc, //
 				(byte) 0xfb, (byte) 0xfa, (byte) 0xf9, (byte) 0xf8, //
 				(byte) 0xf7, (byte) 0xf6, (byte) 0xf5, (byte) 0xf4, //
-				(byte) 0xf3, (byte) 0xf2, (byte) 0xf1, (byte) 0xf0, //
-				(byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, //
+				(byte) 0xf3, (byte) 0xf2, (byte) 0xf1, (byte) 0xf0};
+
+		final byte[] aesKey = {(byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, //
 				(byte) 0xf4, (byte) 0xf5, (byte) 0xf6, (byte) 0xf7, //
 				(byte) 0xf8, (byte) 0xf9, (byte) 0xfa, (byte) 0xfb, //
 				(byte) 0xfc, (byte) 0xfd, (byte) 0xfe, (byte) 0x00};
@@ -144,7 +147,7 @@ public class AesSivCipherUtilTest {
 				(byte) 0x99, (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, //
 				(byte) 0xdd, (byte) 0xee};
 
-		final byte[] result = AesSivCipherUtil.sivDecrypt(key, ciphertext, ad);
+		final byte[] result = AesSivCipherUtil.sivDecrypt(aesKey, macKey, ciphertext, ad);
 		Assert.assertArrayEquals(expected, result);
 	}
 
@@ -153,12 +156,12 @@ public class AesSivCipherUtilTest {
 	 */
 	@Test
 	public void testNonceBasedAuthenticatedEncryption() throws InvalidKeyException {
-
-		final byte[] key = {(byte) 0x7f, (byte) 0x7e, (byte) 0x7d, (byte) 0x7c, //
+		final byte[] macKey = {(byte) 0x7f, (byte) 0x7e, (byte) 0x7d, (byte) 0x7c, //
 				(byte) 0x7b, (byte) 0x7a, (byte) 0x79, (byte) 0x78, //
 				(byte) 0x77, (byte) 0x76, (byte) 0x75, (byte) 0x74, //
-				(byte) 0x73, (byte) 0x72, (byte) 0x71, (byte) 0x70, //
-				(byte) 0x40, (byte) 0x41, (byte) 0x42, (byte) 0x43, //
+				(byte) 0x73, (byte) 0x72, (byte) 0x71, (byte) 0x70};
+
+		final byte[] aesKey = {(byte) 0x40, (byte) 0x41, (byte) 0x42, (byte) 0x43, //
 				(byte) 0x44, (byte) 0x45, (byte) 0x46, (byte) 0x47, //
 				(byte) 0x48, (byte) 0x49, (byte) 0x4a, (byte) 0x4b, //
 				(byte) 0x4c, (byte) 0x4d, (byte) 0x4e, (byte) 0x4f};
@@ -196,7 +199,7 @@ public class AesSivCipherUtilTest {
 				(byte) 0x53, (byte) 0x49, (byte) 0x56, (byte) 0x2d, //
 				(byte) 0x41, (byte) 0x45, (byte) 0x53};
 
-		final byte[] result = AesSivCipherUtil.sivEncrypt(key, plaintext, ad1, ad2, nonce);
+		final byte[] result = AesSivCipherUtil.sivEncrypt(aesKey, macKey, plaintext, ad1, ad2, nonce);
 
 		final byte[] expected = {(byte) 0x7b, (byte) 0xdb, (byte) 0x6e, (byte) 0x3b, //
 				(byte) 0x43, (byte) 0x26, (byte) 0x67, (byte) 0xeb, //

+ 0 - 5
main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java

@@ -69,11 +69,6 @@ public interface Cryptor extends SensitiveDataSwipeListener {
 	 */
 	String decryptPath(String encryptedPath, char encryptedPathSep, char cleartextPathSep, CryptorIOSupport ioSupport) throws DecryptFailedException;
 
-	/**
-	 * @return <code>true</code> If the integrity of the file can be assured.
-	 */
-	boolean authenticateContent(SeekableByteChannel encryptedFile) throws IOException;
-
 	/**
 	 * @param metadataSupport Support object allowing the Cryptor to read and write its own metadata to the location of the encrypted file.
 	 * @return Content length of the decrypted file or <code>null</code> if unknown.

+ 0 - 5
main/crypto-api/src/main/java/org/cryptomator/crypto/SamplingDecorator.java

@@ -76,11 +76,6 @@ public class SamplingDecorator implements Cryptor, CryptorIOSampling {
 		return cryptor.decryptPath(encryptedPath, encryptedPathSep, cleartextPathSep, ioSupport);
 	}
 
-	@Override
-	public boolean authenticateContent(SeekableByteChannel encryptedFile) throws IOException {
-		return cryptor.authenticateContent(encryptedFile);
-	}
-
 	@Override
 	public Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException {
 		return cryptor.decryptedContentLength(encryptedFile);

+ 0 - 7
main/ui/src/main/java/org/cryptomator/ui/UnlockController.java

@@ -26,7 +26,6 @@ import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
 import javafx.fxml.Initializable;
 import javafx.scene.control.Button;
-import javafx.scene.control.CheckBox;
 import javafx.scene.control.ComboBox;
 import javafx.scene.control.Label;
 import javafx.scene.control.ProgressIndicator;
@@ -63,9 +62,6 @@ public class UnlockController implements Initializable {
 	@FXML
 	private SecPasswordField passwordField;
 
-	@FXML
-	private CheckBox checkIntegrity;
-
 	@FXML
 	private TextField mountName;
 
@@ -127,7 +123,6 @@ public class UnlockController implements Initializable {
 		try {
 			progressIndicator.setVisible(true);
 			masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ);
-			directory.setVerifyFileIntegrity(checkIntegrity.isSelected());
 			directory.getCryptor().decryptMasterKey(masterKeyInputStream, password);
 			if (!directory.startServer(server, closer)) {
 				messageLabel.setText(rb.getString("unlock.messageLabel.startServerFailed"));
@@ -166,7 +161,6 @@ public class UnlockController implements Initializable {
 	private void setControlsDisabled(boolean disable) {
 		usernameBox.setDisable(disable);
 		passwordField.setDisable(disable);
-		checkIntegrity.setDisable(disable);
 		unlockButton.setDisable(disable);
 	}
 
@@ -216,7 +210,6 @@ public class UnlockController implements Initializable {
 	public void setDirectory(Vault directory) {
 		this.directory = directory;
 		this.findExistingUsernames();
-		this.checkIntegrity.setSelected(directory.shouldVerifyFileIntegrity());
 		this.mountName.setText(directory.getMountName());
 	}
 

+ 1 - 10
main/ui/src/main/java/org/cryptomator/ui/model/Vault.java

@@ -41,7 +41,6 @@ public class Vault implements Serializable {
 	private final Cryptor cryptor = SamplingDecorator.decorate(new Aes256Cryptor());
 	private final ObjectProperty<Boolean> unlocked = new SimpleObjectProperty<Boolean>(this, "unlocked", Boolean.FALSE);
 	private final Path path;
-	private boolean verifyFileIntegrity;
 	private String mountName;
 	private DeferredClosable<ServletLifeCycleAdapter> webDavServlet = DeferredClosable.empty();
 	private DeferredClosable<WebDavMount> webDavMount = DeferredClosable.empty();
@@ -68,7 +67,7 @@ public class Vault implements Serializable {
 		if (o.isPresent() && o.get().isRunning()) {
 			return false;
 		}
-		ServletLifeCycleAdapter servlet = server.createServlet(path, verifyFileIntegrity, cryptor, getMountName());
+		ServletLifeCycleAdapter servlet = server.createServlet(path, cryptor, getMountName());
 		if (servlet.start()) {
 			webDavServlet = closer.closeLater(servlet, ServletLifeCycleAdapter::stop);
 			return true;
@@ -106,14 +105,6 @@ public class Vault implements Serializable {
 		return path;
 	}
 
-	public boolean shouldVerifyFileIntegrity() {
-		return verifyFileIntegrity;
-	}
-
-	public void setVerifyFileIntegrity(boolean verifyFileIntegrity) {
-		this.verifyFileIntegrity = verifyFileIntegrity;
-	}
-
 	/**
 	 * @return Directory name without preceeding path components and file extension
 	 */

+ 0 - 2
main/ui/src/main/java/org/cryptomator/ui/model/VaultDeserializer.java

@@ -18,8 +18,6 @@ public class VaultDeserializer extends JsonDeserializer<Vault> {
 		final String pathStr = node.get("path").asText();
 		final Path path = FileSystems.getDefault().getPath(pathStr);
 		final Vault dir = new Vault(path);
-		final boolean verifyFileIntegrity = node.has("checkIntegrity") ? node.get("checkIntegrity").asBoolean() : false;
-		dir.setVerifyFileIntegrity(verifyFileIntegrity);
 		if (node.has("mountName")) {
 			dir.setMountName(node.get("mountName").asText());
 		}

+ 0 - 1
main/ui/src/main/java/org/cryptomator/ui/model/VaultSerializer.java

@@ -13,7 +13,6 @@ public class VaultSerializer extends JsonSerializer<Vault> {
 	public void serialize(Vault value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
 		jgen.writeStartObject();
 		jgen.writeStringField("path", value.getPath().toString());
-		jgen.writeBooleanField("checkIntegrity", value.shouldVerifyFileIntegrity());
 		jgen.writeStringField("mountName", value.getMountName().toString());
 		jgen.writeEndObject();
 	}

+ 7 - 11
main/ui/src/main/resources/fxml/unlock.fxml

@@ -39,21 +39,17 @@
 		<SecPasswordField fx:id="passwordField" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" />
 		
 		<!-- Row 2 -->
-		<Label text="%unlock.label.checkIntegrity" GridPane.rowIndex="2" GridPane.columnIndex="0" />
-		<CheckBox fx:id="checkIntegrity" wrapText="true" text="%unlock.checkbox.checkIntegrity" GridPane.rowIndex="2" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" />
+		<Label text="%unlock.label.mountName" GridPane.rowIndex="2" GridPane.columnIndex="0" />
+		<TextField fx:id="mountName" GridPane.rowIndex="2" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" />
 		
 		<!-- Row 3 -->
-		<Label text="%unlock.label.mountName" GridPane.rowIndex="3" GridPane.columnIndex="0" />
-		<TextField fx:id="mountName" GridPane.rowIndex="3" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" />
+		<Button fx:id="unlockButton" text="%unlock.button.unlock" defaultButton="true" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" prefWidth="150.0" onAction="#didClickUnlockButton"/>
 		
-		<!-- Row 4 -->
-		<Button fx:id="unlockButton" text="%unlock.button.unlock" defaultButton="true" GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" prefWidth="150.0" onAction="#didClickUnlockButton"/>
+		<!-- Row 4-->
+		<ProgressIndicator progress="-1" fx:id="progressIndicator" GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="CENTER" visible="false"/>
 		
-		<!-- Row 5-->
-		<ProgressIndicator progress="-1" fx:id="progressIndicator" GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="CENTER" visible="false"/>
-		
-		<!-- Row 6 -->
-		<Label fx:id="messageLabel" GridPane.rowIndex="6" GridPane.columnIndex="0" GridPane.columnSpan="2" />
+		<!-- Row 5 -->
+		<Label fx:id="messageLabel" GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" />
 	</children>
 </GridPane>