diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentDecryptor.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentDecryptor.java index 016e9871e..be7c468a9 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentDecryptor.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentDecryptor.java @@ -19,11 +19,6 @@ import javax.security.auth.Destroyable; */ public interface FileContentDecryptor extends Destroyable, Closeable { - /** - * @return Number of bytes of the decrypted file. - */ - long contentLength(); - /** * Appends further ciphertext to this decryptor. This method might block until space becomes available. If so, it is interruptable. * diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java index efbe84bfc..218f4c3aa 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java @@ -1,16 +1,11 @@ package org.cryptomator.crypto.engine.impl; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; - public final class Constants { private Constants() { } - static final Collection SUPPORTED_VAULT_VERSIONS = Collections.unmodifiableCollection(Arrays.asList(3, 4)); - static final Integer CURRENT_VAULT_VERSION = 4; + static final Integer CURRENT_VAULT_VERSION = 5; public static final int PAYLOAD_SIZE = 32 * 1024; public static final int NONCE_SIZE = 16; diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java index 836e6c134..166a34b12 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java @@ -9,7 +9,6 @@ package org.cryptomator.crypto.engine.impl; import static org.cryptomator.crypto.engine.impl.Constants.CURRENT_VAULT_VERSION; -import static org.cryptomator.crypto.engine.impl.Constants.SUPPORTED_VAULT_VERSIONS; import java.io.IOException; import java.nio.ByteBuffer; @@ -110,7 +109,7 @@ class CryptorImpl implements Cryptor { assert keyFile != null; // check version - if (!SUPPORTED_VAULT_VERSIONS.contains(keyFile.getVersion())) { + if (CURRENT_VAULT_VERSION != keyFile.getVersion()) { throw new UnsupportedVaultFormatException(keyFile.getVersion(), CURRENT_VAULT_VERSION); } diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java index e619cddc5..0088f6a51 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java @@ -22,7 +22,6 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.atomic.LongAdder; import java.util.function.Supplier; import javax.crypto.Cipher; @@ -47,7 +46,6 @@ class FileContentDecryptorImpl implements FileContentDecryptor { private final Supplier hmacSha256; private final FileHeader header; private final boolean authenticate; - private final LongAdder cleartextBytesDecrypted = new LongAdder(); private ByteBuffer ciphertextBuffer = ByteBuffer.allocate(CHUNK_SIZE); private long chunkNumber = 0; @@ -58,11 +56,6 @@ class FileContentDecryptorImpl implements FileContentDecryptor { this.chunkNumber = firstCiphertextByte / CHUNK_SIZE; // floor() by int-truncation } - @Override - public long contentLength() { - return header.getPayload().getFilesize(); - } - @Override public void append(ByteBuffer ciphertext) throws InterruptedException { if (ciphertext == FileContentCryptor.EOF) { @@ -105,15 +98,7 @@ class FileContentDecryptorImpl implements FileContentDecryptor { @Override public ByteBuffer cleartext() throws InterruptedException { try { - final ByteBuffer cleartext = dataProcessor.processedData(); - long bytesUntilLogicalEof = contentLength() - cleartextBytesDecrypted.sum(); - if (bytesUntilLogicalEof <= 0) { - return FileContentCryptor.EOF; - } else if (bytesUntilLogicalEof < cleartext.remaining()) { - cleartext.limit((int) bytesUntilLogicalEof); - } - cleartextBytesDecrypted.add(cleartext.remaining()); - return cleartext; + return dataProcessor.processedData(); } catch (ExecutionException e) { if (e.getCause() instanceof AuthenticationFailedException) { throw new AuthenticationFailedException(e); diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java index 20b2eeea7..71c0b121b 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java @@ -36,8 +36,6 @@ import org.cryptomator.io.ByteBuffers; class FileContentEncryptorImpl implements FileContentEncryptor { private static final String HMAC_SHA256 = "HmacSHA256"; - private static final int PADDING_LOWER_BOUND = 4 * 1024; // 4k - private static final int PADDING_UPPER_BOUND = 16 * 1024 * 1024; // 16M private static final int NUM_THREADS = Runtime.getRuntime().availableProcessors(); private static final int READ_AHEAD = 2; private static final ExecutorService SHARED_DECRYPTION_EXECUTOR = Executors.newFixedThreadPool(NUM_THREADS); @@ -63,7 +61,7 @@ class FileContentEncryptorImpl implements FileContentEncryptor { @Override public ByteBuffer getHeader() { - header.getPayload().setFilesize(cleartextBytesScheduledForEncryption.sum()); + header.getPayload().setFilesize(-1l); return header.toByteBuffer(headerKey, hmacSha256); } @@ -76,7 +74,6 @@ class FileContentEncryptorImpl implements FileContentEncryptor { public void append(ByteBuffer cleartext) throws InterruptedException { cleartextBytesScheduledForEncryption.add(cleartext.remaining()); if (cleartext == FileContentCryptor.EOF) { - appendSizeObfuscationPadding(cleartextBytesScheduledForEncryption.sum()); submitCleartextBuffer(); submitEof(); } else { @@ -84,19 +81,6 @@ class FileContentEncryptorImpl implements FileContentEncryptor { } } - private void appendSizeObfuscationPadding(long actualSize) throws InterruptedException { - final int maxPaddingLength = (int) Math.min(Math.max(actualSize / 10, PADDING_LOWER_BOUND), PADDING_UPPER_BOUND); // preferably 10%, but at least lower bound and no more than upper bound - final int randomPaddingLength = randomSource.nextInt(maxPaddingLength); - final ByteBuffer buf = ByteBuffer.allocate(PAYLOAD_SIZE); - int remainingPadding = randomPaddingLength; - while (remainingPadding > 0) { - int bytesInCurrentIteration = Math.min(remainingPadding, PAYLOAD_SIZE); - buf.clear().limit(bytesInCurrentIteration); - appendAllAndSubmitIfFull(buf); - remainingPadding -= bytesInCurrentIteration; - } - } - private void appendAllAndSubmitIfFull(ByteBuffer cleartext) throws InterruptedException { while (cleartext.hasRemaining()) { ByteBuffers.copy(cleartext, cleartextBuffer); diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java index c915750f0..ad0dd3e08 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java @@ -8,6 +8,9 @@ *******************************************************************************/ package org.cryptomator.filesystem.crypto; +import static org.cryptomator.crypto.engine.impl.Constants.CHUNK_SIZE; +import static org.cryptomator.crypto.engine.impl.Constants.PAYLOAD_SIZE; + import java.io.IOException; import java.io.InterruptedIOException; import java.io.UncheckedIOException; @@ -72,8 +75,16 @@ class CryptoReadableFile implements ReadableFile { @Override public long size() throws UncheckedIOException { - assert decryptor != null : "decryptor is always being set during position(long)"; - return decryptor.contentLength(); + long ciphertextSize = file.size() - cryptor.getHeaderSize(); + long overheadPerChunk = CHUNK_SIZE - PAYLOAD_SIZE; + long numFullChunks = ciphertextSize / CHUNK_SIZE; // floor by int-truncation + long additionalCiphertextBytes = ciphertextSize % CHUNK_SIZE; + if (additionalCiphertextBytes > 0 && additionalCiphertextBytes <= overheadPerChunk) { + throw new IllegalArgumentException("Method not defined for input value " + ciphertextSize); + } + long additionalCleartextBytes = (additionalCiphertextBytes == 0) ? 0 : additionalCiphertextBytes - overheadPerChunk; + assert additionalCleartextBytes >= 0; + return PAYLOAD_SIZE * numFullChunks + additionalCleartextBytes; } @Override diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFileContentCryptor.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFileContentCryptor.java index d7a061583..5a342d6e4 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFileContentCryptor.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFileContentCryptor.java @@ -44,16 +44,9 @@ class NoFileContentCryptor implements FileContentCryptor { private class Decryptor implements FileContentDecryptor { private final BlockingQueue> cleartextQueue = new LinkedBlockingQueue<>(); - private final long contentLength; private Decryptor(ByteBuffer header) { assert header.remaining() == Long.BYTES; - this.contentLength = header.getLong(); - } - - @Override - public long contentLength() { - return contentLength; } @Override diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/CryptorImplTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/CryptorImplTest.java index 8bd890a76..cb30b9bd5 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/CryptorImplTest.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/CryptorImplTest.java @@ -21,7 +21,7 @@ public class CryptorImplTest { @Test public void testMasterkeyDecryptionWithCorrectPassphrase() throws IOException { - final String testMasterKey = "{\"version\":4,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," // + final String testMasterKey = "{\"version\":5,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," // + "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // + "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // + "\"versionMac\":\"Z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfok=\"}"; @@ -31,7 +31,7 @@ public class CryptorImplTest { @Test(expected = InvalidPassphraseException.class) public void testMasterkeyDecryptionWithWrongPassphrase() throws IOException { - final String testMasterKey = "{\"version\":4,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," // + final String testMasterKey = "{\"version\":5,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," // + "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // + "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // + "\"versionMac\":\"Z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfok=\"}"; @@ -52,7 +52,7 @@ public class CryptorImplTest { @Ignore @Test(expected = UnsupportedVaultFormatException.class) public void testMasterkeyDecryptionWithMissingVersionMac() throws IOException { - final String testMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," // + final String testMasterKey = "{\"version\":5,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," // + "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // + "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"}"; final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl(); @@ -62,7 +62,7 @@ public class CryptorImplTest { @Ignore @Test(expected = UnsupportedVaultFormatException.class) public void testMasterkeyDecryptionWithWrongVersionMac() throws IOException { - final String testMasterKey = "{\"version\":4,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," // + final String testMasterKey = "{\"version\":5,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," // + "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // + "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // + "\"versionMac\":\"z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfoK=\"}"; @@ -72,14 +72,13 @@ public class CryptorImplTest { @Test public void testMasterkeyEncryption() throws IOException { - final String expectedMasterKey = "{\"version\":4,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":16384,\"scryptBlockSize\":8," // + final String expectedMasterKey = "{\"version\":5,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":16384,\"scryptBlockSize\":8," // + "\"primaryMasterKey\":\"BJPIq5pvhN24iDtPJLMFPLaVJWdGog9k4n0P03j4ru+ivbWY9OaRGQ==\"," // + "\"hmacMasterKey\":\"BJPIq5pvhN24iDtPJLMFPLaVJWdGog9k4n0P03j4ru+ivbWY9OaRGQ==\"," // - + "\"versionMac\":\"Z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfok=\"}"; + + "\"versionMac\":\"yuwoRE9GSdgQ2b//qRpTCj3W0qsVLxYVa7/KB3PkfA4=\"}"; final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl(); cryptor.randomizeMasterkey(); final byte[] masterkeyFile = cryptor.writeKeysToMasterkeyFile("asd"); - System.out.println(new String(masterkeyFile)); Assert.assertArrayEquals(expectedMasterKey.getBytes(), masterkeyFile); } diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImplTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImplTest.java index 71ed1b825..e1cdee671 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImplTest.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImplTest.java @@ -137,45 +137,6 @@ public class FileContentCryptorImplTest { Assert.assertArrayEquals("cleartext message".getBytes(), result); } - @Test - public void testEncryptionAndDecryptionWithSizeObfuscationPadding() throws InterruptedException { - final byte[] keyBytes = new byte[32]; - final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - FileContentCryptor cryptor = new FileContentCryptorImpl(encryptionKey, macKey, RANDOM_MOCK_2); - - ByteBuffer header = ByteBuffer.allocate(cryptor.getHeaderSize()); - ByteBuffer ciphertext = ByteBuffer.allocate(16 + 11 + 500 + 32 + 1); // 16 bytes iv + 11 bytes ciphertext + 500 bytes padding + 32 bytes mac + 1. - try (FileContentEncryptor encryptor = cryptor.createFileContentEncryptor(Optional.empty(), 0)) { - encryptor.append(ByteBuffer.wrap("hello world".getBytes())); - encryptor.append(FileContentCryptor.EOF); - ByteBuffer buf; - while ((buf = encryptor.ciphertext()) != FileContentCryptor.EOF) { - ByteBuffers.copy(buf, ciphertext); - } - ByteBuffers.copy(encryptor.getHeader(), header); - } - header.flip(); - ciphertext.flip(); - - Assert.assertEquals(16 + 11 + 500 + 32, ciphertext.remaining()); - - ByteBuffer plaintext = ByteBuffer.allocate(12); // 11 bytes plaintext + 1 - try (FileContentDecryptor decryptor = cryptor.createFileContentDecryptor(header, 0, true)) { - decryptor.append(ciphertext); - decryptor.append(FileContentCryptor.EOF); - ByteBuffer buf; - while ((buf = decryptor.cleartext()) != FileContentCryptor.EOF) { - ByteBuffers.copy(buf, plaintext); - } - } - plaintext.flip(); - - byte[] result = new byte[plaintext.remaining()]; - plaintext.get(result); - Assert.assertArrayEquals("hello world".getBytes(), result); - } - @Test(timeout = 20000) // assuming a minimum speed of 10mb/s during encryption and decryption 20s should be enough public void testEncryptionAndDecryptionSpeed() throws InterruptedException, IOException { final byte[] keyBytes = new byte[32]; diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImplTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImplTest.java index b99964a77..f9503d6d8 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImplTest.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImplTest.java @@ -95,24 +95,4 @@ public class FileContentEncryptorImplTest { } } - @Test - public void testSizeObfuscation() throws InterruptedException { - final byte[] keyBytes = new byte[32]; - final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); - final SecretKey macKey = new SecretKeySpec(keyBytes, "AES"); - - try (FileContentEncryptor encryptor = new FileContentEncryptorImpl(headerKey, macKey, RANDOM_MOCK_2, 0)) { - encryptor.append(FileContentCryptor.EOF); - - ByteBuffer result = ByteBuffer.allocate(91); // 16 bytes iv + 42 bytes size obfuscation + 32 bytes mac + 1 - ByteBuffer buf; - while ((buf = encryptor.ciphertext()) != FileContentCryptor.EOF) { - ByteBuffers.copy(buf, result); - } - result.flip(); - - Assert.assertEquals(90, result.remaining()); - } - } - }