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 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.
|
||||
|
|
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)
|
||||
throws IOException {
|
||||
byte[] buf = new byte[EIGHT_KILO];
|
||||
|
|
|
@ -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]);
|
||||
|
||||
if (!Arrays.equals(ivSeed, mac32)) {
|
||||
throw new EncFSChecksumException("Volume key checksum mismatch");
|
||||
DecryptInfo info = new DecryptInfo();
|
||||
info.clearVolKeyData = clearVolKeyData;
|
||||
info.mac = mac;
|
||||
return info;
|
||||
}
|
||||
|
||||
return clearVolKeyData;
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue