Compare commits
2 commits
1e2b240599
...
e63300e3a2
Author | SHA1 | Date | |
---|---|---|---|
e63300e3a2 | |||
9d452a6ba2 |
5 changed files with 197 additions and 103 deletions
72
README.md
72
README.md
|
@ -1,73 +1,9 @@
|
||||||
encfs-java
|
encfs-java
|
||||||
==========
|
==========
|
||||||
|
|
||||||
encfs-java is a Java library for accessing data in
|
I made the modifications for encfs-brute. The aim was
|
||||||
[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
|
1. to cache decoded values better,
|
||||||
with the official encfs implementation (http://www.arg0.net/encfs). Currently
|
2. no exception throwing if the key is incorrect.
|
||||||
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
|
Those two items are important to improve speed.
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
52
src/main/java/org/mrpdaemon/sec/encfs/EncFSBinaryConfig.java
Normal file
52
src/main/java/org/mrpdaemon/sec/encfs/EncFSBinaryConfig.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
77
src/main/java/org/mrpdaemon/sec/encfs/EncFSPBKDF2Data.java
Normal file
77
src/main/java/org/mrpdaemon/sec/encfs/EncFSPBKDF2Data.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
private static void readFromAndWriteTo(InputStream in, OutputStream out)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
byte[] buf = new byte[EIGHT_KILO];
|
byte[] buf = new byte[EIGHT_KILO];
|
||||||
|
|
|
@ -15,11 +15,9 @@
|
||||||
package org.mrpdaemon.sec.encfs;
|
package org.mrpdaemon.sec.encfs;
|
||||||
|
|
||||||
import javax.crypto.*;
|
import javax.crypto.*;
|
||||||
import javax.crypto.spec.PBEKeySpec;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.security.spec.KeySpec;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
// Class containing static methods implementing volume key functionality
|
// Class containing static methods implementing volume key functionality
|
||||||
|
@ -74,39 +72,73 @@ class VolumeKey {
|
||||||
EncFSUnsupportedException {
|
EncFSUnsupportedException {
|
||||||
// Decode Base64 encoded ciphertext data
|
// Decode Base64 encoded ciphertext data
|
||||||
// TODO: validate key/IV lengths
|
// TODO: validate key/IV lengths
|
||||||
byte[] cipherVolKeyData;
|
EncFSBinaryConfig binaryConfig;
|
||||||
|
EncFSPBKDF2Data pbkdf2DataObj;
|
||||||
try {
|
try {
|
||||||
cipherVolKeyData = EncFSBase64.decode(config
|
binaryConfig = EncFSBinaryConfig.from(config);
|
||||||
.getBase64EncodedVolumeKey());
|
pbkdf2DataObj = EncFSPBKDF2Data.from(config, pbkdf2Data);
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
throw new EncFSInvalidConfigException("Corrupt key data in config");
|
throw new EncFSInvalidConfigException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] encryptedVolKey = Arrays.copyOfRange(cipherVolKeyData, 4,
|
DecryptInfo info = decryptAnyVolumeKey(binaryConfig, pbkdf2DataObj);
|
||||||
cipherVolKeyData.length);
|
|
||||||
|
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
|
// Prepare key/IV for decryption
|
||||||
int keySizeInBytes = config.getVolumeKeySizeInBits() / 8;
|
byte[] passKeyData = pbkdf2Data.getPassKeyData();
|
||||||
byte[] passKeyData = Arrays.copyOfRange(pbkdf2Data, 0, keySizeInBytes);
|
byte[] passIvData = pbkdf2Data.getPassIvData();
|
||||||
byte[] passIvData = Arrays.copyOfRange(pbkdf2Data, keySizeInBytes,
|
|
||||||
keySizeInBytes + EncFSVolume.IV_LENGTH_IN_BYTES);
|
|
||||||
|
|
||||||
Key passKey = EncFSCrypto.newKey(passKeyData);
|
Key passKey = EncFSCrypto.newKey(passKeyData);
|
||||||
byte[] ivSeed = Arrays.copyOfRange(cipherVolKeyData, 0, 4);
|
byte[] ivSeed = config.getIvSeed();
|
||||||
|
|
||||||
// Decrypt the volume key data
|
// Decrypt the volume key data
|
||||||
Mac mac = encryptVolumeKeyData(passKey);
|
Mac mac = encryptVolumeKeyData(passKey);
|
||||||
byte[] clearVolKeyData = decryptVolumeKeyData(encryptedVolKey,
|
byte[] clearVolKeyData = decryptVolumeKeyData(encryptedVolKey,
|
||||||
passIvData, passKey, ivSeed, mac);
|
passIvData, passKey, ivSeed, mac);
|
||||||
|
|
||||||
// Perform checksum computation
|
DecryptInfo info = new DecryptInfo();
|
||||||
byte[] mac32 = EncFSCrypto.mac32(mac, clearVolKeyData, new byte[0]);
|
info.clearVolKeyData = clearVolKeyData;
|
||||||
|
info.mac = mac;
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
if (!Arrays.equals(ivSeed, mac32)) {
|
public static boolean checkVolumeKey(EncFSBinaryConfig binaryConfig,
|
||||||
throw new EncFSChecksumException("Volume key checksum mismatch");
|
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
|
// Decrypt volume key data
|
||||||
|
@ -142,25 +174,11 @@ class VolumeKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pbkdf2Provider == null) {
|
if (pbkdf2Provider == null) {
|
||||||
// Execute PBKDF2 to derive key data from the password
|
|
||||||
SecretKeyFactory f;
|
|
||||||
try {
|
try {
|
||||||
f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
|
return EncFSPBKDF2Data.from(EncFSBinaryConfig.from(config), password).getPbkdf2Data();
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (Exception 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) {
|
|
||||||
throw new EncFSInvalidConfigException(e);
|
throw new EncFSInvalidConfigException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return pbkdf2Key.getEncoded();
|
|
||||||
} else {
|
} else {
|
||||||
return pbkdf2Provider.doPBKDF2(password, cipherSaltData.length,
|
return pbkdf2Provider.doPBKDF2(password, cipherSaltData.length,
|
||||||
cipherSaltData,
|
cipherSaltData,
|
||||||
|
@ -193,4 +211,9 @@ class VolumeKey {
|
||||||
config.setEncodedKeyLengthInBytes(encodedVolKey.length);
|
config.setEncodedKeyLengthInBytes(encodedVolKey.length);
|
||||||
config.setBase64EncodedVolumeKey(EncFSBase64.encodeBytes(encodedVolKey));
|
config.setBase64EncodedVolumeKey(EncFSBase64.encodeBytes(encodedVolKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static class DecryptInfo {
|
||||||
|
byte[] clearVolKeyData;
|
||||||
|
Mac mac;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue