Ver código fonte

Proper error handling for outdated vault formats

Sebastian Stenzel 10 anos atrás
pai
commit
a07efc5209

+ 46 - 30
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java

@@ -50,6 +50,7 @@ import org.cryptomator.crypto.exceptions.DecryptFailedException;
 import org.cryptomator.crypto.exceptions.EncryptFailedException;
 import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
 import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
+import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
 import org.cryptomator.crypto.exceptions.WrongPasswordException;
 import org.cryptomator.crypto.io.SeekableByteChannelInputStream;
 import org.cryptomator.crypto.io.SeekableByteChannelOutputStream;
@@ -133,6 +134,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
 
 			// save encrypted masterkey:
 			final KeyFile keyfile = new KeyFile();
+			keyfile.setVersion(KeyFile.CURRENT_VERSION);
 			keyfile.setScryptSalt(kekSalt);
 			keyfile.setScryptCostParam(SCRYPT_COST_PARAM);
 			keyfile.setScryptBlockSize(SCRYPT_BLOCK_SIZE);
@@ -151,13 +153,19 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
 	 * @throws DecryptFailedException If the decryption failed for various reasons (including wrong password).
 	 * @throws WrongPasswordException If the provided password was wrong. Note: Sometimes the algorithm itself fails due to a wrong password. In this case a DecryptFailedException will be thrown.
 	 * @throws UnsupportedKeyLengthException If the masterkey has been encrypted with a higher key length than supported by the system. In this case Java JCE needs to be installed.
+	 * @throws UnsupportedVaultException If the masterkey file is too old or too modern.
 	 */
 	@Override
-	public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException {
+	public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException, UnsupportedVaultException {
 		try {
 			// load encrypted masterkey:
 			final KeyFile keyfile = objectMapper.readValue(in, KeyFile.class);
 
+			// check version
+			if (keyfile.getVersion() != KeyFile.CURRENT_VERSION) {
+				throw new UnsupportedVaultException(keyfile.getVersion(), KeyFile.CURRENT_VERSION);
+			}
+
 			// check, whether the key length is supported:
 			final int maxKeyLen = Cipher.getMaxAllowedKeyLength(AES_KEY_ALGORITHM);
 			if (keyfile.getKeyLength() > maxKeyLen) {
@@ -225,17 +233,16 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
 		}
 	}
 
-	private Cipher aesEcbCipher(SecretKey key, int cipherMode) {
+	private Cipher aesCbcCipher(SecretKey key, byte[] iv, int cipherMode) {
 		try {
-			final Cipher cipher = Cipher.getInstance(AES_ECB_CIPHER);
-			cipher.init(cipherMode, key);
+			final Cipher cipher = Cipher.getInstance(AES_CBC_CIPHER);
+			cipher.init(cipherMode, key, new IvParameterSpec(iv));
 			return cipher;
 		} catch (InvalidKeyException ex) {
 			throw new IllegalArgumentException("Invalid key.", ex);
-		} catch (NoSuchAlgorithmException | NoSuchPaddingException ex) {
-			throw new AssertionError("Every implementation of the Java platform is required to support AES/ECB/PKCS5Padding.", ex);
+		} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException ex) {
+			throw new AssertionError("Every implementation of the Java platform is required to support AES/CBC/PKCS5Padding, which accepts an IV", ex);
 		}
-
 	}
 
 	private Mac hmacSha256(SecretKey key) {
@@ -291,7 +298,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
 
 	/**
 	 * Each path component, i.e. file or directory name separated by path separators, gets encrypted for its own.<br/>
-	 * Encryption will blow up the filename length due to aes block sizes and base32 encoding. The result may be too long for some old file systems.<br/>
+	 * Encryption will blow up the filename length due to aes block sizes, IVs and base32 encoding. The result may be too long for some old file systems.<br/>
 	 * This means that we need a workaround for filenames longer than the limit defined in {@link FileNamingConventions#ENCRYPTED_FILENAME_LENGTH_LIMIT}.<br/>
 	 * <br/>
 	 * In any case we will create the encrypted filename normally. For those, that are too long, we calculate a checksum. No cryptographically secure hash is needed here. We just want an uniform
@@ -364,15 +371,20 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
 			return null;
 		}
 
+		// read iv:
+		final byte[] iv = new byte[AES_BLOCK_LENGTH];
+		headerBuf.position(0);
+		headerBuf.get(iv);
+
 		// read content length:
-		headerBuf.position(16);
 		final byte[] encryptedContentLengthBytes = new byte[AES_BLOCK_LENGTH];
+		headerBuf.position(16);
 		headerBuf.get(encryptedContentLengthBytes);
-		final Long fileSize = decryptContentLength(encryptedContentLengthBytes);
+		final Long fileSize = decryptContentLength(encryptedContentLengthBytes, iv);
 
 		// read stored header mac:
-		headerBuf.position(32);
 		final byte[] storedHeaderMac = new byte[32];
+		headerBuf.position(32);
 		headerBuf.get(storedHeaderMac);
 
 		// calculate mac over first 32 bytes of header:
@@ -389,9 +401,9 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
 		return fileSize;
 	}
 
-	private long decryptContentLength(byte[] encryptedContentLengthBytes) {
+	private long decryptContentLength(byte[] encryptedContentLengthBytes, byte[] iv) {
 		try {
-			final Cipher sizeCipher = aesEcbCipher(primaryMasterKey, Cipher.DECRYPT_MODE);
+			final Cipher sizeCipher = aesCbcCipher(primaryMasterKey, iv, Cipher.DECRYPT_MODE);
 			final byte[] decryptedFileSize = sizeCipher.doFinal(encryptedContentLengthBytes);
 			final ByteBuffer fileSizeBuffer = ByteBuffer.wrap(decryptedFileSize);
 			return fileSizeBuffer.getLong();
@@ -400,11 +412,11 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
 		}
 	}
 
-	private byte[] encryptContentLength(long contentLength) {
+	private byte[] encryptContentLength(long contentLength, byte[] iv) {
 		try {
 			final ByteBuffer fileSizeBuffer = ByteBuffer.allocate(Long.BYTES);
 			fileSizeBuffer.putLong(contentLength);
-			final Cipher sizeCipher = aesEcbCipher(primaryMasterKey, Cipher.ENCRYPT_MODE);
+			final Cipher sizeCipher = aesCbcCipher(primaryMasterKey, iv, Cipher.ENCRYPT_MODE);
 			return sizeCipher.doFinal(fileSizeBuffer.array());
 		} catch (IllegalBlockSizeException | BadPaddingException e) {
 			throw new IllegalStateException("Block size must be valid, as padding is requested. BadPaddingException not possible in encrypt mode.", e);
@@ -414,7 +426,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
 	@Override
 	public boolean isAuthentic(SeekableByteChannel encryptedFile) throws IOException {
 		// read header:
-		encryptedFile.position(0);
+		encryptedFile.position(0l);
 		final ByteBuffer headerBuf = ByteBuffer.allocate(96);
 		final int headerBytesRead = encryptedFile.read(headerBuf);
 		if (headerBytesRead != headerBuf.capacity()) {
@@ -422,23 +434,23 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
 		}
 
 		// read header mac:
-		headerBuf.position(32);
 		final byte[] storedHeaderMac = new byte[32];
+		headerBuf.position(32);
 		headerBuf.get(storedHeaderMac);
 
 		// read content mac:
-		headerBuf.position(64);
 		final byte[] storedContentMac = new byte[32];
+		headerBuf.position(64);
 		headerBuf.get(storedContentMac);
 
 		// calculate mac over first 32 bytes of header:
 		final Mac headerMac = this.hmacSha256(hMacMasterKey);
-		headerBuf.rewind();
+		headerBuf.position(0);
 		headerBuf.limit(32);
 		headerMac.update(headerBuf);
 
 		// calculate mac over content:
-		encryptedFile.position(96);
+		encryptedFile.position(96l);
 		final Mac contentMac = this.hmacSha256(hMacMasterKey);
 		final InputStream in = new SeekableByteChannelInputStream(encryptedFile);
 		final InputStream macIn = new MacInputStream(in, contentMac);
@@ -453,29 +465,32 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
 	@Override
 	public Long decryptFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException, DecryptFailedException {
 		// read header:
-		encryptedFile.position(0);
+		encryptedFile.position(0l);
 		final ByteBuffer headerBuf = ByteBuffer.allocate(96);
 		final int headerBytesRead = encryptedFile.read(headerBuf);
 		if (headerBytesRead != headerBuf.capacity()) {
 			throw new IOException("Failed to read file header.");
 		}
-		headerBuf.rewind();
 
 		// read iv:
 		final byte[] iv = new byte[AES_BLOCK_LENGTH];
+		headerBuf.position(0);
 		headerBuf.get(iv);
 
 		// read content length:
 		final byte[] encryptedContentLengthBytes = new byte[AES_BLOCK_LENGTH];
+		headerBuf.position(16);
 		headerBuf.get(encryptedContentLengthBytes);
-		final Long fileSize = decryptContentLength(encryptedContentLengthBytes);
+		final Long fileSize = decryptContentLength(encryptedContentLengthBytes, iv);
 
 		// read header mac:
 		final byte[] headerMac = new byte[32];
+		headerBuf.position(32);
 		headerBuf.get(headerMac);
 
 		// read content mac:
 		final byte[] contentMac = new byte[32];
+		headerBuf.position(64);
 		headerBuf.get(contentMac);
 
 		// decrypt content
@@ -507,7 +522,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
 	@Override
 	public Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plaintextFile, long pos, long length) throws IOException, DecryptFailedException {
 		// read iv:
-		encryptedFile.position(0);
+		encryptedFile.position(0l);
 		final ByteBuffer countingIv = ByteBuffer.allocate(AES_BLOCK_LENGTH);
 		final int numIvBytesRead = encryptedFile.read(countingIv);
 
@@ -540,23 +555,24 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
 	@Override
 	public Long encryptFile(InputStream plaintextFile, SeekableByteChannel encryptedFile) throws IOException, EncryptFailedException {
 		// truncate file
-		encryptedFile.truncate(0);
+		encryptedFile.truncate(0l);
 
 		// use an IV, whose last 8 bytes store a long used in counter mode and write initial value to file.
-		final ByteBuffer iv = ByteBuffer.wrap(randomData(AES_BLOCK_LENGTH));
-		iv.putInt(AES_BLOCK_LENGTH - Integer.BYTES, 0);
+		final ByteBuffer ivBuf = ByteBuffer.wrap(randomData(AES_BLOCK_LENGTH));
+		ivBuf.putInt(AES_BLOCK_LENGTH - Integer.BYTES, 0);
+		final byte[] iv = ivBuf.array();
 
 		// 96 byte header buffer (16 IV, 16 size, 32 headerMac, 32 contentMac)
 		// prefilled with "zero" content length for impatient processes, which want to know the size, before file has been completely written:
 		final ByteBuffer headerBuf = ByteBuffer.allocate(96);
 		headerBuf.position(16);
-		headerBuf.put(encryptContentLength(0l));
+		headerBuf.put(encryptContentLength(0l, iv));
 		headerBuf.flip();
 		headerBuf.limit(96);
 		encryptedFile.write(headerBuf);
 
 		// content encryption:
-		final Cipher cipher = this.aesCtrCipher(primaryMasterKey, iv.array(), Cipher.ENCRYPT_MODE);
+		final Cipher cipher = this.aesCtrCipher(primaryMasterKey, iv, Cipher.ENCRYPT_MODE);
 		final Mac contentMac = this.hmacSha256(hMacMasterKey);
 		final OutputStream out = new SeekableByteChannelOutputStream(encryptedFile);
 		final OutputStream macOut = new MacOutputStream(out, contentMac);
@@ -586,7 +602,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
 		// create and write header:
 		headerBuf.clear();
 		headerBuf.put(iv);
-		headerBuf.put(encryptContentLength(plaintextSize));
+		headerBuf.put(encryptContentLength(plaintextSize, iv));
 		headerBuf.flip();
 		final Mac headerMac = this.hmacSha256(hMacMasterKey);
 		headerMac.update(headerBuf);

+ 5 - 13
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java

@@ -26,14 +26,14 @@ interface AesCryptographicConfiguration {
 	int SCRYPT_BLOCK_SIZE = 8;
 
 	/**
-	 * Number of bytes of the master key. Should be the maximum possible AES key length to provide best security.
+	 * Preferred number of bytes of the master key.
 	 */
 	int PREF_MASTER_KEY_LENGTH_IN_BITS = 256;
 
 	/**
 	 * Number of bytes used as seed for the PRNG.
 	 */
-	int PRNG_SEED_LENGTH = 32;
+	int PRNG_SEED_LENGTH = 16;
 
 	/**
 	 * Algorithm used for random number generation.
@@ -60,30 +60,22 @@ interface AesCryptographicConfiguration {
 	String AES_KEYWRAP_CIPHER = "AESWrap";
 
 	/**
-	 * Cipher specs for file name and file content encryption. Using CTR-mode for random access.<br/>
-	 * <strong>Important</strong>: As JCE doesn't support a padding, input must be a multiple of the block size.
+	 * Cipher specs for file content encryption. Using CTR-mode for random access.<br/>
 	 * 
 	 * @see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Cipher
 	 */
 	String AES_CTR_CIPHER = "AES/CTR/NoPadding";
 
 	/**
-	 * Cipher specs for single block encryption (like file size).
+	 * Cipher specs for file header encryption (fixed-length block cipher).<br/>
 	 * 
 	 * @see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#impl
 	 */
-	String AES_ECB_CIPHER = "AES/ECB/PKCS5Padding";
+	String AES_CBC_CIPHER = "AES/CBC/PKCS5Padding";
 
 	/**
 	 * AES block size is 128 bit or 16 bytes.
 	 */
 	int AES_BLOCK_LENGTH = 16;
 
-	/**
-	 * Number of non-zero bytes in the IV used for file name encryption. Less means shorter encrypted filenames, more means higher entropy.
-	 * Maximum length is {@value #AES_BLOCK_LENGTH}. Even the shortest base32 (see {@link FileNamingConventions#ENCRYPTED_FILENAME_CODEC})
-	 * encoded byte array will need 8 chars. The maximum number of bytes that fit in 8 base32 chars is 5. Thus 5 is the ideal length.
-	 */
-	int FILE_NAME_IV_LENGTH = 5;
-
 }

+ 1 - 1
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/FileNamingConventions.java

@@ -22,7 +22,7 @@ interface FileNamingConventions {
 	BaseNCodec ENCRYPTED_FILENAME_CODEC = new Base32();
 
 	/**
-	 * Maximum length possible on file systems with a filename limit of 255 chars.<br/>
+	 * Maximum length possible on file systems with a filename or even path length limit of 255 chars.<br/>
 	 * Also we would need a few chars for our file extension, so lets use {@value #ENCRYPTED_FILENAME_LENGTH_LIMIT}.
 	 */
 	int ENCRYPTED_FILENAME_LENGTH_LIMIT = 128;

+ 12 - 1
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/KeyFile.java

@@ -4,10 +4,13 @@ import java.io.Serializable;
 
 import com.fasterxml.jackson.annotation.JsonPropertyOrder;
 
-@JsonPropertyOrder(value = {"scryptSalt", "scryptCostParam", "scryptBlockSize", "keyLength", "primaryMasterKey", "hMacMasterKey"})
+@JsonPropertyOrder(value = {"version", "scryptSalt", "scryptCostParam", "scryptBlockSize", "keyLength", "primaryMasterKey", "hMacMasterKey"})
 public class KeyFile implements Serializable {
 
+	static final Integer CURRENT_VERSION = 1;
 	private static final long serialVersionUID = 8578363158959619885L;
+
+	private Integer version;
 	private byte[] scryptSalt;
 	private int scryptCostParam;
 	private int scryptBlockSize;
@@ -15,6 +18,14 @@ public class KeyFile implements Serializable {
 	private byte[] primaryMasterKey;
 	private byte[] hMacMasterKey;
 
+	public Integer getVersion() {
+		return version;
+	}
+
+	public void setVersion(Integer version) {
+		this.version = version;
+	}
+
 	public byte[] getScryptSalt() {
 		return scryptSalt;
 	}

+ 3 - 2
main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java

@@ -25,6 +25,7 @@ import org.cryptomator.crypto.CryptorMetadataSupport;
 import org.cryptomator.crypto.exceptions.DecryptFailedException;
 import org.cryptomator.crypto.exceptions.EncryptFailedException;
 import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
+import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
 import org.cryptomator.crypto.exceptions.WrongPasswordException;
 import org.junit.Assert;
 import org.junit.Test;
@@ -32,7 +33,7 @@ import org.junit.Test;
 public class Aes256CryptorTest {
 
 	@Test
-	public void testCorrectPassword() throws IOException, WrongPasswordException, DecryptFailedException, UnsupportedKeyLengthException, DestroyFailedException {
+	public void testCorrectPassword() throws IOException, WrongPasswordException, DecryptFailedException, UnsupportedKeyLengthException, DestroyFailedException, UnsupportedVaultException {
 		final String pw = "asd";
 		final Aes256Cryptor cryptor = new Aes256Cryptor();
 		final ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -48,7 +49,7 @@ public class Aes256CryptorTest {
 	}
 
 	@Test
-	public void testWrongPassword() throws IOException, DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, DestroyFailedException {
+	public void testWrongPassword() throws IOException, DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, DestroyFailedException, UnsupportedVaultException {
 		final String pw = "asd";
 		final Aes256Cryptor cryptor = new Aes256Cryptor();
 		final ByteArrayOutputStream out = new ByteArrayOutputStream();

+ 2 - 1
main/crypto-api/src/main/java/org/cryptomator/crypto/AbstractCryptorDecorator.java

@@ -13,6 +13,7 @@ import org.cryptomator.crypto.exceptions.DecryptFailedException;
 import org.cryptomator.crypto.exceptions.EncryptFailedException;
 import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
 import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
+import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
 import org.cryptomator.crypto.exceptions.WrongPasswordException;
 
 public class AbstractCryptorDecorator implements Cryptor {
@@ -29,7 +30,7 @@ public class AbstractCryptorDecorator implements Cryptor {
 	}
 
 	@Override
-	public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException {
+	public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException, UnsupportedVaultException {
 		cryptor.decryptMasterKey(in, password);
 	}
 

+ 3 - 1
main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java

@@ -21,6 +21,7 @@ import org.cryptomator.crypto.exceptions.DecryptFailedException;
 import org.cryptomator.crypto.exceptions.EncryptFailedException;
 import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
 import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
+import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
 import org.cryptomator.crypto.exceptions.WrongPasswordException;
 
 /**
@@ -39,8 +40,9 @@ public interface Cryptor extends Destroyable {
 	 * @throws DecryptFailedException If the decryption failed for various reasons (including wrong password).
 	 * @throws WrongPasswordException If the provided password was wrong. Note: Sometimes the algorithm itself fails due to a wrong password. In this case a DecryptFailedException will be thrown.
 	 * @throws UnsupportedKeyLengthException If the masterkey has been encrypted with a higher key length than supported by the system. In this case Java JCE needs to be installed.
+	 * @throws UnsupportedVaultException If the masterkey file is too old or too modern.
 	 */
-	void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException;
+	void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException, UnsupportedVaultException;
 
 	/**
 	 * Encrypts a given plaintext path representing a directory structure. See {@link #encryptFilename(String, CryptorMetadataSupport)} for contents inside directories.

+ 32 - 0
main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/UnsupportedVaultException.java

@@ -0,0 +1,32 @@
+package org.cryptomator.crypto.exceptions;
+
+public class UnsupportedVaultException extends Exception {
+
+	private static final long serialVersionUID = -5147549533387945622L;
+
+	private final Integer detectedVersion;
+	private final Integer supportedVersion;
+
+	public UnsupportedVaultException(Integer detectedVersion, Integer supportedVersion) {
+		super("Tried to open vault of version " + detectedVersion + ", but can only handle version " + supportedVersion);
+		this.detectedVersion = detectedVersion;
+		this.supportedVersion = supportedVersion;
+	}
+
+	public Integer getDetectedVersion() {
+		return detectedVersion;
+	}
+
+	public Integer getSupportedVersion() {
+		return supportedVersion;
+	}
+
+	public boolean isVaultOlderThanSoftware() {
+		return detectedVersion == null || detectedVersion < supportedVersion;
+	}
+
+	public boolean isSoftwareOlderThanVault() {
+		return detectedVersion > supportedVersion;
+	}
+
+}

+ 9 - 0
main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java

@@ -20,6 +20,7 @@ import javafx.scene.control.Label;
 
 import org.cryptomator.crypto.exceptions.DecryptFailedException;
 import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
+import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
 import org.cryptomator.crypto.exceptions.WrongPasswordException;
 import org.cryptomator.ui.controls.SecPasswordField;
 import org.cryptomator.ui.model.Vault;
@@ -108,6 +109,14 @@ public class ChangePasswordController implements Initializable {
 			newPasswordField.swipe();
 			retypePasswordField.swipe();
 			return;
+		} catch (UnsupportedVaultException e) {
+			if (e.isVaultOlderThanSoftware()) {
+				messageLabel.setText(rb.getString("changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware"));
+			} else if (e.isSoftwareOlderThanVault()) {
+				messageLabel.setText(rb.getString("changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault"));
+			}
+			newPasswordField.swipe();
+			retypePasswordField.swipe();
 		} finally {
 			oldPasswordField.swipe();
 		}

+ 9 - 0
main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java

@@ -35,6 +35,7 @@ import javax.security.auth.DestroyFailedException;
 import org.apache.commons.lang3.CharUtils;
 import org.cryptomator.crypto.exceptions.DecryptFailedException;
 import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
+import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
 import org.cryptomator.crypto.exceptions.WrongPasswordException;
 import org.cryptomator.ui.controls.SecPasswordField;
 import org.cryptomator.ui.model.Vault;
@@ -131,6 +132,14 @@ public class UnlockController implements Initializable {
 			progressIndicator.setVisible(false);
 			messageLabel.setText(rb.getString("unlock.errorMessage.unsupportedKeyLengthInstallJCE"));
 			LOG.warn("Unsupported Key-Length. Please install Oracle Java Cryptography Extension (JCE).", ex);
+		} catch (UnsupportedVaultException e) {
+			setControlsDisabled(false);
+			progressIndicator.setVisible(false);
+			if (e.isVaultOlderThanSoftware()) {
+				messageLabel.setText(rb.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware"));
+			} else if (e.isSoftwareOlderThanVault()) {
+				messageLabel.setText(rb.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault"));
+			}
 		} catch (DestroyFailedException e) {
 			setControlsDisabled(false);
 			progressIndicator.setVisible(false);

+ 4 - 0
main/ui/src/main/resources/localization.properties

@@ -32,6 +32,8 @@ unlock.button.unlock=Unlock vault
 unlock.errorMessage.wrongPassword=Wrong password.
 unlock.errorMessage.decryptionFailed=Decryption failed.
 unlock.errorMessage.unsupportedKeyLengthInstallJCE=Decryption failed. Please install Oracle JCE Unlimited Strength Policy.
+unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Unsupported vault. This vault has been created with an older version of Cryptomator.
+unlock.errorMessage.unsupportedVersion.softwareOlderThanVault=Unsupported vault. This vault has been created with a newer version of Cryptomator.
 unlock.messageLabel.startServerFailed=Starting WebDAV server failed.
 
 # change_password.fxml
@@ -42,6 +44,8 @@ changePassword.button.unlock=Change password
 changePassword.errorMessage.wrongPassword=Wrong password.
 changePassword.errorMessage.decryptionFailed=Decryption failed.
 changePassword.errorMessage.unsupportedKeyLengthInstallJCE=Decryption failed. Please install Oracle JCE Unlimited Strength Policy.
+changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Unsupported vault. This vault has been created with an older version of Cryptomator.
+changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault=Unsupported vault. This vault has been created with a newer version of Cryptomator.
 changePassword.infoMessage.success=Password changed.
 
 # unlocked.fxml