first multithreaded decryption test (PoC quality)

This commit is contained in:
Sebastian Stenzel
2015-07-25 17:28:00 +02:00
parent 9771c6d1e7
commit 738d2dfc34
2 changed files with 146 additions and 31 deletions
@@ -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");
@@ -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