From 4a60e941830af3a3508d1813007fbfec38fc99fc Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 8 Feb 2016 13:14:20 +0100 Subject: [PATCH] version 3 header (no nonce, CTR mode) --- .../crypto/engine/impl/FileHeader.java | 29 ++++--------------- .../crypto/engine/impl/FileHeaderPayload.java | 15 ++++------ .../engine/impl/FileHeaderPayloadTest.java | 6 ++-- .../crypto/engine/impl/FileHeaderTest.java | 12 ++++---- 4 files changed, 20 insertions(+), 42 deletions(-) diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeader.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeader.java index fb7292561..c6ab7bc9b 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeader.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeader.java @@ -19,32 +19,26 @@ import javax.security.auth.Destroyable; class FileHeader implements Destroyable { - static final int HEADER_SIZE = 104; + static final int HEADER_SIZE = 88; private static final int IV_POS = 0; private static final int IV_LEN = 16; - private static final int NONCE_POS = 16; - private static final int NONCE_LEN = 8; - private static final int PAYLOAD_POS = 24; - private static final int PAYLOAD_LEN = 48; - private static final int MAC_POS = 72; + private static final int PAYLOAD_POS = 16; + private static final int PAYLOAD_LEN = 40; + private static final int MAC_POS = 56; private static final int MAC_LEN = 32; private final byte[] iv; - private final byte[] nonce; private final FileHeaderPayload payload; public FileHeader(SecureRandom randomSource) { this.iv = new byte[IV_LEN]; - this.nonce = new byte[NONCE_LEN]; this.payload = new FileHeaderPayload(randomSource); randomSource.nextBytes(iv); - randomSource.nextBytes(nonce); } - private FileHeader(byte[] iv, byte[] nonce, FileHeaderPayload payload) { + private FileHeader(byte[] iv, FileHeaderPayload payload) { this.iv = iv; - this.nonce = nonce; this.payload = payload; } @@ -52,10 +46,6 @@ class FileHeader implements Destroyable { return iv; } - public byte[] getNonce() { - return nonce; - } - public FileHeaderPayload getPayload() { return payload; } @@ -64,8 +54,6 @@ class FileHeader implements Destroyable { ByteBuffer result = ByteBuffer.allocate(HEADER_SIZE); result.position(IV_POS).limit(IV_POS + IV_LEN); result.put(iv); - result.position(NONCE_POS).limit(NONCE_POS + NONCE_LEN); - result.put(nonce); result.position(PAYLOAD_POS).limit(PAYLOAD_POS + PAYLOAD_LEN); result.put(payload.toCiphertextByteBuffer(headerKey, iv)); ByteBuffer resultSoFar = result.asReadOnlyBuffer(); @@ -101,17 +89,12 @@ class FileHeader implements Destroyable { ivBuf.position(IV_POS).limit(IV_POS + IV_LEN); ivBuf.get(iv); - final byte[] nonce = new byte[NONCE_LEN]; - final ByteBuffer nonceBuf = header.asReadOnlyBuffer(); - nonceBuf.position(NONCE_POS).limit(NONCE_POS + NONCE_LEN); - nonceBuf.get(nonce); - final ByteBuffer payloadBuf = header.asReadOnlyBuffer(); payloadBuf.position(PAYLOAD_POS).limit(PAYLOAD_POS + PAYLOAD_LEN); final FileHeaderPayload payload = FileHeaderPayload.fromCiphertextByteBuffer(payloadBuf, headerKey, iv); - return new FileHeader(iv, nonce, payload); + return new FileHeader(iv, payload); } private static void checkHeaderMac(ByteBuffer header, Mac mac) throws IllegalArgumentException { diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeaderPayload.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeaderPayload.java index 967c8c872..229443e8c 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeaderPayload.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileHeaderPayload.java @@ -11,14 +11,12 @@ package org.cryptomator.crypto.engine.impl; import java.nio.ByteBuffer; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.ShortBufferException; import javax.crypto.spec.IvParameterSpec; @@ -33,7 +31,6 @@ class FileHeaderPayload implements Destroyable { private static final int CONTENT_KEY_POS = 8; private static final int CONTENT_KEY_LEN = 32; private static final String AES = "AES"; - private static final String AES_CBC = "AES/CBC/PKCS5Padding"; private long filesize; private final SecretKey contentKey; @@ -93,15 +90,15 @@ class FileHeaderPayload implements Destroyable { public ByteBuffer toCiphertextByteBuffer(SecretKey headerKey, byte[] iv) { final ByteBuffer cleartext = toCleartextByteBuffer(); try { - final Cipher cipher = Cipher.getInstance(AES_CBC); + Cipher cipher = ThreadLocalAesCtrCipher.get(); cipher.init(Cipher.ENCRYPT_MODE, headerKey, new IvParameterSpec(iv)); final int ciphertextLength = cipher.getOutputSize(cleartext.remaining()); - assert ciphertextLength == 48 : "8 byte long and 32 byte file key should fit into 3 blocks"; + assert ciphertextLength == cleartext.remaining() : "in counter mode outputlength == input length"; final ByteBuffer ciphertext = ByteBuffer.allocate(ciphertextLength); cipher.doFinal(cleartext, ciphertext); ciphertext.flip(); return ciphertext; - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | ShortBufferException | IllegalBlockSizeException | BadPaddingException e) { + } catch (InvalidKeyException | InvalidAlgorithmParameterException | ShortBufferException | IllegalBlockSizeException | BadPaddingException e) { throw new IllegalStateException("Unable to compute encrypted header.", e); } finally { Arrays.fill(cleartext.array(), (byte) 0x00); @@ -134,15 +131,15 @@ class FileHeaderPayload implements Destroyable { private static ByteBuffer decryptPayload(ByteBuffer ciphertext, SecretKey headerKey, byte[] iv) { try { - final Cipher cipher = Cipher.getInstance(AES_CBC); + Cipher cipher = ThreadLocalAesCtrCipher.get(); cipher.init(Cipher.DECRYPT_MODE, headerKey, new IvParameterSpec(iv)); final int cleartextLength = cipher.getOutputSize(ciphertext.remaining()); - assert cleartextLength == ciphertext.remaining() : "decryption shouldn't need more output than input buffer size."; + assert cleartextLength == ciphertext.remaining() : "in counter mode outputlength == input length"; final ByteBuffer cleartext = ByteBuffer.allocate(cleartextLength); cipher.doFinal(ciphertext, cleartext); cleartext.flip(); return cleartext; - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | ShortBufferException | IllegalBlockSizeException | BadPaddingException e) { + } catch (InvalidKeyException | InvalidAlgorithmParameterException | ShortBufferException | IllegalBlockSizeException | BadPaddingException e) { throw new IllegalStateException("Unable to decrypt header.", e); } } diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileHeaderPayloadTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileHeaderPayloadTest.java index 8200d76a1..e53164f26 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileHeaderPayloadTest.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileHeaderPayloadTest.java @@ -40,16 +40,16 @@ public class FileHeaderPayloadTest { header.setFilesize(42); final ByteBuffer encrypted = header.toCiphertextByteBuffer(headerKey, new byte[16]); - // echo -n "AAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" | base64 --decode | openssl enc -aes-256-cbc -K 0000000000000000000000000000000000000000000000000000000000000000 -iv + // echo -n "AAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" | base64 --decode | openssl enc -aes-256-ctr -K 0000000000000000000000000000000000000000000000000000000000000000 -iv // 00000000000000000000000000000000 | base64 - Assert.assertArrayEquals(Base64.decode("S+uR3CoV6Mp/PWStVf2upywdYw2W84hMLWfINiTodqKaCopvSvdY6sqRYcnQF9J5"), Arrays.copyOfRange(encrypted.array(), 0, encrypted.remaining())); + Assert.assertArrayEquals(Base64.decode("3JXAeKJAiaOtSKIUkoQgh1MPivvHRTa5qWO08cTLc4vOp0A9TWBrbg=="), Arrays.copyOfRange(encrypted.array(), 0, encrypted.remaining())); } @Test public void testDecryption() { final byte[] keyBytes = new byte[32]; final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); - final ByteBuffer ciphertextBuf = ByteBuffer.wrap(Base64.decode("S+uR3CoV6Mp/PWStVf2upywdYw2W84hMLWfINiTodqKaCopvSvdY6sqRYcnQF9J5")); + final ByteBuffer ciphertextBuf = ByteBuffer.wrap(Base64.decode("3JXAeKJAiaOtSKIUkoQgh1MPivvHRTa5qWO08cTLc4vOp0A9TWBrbg==")); final FileHeaderPayload header = FileHeaderPayload.fromCiphertextByteBuffer(ciphertextBuf, headerKey, new byte[16]); Assert.assertEquals(42, header.getFilesize()); Assert.assertArrayEquals(new byte[32], header.getContentKey().getEncoded()); diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileHeaderTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileHeaderTest.java index 08960045c..97edc75a4 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileHeaderTest.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileHeaderTest.java @@ -40,14 +40,13 @@ public class FileHeaderTest { final FileHeader header = new FileHeader(RANDOM_MOCK); header.getPayload().setFilesize(42); Assert.assertArrayEquals(new byte[16], header.getIv()); - Assert.assertArrayEquals(new byte[8], header.getNonce()); Assert.assertArrayEquals(new byte[32], header.getPayload().getContentKey().getEncoded()); final ByteBuffer headerAsByteBuffer = header.toByteBuffer(headerKey, new ThreadLocalMac(macKey, "HmacSHA256")); - // 24 bytes 0x00 + // 16 bytes 0x00 (IV) // + 48 bytes encrypted payload (see FileHeaderPayloadTest) // + 32 bytes HMAC of both (openssl dgst -sha256 -mac HMAC -macopt hexkey:0000000000000000000000000000000000000000000000000000000000000000 -binary) - final String expected = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAS+uR3CoV6Mp/PWStVf2upywdYw2W84hMLWfINiTodqKaCopvSvdY6sqRYcnQF9J5ZVoITcmvp7VPXI4Tzdc87/cBHxjkBbY0QkRa0iow+iQ="; + final String expected = "AAAAAAAAAAAAAAAAAAAAANyVwHiiQImjrUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga26lJzstK9RUv1hj5zDC4wC9FgMfoVE1mD0HnuENuYXkJA=="; Assert.assertArrayEquals(Base64.decode(expected), Arrays.copyOf(headerAsByteBuffer.array(), headerAsByteBuffer.remaining())); } @@ -56,12 +55,11 @@ public class FileHeaderTest { final byte[] keyBytes = new byte[32]; final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - final ByteBuffer headerBuf = ByteBuffer.wrap(Base64.decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAS+uR3CoV6Mp/PWStVf2upywdYw2W84hMLWfINiTodqKaCopvSvdY6sqRYcnQF9J5ZVoITcmvp7VPXI4Tzdc87/cBHxjkBbY0QkRa0iow+iQ=")); + final ByteBuffer headerBuf = ByteBuffer.wrap(Base64.decode("AAAAAAAAAAAAAAAAAAAAANyVwHiiQImjrUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga26lJzstK9RUv1hj5zDC4wC9FgMfoVE1mD0HnuENuYXkJA==")); final FileHeader header = FileHeader.decrypt(headerKey, new ThreadLocalMac(macKey, "HmacSHA256"), headerBuf); Assert.assertEquals(42, header.getPayload().getFilesize()); Assert.assertArrayEquals(new byte[16], header.getIv()); - Assert.assertArrayEquals(new byte[8], header.getNonce()); Assert.assertArrayEquals(new byte[32], header.getPayload().getContentKey().getEncoded()); } @@ -70,7 +68,7 @@ public class FileHeaderTest { final byte[] keyBytes = new byte[32]; final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - final ByteBuffer headerBuf = ByteBuffer.wrap(Base64.decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAS+uR3CoV6Mp/PWStVf2upywdYw2W84hMLWfINiTodqKaCopvSvdY6sqRYcnQF9J5ZVoITcmvp7VPXI4Tzdc87/cBHxjkBbY0QkRa0iow+iq=")); + final ByteBuffer headerBuf = ByteBuffer.wrap(Base64.decode("AAAAAAAAAAAAAAAAAAAAANyVwHiiQImjrUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga26lJzstK9RUv1hj5zDC4wC9FgMfoVE1mD0HnuENuYXkJa==")); FileHeader.decrypt(headerKey, new ThreadLocalMac(macKey, "HmacSHA256"), headerBuf); } @@ -79,7 +77,7 @@ public class FileHeaderTest { final byte[] keyBytes = new byte[32]; final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES"); final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256"); - final ByteBuffer headerBuf = ByteBuffer.wrap(Base64.decode("aAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAS+uR3CoV6Mp/PWStVf2upywdYw2W84hMLWfINiTodqKaCopvSvdY6sqRYcnQF9J5ZVoITcmvp7VPXI4Tzdc87/cBHxjkBbY0QkRa0iow+iQ=")); + final ByteBuffer headerBuf = ByteBuffer.wrap(Base64.decode("aAAAAAAAAAAAAAAAAAAAANyVwHiiQImjrUiiFJKEIIdTD4r7x0U2ualjtPHEy3OLzqdAPU1ga26lJzstK9RUv1hj5zDC4wC9FgMfoVE1mD0HnuENuYXkJA==")); FileHeader.decrypt(headerKey, new ThreadLocalMac(macKey, "HmacSHA256"), headerBuf); }