added possibility to add associated data to filename encryption (references #128, #119)

This commit is contained in:
Sebastian Stenzel
2016-01-10 13:39:12 +01:00
parent 8f319b3f87
commit b2d425e11f
7 changed files with 55 additions and 24 deletions

View File

@@ -23,13 +23,15 @@ public interface FilenameCryptor {
/**
* @param cleartextName original filename including cleartext file extension
* @param associatedData optional associated data, that will not get encrypted but needs to be provided during decryption
* @return encrypted filename without any file extension
*/
String encryptFilename(String cleartextName);
String encryptFilename(String cleartextName, byte[]... associatedData);
/**
* @param ciphertextName Ciphertext only, with any additional strings like file extensions stripped first.
* @param associatedData the same associated data used during encryption, otherwise and {@link AuthenticationFailedException} will be thrown
* @return cleartext filename, probably including its cleartext file extension.
*/
String decryptFilename(String ciphertextName) throws AuthenticationFailedException;
String decryptFilename(String ciphertextName, byte[]... associatedData) throws AuthenticationFailedException;
}

View File

@@ -8,7 +8,8 @@
*******************************************************************************/
package org.cryptomator.crypto.engine.impl;
import java.nio.charset.StandardCharsets;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -37,25 +38,25 @@ class FilenameCryptorImpl implements FilenameCryptor {
@Override
public String hashDirectoryId(String cleartextDirectoryId) {
final byte[] cleartextBytes = cleartextDirectoryId.getBytes(StandardCharsets.UTF_8);
final byte[] cleartextBytes = cleartextDirectoryId.getBytes(UTF_8);
byte[] encryptedBytes = AES_SIV.encrypt(encryptionKey, macKey, cleartextBytes);
final byte[] hashedBytes = SHA1.get().digest(encryptedBytes);
return BASE32.encodeAsString(hashedBytes);
}
@Override
public String encryptFilename(String cleartextName) {
final byte[] cleartextBytes = cleartextName.getBytes(StandardCharsets.UTF_8);
final byte[] encryptedBytes = AES_SIV.encrypt(encryptionKey, macKey, cleartextBytes);
public String encryptFilename(String cleartextName, byte[]... associatedData) {
final byte[] cleartextBytes = cleartextName.getBytes(UTF_8);
final byte[] encryptedBytes = AES_SIV.encrypt(encryptionKey, macKey, cleartextBytes, associatedData);
return BASE32.encodeAsString(encryptedBytes);
}
@Override
public String decryptFilename(String ciphertextName) throws AuthenticationFailedException {
public String decryptFilename(String ciphertextName, byte[]... associatedData) throws AuthenticationFailedException {
final byte[] encryptedBytes = BASE32.decode(ciphertextName);
try {
final byte[] cleartextBytes = AES_SIV.decrypt(encryptionKey, macKey, encryptedBytes);
return new String(cleartextBytes, StandardCharsets.UTF_8);
final byte[] cleartextBytes = AES_SIV.decrypt(encryptionKey, macKey, encryptedBytes, associatedData);
return new String(cleartextBytes, UTF_8);
} catch (AEADBadTagException e) {
throw new AuthenticationFailedException("Authentication failed.", e);
}

View File

@@ -8,9 +8,10 @@
*******************************************************************************/
package org.cryptomator.crypto.engine.impl;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import org.bouncycastle.crypto.generators.SCrypt;
@@ -34,7 +35,7 @@ final class Scrypt {
*/
public static byte[] scrypt(CharSequence passphrase, byte[] salt, int costParam, int blockSize, int keyLengthInBytes) {
// This is an attempt to get the password bytes without copies of the password being created in some dark places inside the JVM:
final ByteBuffer buf = StandardCharsets.UTF_8.encode(CharBuffer.wrap(passphrase));
final ByteBuffer buf = UTF_8.encode(CharBuffer.wrap(passphrase));
final byte[] pw = new byte[buf.remaining()];
buf.get(pw);
try {

View File

@@ -8,12 +8,13 @@
*******************************************************************************/
package org.cryptomator.filesystem.crypto;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Optional;
import java.util.UUID;
@@ -52,7 +53,7 @@ class CryptoFolder extends CryptoNode implements Folder {
if (directoryId.get() == null) {
File dirFile = physicalFile();
if (dirFile.exists()) {
try (Reader reader = Channels.newReader(dirFile.openReadable(), StandardCharsets.UTF_8.newDecoder(), -1)) {
try (Reader reader = Channels.newReader(dirFile.openReadable(), UTF_8.newDecoder(), -1)) {
directoryId.set(IOUtils.toString(reader));
} catch (IOException e) {
throw new UncheckedIOException(e);

View File

@@ -8,7 +8,8 @@
*******************************************************************************/
package org.cryptomator.crypto.engine;
import java.nio.charset.StandardCharsets;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -22,18 +23,18 @@ class NoFilenameCryptor implements FilenameCryptor {
@Override
public String hashDirectoryId(String cleartextDirectoryId) {
final byte[] cleartextBytes = cleartextDirectoryId.getBytes(StandardCharsets.UTF_8);
final byte[] cleartextBytes = cleartextDirectoryId.getBytes(UTF_8);
final byte[] hashedBytes = SHA1.get().digest(cleartextBytes);
return BASE32.encodeAsString(hashedBytes);
}
@Override
public String encryptFilename(String cleartextName) {
public String encryptFilename(String cleartextName, byte[]... associatedData) {
return cleartextName;
}
@Override
public String decryptFilename(String ciphertextName) {
public String decryptFilename(String ciphertextName, byte[]... associatedData) {
return ciphertextName;
}

View File

@@ -8,8 +8,9 @@
*******************************************************************************/
package org.cryptomator.crypto.engine.impl;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import javax.crypto.SecretKey;
@@ -71,9 +72,32 @@ public class FilenameCryptorImplTest {
final SecretKey macKey = new SecretKeySpec(keyBytes, "AES");
final FilenameCryptor filenameCryptor = new FilenameCryptorImpl(encryptionKey, macKey);
final byte[] encrypted = filenameCryptor.encryptFilename("test").getBytes(StandardCharsets.UTF_8);
final byte[] encrypted = filenameCryptor.encryptFilename("test").getBytes(UTF_8);
encrypted[0] ^= (byte) 0x01; // change 1 bit in first byte
filenameCryptor.decryptFilename(new String(encrypted, StandardCharsets.UTF_8));
filenameCryptor.decryptFilename(new String(encrypted, UTF_8));
}
@Test
public void testDeterministicEncryptionOfFilenamesWithAssociatedData() {
final byte[] keyBytes = new byte[32];
final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES");
final SecretKey macKey = new SecretKeySpec(keyBytes, "AES");
final FilenameCryptor filenameCryptor = new FilenameCryptorImpl(encryptionKey, macKey);
final String encrypted = filenameCryptor.encryptFilename("test", "ad".getBytes(UTF_8));
final String decrypted = filenameCryptor.decryptFilename(encrypted, "ad".getBytes(UTF_8));
Assert.assertEquals("test", decrypted);
}
@Test(expected = AuthenticationFailedException.class)
public void testDeterministicEncryptionOfFilenamesWithWrongAssociatedData() {
final byte[] keyBytes = new byte[32];
final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES");
final SecretKey macKey = new SecretKeySpec(keyBytes, "AES");
final FilenameCryptor filenameCryptor = new FilenameCryptorImpl(encryptionKey, macKey);
final String encrypted = filenameCryptor.encryptFilename("test", "right".getBytes(UTF_8));
filenameCryptor.decryptFilename(encrypted, "wrong".getBytes(UTF_8));
}
}

View File

@@ -1,12 +1,13 @@
package org.cryptomator.filesystem.shortening;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.nio.channels.Channels;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -55,7 +56,7 @@ class FilenameShortener {
final File mappingFile = mappingFile(shortName);
if (!mappingFile.exists()) {
mappingFile.parent().get().create();
try (Writer writer = Channels.newWriter(mappingFile.openWritable(), StandardCharsets.UTF_8.newEncoder(), -1)) {
try (Writer writer = Channels.newWriter(mappingFile.openWritable(), UTF_8.newEncoder(), -1)) {
writer.write(longName);
} catch (IOException e) {
throw new UncheckedIOException(e);
@@ -73,7 +74,7 @@ class FilenameShortener {
if (!mappingFile.exists()) {
throw new UncheckedIOException(new FileNotFoundException("Mapping file not found " + mappingFile));
} else {
try (Reader reader = Channels.newReader(mappingFile.openReadable(), StandardCharsets.UTF_8.newDecoder(), -1)) {
try (Reader reader = Channels.newReader(mappingFile.openReadable(), UTF_8.newDecoder(), -1)) {
return IOUtils.toString(reader);
} catch (IOException e) {
throw new UncheckedIOException(e);