فهرست منبع

first multithreaded decryption test (PoC quality)

Sebastian Stenzel 9 سال پیش
والد
کامیت
738d2dfc34

+ 32 - 0
main/core/src/test/java/org/cryptomator/webdav/jackrabbit/RangeRequestTest.java

@@ -1,7 +1,9 @@
 package org.cryptomator.webdav.jackrabbit;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
@@ -62,6 +64,36 @@ public class RangeRequestTest {
 		FileUtils.deleteQuietly(TMP_VAULT);
 	}
 
+	@Test
+	public void testFullFileDecryption() throws IOException, URISyntaxException {
+		final URL testResourceUrl = new URL(VAULT_BASE_URI.toURL(), "fullFileDecryptionTestFile.txt");
+		final HttpClient client = new HttpClient();
+
+		// prepare 64MiB test data:
+		final byte[] plaintextData = new byte[6777216 * Integer.BYTES];
+		final ByteBuffer bbIn = ByteBuffer.wrap(plaintextData);
+		for (int i = 0; i < 6777216; i++) {
+			bbIn.putInt(i);
+		}
+		final InputStream plaintextDataInputStream = new ByteArrayInputStream(plaintextData);
+
+		// put request:
+		final EntityEnclosingMethod putMethod = new PutMethod(testResourceUrl.toString());
+		putMethod.setRequestEntity(new ByteArrayRequestEntity(plaintextData));
+		final int putResponse = client.executeMethod(putMethod);
+		putMethod.releaseConnection();
+		Assert.assertEquals(201, putResponse);
+
+		// get request:
+		final HttpMethod getMethod = new GetMethod(testResourceUrl.toString());
+		final int statusCode = client.executeMethod(getMethod);
+		Assert.assertEquals(200, statusCode);
+		Assert.assertTrue(IOUtils.contentEquals(plaintextDataInputStream, getMethod.getResponseBodyAsStream()));
+		getMethod.releaseConnection();
+
+		System.out.println("hello world");
+	}
+
 	@Test
 	public void testAsyncRangeRequests() throws IOException, URISyntaxException, InterruptedException {
 		final URL testResourceUrl = new URL(VAULT_BASE_URI.toURL(), "asyncRangeRequestTestFile.txt");

+ 114 - 31
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java

@@ -19,7 +19,17 @@ import java.security.InvalidKeyException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
@@ -362,6 +372,8 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 
 	@Override
 	public Long decryptFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile, boolean authenticate) throws IOException, DecryptFailedException {
+		long t0 = System.nanoTime();
+
 		// read header:
 		encryptedFile.position(0l);
 		final ByteBuffer headerBuf = ByteBuffer.allocate(104);
@@ -369,26 +381,36 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 		if (headerBytesRead != headerBuf.capacity()) {
 			throw new IOException("Failed to read file header.");
 		}
+		System.err.println("read header: " + (System.nanoTime() - t0) / 1000 / 1000.0 + "ms");
+		t0 = System.nanoTime();
 
 		// read iv:
 		final byte[] iv = new byte[AES_BLOCK_LENGTH];
 		headerBuf.position(0);
 		headerBuf.get(iv);
+		System.err.println("read iv: " + (System.nanoTime() - t0) / 1000 / 1000.0 + "ms");
+		t0 = System.nanoTime();
 
 		// read nonce:
 		final byte[] nonce = new byte[8];
 		headerBuf.position(16);
 		headerBuf.get(nonce);
+		System.err.println("read nonce: " + (System.nanoTime() - t0) / 1000 / 1000.0 + "ms");
+		t0 = System.nanoTime();
 
 		// read sensitive header data:
 		final byte[] encryptedSensitiveHeaderContentBytes = new byte[48];
 		headerBuf.position(24);
 		headerBuf.get(encryptedSensitiveHeaderContentBytes);
+		System.err.println("read sensitive header data: " + (System.nanoTime() - t0) / 1000 / 1000.0 + "ms");
+		t0 = System.nanoTime();
 
 		// read header mac:
 		final byte[] storedHeaderMac = new byte[32];
 		headerBuf.position(72);
 		headerBuf.get(storedHeaderMac);
+		System.err.println("read header mac: " + (System.nanoTime() - t0) / 1000 / 1000.0 + "ms");
+		t0 = System.nanoTime();
 
 		// calculate mac over first 72 bytes of header:
 		if (authenticate) {
@@ -400,6 +422,8 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 				throw new MacAuthenticationFailedException("Header MAC authentication failed.");
 			}
 		}
+		System.err.println("calculate mac over first 72 bytes of header: " + (System.nanoTime() - t0) / 1000 / 1000.0 + "ms");
+		t0 = System.nanoTime();
 
 		// decrypt sensitive header data:
 		final byte[] fileKeyBytes = new byte[32];
@@ -407,53 +431,112 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 		final ByteBuffer sensitiveHeaderContentBuf = ByteBuffer.wrap(decryptedSensitiveHeaderContentBytes);
 		final Long fileSize = sensitiveHeaderContentBuf.getLong();
 		sensitiveHeaderContentBuf.get(fileKeyBytes);
-
-		// append counter to nonce:
-		final ByteBuffer nonceAndCounterBuf = ByteBuffer.allocate(AES_BLOCK_LENGTH);
-		nonceAndCounterBuf.put(nonce);
-		nonceAndCounterBuf.putLong(0L);
-		final byte[] nonceAndCounter = nonceAndCounterBuf.array();
+		System.err.println("decrypt sensitive header data: " + (System.nanoTime() - t0) / 1000 / 1000.0 + "ms");
+		t0 = System.nanoTime();
 
 		// content decryption:
 		encryptedFile.position(104l);
 		final SecretKey fileKey = new SecretKeySpec(fileKeyBytes, AES_KEY_ALGORITHM);
-		final Cipher cipher = this.aesCtrCipher(fileKey, nonceAndCounter, Cipher.DECRYPT_MODE);
-		final Mac contentMac = this.hmacSha256(hMacMasterKey);
+		System.err.println("init DEC: " + (System.nanoTime() - t0) / 1000 / 1000.0 + "ms");
+
+		final int numWorkers = 1;
+		final ExecutorService executorService = Executors.newFixedThreadPool(numWorkers);
+		final Lock lock = new ReentrantLock();
+		final Condition blockCondition = lock.newCondition();
 
 		// reading ciphered input and MACs interleaved:
-		long bytesDecrypted = 0;
+		final AtomicLong bytesDecrypted = new AtomicLong();
 		final InputStream in = new SeekableByteChannelInputStream(encryptedFile);
-		byte[] buffer = new byte[CONTENT_MAC_BLOCK + 32];
+		final byte[] buffer = new byte[(CONTENT_MAC_BLOCK + 32) * numWorkers];
 		int n = 0;
-		long blockNum = 0;
-		while ((n = IOUtils.read(in, buffer)) > 0 && bytesDecrypted < fileSize) {
-			if (n < 32) {
-				throw new DecryptFailedException("Invalid file content, missing MAC.");
+		final AtomicLong blockNum = new AtomicLong();
+		final AtomicLong numBlocksWritten = new AtomicLong();
+		while ((n = IOUtils.read(in, buffer)) > 0 && bytesDecrypted.get() < fileSize) {
+			t0 = System.nanoTime();
+
+			final int finalN = n;
+			final List<Future<Boolean>> tasks = new ArrayList<>();
+			for (int i = 0; i < numWorkers; i++) {
+				final Future<Boolean> task = executorService.submit(() -> {
+					final long myBlockNum = blockNum.getAndIncrement();
+					final int myBufferOffset = (int) ((myBlockNum % numWorkers) * (CONTENT_MAC_BLOCK + 32));
+					final int myN = Math.min(finalN - myBufferOffset, CONTENT_MAC_BLOCK + 32);
+
+					if (myN <= 0) {
+						// EOF
+					} else if (myN < 32) {
+						throw new DecryptFailedException("Invalid file content, missing MAC.");
+					}
+
+					// check MAC of current block:
+					if (authenticate) {
+						final Mac contentMac = this.hmacSha256(hMacMasterKey);
+						contentMac.update(iv);
+						contentMac.update(longToByteArray(myBlockNum));
+						contentMac.update(buffer, myBufferOffset, myN - 32);
+						final byte[] calculatedMac = contentMac.doFinal();
+						final byte[] storedMac = new byte[32];
+						System.arraycopy(buffer, myBufferOffset + myN - 32, storedMac, 0, 32);
+						if (!MessageDigest.isEqual(calculatedMac, storedMac)) {
+							throw new MacAuthenticationFailedException("Content MAC authentication failed.");
+						}
+					}
+
+					// decrypt block:
+					final ByteBuffer nonceAndCounterBuf = ByteBuffer.allocate(AES_BLOCK_LENGTH);
+					nonceAndCounterBuf.put(nonce);
+					nonceAndCounterBuf.putLong(myBlockNum * CONTENT_MAC_BLOCK / AES_BLOCK_LENGTH);
+					final byte[] nonceAndCounter = nonceAndCounterBuf.array();
+					final Cipher cipher = this.aesCtrCipher(fileKey, nonceAndCounter, Cipher.DECRYPT_MODE);
+					final byte[] plaintext = cipher.update(buffer, myBufferOffset, myN - 32);
+
+					// wait for our turn to write the plaintext:
+					lock.lock();
+					try {
+						while (numBlocksWritten.get() != myBlockNum) {
+							blockCondition.await();
+						}
+						final int plaintextLengthWithoutPadding = (int) Math.min(plaintext.length, fileSize - bytesDecrypted.get()); // plaintext.length is known to be a 32 bit int
+						plaintextFile.write(plaintext, 0, plaintextLengthWithoutPadding);
+						bytesDecrypted.addAndGet(plaintextLengthWithoutPadding);
+						numBlocksWritten.incrementAndGet();
+						blockCondition.signalAll();
+					} catch (InterruptedException e) {
+
+					} finally {
+						lock.unlock();
+					}
+					return true;
+				});
+				tasks.add(task);
 			}
 
-			// check MAC of current block:
-			if (authenticate) {
-				contentMac.update(iv);
-				contentMac.update(longToByteArray(blockNum));
-				contentMac.update(buffer, 0, n - 32);
-				final byte[] calculatedMac = contentMac.doFinal();
-				final byte[] storedMac = new byte[32];
-				System.arraycopy(buffer, n - 32, storedMac, 0, 32);
-				if (!MessageDigest.isEqual(calculatedMac, storedMac)) {
-					throw new MacAuthenticationFailedException("Content MAC authentication failed.");
+			for (Future<Boolean> task : tasks) {
+				try {
+					task.get();
+				} catch (InterruptedException e) {
+					// TODO Auto-generated catch block
+				} catch (ExecutionException e) {
+					final Throwable cause = e.getCause();
+					if (cause instanceof DecryptFailedException) {
+						throw (DecryptFailedException) cause;
+					} else if (cause instanceof MacAuthenticationFailedException) {
+						throw (MacAuthenticationFailedException) cause;
+					} else if (cause instanceof IOException) {
+						throw (IOException) cause;
+					} else {
+						// TODO loggingpower
+					}
 				}
 			}
 
-			// decrypt block:
-			final byte[] plaintext = cipher.update(buffer, 0, n - 32);
-			final int plaintextLengthWithoutPadding = (int) Math.min(plaintext.length, fileSize - bytesDecrypted); // plaintext.length is known to be a 32 bit int
-			plaintextFile.write(plaintext, 0, plaintextLengthWithoutPadding);
-			bytesDecrypted += plaintextLengthWithoutPadding;
-			blockNum++;
+			// System.err.println("dec " + numWorkers + " blocks: " + (System.nanoTime() - t0) / 1000 / 1000.0 + "ms");
 		}
 		destroyQuietly(fileKey);
 
-		return bytesDecrypted;
+		System.err.println("cleanup: " + (System.nanoTime() - t0) / 1000 / 1000.0 + "ms");
+
+		return bytesDecrypted.get();
 	}
 
 	@Override