From fadef46364514f9081c76a642887a1d0f701d5ae Mon Sep 17 00:00:00 2001 From: Adrian Date: Mon, 9 Nov 2015 15:19:15 +0100 Subject: [PATCH 1/4] Dirty hack to cache decoded values better --- .../sec/encfs/EncFSBinaryConfig.java | 52 +++++++++++ .../mrpdaemon/sec/encfs/EncFSPBKDF2Data.java | 77 +++++++++++++++ .../org/mrpdaemon/sec/encfs/EncFSUtil.java | 6 ++ .../org/mrpdaemon/sec/encfs/VolumeKey.java | 93 ++++++++++++------- 4 files changed, 193 insertions(+), 35 deletions(-) create mode 100644 src/main/java/org/mrpdaemon/sec/encfs/EncFSBinaryConfig.java create mode 100644 src/main/java/org/mrpdaemon/sec/encfs/EncFSPBKDF2Data.java diff --git a/src/main/java/org/mrpdaemon/sec/encfs/EncFSBinaryConfig.java b/src/main/java/org/mrpdaemon/sec/encfs/EncFSBinaryConfig.java new file mode 100644 index 0000000..3aed19d --- /dev/null +++ b/src/main/java/org/mrpdaemon/sec/encfs/EncFSBinaryConfig.java @@ -0,0 +1,52 @@ +package org.mrpdaemon.sec.encfs; + +import java.io.IOException; +import java.util.Arrays; + +public class EncFSBinaryConfig +{ + private EncFSConfig config; + private byte[] encodedVolumeKey; + private byte[] ivSeed; + private byte[] encryptedVolumeKey; + private byte[] saltData; + + private EncFSBinaryConfig() { } + + public static EncFSBinaryConfig from(EncFSConfig config) + throws IOException + { + EncFSBinaryConfig obj = new EncFSBinaryConfig(); + obj.config = config; + obj.encodedVolumeKey = EncFSBase64.decode(config.getBase64EncodedVolumeKey()); + obj.ivSeed = Arrays.copyOfRange(obj.encodedVolumeKey, 0, 4); + obj.encryptedVolumeKey = Arrays.copyOfRange(obj.encodedVolumeKey, 4, obj.encodedVolumeKey.length); + obj.saltData = EncFSBase64.decode(config.getBase64Salt()); + return obj; + } + + public EncFSConfig getConfig() + { + return config; + } + + public byte[] getEncodedVolumeKey() + { + return encodedVolumeKey; + } + + public byte[] getIvSeed() + { + return ivSeed; + } + + public byte[] getEncryptedVolumeKey() + { + return encryptedVolumeKey; + } + + public byte[] getSaltData() + { + return saltData; + } +} diff --git a/src/main/java/org/mrpdaemon/sec/encfs/EncFSPBKDF2Data.java b/src/main/java/org/mrpdaemon/sec/encfs/EncFSPBKDF2Data.java new file mode 100644 index 0000000..56d4aae --- /dev/null +++ b/src/main/java/org/mrpdaemon/sec/encfs/EncFSPBKDF2Data.java @@ -0,0 +1,77 @@ +package org.mrpdaemon.sec.encfs; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Arrays; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + +public class EncFSPBKDF2Data +{ + private static final SecretKeyFactory skf; + + static { + try { + skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); + } + } + + private String password; + private byte[] pbkdf2Data; + private byte[] passKeyData; + private byte[] passIvData; + + private EncFSPBKDF2Data() { } + + public static EncFSPBKDF2Data from(EncFSBinaryConfig config, String password) + throws InvalidKeySpecException + { + byte[] cipherSaltData = config.getSaltData(); + int keySizeInBytes = config.getConfig().getVolumeKeySizeInBits() / 8; + KeySpec ks = new PBEKeySpec(password.toCharArray(), cipherSaltData, + config.getConfig().getIterationForPasswordKeyDerivationCount(), + (keySizeInBytes + EncFSVolume.IV_LENGTH_IN_BYTES) * 8 + ); + byte[] pbkdf2Data = skf.generateSecret(ks).getEncoded(); + + EncFSPBKDF2Data obj; + obj = EncFSPBKDF2Data.from(config, pbkdf2Data); + obj.password = password; + return obj; + } + + public static EncFSPBKDF2Data from(EncFSBinaryConfig config, byte[] pbkdf2Data) + throws InvalidKeySpecException + { + EncFSPBKDF2Data obj = new EncFSPBKDF2Data(); + int keySizeInBytes = config.getConfig().getVolumeKeySizeInBits() / 8; + obj.pbkdf2Data = pbkdf2Data; + obj.passKeyData = Arrays.copyOfRange(pbkdf2Data, 0, keySizeInBytes); + obj.passIvData = Arrays.copyOfRange(pbkdf2Data, keySizeInBytes, + keySizeInBytes + EncFSVolume.IV_LENGTH_IN_BYTES); + + return obj; + } + + public String getPassword() + { + return password; + } + public byte[] getPbkdf2Data() + { + return pbkdf2Data; + } + + public byte[] getPassKeyData() + { + return passKeyData; + } + + public byte[] getPassIvData() + { + return passIvData; + } +} diff --git a/src/main/java/org/mrpdaemon/sec/encfs/EncFSUtil.java b/src/main/java/org/mrpdaemon/sec/encfs/EncFSUtil.java index 314a6d2..290b873 100644 --- a/src/main/java/org/mrpdaemon/sec/encfs/EncFSUtil.java +++ b/src/main/java/org/mrpdaemon/sec/encfs/EncFSUtil.java @@ -129,6 +129,12 @@ public final class EncFSUtil { } } + public static boolean checkPassword(EncFSBinaryConfig config, + String password) throws EncFSUnsupportedException, + EncFSInvalidConfigException, EncFSCorruptDataException { + return VolumeKey.checkVolumeKey(config, password); + } + private static void readFromAndWriteTo(InputStream in, OutputStream out) throws IOException { byte[] buf = new byte[EIGHT_KILO]; diff --git a/src/main/java/org/mrpdaemon/sec/encfs/VolumeKey.java b/src/main/java/org/mrpdaemon/sec/encfs/VolumeKey.java index 4f65ae0..6f24bda 100644 --- a/src/main/java/org/mrpdaemon/sec/encfs/VolumeKey.java +++ b/src/main/java/org/mrpdaemon/sec/encfs/VolumeKey.java @@ -15,11 +15,9 @@ package org.mrpdaemon.sec.encfs; import javax.crypto.*; -import javax.crypto.spec.PBEKeySpec; import java.io.IOException; import java.security.*; import java.security.spec.InvalidKeySpecException; -import java.security.spec.KeySpec; import java.util.Arrays; // Class containing static methods implementing volume key functionality @@ -74,39 +72,73 @@ class VolumeKey { EncFSUnsupportedException { // Decode Base64 encoded ciphertext data // TODO: validate key/IV lengths - byte[] cipherVolKeyData; + EncFSBinaryConfig binaryConfig; + EncFSPBKDF2Data pbkdf2DataObj; try { - cipherVolKeyData = EncFSBase64.decode(config - .getBase64EncodedVolumeKey()); - } catch (IOException e) { - throw new EncFSInvalidConfigException("Corrupt key data in config"); + binaryConfig = EncFSBinaryConfig.from(config); + pbkdf2DataObj = EncFSPBKDF2Data.from(binaryConfig, pbkdf2Data); + } catch (Exception e) { + throw new EncFSInvalidConfigException(e); } - byte[] encryptedVolKey = Arrays.copyOfRange(cipherVolKeyData, 4, - cipherVolKeyData.length); + DecryptInfo info = decryptAnyVolumeKey(binaryConfig, pbkdf2DataObj); + + boolean checksumOk = checkVolumeKey(binaryConfig, info); + + if (!checksumOk) { + throw new EncFSChecksumException("Volume key checksum mismatch"); + } + + return info.clearVolKeyData; + } + + protected static DecryptInfo decryptAnyVolumeKey(EncFSBinaryConfig config, + EncFSPBKDF2Data pbkdf2Data) + throws EncFSInvalidConfigException, EncFSCorruptDataException, + EncFSUnsupportedException { + byte[] encryptedVolKey = config.getEncryptedVolumeKey(); // Prepare key/IV for decryption - int keySizeInBytes = config.getVolumeKeySizeInBits() / 8; - byte[] passKeyData = Arrays.copyOfRange(pbkdf2Data, 0, keySizeInBytes); - byte[] passIvData = Arrays.copyOfRange(pbkdf2Data, keySizeInBytes, - keySizeInBytes + EncFSVolume.IV_LENGTH_IN_BYTES); + byte[] passKeyData = pbkdf2Data.getPassKeyData(); + byte[] passIvData = pbkdf2Data.getPassIvData(); Key passKey = EncFSCrypto.newKey(passKeyData); - byte[] ivSeed = Arrays.copyOfRange(cipherVolKeyData, 0, 4); + byte[] ivSeed = config.getIvSeed(); // Decrypt the volume key data Mac mac = encryptVolumeKeyData(passKey); byte[] clearVolKeyData = decryptVolumeKeyData(encryptedVolKey, passIvData, passKey, ivSeed, mac); - // Perform checksum computation - byte[] mac32 = EncFSCrypto.mac32(mac, clearVolKeyData, new byte[0]); + DecryptInfo info = new DecryptInfo(); + info.clearVolKeyData = clearVolKeyData; + info.mac = mac; + return info; + } - if (!Arrays.equals(ivSeed, mac32)) { - throw new EncFSChecksumException("Volume key checksum mismatch"); + public static boolean checkVolumeKey(EncFSBinaryConfig binaryConfig, + String password) throws EncFSUnsupportedException, + EncFSInvalidConfigException, EncFSCorruptDataException { + EncFSPBKDF2Data pbkdf2DataObj; + try { + pbkdf2DataObj = EncFSPBKDF2Data.from(binaryConfig, password); + } catch (InvalidKeySpecException ex) { + throw new EncFSUnsupportedException(ex); } - return clearVolKeyData; + DecryptInfo info = decryptAnyVolumeKey(binaryConfig, pbkdf2DataObj); + + boolean checksumOk = checkVolumeKey(binaryConfig, info); + return checksumOk; + } + + private static boolean checkVolumeKey(EncFSBinaryConfig config, + DecryptInfo info) throws EncFSInvalidConfigException, + EncFSCorruptDataException, EncFSUnsupportedException { + byte[] mac32 = EncFSCrypto.mac32(info.mac, info.clearVolKeyData, new byte[0]); + byte[] ivSeed = config.getIvSeed(); + + return Arrays.equals(ivSeed, mac32); } // Decrypt volume key data @@ -142,25 +174,11 @@ class VolumeKey { } if (pbkdf2Provider == null) { - // Execute PBKDF2 to derive key data from the password - SecretKeyFactory f; try { - f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); - } catch (NoSuchAlgorithmException e) { - throw new EncFSUnsupportedException(e); - } - KeySpec ks = new PBEKeySpec(password.toCharArray(), cipherSaltData, - config.getIterationForPasswordKeyDerivationCount(), - config.getVolumeKeySizeInBits() - + EncFSVolume.IV_LENGTH_IN_BYTES * 8); - SecretKey pbkdf2Key; - try { - pbkdf2Key = f.generateSecret(ks); - } catch (InvalidKeySpecException e) { + return EncFSPBKDF2Data.from(EncFSBinaryConfig.from(config), password).getPbkdf2Data(); + } catch (Exception e) { throw new EncFSInvalidConfigException(e); } - - return pbkdf2Key.getEncoded(); } else { return pbkdf2Provider.doPBKDF2(password, cipherSaltData.length, cipherSaltData, @@ -193,4 +211,9 @@ class VolumeKey { config.setEncodedKeyLengthInBytes(encodedVolKey.length); config.setBase64EncodedVolumeKey(EncFSBase64.encodeBytes(encodedVolKey)); } + + private static class DecryptInfo { + byte[] clearVolKeyData; + Mac mac; + } } From 1e2b240599e8c13edf5d48e07f25d32a814c3091 Mon Sep 17 00:00:00 2001 From: Adrian Date: Mon, 9 Nov 2015 15:20:42 +0100 Subject: [PATCH 2/4] Update branch README --- README.md | 72 ++----------------------------------------------------- 1 file changed, 2 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index bc82a70..1dd0907 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,5 @@ encfs-java ========== -encfs-java is a Java library for accessing data in -[EncFS](http://www.arg0.net/encfs) volumes. It is able to derive the volume key -from a user password, decode encrypted filenames (IV chaining is implemented), -decrypt file contents, create new volumes, move/copy/rename files and -directories within the volume as well as encrypt file contents into the -volume - -We do support all encfs volume configuration options, and are interoperable -with the official encfs implementation (http://www.arg0.net/encfs). Currently -we only support volume created with the latest version (1.7.4), but we target -to be able to support volumes created with legacy versions as well. - -## Building - -encfs-java uses [Maven](http://maven.apache.org) for building. Assuming you -have a working installation, simply run the following to build the code: - - $ mvn compile - -To create a JAR file for using encfs-java from another application, do: - - $ mvn package - -Which will create a JAR file in the {$PROJECT_ROOT}/target/ directory. - -## Usage - -This library comes with a demo/example application called EncFSShell. It is a -simple shell supporting a few commands such as 'ls', 'cd', 'cat', 'mv' and 'cp' -on an EncFS volume. After building the library, add the -{$PROJECT_ROOT}/target/classes/ directory to your CLASSPATH, and run like so: - - $ java -classpath ${PROJECT_ROOT}/target/classes EncFSShell /path/to/an/encfs/volume - -For using the library from another project, include the .JAR file in your -classpath and import the org.mrpdaemon.encfs.sec package.s - -## API Documentation - -For API documentation, see: - -http://mrpdaemon.github.com/encfs-java/apidocs/index.html - -To test your own comment changes, generate up-to-date docs using Maven: - - $ mvn javadoc:javadoc - -Which will place your documentation in ${PROJECT_ROOT}/target/site/apidocs/ - -## Common issues - -If you are getting an exception due to "Illegal key size" and you are using Sun's JDK, -you need to install the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction -Policy Files. See the following link for more information: - -Java 6 JCE Link http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html -Java 7 JCE Link http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html -Java 8 JCE Link http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html -Extract files into JDK/JRE/lib/security folder (To whichever version of JRE/JDK x64/x86 you are using) - -Thanks to aormerod for pointing this out! - -## Licensing - -encfs-java is licensed under the Lesser GNU Public License, which allows non-GPL -applications to make use of the library with the restriction that the source code -for any modifications to the library itself need to be made available to be able -to legally redistribute the modified library. For more information, please see the -LICENSE file and the Free Software Foundation -[website](http://www.gnu.org/licenses/lgpl.html). +This branch contains a hack to cache decoded values better. +This is to use with encfs-brute. From 9d452a6ba21de08bb5f8b8d26a78b291a66aea2f Mon Sep 17 00:00:00 2001 From: Adrian Date: Mon, 9 Nov 2015 15:20:42 +0100 Subject: [PATCH 3/4] Update branch README --- README.md | 72 ++++--------------------------------------------------- 1 file changed, 4 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index bc82a70..82295c6 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,9 @@ encfs-java ========== -encfs-java is a Java library for accessing data in -[EncFS](http://www.arg0.net/encfs) volumes. It is able to derive the volume key -from a user password, decode encrypted filenames (IV chaining is implemented), -decrypt file contents, create new volumes, move/copy/rename files and -directories within the volume as well as encrypt file contents into the -volume +I made the modifications for encfs-brute. The aim was -We do support all encfs volume configuration options, and are interoperable -with the official encfs implementation (http://www.arg0.net/encfs). Currently -we only support volume created with the latest version (1.7.4), but we target -to be able to support volumes created with legacy versions as well. +1. to cache decoded values better, +2. no exception throwing if the key is incorrect. -## Building - -encfs-java uses [Maven](http://maven.apache.org) for building. Assuming you -have a working installation, simply run the following to build the code: - - $ mvn compile - -To create a JAR file for using encfs-java from another application, do: - - $ mvn package - -Which will create a JAR file in the {$PROJECT_ROOT}/target/ directory. - -## Usage - -This library comes with a demo/example application called EncFSShell. It is a -simple shell supporting a few commands such as 'ls', 'cd', 'cat', 'mv' and 'cp' -on an EncFS volume. After building the library, add the -{$PROJECT_ROOT}/target/classes/ directory to your CLASSPATH, and run like so: - - $ java -classpath ${PROJECT_ROOT}/target/classes EncFSShell /path/to/an/encfs/volume - -For using the library from another project, include the .JAR file in your -classpath and import the org.mrpdaemon.encfs.sec package.s - -## API Documentation - -For API documentation, see: - -http://mrpdaemon.github.com/encfs-java/apidocs/index.html - -To test your own comment changes, generate up-to-date docs using Maven: - - $ mvn javadoc:javadoc - -Which will place your documentation in ${PROJECT_ROOT}/target/site/apidocs/ - -## Common issues - -If you are getting an exception due to "Illegal key size" and you are using Sun's JDK, -you need to install the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction -Policy Files. See the following link for more information: - -Java 6 JCE Link http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html -Java 7 JCE Link http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html -Java 8 JCE Link http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html -Extract files into JDK/JRE/lib/security folder (To whichever version of JRE/JDK x64/x86 you are using) - -Thanks to aormerod for pointing this out! - -## Licensing - -encfs-java is licensed under the Lesser GNU Public License, which allows non-GPL -applications to make use of the library with the restriction that the source code -for any modifications to the library itself need to be made available to be able -to legally redistribute the modified library. For more information, please see the -LICENSE file and the Free Software Foundation -[website](http://www.gnu.org/licenses/lgpl.html). +Those two items are important to improve speed. From e63300e3a28b1ecabca3fa10485a604e9b369831 Mon Sep 17 00:00:00 2001 From: Adrian Date: Mon, 9 Nov 2015 15:19:15 +0100 Subject: [PATCH 4/4] Dirty hack to cache decoded values better --- .../sec/encfs/EncFSBinaryConfig.java | 52 +++++++++++ .../mrpdaemon/sec/encfs/EncFSPBKDF2Data.java | 77 +++++++++++++++ .../org/mrpdaemon/sec/encfs/EncFSUtil.java | 6 ++ .../org/mrpdaemon/sec/encfs/VolumeKey.java | 93 ++++++++++++------- 4 files changed, 193 insertions(+), 35 deletions(-) create mode 100644 src/main/java/org/mrpdaemon/sec/encfs/EncFSBinaryConfig.java create mode 100644 src/main/java/org/mrpdaemon/sec/encfs/EncFSPBKDF2Data.java diff --git a/src/main/java/org/mrpdaemon/sec/encfs/EncFSBinaryConfig.java b/src/main/java/org/mrpdaemon/sec/encfs/EncFSBinaryConfig.java new file mode 100644 index 0000000..3aed19d --- /dev/null +++ b/src/main/java/org/mrpdaemon/sec/encfs/EncFSBinaryConfig.java @@ -0,0 +1,52 @@ +package org.mrpdaemon.sec.encfs; + +import java.io.IOException; +import java.util.Arrays; + +public class EncFSBinaryConfig +{ + private EncFSConfig config; + private byte[] encodedVolumeKey; + private byte[] ivSeed; + private byte[] encryptedVolumeKey; + private byte[] saltData; + + private EncFSBinaryConfig() { } + + public static EncFSBinaryConfig from(EncFSConfig config) + throws IOException + { + EncFSBinaryConfig obj = new EncFSBinaryConfig(); + obj.config = config; + obj.encodedVolumeKey = EncFSBase64.decode(config.getBase64EncodedVolumeKey()); + obj.ivSeed = Arrays.copyOfRange(obj.encodedVolumeKey, 0, 4); + obj.encryptedVolumeKey = Arrays.copyOfRange(obj.encodedVolumeKey, 4, obj.encodedVolumeKey.length); + obj.saltData = EncFSBase64.decode(config.getBase64Salt()); + return obj; + } + + public EncFSConfig getConfig() + { + return config; + } + + public byte[] getEncodedVolumeKey() + { + return encodedVolumeKey; + } + + public byte[] getIvSeed() + { + return ivSeed; + } + + public byte[] getEncryptedVolumeKey() + { + return encryptedVolumeKey; + } + + public byte[] getSaltData() + { + return saltData; + } +} diff --git a/src/main/java/org/mrpdaemon/sec/encfs/EncFSPBKDF2Data.java b/src/main/java/org/mrpdaemon/sec/encfs/EncFSPBKDF2Data.java new file mode 100644 index 0000000..4578184 --- /dev/null +++ b/src/main/java/org/mrpdaemon/sec/encfs/EncFSPBKDF2Data.java @@ -0,0 +1,77 @@ +package org.mrpdaemon.sec.encfs; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.Arrays; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + +public class EncFSPBKDF2Data +{ + private static final SecretKeyFactory skf; + + static { + try { + skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); + } + } + + private String password; + private byte[] pbkdf2Data; + private byte[] passKeyData; + private byte[] passIvData; + + private EncFSPBKDF2Data() { } + + public static EncFSPBKDF2Data from(EncFSBinaryConfig config, String password) + throws InvalidKeySpecException + { + byte[] cipherSaltData = config.getSaltData(); + int keySizeInBits = config.getConfig().getVolumeKeySizeInBits(); + KeySpec ks = new PBEKeySpec(password.toCharArray(), cipherSaltData, + config.getConfig().getIterationForPasswordKeyDerivationCount(), + keySizeInBits + EncFSVolume.IV_LENGTH_IN_BYTES * 8 + ); + byte[] pbkdf2Data = skf.generateSecret(ks).getEncoded(); + + EncFSPBKDF2Data obj; + obj = EncFSPBKDF2Data.from(config.getConfig(), pbkdf2Data); + obj.password = password; + return obj; + } + + public static EncFSPBKDF2Data from(EncFSConfig config, byte[] pbkdf2Data) + throws InvalidKeySpecException + { + EncFSPBKDF2Data obj = new EncFSPBKDF2Data(); + int keySizeInBytes = config.getVolumeKeySizeInBits() / 8; + obj.pbkdf2Data = pbkdf2Data; + obj.passKeyData = Arrays.copyOfRange(pbkdf2Data, 0, keySizeInBytes); + obj.passIvData = Arrays.copyOfRange(pbkdf2Data, keySizeInBytes, + keySizeInBytes + EncFSVolume.IV_LENGTH_IN_BYTES); + + return obj; + } + + public String getPassword() + { + return password; + } + public byte[] getPbkdf2Data() + { + return pbkdf2Data; + } + + public byte[] getPassKeyData() + { + return passKeyData; + } + + public byte[] getPassIvData() + { + return passIvData; + } +} diff --git a/src/main/java/org/mrpdaemon/sec/encfs/EncFSUtil.java b/src/main/java/org/mrpdaemon/sec/encfs/EncFSUtil.java index 314a6d2..290b873 100644 --- a/src/main/java/org/mrpdaemon/sec/encfs/EncFSUtil.java +++ b/src/main/java/org/mrpdaemon/sec/encfs/EncFSUtil.java @@ -129,6 +129,12 @@ public final class EncFSUtil { } } + public static boolean checkPassword(EncFSBinaryConfig config, + String password) throws EncFSUnsupportedException, + EncFSInvalidConfigException, EncFSCorruptDataException { + return VolumeKey.checkVolumeKey(config, password); + } + private static void readFromAndWriteTo(InputStream in, OutputStream out) throws IOException { byte[] buf = new byte[EIGHT_KILO]; diff --git a/src/main/java/org/mrpdaemon/sec/encfs/VolumeKey.java b/src/main/java/org/mrpdaemon/sec/encfs/VolumeKey.java index 4f65ae0..8752414 100644 --- a/src/main/java/org/mrpdaemon/sec/encfs/VolumeKey.java +++ b/src/main/java/org/mrpdaemon/sec/encfs/VolumeKey.java @@ -15,11 +15,9 @@ package org.mrpdaemon.sec.encfs; import javax.crypto.*; -import javax.crypto.spec.PBEKeySpec; import java.io.IOException; import java.security.*; import java.security.spec.InvalidKeySpecException; -import java.security.spec.KeySpec; import java.util.Arrays; // Class containing static methods implementing volume key functionality @@ -74,39 +72,73 @@ class VolumeKey { EncFSUnsupportedException { // Decode Base64 encoded ciphertext data // TODO: validate key/IV lengths - byte[] cipherVolKeyData; + EncFSBinaryConfig binaryConfig; + EncFSPBKDF2Data pbkdf2DataObj; try { - cipherVolKeyData = EncFSBase64.decode(config - .getBase64EncodedVolumeKey()); - } catch (IOException e) { - throw new EncFSInvalidConfigException("Corrupt key data in config"); + binaryConfig = EncFSBinaryConfig.from(config); + pbkdf2DataObj = EncFSPBKDF2Data.from(config, pbkdf2Data); + } catch (Exception e) { + throw new EncFSInvalidConfigException(e); } - byte[] encryptedVolKey = Arrays.copyOfRange(cipherVolKeyData, 4, - cipherVolKeyData.length); + DecryptInfo info = decryptAnyVolumeKey(binaryConfig, pbkdf2DataObj); + + boolean checksumOk = checkVolumeKey(binaryConfig, info); + + if (!checksumOk) { + throw new EncFSChecksumException("Volume key checksum mismatch"); + } + + return info.clearVolKeyData; + } + + protected static DecryptInfo decryptAnyVolumeKey(EncFSBinaryConfig config, + EncFSPBKDF2Data pbkdf2Data) + throws EncFSInvalidConfigException, EncFSCorruptDataException, + EncFSUnsupportedException { + byte[] encryptedVolKey = config.getEncryptedVolumeKey(); // Prepare key/IV for decryption - int keySizeInBytes = config.getVolumeKeySizeInBits() / 8; - byte[] passKeyData = Arrays.copyOfRange(pbkdf2Data, 0, keySizeInBytes); - byte[] passIvData = Arrays.copyOfRange(pbkdf2Data, keySizeInBytes, - keySizeInBytes + EncFSVolume.IV_LENGTH_IN_BYTES); + byte[] passKeyData = pbkdf2Data.getPassKeyData(); + byte[] passIvData = pbkdf2Data.getPassIvData(); Key passKey = EncFSCrypto.newKey(passKeyData); - byte[] ivSeed = Arrays.copyOfRange(cipherVolKeyData, 0, 4); + byte[] ivSeed = config.getIvSeed(); // Decrypt the volume key data Mac mac = encryptVolumeKeyData(passKey); byte[] clearVolKeyData = decryptVolumeKeyData(encryptedVolKey, passIvData, passKey, ivSeed, mac); - // Perform checksum computation - byte[] mac32 = EncFSCrypto.mac32(mac, clearVolKeyData, new byte[0]); + DecryptInfo info = new DecryptInfo(); + info.clearVolKeyData = clearVolKeyData; + info.mac = mac; + return info; + } - if (!Arrays.equals(ivSeed, mac32)) { - throw new EncFSChecksumException("Volume key checksum mismatch"); + public static boolean checkVolumeKey(EncFSBinaryConfig binaryConfig, + String password) throws EncFSUnsupportedException, + EncFSInvalidConfigException, EncFSCorruptDataException { + EncFSPBKDF2Data pbkdf2DataObj; + try { + pbkdf2DataObj = EncFSPBKDF2Data.from(binaryConfig, password); + } catch (InvalidKeySpecException ex) { + throw new EncFSUnsupportedException(ex); } - return clearVolKeyData; + DecryptInfo info = decryptAnyVolumeKey(binaryConfig, pbkdf2DataObj); + + boolean checksumOk = checkVolumeKey(binaryConfig, info); + return checksumOk; + } + + private static boolean checkVolumeKey(EncFSBinaryConfig config, + DecryptInfo info) throws EncFSInvalidConfigException, + EncFSCorruptDataException, EncFSUnsupportedException { + byte[] mac32 = EncFSCrypto.mac32(info.mac, info.clearVolKeyData, new byte[0]); + byte[] ivSeed = config.getIvSeed(); + + return Arrays.equals(ivSeed, mac32); } // Decrypt volume key data @@ -142,25 +174,11 @@ class VolumeKey { } if (pbkdf2Provider == null) { - // Execute PBKDF2 to derive key data from the password - SecretKeyFactory f; try { - f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); - } catch (NoSuchAlgorithmException e) { - throw new EncFSUnsupportedException(e); - } - KeySpec ks = new PBEKeySpec(password.toCharArray(), cipherSaltData, - config.getIterationForPasswordKeyDerivationCount(), - config.getVolumeKeySizeInBits() - + EncFSVolume.IV_LENGTH_IN_BYTES * 8); - SecretKey pbkdf2Key; - try { - pbkdf2Key = f.generateSecret(ks); - } catch (InvalidKeySpecException e) { + return EncFSPBKDF2Data.from(EncFSBinaryConfig.from(config), password).getPbkdf2Data(); + } catch (Exception e) { throw new EncFSInvalidConfigException(e); } - - return pbkdf2Key.getEncoded(); } else { return pbkdf2Provider.doPBKDF2(password, cipherSaltData.length, cipherSaltData, @@ -193,4 +211,9 @@ class VolumeKey { config.setEncodedKeyLengthInBytes(encodedVolKey.length); config.setBase64EncodedVolumeKey(EncFSBase64.encodeBytes(encodedVolKey)); } + + protected static class DecryptInfo { + byte[] clearVolKeyData; + Mac mac; + } }