Преглед изворни кода

sensitive header data is now encrypted using a single enc_cbc(concat(fileSize, fileKey)) instead of encrypting both data separately

Sebastian Stenzel пре 9 година
родитељ
комит
93a87c86a4

+ 1 - 1
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileConstants.java

@@ -21,7 +21,7 @@ interface FileConstants {
 	/**
 	 * Number of bytes in the file header.
 	 */
-	long FILE_HEADER_LENGTH = 96;
+	long FILE_HEADER_LENGTH = 104;
 
 	/**
 	 * Allow range requests for files > 32MiB.

+ 90 - 72
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java

@@ -302,7 +302,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 	public Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException, MacAuthenticationFailedException {
 		// read header:
 		encryptedFile.position(0);
-		final ByteBuffer headerBuf = ByteBuffer.allocate(96);
+		final ByteBuffer headerBuf = ByteBuffer.allocate(104);
 		final int headerBytesRead = encryptedFile.read(headerBuf);
 		if (headerBytesRead != headerBuf.capacity()) {
 			return null;
@@ -313,20 +313,20 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 		headerBuf.position(0);
 		headerBuf.get(iv);
 
-		// read content length:
-		final byte[] encryptedContentLengthBytes = new byte[AES_BLOCK_LENGTH];
-		headerBuf.position(16);
-		headerBuf.get(encryptedContentLengthBytes);
+		// read sensitive header data:
+		final byte[] encryptedSensitiveHeaderContentBytes = new byte[48];
+		headerBuf.position(24);
+		headerBuf.get(encryptedSensitiveHeaderContentBytes);
 
 		// read stored header mac:
 		final byte[] storedHeaderMac = new byte[32];
-		headerBuf.position(64);
+		headerBuf.position(72);
 		headerBuf.get(storedHeaderMac);
 
-		// calculate mac over first 64 bytes of header:
+		// calculate mac over first 72 bytes of header:
 		final Mac headerMac = this.hmacSha256(hMacMasterKey);
 		headerBuf.rewind();
-		headerBuf.limit(64);
+		headerBuf.limit(72);
 		headerMac.update(headerBuf);
 
 		final boolean macMatches = MessageDigest.isEqual(storedHeaderMac, headerMac.doFinal());
@@ -334,9 +334,12 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 			throw new MacAuthenticationFailedException("MAC authentication failed.");
 		}
 
-		final byte[] decryptedContentLengthBytes = decryptHeaderData(encryptedContentLengthBytes, iv);
-		final ByteBuffer fileSizeBuffer = ByteBuffer.wrap(decryptedContentLengthBytes);
-		return fileSizeBuffer.getLong();
+		// decrypt sensitive header data:
+		final byte[] decryptedSensitiveHeaderContentBytes = decryptHeaderData(encryptedSensitiveHeaderContentBytes, iv);
+		final ByteBuffer sensitiveHeaderContentBuf = ByteBuffer.wrap(decryptedSensitiveHeaderContentBytes);
+		final Long fileSize = sensitiveHeaderContentBuf.getLong();
+
+		return fileSize;
 	}
 
 	private byte[] decryptHeaderData(byte[] ciphertextBytes, byte[] iv) {
@@ -361,7 +364,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 	public Long decryptFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile, boolean authenticate) throws IOException, DecryptFailedException {
 		// read header:
 		encryptedFile.position(0l);
-		final ByteBuffer headerBuf = ByteBuffer.allocate(96);
+		final ByteBuffer headerBuf = ByteBuffer.allocate(104);
 		final int headerBytesRead = encryptedFile.read(headerBuf);
 		if (headerBytesRead != headerBuf.capacity()) {
 			throw new IOException("Failed to read file header.");
@@ -372,45 +375,47 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 		headerBuf.position(0);
 		headerBuf.get(iv);
 
-		// derive nonce used in counter mode from IV by setting last 64bit to 0:
-		final ByteBuffer nonceBuf = ByteBuffer.wrap(iv.clone());
-		nonceBuf.putLong(AES_BLOCK_LENGTH - Long.BYTES, 0);
-		final byte[] nonce = nonceBuf.array();
-
-		// read content length:
-		final byte[] encryptedContentLengthBytes = new byte[AES_BLOCK_LENGTH];
+		// read nonce:
+		final byte[] nonce = new byte[8];
 		headerBuf.position(16);
-		headerBuf.get(encryptedContentLengthBytes);
-		final byte[] decryptedContentLengthBytes = decryptHeaderData(encryptedContentLengthBytes, iv);
-		final ByteBuffer fileSizeBuffer = ByteBuffer.wrap(decryptedContentLengthBytes);
-		final Long fileSize = fileSizeBuffer.getLong();
+		headerBuf.get(nonce);
+		final ByteBuffer nonceAndCounterBuf = ByteBuffer.allocate(AES_BLOCK_LENGTH);
+		nonceAndCounterBuf.put(nonce);
+		nonceAndCounterBuf.putLong(0L);
+		final byte[] nonceAndCounter = nonceAndCounterBuf.array();
 
-		// read content key:
-		final byte[] encryptedContentKeyBytes = new byte[32];
-		headerBuf.position(32);
-		headerBuf.get(encryptedContentKeyBytes);
-		final byte[] contentKeyBytes = decryptHeaderData(encryptedContentKeyBytes, iv);
+		// read sensitive header data:
+		final byte[] encryptedSensitiveHeaderContentBytes = new byte[48];
+		headerBuf.position(24);
+		headerBuf.get(encryptedSensitiveHeaderContentBytes);
 
 		// read header mac:
 		final byte[] storedHeaderMac = new byte[32];
-		headerBuf.position(64);
+		headerBuf.position(72);
 		headerBuf.get(storedHeaderMac);
 
-		// calculate mac over first 64 bytes of header:
+		// calculate mac over first 72 bytes of header:
 		if (authenticate) {
 			final Mac headerMac = this.hmacSha256(hMacMasterKey);
 			headerBuf.position(0);
-			headerBuf.limit(64);
+			headerBuf.limit(72);
 			headerMac.update(headerBuf);
 			if (!MessageDigest.isEqual(storedHeaderMac, headerMac.doFinal())) {
 				throw new MacAuthenticationFailedException("Header MAC authentication failed.");
 			}
 		}
 
+		// decrypt sensitive header data:
+		final byte[] fileKeyBytes = new byte[32];
+		final byte[] decryptedSensitiveHeaderContentBytes = decryptHeaderData(encryptedSensitiveHeaderContentBytes, iv);
+		final ByteBuffer sensitiveHeaderContentBuf = ByteBuffer.wrap(decryptedSensitiveHeaderContentBytes);
+		final Long fileSize = sensitiveHeaderContentBuf.getLong();
+		sensitiveHeaderContentBuf.get(fileKeyBytes);
+
 		// content decryption:
-		encryptedFile.position(96l);
-		final SecretKey contentKey = new SecretKeySpec(contentKeyBytes, AES_KEY_ALGORITHM);
-		final Cipher cipher = this.aesCtrCipher(contentKey, nonce, Cipher.DECRYPT_MODE);
+		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);
 
 		// reading ciphered input and MACs interleaved:
@@ -440,7 +445,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 			plaintextFile.write(plaintext, 0, plaintextLengthWithoutPadding);
 			bytesDecrypted += plaintextLengthWithoutPadding;
 		}
-		destroyQuietly(contentKey);
+		destroyQuietly(fileKey);
 
 		return bytesDecrypted;
 	}
@@ -449,7 +454,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 	public Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plaintextFile, long pos, long length, boolean authenticate) throws IOException, DecryptFailedException {
 		// read header:
 		encryptedFile.position(0l);
-		final ByteBuffer headerBuf = ByteBuffer.allocate(96);
+		final ByteBuffer headerBuf = ByteBuffer.allocate(104);
 		final int headerBytesRead = encryptedFile.read(headerBuf);
 		if (headerBytesRead != headerBuf.capacity()) {
 			throw new IOException("Failed to read file header.");
@@ -460,46 +465,57 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 		headerBuf.position(0);
 		headerBuf.get(iv);
 
-		// read content key:
-		final byte[] encryptedContentKeyBytes = new byte[32];
-		headerBuf.position(32);
-		headerBuf.get(encryptedContentKeyBytes);
-		final byte[] contentKeyBytes = decryptHeaderData(encryptedContentKeyBytes, iv);
+		// read nonce:
+		final byte[] nonce = new byte[8];
+		headerBuf.position(16);
+		headerBuf.get(nonce);
+
+		// read sensitive header data:
+		final byte[] encryptedSensitiveHeaderContentBytes = new byte[48];
+		headerBuf.position(24);
+		headerBuf.get(encryptedSensitiveHeaderContentBytes);
 
 		// read header mac:
 		final byte[] storedHeaderMac = new byte[32];
-		headerBuf.position(64);
+		headerBuf.position(72);
 		headerBuf.get(storedHeaderMac);
 
-		// calculate mac over first 64 bytes of header:
+		// calculate mac over first 72 bytes of header:
 		if (authenticate) {
 			final Mac headerMac = this.hmacSha256(hMacMasterKey);
 			headerBuf.position(0);
-			headerBuf.limit(64);
+			headerBuf.limit(72);
 			headerMac.update(headerBuf);
 			if (!MessageDigest.isEqual(storedHeaderMac, headerMac.doFinal())) {
 				throw new MacAuthenticationFailedException("Header MAC authentication failed.");
 			}
 		}
 
+		// decrypt sensitive header data:
+		final byte[] fileKeyBytes = new byte[32];
+		final byte[] decryptedSensitiveHeaderContentBytes = decryptHeaderData(encryptedSensitiveHeaderContentBytes, iv);
+		final ByteBuffer sensitiveHeaderContentBuf = ByteBuffer.wrap(decryptedSensitiveHeaderContentBytes);
+		sensitiveHeaderContentBuf.position(Long.BYTES); // skip file size
+		sensitiveHeaderContentBuf.get(fileKeyBytes);
+
 		// find first relevant block:
 		final long startBlock = pos / CONTENT_MAC_BLOCK; // floor
-		final long startByte = startBlock * (CONTENT_MAC_BLOCK + 32) + 96l;
+		final long startByte = startBlock * (CONTENT_MAC_BLOCK + 32) + 104;
 		final long offsetFromFirstBlock = pos - startBlock * CONTENT_MAC_BLOCK;
 
-		// derive nonce used in counter mode from IV by setting last 64bit to 0:
-		final ByteBuffer nonceBuf = ByteBuffer.wrap(iv.clone());
-		nonceBuf.putLong(AES_BLOCK_LENGTH - Long.BYTES, startBlock * CONTENT_MAC_BLOCK / AES_BLOCK_LENGTH);
-		final byte[] nonce = nonceBuf.array();
+		// append correct counter value to nonce:
+		final ByteBuffer nonceAndCounterBuf = ByteBuffer.allocate(AES_BLOCK_LENGTH);
+		nonceAndCounterBuf.put(nonce);
+		nonceAndCounterBuf.putLong(startBlock * CONTENT_MAC_BLOCK / AES_BLOCK_LENGTH);
+		final byte[] nonceAndCounter = nonceAndCounterBuf.array();
 
 		// content decryption:
 		encryptedFile.position(startByte);
-		final SecretKey contentKey = new SecretKeySpec(contentKeyBytes, AES_KEY_ALGORITHM);
-		final Cipher cipher = this.aesCtrCipher(contentKey, nonce, Cipher.DECRYPT_MODE);
+		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);
 
 		try {
-
 			// reading ciphered input and MACs interleaved:
 			long bytesWritten = 0;
 			final InputStream in = new SeekableByteChannelInputStream(encryptedFile);
@@ -531,35 +547,36 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 				plaintextFile.write(plaintext, offset, currentBatch);
 				bytesWritten += currentBatch;
 			}
-
 			return bytesWritten;
 		} finally {
-			destroyQuietly(contentKey);
+			destroyQuietly(fileKey);
 		}
 	}
 
 	/**
-	 * header = {16 byte iv, 16 byte filesize, 32 byte contentKey, 32 byte headerMac}
+	 * header = {16 byte iv, 8 byte nonce, 48 byte sensitive header data (file size + file key + padding), 32 byte headerMac}
 	 */
 	@Override
 	public Long encryptFile(InputStream plaintextFile, SeekableByteChannel encryptedFile) throws IOException, EncryptFailedException {
 		// truncate file
 		encryptedFile.truncate(0l);
 
-		// choose a random IV:
+		// choose a random header IV:
 		final byte[] iv = randomData(AES_BLOCK_LENGTH);
 
-		// derive nonce used in counter mode from IV by setting last 64bit to 0:
-		final ByteBuffer nonceBuf = ByteBuffer.wrap(iv.clone());
-		nonceBuf.putLong(AES_BLOCK_LENGTH - Long.BYTES, 0);
-		final byte[] nonce = nonceBuf.array();
+		// chosse 8 byte random nonce and 8 byte counter set to zero:
+		final byte[] nonce = randomData(8);
+		final ByteBuffer nonceAndCounterBuf = ByteBuffer.allocate(AES_BLOCK_LENGTH);
+		nonceAndCounterBuf.put(nonce);
+		nonceAndCounterBuf.putLong(0L);
+		final byte[] nonceAndCounter = nonceAndCounterBuf.array();
 
 		// choose a random content key:
-		final byte[] contentKeyBytes = randomData(32);
+		final byte[] fileKeyBytes = randomData(32);
 
-		// 96 byte header buffer (16 IV, 16 size, 32 content key, 32 headerMac), filled after writing the content
-		final ByteBuffer headerBuf = ByteBuffer.allocate(96);
-		headerBuf.limit(96);
+		// 104 byte header buffer (16 header IV, 8 content nonce, 48 sensitive header data, 32 headerMac), filled after writing the content
+		final ByteBuffer headerBuf = ByteBuffer.allocate(104);
+		headerBuf.limit(104);
 		encryptedFile.write(headerBuf);
 
 		// add random length padding to obfuscate file length:
@@ -567,8 +584,8 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 		final LengthObfuscationInputStream in = new LengthObfuscationInputStream(plaintextFile, randomPadding);
 
 		// content encryption:
-		final SecretKey contentKey = new SecretKeySpec(contentKeyBytes, AES_KEY_ALGORITHM);
-		final Cipher cipher = this.aesCtrCipher(contentKey, nonce, Cipher.ENCRYPT_MODE);
+		final SecretKey fileKey = new SecretKeySpec(fileKeyBytes, AES_KEY_ALGORITHM);
+		final Cipher cipher = this.aesCtrCipher(fileKey, nonceAndCounter, Cipher.ENCRYPT_MODE);
 		final Mac contentMac = this.hmacSha256(hMacMasterKey);
 		@SuppressWarnings("resource")
 		final OutputStream out = new SeekableByteChannelOutputStream(encryptedFile);
@@ -583,20 +600,21 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
 			final byte[] mac = contentMac.doFinal();
 			out.write(mac);
 		}
-		destroyQuietly(contentKey);
+		destroyQuietly(fileKey);
 
 		// create and write header:
 		final long plaintextSize = in.getRealInputLength();
-		final ByteBuffer fileSizeBuffer = ByteBuffer.allocate(AES_BLOCK_LENGTH);
-		fileSizeBuffer.putLong(plaintextSize);
+		final ByteBuffer sensitiveHeaderContentBuf = ByteBuffer.allocate(Long.BYTES + fileKeyBytes.length);
+		sensitiveHeaderContentBuf.putLong(plaintextSize);
+		sensitiveHeaderContentBuf.put(fileKeyBytes);
 		headerBuf.clear();
 		headerBuf.put(iv);
-		headerBuf.put(encryptHeaderData(fileSizeBuffer.array(), iv));
-		headerBuf.put(encryptHeaderData(contentKeyBytes, iv));
+		headerBuf.put(nonce);
+		headerBuf.put(encryptHeaderData(sensitiveHeaderContentBuf.array(), iv));
 		headerBuf.flip();
 		final Mac headerMac = this.hmacSha256(hMacMasterKey);
 		headerMac.update(headerBuf);
-		headerBuf.limit(96);
+		headerBuf.limit(104);
 		headerBuf.put(headerMac.doFinal());
 		headerBuf.flip();
 		encryptedFile.position(0);

+ 1 - 1
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java

@@ -74,7 +74,7 @@ interface AesCryptographicConfiguration {
 	 * 
 	 * @see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#impl
 	 */
-	String AES_CBC_CIPHER = "AES/CBC/NoPadding";
+	String AES_CBC_CIPHER = "AES/CBC/PKCS5Padding";
 
 	/**
 	 * AES block size is 128 bit or 16 bytes.