Compare commits

...

2 Commits

Author SHA1 Message Date
Adrian e63300e3a2 Dirty hack to cache decoded values better 2015-11-09 15:40:46 +01:00
Adrian 9d452a6ba2 Update branch README 2015-11-09 15:40:35 +01:00
5 changed files with 197 additions and 103 deletions

View File

@ -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.

View File

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

View File

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

View File

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

View File

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