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

This commit is contained in:
Sebastian Stenzel
2016-02-08 18:47:02 +01:00
parent 4d2a786504
commit 853744002c
4 changed files with 50 additions and 28 deletions

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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)));

View File

@@ -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());
}
}