From 853744002c6f723b627faba23cb390d0f59f9be9 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 8 Feb 2016 18:47:02 +0100 Subject: [PATCH] prohibiting block swapping by adding file-IV and blocknumber to MAC --- .../engine/impl/FileContentDecryptorImpl.java | 21 +++++++----- .../engine/impl/FileContentEncryptorImpl.java | 32 ++++++++++++------- .../impl/FileContentDecryptorImplTest.java | 6 ++-- .../impl/FileContentEncryptorImplTest.java | 19 ++++++++--- 4 files changed, 50 insertions(+), 28 deletions(-) diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java index 1b52e2729..af9e46061 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java +++ b/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 { 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) { diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java index 62c789478..e2927c224 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java +++ b/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 { - 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) { diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImplTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImplTest.java index 91b80699b..885c453db 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImplTest.java +++ b/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))); diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImplTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImplTest.java index 45fa06c01..8653e1f47 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImplTest.java +++ b/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()); } }