diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFile.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFile.java
index 9b6ee3ee5..83d017a8f 100644
--- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFile.java
+++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFile.java
@@ -29,6 +29,7 @@ import org.apache.jackrabbit.webdav.lock.LockManager;
import org.apache.jackrabbit.webdav.property.DavPropertyName;
import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
import org.cryptomator.crypto.Cryptor;
+import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.cryptomator.webdav.exceptions.IORuntimeException;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
@@ -78,9 +79,8 @@ public class EncryptedFile extends AbstractEncryptedNode {
}
} catch (EOFException e) {
LOG.warn("Unexpected end of stream (possibly client hung up).");
- } catch (IOException e) {
- LOG.error("Error reading file " + path.toString(), e);
- throw new IORuntimeException(e);
+ } catch (DecryptFailedException e) {
+ throw new IOException("Error decrypting file " + path.toString(), e);
} finally {
IOUtils.closeQuietly(channel);
}
diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFilePart.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFilePart.java
index 953ba4341..c257b39fb 100644
--- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFilePart.java
+++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFilePart.java
@@ -21,7 +21,7 @@ import org.apache.jackrabbit.webdav.DavSession;
import org.apache.jackrabbit.webdav.io.OutputContext;
import org.apache.jackrabbit.webdav.lock.LockManager;
import org.cryptomator.crypto.Cryptor;
-import org.cryptomator.webdav.exceptions.IORuntimeException;
+import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.eclipse.jetty.http.HttpHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -128,9 +128,8 @@ public class EncryptedFilePart extends EncryptedFile {
if (LOG.isDebugEnabled()) {
LOG.debug("Unexpected end of stream during delivery of partial content (client hung up).");
}
- } catch (IOException e) {
- LOG.error("Error reading file " + path.toString(), e);
- throw new IORuntimeException(e);
+ } catch (DecryptFailedException e) {
+ throw new IOException("Error decrypting file " + path.toString(), e);
} finally {
IOUtils.closeQuietly(channel);
}
diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java
index 58afb9129..59ff7b74b 100644
--- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java
+++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java
@@ -86,8 +86,6 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
*/
private SecretKey hMacMasterKey;
- private static final int SIZE_OF_LONG = Long.BYTES;
-
static {
try {
SECURE_PRNG = SecureRandom.getInstance(PRNG_ALGORITHM);
@@ -240,6 +238,31 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
}
}
+ private Cipher aesEcbCipher(SecretKey key, int cipherMode) {
+ try {
+ final Cipher cipher = Cipher.getInstance(AES_ECB_CIPHER);
+ cipher.init(cipherMode, key);
+ return cipher;
+ } catch (InvalidKeyException ex) {
+ throw new IllegalArgumentException("Invalid key.", ex);
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException ex) {
+ throw new AssertionError("Every implementation of the Java platform is required to support AES/ECB/PKCS5Padding.", ex);
+ }
+
+ }
+
+ private Mac hmacSha256(SecretKey key) {
+ try {
+ final Mac mac = Mac.getInstance(HMAC_KEY_ALGORITHM);
+ mac.init(key);
+ return mac;
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError("Every implementation of the Java platform is required to support HmacSHA256.", e);
+ } catch (InvalidKeyException e) {
+ throw new IllegalArgumentException("Invalid key", e);
+ }
+ }
+
private byte[] randomData(int length) {
final byte[] result = new byte[length];
SECURE_PRNG.setSeed(SECURE_PRNG.generateSeed(PRNG_SEED_LENGTH));
@@ -269,18 +292,6 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
return crc32.getValue();
}
- private byte[] hmacSha256(byte[] data) {
- try {
- final Mac mac = Mac.getInstance(HMAC_KEY_ALGORITHM);
- mac.init(hMacMasterKey);
- return mac.doFinal(data);
- } catch (NoSuchAlgorithmException e) {
- throw new AssertionError("Every implementation of the Java platform is required to support HmacSHA256.", e);
- } catch (InvalidKeyException e) {
- throw new IllegalArgumentException("Invalid key", e);
- }
- }
-
@Override
public String encryptPath(String cleartextPath, char encryptedPathSep, char cleartextPathSep, CryptorIOSupport ioSupport) {
try {
@@ -312,7 +323,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
* {@link FileNamingConventions#LONG_NAME_FILE_EXT}.
*/
private String encryptPathComponent(final String cleartext, final SecretKey key, CryptorIOSupport ioSupport) throws IllegalBlockSizeException, BadPaddingException, IOException {
- final byte[] mac = hmacSha256(cleartext.getBytes());
+ final byte[] mac = hmacSha256(hMacMasterKey).doFinal(cleartext.getBytes());
final byte[] partialIv = ArrayUtils.subarray(mac, 0, 10);
final ByteBuffer iv = ByteBuffer.allocate(AES_BLOCK_LENGTH);
iv.put(partialIv);
@@ -390,58 +401,83 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
ioSupport.writePathSpecificMetadata(metadataFile, objectMapper.writeValueAsBytes(metadata));
}
+ @Override
+ public boolean authenticateContent(SeekableByteChannel encryptedFile) throws IOException {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
+
@Override
public Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException {
- final ByteBuffer sizeBuffer = ByteBuffer.allocate(SIZE_OF_LONG);
- final int read = encryptedFile.read(sizeBuffer);
- if (read == SIZE_OF_LONG) {
- return sizeBuffer.getLong(0);
- } else {
+ // skip 128bit IV + 256 bit MAC:
+ encryptedFile.position(48);
+
+ // read encrypted value:
+ final ByteBuffer encryptedFileSizeBuffer = ByteBuffer.allocate(AES_BLOCK_LENGTH);
+ final int numFileSizeBytesRead = encryptedFile.read(encryptedFileSizeBuffer);
+
+ // return "unknown" value, if EOF
+ if (numFileSizeBytesRead != encryptedFileSizeBuffer.capacity()) {
return null;
}
+
+ // decrypt size:
+ try {
+ final Cipher sizeCipher = aesEcbCipher(primaryMasterKey, Cipher.DECRYPT_MODE);
+ final byte[] decryptedFileSize = sizeCipher.doFinal(encryptedFileSizeBuffer.array());
+ final ByteBuffer fileSizeBuffer = ByteBuffer.wrap(decryptedFileSize);
+ return fileSizeBuffer.getLong();
+ } catch (IllegalBlockSizeException | BadPaddingException e) {
+ throw new IllegalStateException(e);
+ }
}
@Override
public Long decryptedFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException {
- // skip content size:
- encryptedFile.position(SIZE_OF_LONG);
-
// read iv:
+ encryptedFile.position(0);
final ByteBuffer countingIv = ByteBuffer.allocate(AES_BLOCK_LENGTH);
- final int read = encryptedFile.read(countingIv);
- if (read != AES_BLOCK_LENGTH) {
- throw new IOException("Failed to read encrypted file header.");
+ final int numIvBytesRead = encryptedFile.read(countingIv);
+
+ // read file size:
+ final Long fileSize = decryptedContentLength(encryptedFile);
+
+ // check validity of header:
+ if (numIvBytesRead != AES_BLOCK_LENGTH || fileSize == null) {
+ throw new IOException("Failed to read file header.");
}
+ // go to begin of content:
+ encryptedFile.position(64);
+
// generate cipher:
final Cipher cipher = this.aesCtrCipher(primaryMasterKey, countingIv.array(), Cipher.DECRYPT_MODE);
// read content
final InputStream in = new SeekableByteChannelInputStream(encryptedFile);
final InputStream cipheredIn = new CipherInputStream(in, cipher);
- return IOUtils.copyLarge(cipheredIn, plaintextFile);
+ return IOUtils.copyLarge(cipheredIn, plaintextFile, 0, fileSize);
}
@Override
public Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plaintextFile, long pos, long length) throws IOException {
- // skip content size:
- encryptedFile.position(SIZE_OF_LONG);
-
// read iv:
+ encryptedFile.position(0);
final ByteBuffer countingIv = ByteBuffer.allocate(AES_BLOCK_LENGTH);
- final int read = encryptedFile.read(countingIv);
- if (read != AES_BLOCK_LENGTH) {
- throw new IOException("Failed to read encrypted file header.");
+ final int numIvBytesRead = encryptedFile.read(countingIv);
+
+ // check validity of header:
+ if (numIvBytesRead != AES_BLOCK_LENGTH) {
+ throw new IOException("Failed to read file header.");
}
// seek relevant position and update iv:
long firstRelevantBlock = pos / AES_BLOCK_LENGTH; // cut of fraction!
long beginOfFirstRelevantBlock = firstRelevantBlock * AES_BLOCK_LENGTH;
long offsetInsideFirstRelevantBlock = pos - beginOfFirstRelevantBlock;
- countingIv.putLong(AES_BLOCK_LENGTH - SIZE_OF_LONG, firstRelevantBlock);
+ countingIv.putLong(AES_BLOCK_LENGTH - Long.BYTES, firstRelevantBlock);
// fast forward stream:
- encryptedFile.position(SIZE_OF_LONG + AES_BLOCK_LENGTH + beginOfFirstRelevantBlock);
+ encryptedFile.position(64 + beginOfFirstRelevantBlock);
// generate cipher:
final Cipher cipher = this.aesCtrCipher(primaryMasterKey, countingIv.array(), Cipher.DECRYPT_MODE);
@@ -459,32 +495,58 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
// use an IV, whose last 8 bytes store a long used in counter mode and write initial value to file.
final ByteBuffer countingIv = ByteBuffer.wrap(randomData(AES_BLOCK_LENGTH));
- countingIv.putLong(AES_BLOCK_LENGTH - SIZE_OF_LONG, 0l);
+ countingIv.putLong(AES_BLOCK_LENGTH - Long.BYTES, 0l);
countingIv.position(0);
+ encryptedFile.write(countingIv);
- // generate cipher:
+ // init crypto stuff:
+ final Mac mac = this.hmacSha256(hMacMasterKey);
final Cipher cipher = this.aesCtrCipher(primaryMasterKey, countingIv.array(), Cipher.ENCRYPT_MODE);
- // 8 bytes (file size: temporarily -1):
- final ByteBuffer fileSize = ByteBuffer.allocate(SIZE_OF_LONG);
- fileSize.putLong(-1L);
- fileSize.position(0);
- encryptedFile.write(fileSize);
+ // init mac buffer and skip 32 bytes
+ final ByteBuffer macBuffer = ByteBuffer.allocate(mac.getMacLength());
+ encryptedFile.write(macBuffer);
- // 16 bytes (iv):
- encryptedFile.write(countingIv);
+ // init filesize buffer and skip 16 bytes
+ final ByteBuffer encryptedFileSizeBuffer = ByteBuffer.allocate(AES_BLOCK_LENGTH);
+ encryptedFile.write(encryptedFileSizeBuffer);
// write content:
final OutputStream out = new SeekableByteChannelOutputStream(encryptedFile);
- final OutputStream cipheredOut = new CipherOutputStream(out, cipher);
+ final OutputStream macOut = new MacOutputStream(out, mac);
+ final OutputStream cipheredOut = new CipherOutputStream(macOut, cipher);
final Long actualSize = IOUtils.copyLarge(plaintextFile, cipheredOut);
- // write filesize
- fileSize.position(0);
- fileSize.putLong(actualSize);
- fileSize.position(0);
- encryptedFile.position(0);
- encryptedFile.write(fileSize);
+ // append fake content:
+ final int randomContentLength = (int) Math.ceil(Math.random() * actualSize / 10.0);
+ final byte[] emptyBytes = new byte[AES_BLOCK_LENGTH];
+ for (int i = 0; i < randomContentLength; i += AES_BLOCK_LENGTH) {
+ cipheredOut.write(emptyBytes);
+ }
+ cipheredOut.flush();
+
+ // copy MAC:
+ macBuffer.position(0);
+ macBuffer.put(mac.doFinal());
+
+ // encrypt actualSize
+ try {
+ final ByteBuffer fileSizeBuffer = ByteBuffer.allocate(Long.BYTES);
+ fileSizeBuffer.putLong(actualSize);
+ final Cipher sizeCipher = aesEcbCipher(primaryMasterKey, Cipher.ENCRYPT_MODE);
+ final byte[] encryptedFileSize = sizeCipher.doFinal(fileSizeBuffer.array());
+ encryptedFileSizeBuffer.position(0);
+ encryptedFileSizeBuffer.put(encryptedFileSize);
+ } catch (IllegalBlockSizeException | BadPaddingException e) {
+ throw new IllegalStateException(e);
+ }
+
+ // write file header
+ encryptedFile.position(16); // skip already written 128 bit IV
+ macBuffer.position(0);
+ encryptedFile.write(macBuffer); // 256 bit MAC
+ encryptedFileSizeBuffer.position(0);
+ encryptedFile.write(encryptedFileSizeBuffer); // 128 bit encrypted file size
return actualSize;
}
diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java
index 2d9c7b360..e2c7ee3ac 100644
--- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java
+++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java
@@ -66,6 +66,13 @@ interface AesCryptographicConfiguration {
*/
String AES_CTR_CIPHER = "AES/CTR/NoPadding";
+ /**
+ * Cipher specs for single block encryption (like file size).
+ *
+ * @see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#impl
+ */
+ String AES_ECB_CIPHER = "AES/ECB/PKCS5Padding";
+
/**
* AES block size is 128 bit or 16 bytes.
*/
diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/MacInputStream.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/MacInputStream.java
new file mode 100644
index 000000000..b59ae49c7
--- /dev/null
+++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/MacInputStream.java
@@ -0,0 +1,39 @@
+package org.cryptomator.crypto.aes256;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.crypto.Mac;
+
+/**
+ * Updates a {@link Mac} with the bytes read from this stream.
+ */
+class MacInputStream extends FilterInputStream {
+
+ private final Mac mac;
+
+ /**
+ * @param in Stream from which to read contents, which will update the Mac.
+ * @param mac Mac to be updated during writes.
+ */
+ public MacInputStream(InputStream in, Mac mac) {
+ super(in);
+ this.mac = mac;
+ }
+
+ @Override
+ public int read() throws IOException {
+ int b = in.read();
+ mac.update((byte) b);
+ return b;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ int read = in.read(b, off, len);
+ mac.update(b);
+ return read;
+ }
+
+}
diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/MacOutputStream.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/MacOutputStream.java
new file mode 100644
index 000000000..785478197
--- /dev/null
+++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/MacOutputStream.java
@@ -0,0 +1,37 @@
+package org.cryptomator.crypto.aes256;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.crypto.Mac;
+
+/**
+ * Updates a {@link Mac} with the bytes written to this stream.
+ */
+class MacOutputStream extends FilterOutputStream {
+
+ private final Mac mac;
+
+ /**
+ * @param out Stream to redirect contents to after updating the mac.
+ * @param mac Mac to be updated during writes.
+ */
+ public MacOutputStream(OutputStream out, Mac mac) {
+ super(out);
+ this.mac = mac;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ mac.update((byte) b);
+ out.write(b);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ mac.update(b, off, len);
+ out.write(b, off, len);
+ }
+
+}
diff --git a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java b/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java
index 163306b5d..51e675358 100644
--- a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java
+++ b/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java
@@ -25,6 +25,7 @@ import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
import org.cryptomator.crypto.exceptions.WrongPasswordException;
import org.junit.Assert;
+import org.junit.Ignore;
import org.junit.Test;
public class Aes256CryptorTest {
@@ -72,6 +73,31 @@ public class Aes256CryptorTest {
}
}
+ @Ignore
+ @Test
+ public void testIntegrityAuthentication() throws IOException {
+ // our test plaintext data:
+ final byte[] plaintextData = "Hello World".getBytes();
+ final InputStream plaintextIn = new ByteArrayInputStream(plaintextData);
+
+ // init cryptor:
+ final Aes256Cryptor cryptor = new Aes256Cryptor(TEST_PRNG);
+
+ // encrypt:
+ final ByteBuffer encryptedData = ByteBuffer.allocate(64 + plaintextData.length * 4);
+ final SeekableByteChannel encryptedOut = new ByteBufferBackedSeekableChannel(encryptedData);
+ cryptor.encryptFile(plaintextIn, encryptedOut);
+ IOUtils.closeQuietly(plaintextIn);
+ IOUtils.closeQuietly(encryptedOut);
+
+ encryptedData.position(0);
+
+ // authenticate unmodified content:
+ final SeekableByteChannel encryptedIn = new ByteBufferBackedSeekableChannel(encryptedData);
+ final boolean unmodifiedContent = cryptor.authenticateContent(encryptedIn);
+ Assert.assertTrue(unmodifiedContent);
+ }
+
@Test
public void testEncryptionAndDecryption() throws IOException, DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException {
// our test plaintext data:
@@ -82,19 +108,25 @@ public class Aes256CryptorTest {
final Aes256Cryptor cryptor = new Aes256Cryptor(TEST_PRNG);
// encrypt:
- final ByteBuffer encryptedData = ByteBuffer.allocate(plaintextData.length + 200);
+ final ByteBuffer encryptedData = ByteBuffer.allocate(64 + plaintextData.length * 4);
final SeekableByteChannel encryptedOut = new ByteBufferBackedSeekableChannel(encryptedData);
cryptor.encryptFile(plaintextIn, encryptedOut);
IOUtils.closeQuietly(plaintextIn);
IOUtils.closeQuietly(encryptedOut);
- // decrypt:
+ encryptedData.position(0);
+
+ // decrypt file size:
final SeekableByteChannel encryptedIn = new ByteBufferBackedSeekableChannel(encryptedData);
+ final Long filesize = cryptor.decryptedContentLength(encryptedIn);
+ Assert.assertEquals(plaintextData.length, filesize.longValue());
+
+ // decrypt:
final ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream();
final Long numDecryptedBytes = cryptor.decryptedFile(encryptedIn, plaintextOut);
IOUtils.closeQuietly(encryptedIn);
IOUtils.closeQuietly(plaintextOut);
- Assert.assertTrue(numDecryptedBytes > 0);
+ Assert.assertEquals(filesize.longValue(), numDecryptedBytes.longValue());
// check decrypted data:
final byte[] result = plaintextOut.toByteArray();
@@ -115,12 +147,14 @@ public class Aes256CryptorTest {
final Aes256Cryptor cryptor = new Aes256Cryptor(TEST_PRNG);
// encrypt:
- final ByteBuffer encryptedData = ByteBuffer.allocate(plaintextData.length + 200);
+ final ByteBuffer encryptedData = ByteBuffer.allocate(64 + plaintextData.length * 4);
final SeekableByteChannel encryptedOut = new ByteBufferBackedSeekableChannel(encryptedData);
cryptor.encryptFile(plaintextIn, encryptedOut);
IOUtils.closeQuietly(plaintextIn);
IOUtils.closeQuietly(encryptedOut);
+ encryptedData.position(0);
+
// decrypt:
final SeekableByteChannel encryptedIn = new ByteBufferBackedSeekableChannel(encryptedData);
final ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream();
diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java
index 99a3608dd..48b962cdf 100644
--- a/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java
+++ b/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java
@@ -68,6 +68,11 @@ public interface Cryptor extends SensitiveDataSwipeListener {
*/
String decryptPath(String encryptedPath, char encryptedPathSep, char cleartextPathSep, CryptorIOSupport ioSupport);
+ /**
+ * @return true 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 null if unknown.
@@ -76,15 +81,17 @@ public interface Cryptor extends SensitiveDataSwipeListener {
/**
* @return Number of decrypted bytes. This might not be equal to the encrypted file size due to optional metadata written to it.
+ * @throws DecryptFailedException If decryption failed
*/
- Long decryptedFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException;
+ Long decryptedFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException, DecryptFailedException;
/**
* @param pos First byte (inclusive)
* @param length Number of requested bytes beginning at pos.
* @return Number of decrypted bytes. This might not be equal to the number of bytes requested due to potential overheads.
+ * @throws DecryptFailedException If decryption failed
*/
- Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plaintextFile, long pos, long length) throws IOException;
+ Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plaintextFile, long pos, long length) throws IOException, DecryptFailedException;
/**
* @return Number of encrypted bytes. This might not be equal to the encrypted file size due to optional metadata written to it.
diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/SamplingDecorator.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/SamplingDecorator.java
index 656e7e840..19cd9a2ca 100644
--- a/main/crypto-api/src/main/java/org/cryptomator/crypto/SamplingDecorator.java
+++ b/main/crypto-api/src/main/java/org/cryptomator/crypto/SamplingDecorator.java
@@ -76,19 +76,24 @@ 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);
}
@Override
- public Long decryptedFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException {
+ public Long decryptedFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException, DecryptFailedException {
final OutputStream countingInputStream = new CountingOutputStream(decryptedBytes, plaintextFile);
return cryptor.decryptedFile(encryptedFile, countingInputStream);
}
@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 {
final OutputStream countingInputStream = new CountingOutputStream(decryptedBytes, plaintextFile);
return cryptor.decryptRange(encryptedFile, countingInputStream, pos, length);
}
diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/DecryptFailedException.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/DecryptFailedException.java
index b815e19e7..f90a97a4b 100644
--- a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/DecryptFailedException.java
+++ b/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/DecryptFailedException.java
@@ -6,4 +6,8 @@ public class DecryptFailedException extends StorageCryptingException {
public DecryptFailedException(Throwable t) {
super("Decryption failed.", t);
}
+
+ protected DecryptFailedException(String msg) {
+ super(msg);
+ }
}
\ No newline at end of file