unlock version 5 vaults

This commit is contained in:
Sebastian Stenzel
2016-08-23 21:35:13 +02:00
parent 8a4a29b4d1
commit 66faa13f40
10 changed files with 23 additions and 121 deletions

View File

@@ -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.
*

View File

@@ -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<Integer> 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;

View File

@@ -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);
}

View File

@@ -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<Mac> 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);

View File

@@ -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);

View File

@@ -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

View File

@@ -44,16 +44,9 @@ class NoFileContentCryptor implements FileContentCryptor {
private class Decryptor implements FileContentDecryptor {
private final BlockingQueue<Supplier<ByteBuffer>> 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

View File

@@ -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);
}

View File

@@ -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];

View File

@@ -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());
}
}
}