From e19cf1c942c4688e5866d875e7494c3a97cb7c8e Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 6 Jan 2015 01:23:16 +0100 Subject: [PATCH] - Changed file layout, added MAC (see #17) - Obfuscates file size (fixes #18) --- .../jackrabbit/resources/EncryptedFile.java | 6 +- .../resources/EncryptedFilePart.java | 7 +- .../crypto/aes256/Aes256Cryptor.java | 164 ++++++++++++------ .../aes256/AesCryptographicConfiguration.java | 7 + .../crypto/aes256/MacInputStream.java | 39 +++++ .../crypto/aes256/MacOutputStream.java | 37 ++++ .../crypto/aes256/Aes256CryptorTest.java | 42 ++++- .../java/org/cryptomator/crypto/Cryptor.java | 11 +- .../cryptomator/crypto/SamplingDecorator.java | 9 +- .../exceptions/DecryptFailedException.java | 4 + 10 files changed, 260 insertions(+), 66 deletions(-) create mode 100644 main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/MacInputStream.java create mode 100644 main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/MacOutputStream.java 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