Skip to content

Latest commit

 

History

164 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gocryptfs4j

License: MIT build Java Coverage ReleaseDate Maven Central javadoc core javadoc nio javadoc libfido2

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.

What is gocryptfs4j?

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 the core module) for create, open, list, read, write and delete operations,
  • a java.nio.file.FileSystemProvider (de.sfuhrm.gocryptfs4j.nio.GocryptFsProvider, in the nio module) so the filesystem can be used transparently through the standard java.nio.file.Files / Path API, and
  • an optional FIDO2 token (de.sfuhrm.gocryptfs4j.fido2.libfido2.LibFido2Token, in the libfido2 module) that protects a filesystem with a security key instead of a password, compatible with gocryptfs's -fido2 mode.

Relation to gocryptfs

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).
  • Key derivation: scrypt (RFC 7914) from the password, via Bouncy Castle.
  • Filename encryption: AES-EME, including gocryptfs long-name handling (> 175 character names) and directory IVs (gocryptfs.diriv).
  • Configuration: the same gocryptfs.conf format (JSON), including plaintextnames, diriv/deterministic names, longnames and hkdf options.

Features

  • 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 the libfido2 module: create and open filesystems protected by a security key's hmac-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.

Modules

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.

Requirements

  • Java 8 or newer (to run).
  • JDK 11 or newer (to build and run the tests).
  • Maven 3.x (to build).
  • gocryptfs and FUSE are only needed to run the interop integration tests; those tests are skipped automatically when they are absent.
  • For the optional FIDO2 support (libfido2 module): the fido2-cred and fido2-assert tools (fido2-tools on Debian/Ubuntu, the libfido2 Homebrew formula on macOS) and a security key with the hmac-secret extension.

Building

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 (the core module),
  • nio/target/gocryptfs4j-nio-X.Y.Z.jar — the FileSystemProvider (the nio module), and
  • libfido2/target/gocryptfs4j-libfido2-X.Y.Z.jar — the libfido2-backed FIDO2 token (the libfido2 module).

See Modules for a description of each module.

Tests

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 (from rfjakob/eme) and AES.
  • Core API tests for GocryptFs, CipherFile and DirEntry.
  • NIO provider tests covering Files traversal/read/write, PathMatcher (glob/regex), WatchService and UserPrincipalLookupService.
  • Golden fixtures from the reference gocryptfs, committed under core/src/test/resources/, verify that gocryptfs4j decrypts real gocryptfs-produced ciphertext: legacy v0.11 gocryptfs.conf files 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 real gocryptfs binary, via the plain API and the NIO view respectively. Requires FUSE.
  • GocryptfsToolingIT — uses gocryptfs -info, -fsck and -passwd as an oracle against gocryptfs4j-written directories. -info and -passwd need only the binary; -fsck additionally needs FUSE.
  • LibFido2GocryptFsIT (in the libfido2 module) — opens a real, pre-existing gocryptfs -fido2 cipher 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.

Usage

1. Plain Java API (core module)

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.

2. NIO FileSystemProvider (nio module)

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.

3. Options: content cipher and plaintext names

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.

4. FIDO2-protected filesystems (libfido2 module)

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.

Maven coordinates

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 coverage module is not published and has no Maven coordinates.

License

MIT

Releases

Used by

Contributors

Languages