diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FilenameCryptor.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FilenameCryptor.java index 67a89af67..23f190049 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FilenameCryptor.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FilenameCryptor.java @@ -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; } diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImpl.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImpl.java index 8ed616306..b6f5473ea 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImpl.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImpl.java @@ -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); } diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Scrypt.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Scrypt.java index 73fedfc14..31f94a9de 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Scrypt.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Scrypt.java @@ -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 { diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java index c29a1217c..8604d6844 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java @@ -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); diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFilenameCryptor.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFilenameCryptor.java index 2f5a8c482..59eac07a5 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFilenameCryptor.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFilenameCryptor.java @@ -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; } diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImplTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImplTest.java index b069da2c8..9f6df3b31 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImplTest.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImplTest.java @@ -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)); } } diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/FilenameShortener.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/FilenameShortener.java index 4feaa4ac0..a5277b640 100644 --- a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/FilenameShortener.java +++ b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/FilenameShortener.java @@ -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);