mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-21 04:01:27 +00:00
prohibiting block swapping by adding file-IV and blocknumber to MAC
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user