Browse Source

prohibiting block swapping by adding file-IV and blocknumber to MAC

Sebastian Stenzel 9 years ago
parent
commit
853744002c

+ 13 - 8
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java

@@ -120,7 +120,8 @@ class FileContentDecryptorImpl implements FileContentDecryptor {
 	private class DecryptionJob implements Callable<ByteBuffer> {
 
 		private final byte[] nonce;
-		private final ByteBuffer ciphertextChunk;
+		private final ByteBuffer inBuf;
+		private final ByteBuffer chunkNumberBigEndian = ByteBuffer.allocate(Long.BYTES);
 		private final byte[] expectedMac;
 
 		public DecryptionJob(ByteBuffer ciphertextChunk, long chunkNumber) {
@@ -131,8 +132,10 @@ class FileContentDecryptorImpl implements FileContentDecryptor {
 			ByteBuffer nonceBuf = ciphertextChunk.asReadOnlyBuffer();
 			nonceBuf.position(0).limit(NONCE_SIZE);
 			nonceBuf.get(nonce);
-			this.ciphertextChunk = ciphertextChunk.asReadOnlyBuffer();
-			this.ciphertextChunk.position(NONCE_SIZE).limit(ciphertextChunk.limit() - MAC_SIZE);
+			this.inBuf = ciphertextChunk.asReadOnlyBuffer();
+			this.inBuf.position(NONCE_SIZE).limit(ciphertextChunk.limit() - MAC_SIZE);
+			chunkNumberBigEndian.putLong(chunkNumber);
+			chunkNumberBigEndian.rewind();
 			this.expectedMac = new byte[MAC_SIZE];
 			ByteBuffer macBuf = ciphertextChunk.asReadOnlyBuffer();
 			macBuf.position(macBuf.limit() - MAC_SIZE);
@@ -144,8 +147,10 @@ class FileContentDecryptorImpl implements FileContentDecryptor {
 			try {
 				if (authenticate) {
 					Mac mac = hmacSha256.get();
+					mac.update(header.getIv());
+					mac.update(chunkNumberBigEndian.asReadOnlyBuffer());
 					mac.update(nonce);
-					mac.update(ciphertextChunk.asReadOnlyBuffer());
+					mac.update(inBuf.asReadOnlyBuffer());
 					if (!MessageDigest.isEqual(expectedMac, mac.doFinal())) {
 						throw new AuthenticationFailedException();
 					}
@@ -153,10 +158,10 @@ class FileContentDecryptorImpl implements FileContentDecryptor {
 
 				Cipher cipher = ThreadLocalAesCtrCipher.get();
 				cipher.init(Cipher.DECRYPT_MODE, header.getPayload().getContentKey(), new IvParameterSpec(nonce));
-				ByteBuffer cleartextChunk = ByteBuffer.allocate(cipher.getOutputSize(ciphertextChunk.remaining()));
-				cipher.update(ciphertextChunk, cleartextChunk);
-				cleartextChunk.flip();
-				return cleartextChunk;
+				ByteBuffer outBuf = ByteBuffer.allocate(cipher.getOutputSize(inBuf.remaining()));
+				cipher.update(inBuf, outBuf);
+				outBuf.flip();
+				return outBuf;
 			} catch (InvalidKeyException e) {
 				throw new IllegalStateException("File content key created by current class invalid.", e);
 			} catch (ShortBufferException e) {

+ 20 - 12
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java

@@ -26,6 +26,7 @@ import javax.crypto.SecretKey;
 import javax.crypto.ShortBufferException;
 import javax.crypto.spec.IvParameterSpec;
 
+import org.apache.commons.codec.binary.Hex;
 import org.cryptomator.crypto.engine.FileContentCryptor;
 import org.cryptomator.crypto.engine.FileContentEncryptor;
 import org.cryptomator.io.ByteBuffers;
@@ -127,10 +128,13 @@ class FileContentEncryptorImpl implements FileContentEncryptor {
 
 	private class EncryptionJob implements Callable<ByteBuffer> {
 
-		private final ByteBuffer cleartextChunk;
+		private final ByteBuffer inBuf;
+		private final ByteBuffer chunkNumberBigEndian = ByteBuffer.allocate(Long.BYTES);
 
 		public EncryptionJob(ByteBuffer cleartextChunk, long chunkNumber) {
-			this.cleartextChunk = cleartextChunk;
+			this.inBuf = cleartextChunk;
+			chunkNumberBigEndian.putLong(chunkNumber);
+			chunkNumberBigEndian.rewind();
 		}
 
 		@Override
@@ -138,28 +142,32 @@ class FileContentEncryptorImpl implements FileContentEncryptor {
 			try {
 				final Cipher cipher = ThreadLocalAesCtrCipher.get();
 				final Mac mac = hmacSha256.get();
-				final ByteBuffer ciphertextChunk = ByteBuffer.allocate(NONCE_SIZE + cleartextChunk.remaining() + mac.getMacLength());
+				final ByteBuffer outBuf = ByteBuffer.allocate(NONCE_SIZE + inBuf.remaining() + mac.getMacLength());
 
 				// nonce
 				byte[] nonce = new byte[NONCE_SIZE];
 				randomSource.nextBytes(nonce);
-				ciphertextChunk.put(nonce);
+				outBuf.put(nonce);
 
 				// payload:
 				cipher.init(Cipher.ENCRYPT_MODE, header.getPayload().getContentKey(), new IvParameterSpec(nonce));
-				assert cipher.getOutputSize(cleartextChunk.remaining()) == cleartextChunk.remaining() : "input length should be equal to output length in CTR mode.";
-				cipher.update(cleartextChunk, ciphertextChunk);
+				assert cipher.getOutputSize(inBuf.remaining()) == inBuf.remaining() : "input length should be equal to output length in CTR mode.";
+				int bytesEncrypted = cipher.update(inBuf, outBuf);
 
 				// mac:
-				ByteBuffer ciphertextSoFar = ciphertextChunk.asReadOnlyBuffer();
-				ciphertextSoFar.flip();
-				mac.update(ciphertextSoFar);
+				ByteBuffer ciphertextBuf = outBuf.asReadOnlyBuffer();
+				ciphertextBuf.position(NONCE_SIZE).limit(NONCE_SIZE + bytesEncrypted);
+				mac.update(header.getIv());
+				mac.update(chunkNumberBigEndian.asReadOnlyBuffer());
+				mac.update(nonce);
+				mac.update(ciphertextBuf);
 				byte[] authenticationCode = mac.doFinal();
-				ciphertextChunk.put(authenticationCode);
+				Hex.encodeHexString(authenticationCode);
+				outBuf.put(authenticationCode);
 
 				// flip and return:
-				ciphertextChunk.flip();
-				return ciphertextChunk;
+				outBuf.flip();
+				return outBuf;
 			} catch (InvalidKeyException e) {
 				throw new IllegalStateException("File content key created by current class invalid.", e);
 			} catch (ShortBufferException e) {

+ 3 - 3
main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImplTest.java

@@ -46,7 +46,7 @@ public class FileContentDecryptorImplTest {
 		final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES");
 		final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256");
 		final byte[] header = Base64.decode("AAAAAAAAAAAAAAAAAAAAANyVwHiiQImjrUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga26lJzstK9RUv1hj5zDC4wC9FgMfoVE1mD0HnuENuYXkJA==");
-		final byte[] content = Base64.decode("AAAAAAAAAAAAAAAAAAAAALTwrBTNYP7m3yTG+8Yv6jcvXJj89WiHAxAtgbZR7mpsskLFfGCVDm6NO8U=");
+		final byte[] content = Base64.decode("AAAAAAAAAAAAAAAAAAAAALTwrBTNYP7m3yTGKlhka9WPvX1Lpn5EYfVxlyX1ISgRXtdRnivM7r6F3Og=");
 
 		try (FileContentDecryptor decryptor = new FileContentDecryptorImpl(headerKey, macKey, ByteBuffer.wrap(header), 0, true)) {
 			decryptor.append(ByteBuffer.wrap(Arrays.copyOfRange(content, 0, 15)));
@@ -69,7 +69,7 @@ public class FileContentDecryptorImplTest {
 		final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES");
 		final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256");
 		final byte[] header = Base64.decode("AAAAAAAAAAAAAAAAAAAAANyVwHiiQImjrUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga26lJzstK9RUv1hj5zDC4wC9FgMfoVE1mD0HnuENuYXkJA==");
-		final byte[] content = Base64.decode("aAAAAAAAAAAAAAAAAAAAALTwrBTNYP7m3yTG+8Yv6jcvXJj89WiHAxAtgbZR7mpsskLFfGCVDm6NO8U=");
+		final byte[] content = Base64.decode("aAAAAAAAAAAAAAAAAAAAALTwrBTNYP7m3yTGKlhka9WPvX1Lpn5EYfVxlyX1ISgRXtdRnivM7r6F3Og=");
 
 		try (FileContentDecryptor decryptor = new FileContentDecryptorImpl(headerKey, macKey, ByteBuffer.wrap(header), 0, true)) {
 			decryptor.append(ByteBuffer.wrap(Arrays.copyOfRange(content, 0, 15)));
@@ -90,7 +90,7 @@ public class FileContentDecryptorImplTest {
 		final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES");
 		final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256");
 		final byte[] header = Base64.decode("AAAAAAAAAAAAAAAAAAAAANyVwHiiQImjrUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga26lJzstK9RUv1hj5zDC4wC9FgMfoVE1mD0HnuENuYXkJA==");
-		final byte[] content = Base64.decode("AAAAAAAAAAAAAAAAAAAAALTwrBTNYP7m3yTG+8Yv6jcvXJj89WiHAxAtgbZR7mpsskLFfGCVDm6NO8u=");
+		final byte[] content = Base64.decode("AAAAAAAAAAAAAAAAAAAAALTwrBTNYP7m3yTGKlhka9WPvX1Lpn5EYfVxlyX1ISgRXtdRnivM7r6F3OG=");
 
 		try (FileContentDecryptor decryptor = new FileContentDecryptorImpl(headerKey, macKey, ByteBuffer.wrap(header), 0, false)) {
 			decryptor.append(ByteBuffer.wrap(Arrays.copyOfRange(content, 0, 15)));

+ 14 - 5
main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImplTest.java

@@ -54,11 +54,20 @@ public class FileContentEncryptorImplTest {
 				ByteBuffers.copy(buf, result);
 			}
 
-			// Ciphertext: echo -n "hello world" | openssl enc -aes-256-ctr -K 0000000000000000000000000000000000000000000000000000000000000000 -iv 00000000000000000000000000000000 | base64
-			// MAC: echo -n "AAAAAAAAAAAAAAAAAAAAAA==" | base64 --decode > A; echo -n "tPCsFM1g/ubfJMY=" | base64 --decode >> A; cat A | openssl dgst -sha256 -mac HMAC -macopt
-			// hexkey:0000000000000000000000000000000000000000000000000000000000000000 -binary | base64
-			// echo -n "+8Yv6jcvXJj89WiHAxAtgbZR7mpsskLFfGCVDm6NO8U=" | base64 --decode >> A; cat A | base64
-			Assert.assertArrayEquals(Base64.decode("AAAAAAAAAAAAAAAAAAAAALTwrBTNYP7m3yTG+8Yv6jcvXJj89WiHAxAtgbZR7mpsskLFfGCVDm6NO8U="), result.array());
+			// # CIPHERTEXT:
+			// echo -n "hello world" | openssl enc -aes-256-ctr -K 0000000000000000000000000000000000000000000000000000000000000000 -iv 00000000000000000000000000000000 | base64
+			//
+			// # MAC:
+			// # 0x00-bytes for IV + blocknumber + nonce: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
+			// echo -n "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" | base64 --decode > A; echo -n "tPCsFM1g/ubfJMY=" | base64 --decode >> A;
+			// cat A | openssl dgst -sha256 -mac HMAC -macopt hexkey:0000000000000000000000000000000000000000000000000000000000000000 -binary | base64
+			//
+			// # FULL CHUNK:
+			// echo -n "AAAAAAAAAAAAAAAAAAAAAA==" | base64 --decode > B;
+			// echo -n "tPCsFM1g/ubfJMY=" | base64 --decode >> B;
+			// echo -n "Klhka9WPvX1Lpn5EYfVxlyX1ISgRXtdRnivM7r6F3Og=" | base64 --decode >> B;
+			// cat B | base64
+			Assert.assertArrayEquals(Base64.decode("AAAAAAAAAAAAAAAAAAAAALTwrBTNYP7m3yTGKlhka9WPvX1Lpn5EYfVxlyX1ISgRXtdRnivM7r6F3Og="), result.array());
 		}
 	}