A pure Java implementation of the gocryptfs forward-mode on-disk format. It lets Java applications create, read and write encrypted directories that are fully interchangeable with the original gocryptfs tool — no FUSE, no native libraries and no gocryptfs binary required for password-protected filesystems.
gocryptfs4j understands the same ciphertext format as gocryptfs. A directory encrypted with gocryptfs4j can be mounted and read by the real gocryptfs tool, and vice versa. It exposes the filesystem through two complementary APIs, plus optional FIDO2 support, provided by the project's modules:
- a plain Java API (
de.sfuhrm.gocryptfs4j.core.GocryptFs, in thecoremodule) for create, open, list, read, write and delete operations, - a
java.nio.file.FileSystemProvider(de.sfuhrm.gocryptfs4j.nio.GocryptFsProvider, in theniomodule) so the filesystem can be used transparently through the standardjava.nio.file.Files/PathAPI, and - an optional FIDO2 token (
de.sfuhrm.gocryptfs4j.fido2.libfido2.LibFido2Token, in thelibfido2module) that protects a filesystem with a security key instead of a password, compatible with gocryptfs's-fido2mode.
gocryptfs4j is not a wrapper around the gocryptfs binary. It is a clean-room Java implementation of the gocryptfs forward-mode on-disk format. Files written by gocryptfs4j use the same layout, encryption and key derivation as gocryptfs, so both tools operate on the same data:
- Content encryption: per 4 KiB block, authenticated with a per-file random
file ID stored in a header. Three ciphers are supported:
- AES-256-GCM (the default, matching gocryptfs
-init), - XChaCha20-Poly1305 (gocryptfs
-xchacha), - AES-SIV (RFC 5297, gocryptfs
-aessiv).
- AES-256-GCM (the default, matching gocryptfs
- Key derivation: scrypt (RFC 7914) from the password, via Bouncy Castle.
- Filename encryption: AES-EME, including gocryptfs long-name handling
(
> 175character names) and directory IVs (gocryptfs.diriv). - Configuration: the same
gocryptfs.confformat (JSON), includingplaintextnames,diriv/deterministic names,longnamesandhkdfoptions.
- Read and write gocryptfs forward-mode ciphertext (not read-only).
- Selectable content cipher: AES-256-GCM, XChaCha20-Poly1305 or AES-SIV.
- Random access reads and writes, including partial-block and sparse writes.
- Symlink support.
- Plaintext-names mode for compatibility with setups that disable name encryption.
- Optional FIDO2 (
-fido2) support through thelibfido2module: create and open filesystems protected by a security key'shmac-secret, byte- compatible with credentials created by gocryptfs. - Password-protected filesystems need no external processes, no JNI and no FUSE — runs anywhere the JVM runs.
The project is split into four Maven sub modules. The first three are published to Maven Central; the fourth exists only for the build:
| Module | Directory | Artifact ID | Java module | Purpose |
|---|---|---|---|---|
core |
core/ |
gocryptfs4j-core |
de.sfuhrm.gocryptfs4j.core |
The plain Java API (GocryptFs) plus the internal crypto, config and name handling. |
nio |
nio/ |
gocryptfs4j-nio |
de.sfuhrm.gocryptfs4j.nio |
The java.nio.file FileSystemProvider, built on top of core. |
libfido2 |
libfido2/ |
gocryptfs4j-libfido2 |
de.sfuhrm.gocryptfs4j.fido2.libfido2 |
Optional Fido2Token implementation that drives the libfido2 fido2-cred/fido2-assert tools. |
coverage |
coverage/ |
gocryptfs4j-coverage |
— | Build-time only JaCoCo aggregation module; not published. |
gocryptfs4j-nio depends on gocryptfs4j-core and re-exports it, so adding
only the nio module is enough to use both APIs. gocryptfs4j-libfido2 also
depends on gocryptfs4j-core and provides the optional FIDO2 support; it is not
needed for password-protected filesystems. The coverage module contains no
sources or tests of its own — it merely merges the code-coverage reports of the
other modules and is skipped for publishing.
- Java 8 or newer (to run).
- JDK 11 or newer (to build and run the tests).
- Maven 3.x (to build).
gocryptfsand FUSE are only needed to run the interop integration tests; those tests are skipped automatically when they are absent.- For the optional FIDO2 support (
libfido2module): thefido2-credandfido2-asserttools (fido2-toolson Debian/Ubuntu, thelibfido2Homebrew formula on macOS) and a security key with thehmac-secretextension.
mvn clean verify # build, run unit tests and integration tests
mvn package # build the jar only (skips integration tests)The build produces the three published artifacts:
core/target/gocryptfs4j-core-X.Y.Z.jar— the plain Java API (thecoremodule),nio/target/gocryptfs4j-nio-X.Y.Z.jar— theFileSystemProvider(theniomodule), andlibfido2/target/gocryptfs4j-libfido2-X.Y.Z.jar— the libfido2-backed FIDO2 token (thelibfido2module).
See Modules for a description of each module.
The test suite spans all published modules and is split into unit tests (run by
mvn test) and integration tests (run by mvn verify).
Unit tests — *Test.java, in the core, nio and libfido2 modules:
- Cryptographic known-answer tests against RFC and gocryptfs vectors: scrypt
(RFC 7914), HKDF (RFC 5869 plus gocryptfs's sub-key vectors), AES-SIV
(RFC 5297 plus gocryptfs's
TestK64), AES-GCM (NIST), EME (fromrfjakob/eme) and AES. - Core API tests for
GocryptFs,CipherFileandDirEntry. - NIO provider tests covering
Filestraversal/read/write,PathMatcher(glob/regex),WatchServiceandUserPrincipalLookupService. - Golden fixtures from the reference gocryptfs, committed under
core/src/test/resources/, verify that gocryptfs4j decrypts real gocryptfs-produced ciphertext: legacy v0.11gocryptfs.conffiles and the v1.3 example filesystem (HKDF, EME names, GCM content, long names, symlinks).
Integration tests — *IT.java, skipped automatically when their required
tools or configuration are unavailable:
GocryptfsInteropIT— small bidirectional round-trip (gocryptfs writes → gocryptfs4j reads and vice versa) across all ciphers and name modes.LinuxKernelInteropIT/LinuxKernelNioInteropIT— download the Linux kernel 1.0 source tree and verify it round-trips byte-for-byte (including sizes and timestamps) against the realgocryptfsbinary, via the plain API and the NIO view respectively. Requires FUSE.GocryptfsToolingIT— usesgocryptfs -info,-fsckand-passwdas an oracle against gocryptfs4j-written directories.-infoand-passwdneed only the binary;-fsckadditionally needs FUSE.LibFido2GocryptFsIT(in thelibfido2module) — opens a real, pre-existing gocryptfs-fido2cipher directory with a real security key. It is disabled by default and only runs when-Dgocryptfs.fido2.device=...and-Dgocryptfs.fido2.cipherDir=...are supplied.
The .github/workflows/gocryptfs-matrix.yml workflow runs the same integration
tests against a matrix of pinned gocryptfs releases (1.4, 1.8.0, 2.0 and the
current release), downloading each static binary from GitHub releases to catch
format regressions across the version history.
import de.sfuhrm.gocryptfs4j.core.DirEntry;
import de.sfuhrm.gocryptfs4j.core.GocryptFs;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
// Create a new encrypted filesystem. The directory must exist and be empty.
Path cipherDir = Path.of("/data/cipher");
try (GocryptFs fs = GocryptFs.create(cipherDir, "my-password".toCharArray())) {
fs.mkdir("/docs");
fs.createFile("/docs/hello.txt");
fs.write("/docs/hello.txt", 0, "hello gocryptfs".getBytes(StandardCharsets.UTF_8));
}
// Later, open it again and read back.
try (GocryptFs fs = GocryptFs.open(cipherDir, "my-password".toCharArray())) {
for (DirEntry e : fs.list("/")) {
System.out.println(e.plainName() + " (" + e.kind() + ", " + e.size() + " bytes)");
}
String content = new String(fs.readAll("/docs/hello.txt"), StandardCharsets.UTF_8);
}The GocryptFs object is AutoCloseable and wipes the master key and derived
keys from memory on close(). Other operations include size, truncate,
delete, createSymlink, readSymlinkTarget, setTimes, openRead (a
streaming decrypting InputStream) and openWrite (a streaming encrypting
OutputStream).
Passwords are encoded as UTF-8 before they reach scrypt, matching the raw bytes gocryptfs reads from a UTF-8 terminal, passfile or extpass program. Non-ASCII passwords, such as German umlauts, therefore interoperate with gocryptfs.
The provider is registered as a service, so it can be obtained via
FileSystems.newFileSystem and used with the standard Files API:
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
Map<String, Object> env = new HashMap<>();
env.put("cipherDir", Path.of("/data/cipher")); // Path or String
env.put("password", "my-password".toCharArray()); // char[]
try (FileSystem fs = FileSystems.newFileSystem(URI.create("gocryptfs:///"), env)) {
Path root = fs.getPath("/");
Files.createDirectory(root.resolve("docs"));
Files.writeString(root.resolve("docs/hello.txt"), "hello gocryptfs");
String content = Files.readString(root.resolve("docs/hello.txt"));
}Alternatively, instantiate the provider directly:
import de.sfuhrm.gocryptfs4j.nio.GocryptFsProvider;
try (FileSystem fs = new GocryptFsProvider()
.newFileSystem(Path.of("/data/cipher"), "my-password".toCharArray())) {
// ... use java.nio.file.Files on fs.getPath("/") as above
}FIDO2-protected filesystems work the same way: pass a Fido2Token to
newFileSystem(Path, Fido2Token), or put it under the fido2Token environment
key (instead of password) when using the URI form.
Note: the NIO view exposes basic attributes (type, size, times) and, when the backing filesystem supports it, the POSIX attributes (permissions, owner and group). gocryptfs does not store ownership or permissions itself, so these are read from and written to the backing cipher file.
The content cipher and name-encryption mode are chosen at creation time via the
ContentCipherType enum (de.sfuhrm.gocryptfs4j.core.ContentCipherType):
import de.sfuhrm.gocryptfs4j.core.ContentCipherType;
import de.sfuhrm.gocryptfs4j.core.GocryptFs;
// AES-256-GCM (default), XChaCha20-Poly1305 or AES-SIV:
try (GocryptFs fs = GocryptFs.create(
cipherDir, "pw".toCharArray(), false, ContentCipherType.AES_SIV)) {
// ...
}ContentCipherType |
gocryptfs flag | Description |
|---|---|---|
AES_GCM |
(default) | AES-256-GCM, 128-bit IVs |
XCHACHA20_POLY1305 |
-xchacha |
XChaCha20-Poly1305, 24-byte nonces |
AES_SIV |
-aessiv |
AES-SIV (RFC 5297), nonce-misuse resistant |
The third create argument controls plaintext (unencrypted) names: pass
true to disable filename encryption (gocryptfs -plaintextnames), or false
for the default EME-encrypted names. Filesystems are always opened
automatically with the correct cipher, since it is stored in gocryptfs.conf.
The optional libfido2 module provides LibFido2Token, a Fido2Token
implementation that drives the libfido2 command-line tools. It is byte-compatible
with gocryptfs's -fido2 mode, so a credential created by gocryptfs can be used
from Java and vice versa. Create and open take the token instead of a password:
import de.sfuhrm.gocryptfs4j.core.GocryptFs;
import de.sfuhrm.gocryptfs4j.fido2.Fido2Token;
import de.sfuhrm.gocryptfs4j.fido2.libfido2.LibFido2Token;
import java.nio.charset.StandardCharsets;
Fido2Token token = new LibFido2Token("/dev/hidraw5"); // list with: fido2-token -L
// Create a new filesystem: registers a credential on the key and touches it.
try (GocryptFs fs = GocryptFs.create(cipherDir, token)) {
fs.createFile("/hello.txt");
fs.write("/hello.txt", 0, "hello fido2".getBytes(StandardCharsets.UTF_8));
}
// Open an existing one: the credential ID and salt come from gocryptfs.conf.
try (GocryptFs fs = GocryptFs.open(cipherDir, token)) {
String content = new String(fs.readAll("/hello.txt"), StandardCharsets.UTF_8);
}Pass pin=true in the assertion options when creating the filesystem if the key
is PIN-protected:
import de.sfuhrm.gocryptfs4j.core.ContentCipherType;
import java.util.Collections;
try (GocryptFs fs = GocryptFs.create(cipherDir, token, null, false,
ContentCipherType.AES_GCM, Collections.singletonList("pin=true"))) {
// ...
}The options are stored in the config and reused on every open.
The core module (the plain Java API):
<dependency>
<groupId>de.sfuhrm</groupId>
<artifactId>gocryptfs4j-core</artifactId>
<version>0.5.0</version>
</dependency>The nio module (the FileSystemProvider) is a separate artifact that depends
on, and re-exports, the core module:
<dependency>
<groupId>de.sfuhrm</groupId>
<artifactId>gocryptfs4j-nio</artifactId>
<version>0.5.0</version>
</dependency>For FIDO2 support, add the libfido2 module as well (it depends on core):
<dependency>
<groupId>de.sfuhrm</groupId>
<artifactId>gocryptfs4j-libfido2</artifactId>
<version>0.5.0</version>
</dependency>Note: the
coveragemodule is not published and has no Maven coordinates.