Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional support for AEGIS encryption #900

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
- run: echo user_allow_other | sudo tee -a /etc/fuse.conf

# Build & upload static binary
- run: ./build-without-openssl.bash
- run: ./build-without-cgo.bash
- uses: actions/upload-artifact@v4
with:
name: gocryptfs ${{ github.sha }} static ${{ runner.arch }} binary, Go ${{ matrix.go }}
Expand Down
4 changes: 4 additions & 0 deletions Documentation/MANPAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ specify `-aessiv`.
Use XChaCha20-Poly1305 file content encryption. This should be much faster
than AES-GCM on CPUs that lack AES acceleration.

#### -aegis
Use AEGIS file content encryption. This should be much faster
than AES-GCM on CPUs with AES acceleration.

Run `gocryptfs -speed` to find out if and how much faster.

MOUNT OPTIONS
Expand Down
12 changes: 9 additions & 3 deletions Documentation/file-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ Data block, XChaCha20-Poly1305 (enabled via `-init -xchacha`)
1-4096 bytes encrypted data
16 bytes Poly1305 tag

Full block overhead (AES-GCM and AES-SIV mode) = 32/4096 = 1/128 = 0.78125 %
Data block, AEGIS (enabled via `-init -aegis`)

16 bytes nonce
1-4096 bytes encrypted data
16 bytes tag

Full block overhead (AEGIS, AES-GCM and AES-SIV mode) = 32/4096 = 1/128 = 0.78125 %

Full block overhead (XChaCha20-Poly1305 mode) = 40/4096 = \~1 %

Expand All @@ -36,8 +42,8 @@ Example: 1-byte file, AES-GCM and AES-SIV mode

Total: 51 bytes

Example: 5000-byte file, , AES-GCM and AES-SIV mode
---------------------------------------------------
Example: 5000-byte file, AEGIS, AES-GCM and AES-SIV mode
--------------------------------------------------------

Header 18 bytes
Data block 4128 bytes
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ ci:
uname -a ; go version ; openssl version
df -Th / /tmp /var/tmp

./build-without-openssl.bash
./build-without-cgo.bash
./build.bash
./test.bash
make root_test
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Then, download the source code and compile:

$ git clone https://github.com/rfjakob/gocryptfs.git
$ cd gocryptfs
$ ./build-without-openssl.bash
$ ./build-without-cgo.bash

This will compile a static binary that uses the Go stdlib crypto backend.

Expand Down
7 changes: 6 additions & 1 deletion benchmark.bash
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ OPT_OPENSSL=""
OPT_DIR=""
DD_ONLY=""
OPT_XCHACHA=""
OPT_AEGIS=""

while [[ $# -gt 0 ]] ; do
case $1 in
Expand All @@ -42,6 +43,9 @@ while [[ $# -gt 0 ]] ; do
-xchacha)
OPT_XCHACHA="-xchacha"
;;
-aegis)
OPT_AEGIS="-aegis"
;;
-*)
echo "Invalid option: $1"
usage
Expand Down Expand Up @@ -82,9 +86,10 @@ elif [[ $OPT_LOOPBACK -eq 1 ]]; then
"$HOME/go/src/github.com/hanwen/go-fuse/example/loopback/loopback" "$MNT" "$CRYPT" &
sleep 0.5
else
echo -n "Testing gocryptfs $OPT_XCHACHA $OPT_OPENSSL at $CRYPT: "
echo -n "Testing gocryptfs $OPT_XCHACHA $OPT_AEGIS $OPT_OPENSSL at $CRYPT: "
gocryptfs -version
gocryptfs $OPT_XCHACHA -q -init -extpass="echo test" -scryptn=10 "$CRYPT"
gocryptfs $OPT_AEGIS -q -init -extpass="echo test" -scryptn=10 "$CRYPT"
gocryptfs $OPT_OPENSSL -q -extpass="echo test" "$CRYPT" "$MNT"
fi

Expand Down
10 changes: 10 additions & 0 deletions build-without-aegis.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash -eu

cd "$(dirname "$0")"

CGO_ENABLED=0 source ./build.bash -tags without_aegis

if ldd gocryptfs 2> /dev/null ; then
echo "build-without-aegis.bash: error: compiled binary is not static"
exit 1
fi
10 changes: 10 additions & 0 deletions build-without-cgo.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash -eu

cd "$(dirname "$0")"

CGO_ENABLED=0 source ./build.bash -tags without_openssl,without_aegis

if ldd gocryptfs 2> /dev/null ; then
echo "build-without-cgo.bash: error: compiled binary is not static"
exit 1
fi
10 changes: 0 additions & 10 deletions build-without-openssl.bash

This file was deleted.

5 changes: 3 additions & 2 deletions cli_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ type argContainer struct {
longnames, allow_other, reverse, aessiv, nonempty, raw64,
noprealloc, speed, hkdf, serialize_reads, hh, info,
sharedstorage, fsck, one_file_system, deterministic_names,
xchacha bool
xchacha, aegis bool
// Mount options with opposites
dev, nodev, suid, nosuid, exec, noexec, rw, ro, kernel_cache, acl bool
masterkey, mountpoint, cipherdir, cpuprofile,
memprofile, ko, ctlsock, fsname, force_owner, trace string
// FIDO2
fido2 string
fido2 string
fido2_assert_options []string
// -extpass, -badname, -passfile can be passed multiple times
extpass, badname, passfile []string
Expand Down Expand Up @@ -188,6 +188,7 @@ func parseCliOpts(osArgs []string) (args argContainer) {
flagSet.BoolVar(&args.one_file_system, "one-file-system", false, "Don't cross filesystem boundaries")
flagSet.BoolVar(&args.deterministic_names, "deterministic-names", false, "Disable diriv file name randomisation")
flagSet.BoolVar(&args.xchacha, "xchacha", false, "Use XChaCha20-Poly1305 file content encryption")
flagSet.BoolVar(&args.aegis, "aegis", false, "Use AEGIS file content encryption")

// Mount options with opposites
flagSet.BoolVar(&args.dev, "dev", false, "Allow device files")
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/rfjakob/gocryptfs/v2
go 1.19

require (
github.com/aegis-aead/go-libaegis v0.2.10
github.com/aperturerobotics/jacobsa-crypto v1.0.2
github.com/hanwen/go-fuse/v2 v2.5.0
github.com/moby/sys/mountinfo v0.6.2
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/aegis-aead/go-libaegis v0.2.10 h1:5BJd8dgl2Fv3XwJ+W1dSWoS9FBaA61ryLnu2E4H2Vdw=
github.com/aegis-aead/go-libaegis v0.2.10/go.mod h1:WKSG0pcdvpDrwDPBl9NdAIxHxpZOCqlKWH7EOnPLKHc=
github.com/aperturerobotics/jacobsa-crypto v1.0.2 h1:tNvVy1rev9FagnOyBmTcI6d23FfNceG9IujZROTRtlc=
github.com/aperturerobotics/jacobsa-crypto v1.0.2/go.mod h1:buWU1iY+FjIcfpb1aYfFJZfl07WlS7O30lTyC2iwjv8=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
Expand Down
4 changes: 4 additions & 0 deletions gocryptfs-xray/xray_main.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ type argContainer struct {
encryptPaths *bool
aessiv *bool
xchacha *bool
aegis *bool
sep0 *bool
fido2 *string
version *bool
Expand All @@ -94,6 +95,7 @@ func main() {
args.sep0 = flag.Bool("0", false, "Use \\0 instead of \\n as separator")
args.aessiv = flag.Bool("aessiv", false, "Assume AES-SIV mode instead of AES-GCM")
args.xchacha = flag.Bool("xchacha", false, "Assume XChaCha20-Poly1305 mode instead of AES-GCM")
args.aegis = flag.Bool("aegis", false, "Assume AEGIS mode instead of AES-GCM")
args.fido2 = flag.String("fido2", "", "Protect the masterkey using a FIDO2 token instead of a password")
args.version = flag.Bool("version", false, "Print version information")

Expand Down Expand Up @@ -176,6 +178,8 @@ func inspectCiphertext(args *argContainer, fd *os.File) {
algo = cryptocore.BackendAESSIV
} else if *args.xchacha {
algo = cryptocore.BackendXChaCha20Poly1305
} else if *args.aegis {
algo = cryptocore.BackendAegis
}
headerBytes := make([]byte, contentenc.HeaderLen)
n, err := fd.ReadAt(headerBytes, 0)
Expand Down
1 change: 1 addition & 0 deletions init_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ func initDir(args *argContainer) {
Fido2AssertOptions: args.fido2_assert_options,
DeterministicNames: args.deterministic_names,
XChaCha20Poly1305: args.xchacha,
Aegis: args.aegis,
LongNameMax: args.longnamemax,
Masterkey: handleArgsMasterkey(args),
})
Expand Down
16 changes: 11 additions & 5 deletions internal/configfile/config_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type FIDO2Params struct {
// FIDO2 credential
CredentialID []byte
// FIDO2 hmac-secret salt
HMACSalt []byte
HMACSalt []byte
AssertOptions []string
}

Expand Down Expand Up @@ -75,6 +75,7 @@ type CreateArgs struct {
Fido2AssertOptions []string
DeterministicNames bool
XChaCha20Poly1305 bool
Aegis bool
LongNameMax uint8
Masterkey []byte
}
Expand All @@ -92,6 +93,8 @@ func Create(args *CreateArgs) error {
cf.setFeatureFlag(FlagHKDF)
if args.XChaCha20Poly1305 {
cf.setFeatureFlag(FlagXChaCha20Poly1305)
} else if args.Aegis {
cf.setFeatureFlag(FlagAegis)
} else {
// 128-bit IVs are mandatory for AES-GCM (default is 96!) and AES-SIV,
// XChaCha20Poly1305 uses even an even longer IV of 192 bits.
Expand Down Expand Up @@ -119,9 +122,9 @@ func Create(args *CreateArgs) error {
if len(args.Fido2CredentialID) > 0 {
cf.setFeatureFlag(FlagFIDO2)
cf.FIDO2 = &FIDO2Params{
CredentialID: args.Fido2CredentialID,
HMACSalt: args.Fido2HmacSalt,
AssertOptions: args.Fido2AssertOptions,
CredentialID: args.Fido2CredentialID,
HMACSalt: args.Fido2HmacSalt,
AssertOptions: args.Fido2AssertOptions,
}
}
// Catch bugs and invalid cli flag combinations early
Expand All @@ -133,7 +136,7 @@ func Create(args *CreateArgs) error {
key := args.Masterkey
if key == nil {
// Generate new random master key
key = cryptocore.RandBytes(cryptocore.KeyLen)
key = cryptocore.RandBytes(cryptocore.MaxKeyLen)
}
tlog.PrintMasterkeyReminder(key)
// Encrypt it using the password
Expand Down Expand Up @@ -327,6 +330,9 @@ func (cf *ConfFile) ContentEncryption() (algo cryptocore.AEADTypeEnum, err error
if cf.IsFeatureFlagSet(FlagXChaCha20Poly1305) {
return cryptocore.BackendXChaCha20Poly1305, nil
}
if cf.IsFeatureFlagSet(FlagAegis) {
return cryptocore.BackendAegis, nil
}
if cf.IsFeatureFlagSet(FlagAESSIV) {
return cryptocore.BackendAESSIV, nil
}
Expand Down
3 changes: 3 additions & 0 deletions internal/configfile/feature_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const (
FlagFIDO2
// FlagXChaCha20Poly1305 means we use XChaCha20-Poly1305 file content encryption
FlagXChaCha20Poly1305
// FlagAegis means we use Aegis file content encryption
FlagAegis
)

// knownFlags stores the known feature flags and their string representation
Expand All @@ -49,6 +51,7 @@ var knownFlags = map[flagIota]string{
FlagHKDF: "HKDF",
FlagFIDO2: "FIDO2",
FlagXChaCha20Poly1305: "XChaCha20Poly1305",
FlagAegis: "AEGIS",
}

// isFeatureFlagKnown verifies that we understand a feature flag.
Expand Down
8 changes: 4 additions & 4 deletions internal/configfile/scrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ type ScryptKDF struct {
// NewScryptKDF returns a new instance of ScryptKDF.
func NewScryptKDF(logN int) ScryptKDF {
var s ScryptKDF
s.Salt = cryptocore.RandBytes(cryptocore.KeyLen)
s.Salt = cryptocore.RandBytes(cryptocore.MaxKeyLen)
if logN <= 0 {
s.N = 1 << ScryptDefaultLogN
} else {
s.N = 1 << uint32(logN)
}
s.R = 8 // Always 8
s.P = 1 // Always 1
s.KeyLen = cryptocore.KeyLen
s.KeyLen = cryptocore.MaxKeyLen
return s
}

Expand Down Expand Up @@ -98,8 +98,8 @@ func (s *ScryptKDF) validateParams() error {
if len(s.Salt) < scryptMinSaltLen {
return fmt.Errorf("Fatal: scrypt salt length below minimum: value=%d, min=%d", len(s.Salt), scryptMinSaltLen)
}
if s.KeyLen < cryptocore.KeyLen {
return fmt.Errorf("Fatal: scrypt parameter KeyLen below minimum: value=%d, min=%d", s.KeyLen, cryptocore.KeyLen)
if s.KeyLen < cryptocore.MinKeyLen {
return fmt.Errorf("Fatal: scrypt parameter KeyLen below minimum: value=%d, min=%d", s.KeyLen, cryptocore.MinKeyLen)
}
return nil
}
7 changes: 6 additions & 1 deletion internal/configfile/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,13 @@ func (cf *ConfFile) Validate() error {
return fmt.Errorf("XChaCha20Poly1305 requires HKDF feature flag")
}
}
if cf.IsFeatureFlagSet(FlagAegis) {
if cf.IsFeatureFlagSet(FlagGCMIV128) {
return fmt.Errorf("AEGIS conflicts with GCMIV128 feature flag")
}
}
// The absence of other flags means AES-GCM (oldest algorithm)
if !cf.IsFeatureFlagSet(FlagXChaCha20Poly1305) && !cf.IsFeatureFlagSet(FlagAESSIV) {
if !cf.IsFeatureFlagSet(FlagAegis) && !cf.IsFeatureFlagSet(FlagXChaCha20Poly1305) && !cf.IsFeatureFlagSet(FlagAESSIV) {
if !cf.IsFeatureFlagSet(FlagGCMIV128) {
return fmt.Errorf("AES-GCM requires GCMIV128 feature flag")
}
Expand Down
9 changes: 6 additions & 3 deletions internal/contentenc/content_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ func TestSplitRange(t *testing.T) {
{6654, 8945},
}

key := make([]byte, cryptocore.KeyLen)
keyLen := cryptocore.BackendGoGCM.KeyLen
key := make([]byte, keyLen)
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true)
f := New(cc, DefaultBS)

Expand Down Expand Up @@ -50,7 +51,8 @@ func TestCiphertextRange(t *testing.T) {
{6654, 8945},
}

key := make([]byte, cryptocore.KeyLen)
keyLen := cryptocore.BackendGoGCM.KeyLen
key := make([]byte, keyLen)
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true)
f := New(cc, DefaultBS)

Expand All @@ -73,7 +75,8 @@ func TestCiphertextRange(t *testing.T) {
}

func TestBlockNo(t *testing.T) {
key := make([]byte, cryptocore.KeyLen)
keyLen := cryptocore.BackendGoGCM.KeyLen
key := make([]byte, keyLen)
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true)
f := New(cc, DefaultBS)

Expand Down
3 changes: 2 additions & 1 deletion internal/contentenc/offsets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import (

// TestSizeToSize tests CipherSizeToPlainSize and PlainSizeToCipherSize
func TestSizeToSize(t *testing.T) {
key := make([]byte, cryptocore.KeyLen)
keyLen := cryptocore.BackendGoGCM.KeyLen
key := make([]byte, keyLen)
cc := cryptocore.New(key, cryptocore.BackendGoGCM, DefaultIVBits, true)
ce := New(cc, DefaultBS)

Expand Down
Loading