mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-17 18:21:26 +00:00
version 3 header (no nonce, CTR mode)
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user