diff --git a/README.md b/README.md index e1d186d..1b13c77 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,13 @@ RCW is a cascading symmetric cryptography agent meant to be embedded within Go p It encrypts all data with both AES256-GCM and ChaCha20-Poly1305. -Passphrases are securely cached for three minutes and RPC authentication is used to -ensure that only the binary+user responsible for caching the passphrase can utilize it. +Passwords are securely cached for three minutes and RPC authentication is used to +ensure that only the binary+user responsible for caching the password can utilize it. This feature is supported on Linux, FreeBSD, MacOS, and Windows. It is disabled if built with `-tags=interactive`. RCW also features a sanity check to ensure no data loss occurs due to a user entering the -incorrect passphrase during encryption. +incorrect password during encryption. Please note that RCW is a work-in-progress and breaking changes should be expected. Future versions may not be capable of decrypting the output of the current version. diff --git a/daemon/2server.go b/daemon/2server.go index 341da25..494563f 100644 --- a/daemon/2server.go +++ b/daemon/2server.go @@ -11,16 +11,16 @@ import ( var Timeout = 300 // seconds for RPC server timeout; configurable var daemonHash []byte -var globalPassphrase []byte +var globalPassword []byte // RCWService provides an RPC method. type RCWService struct{} // DecryptRequest is the RPC method that decrypts the incoming data using -// the global passphrase and returns the decrypted data +// the global password and returns the decrypted data func (h *RCWService) DecryptRequest(encBytes []byte, reply *[]byte) error { var err error - *reply, err = wrappers.DecryptAndZeroizePassphrase(encBytes, append([]byte{}, globalPassphrase...)) // pass new slice to avoid zeroizing cached passphrase) + *reply, err = wrappers.DecryptAndZeroizePassword(encBytes, append([]byte{}, globalPassword...)) // pass new slice to avoid zeroizing cached password) if err != nil { return err } @@ -28,9 +28,9 @@ func (h *RCWService) DecryptRequest(encBytes []byte, reply *[]byte) error { } // EncryptRequestAndZeroizeDecBytes is the RPC method that encrypts the incoming data using -// the global passphrase and returns the encrypted data +// the global password and returns the encrypted data func (h *RCWService) EncryptRequestAndZeroizeDecBytes(decBytes []byte, reply *[]byte) error { - *reply = wrappers.EncryptAndZeroizeDecBytesAndPassphrase(decBytes, append([]byte{}, globalPassphrase...)) // pass new slice to avoid zeroizing cached passphrase + *reply = wrappers.EncryptAndZeroizeDecBytesAndPassword(decBytes, append([]byte{}, globalPassword...)) // pass new slice to avoid zeroizing cached password return nil } diff --git a/daemon/2serverUNIXGeneric.go b/daemon/2serverUNIXGeneric.go index 5cada59..e1852a6 100644 --- a/daemon/2serverUNIXGeneric.go +++ b/daemon/2serverUNIXGeneric.go @@ -18,9 +18,9 @@ import ( // Start is the entry point for the RPC server responsible for // returning decrypted data to authenticated clients. -func Start(passphrase []byte) { - // store passphrase to be referenced by DecryptRequest method - globalPassphrase = passphrase +func Start(password []byte) { + // store password to be referenced by DecryptRequest method + globalPassword = password // register RCWService with the RPC package if err := rpc.Register(&RCWService{}); err != nil { @@ -69,9 +69,9 @@ func Start(passphrase []byte) { // handleConn verifies the identity of the client. // It uses the file descriptor of the connection to get the PID of the client, // which is then used to get the path of the client's executable and calculate its hash. -// The passphrase is only returned if the client's executable hash matches the daemon's hash +// The password is only returned if the client's executable hash matches the daemon's hash // and if the request is coming from the same user. -// This ensures that only the binary the daemon is embedded in can retrieve the passphrase. +// This ensures that only the binary the daemon is embedded in can retrieve the password. func handleConn(conn net.Conn, sigChan chan os.Signal) { ucred := peercred.Get(conn) diff --git a/daemon/2serverWindows.go b/daemon/2serverWindows.go index 2adbd88..a4ebd2d 100644 --- a/daemon/2serverWindows.go +++ b/daemon/2serverWindows.go @@ -24,9 +24,9 @@ const ( // Start is the entry point for the RPC server responsible for // returning decrypted data to authenticated clients. -func Start(passphrase []byte) { - // store passphrase to be referenced by DecryptRequest method - globalPassphrase = passphrase +func Start(password []byte) { + // store password to be referenced by DecryptRequest method + globalPassword = password // register RCWService with the RPC package if err := rpc.Register(&RCWService{}); err != nil { diff --git a/example.go b/example.go index 5f597c6..7eca320 100644 --- a/example.go +++ b/example.go @@ -14,25 +14,25 @@ import ( // // Usage: // rcw init : Generates the required sanity check file -// rcw : Runs the rcw daemon to decrypt data for three minutes -// rcw enc : Encrypts the provided text and outputs the ciphertext to ex-cipher.rcw (attempts to use daemon, falls back to user input for passphrase) -// rcw dec : Decrypts ex-cipher.rcw and outputs the plaintext to stdout (attempts to use daemon, falls back to user input for passphrase) +// rcw : Runs the rcw daemon to decrypt data for three minutes +// rcw enc : Encrypts the provided text and outputs the ciphertext to ex-cipher.rcw (attempts to use daemon, falls back to user input for password) +// rcw dec : Decrypts ex-cipher.rcw and outputs the plaintext to stdout (attempts to use daemon, falls back to user input for password) // Implementation Notes: // There are two main ways to use the RCW library: // // 1. Daemon mode: -// The daemon is started with a passphrase and runs in the background. +// The daemon is started with a password and runs in the background. // All encryption/decryption occurs in the daemon. // Avoid using the wrapper.Encrypt/Decrypt functions directly. -// Instead, cache the passphrase with the daemon and use the daemon to encrypt/decrypt data. +// Instead, cache the password with the daemon and use the daemon to encrypt/decrypt data. // // 2. Standalone mode: // The wrapper.Encrypt/Decrypt functions are used directly. -// The passphrase is provided directly to the functions. +// The password is provided directly to the functions. // // It is up to the client to perform the sanity check before encrypting data. -// This means that when using the daemon to cache the passphrase, the client should +// This means that when using the daemon to cache the password, the client should // perform the sanity check before activating the daemon. // TODO Tests: @@ -66,7 +66,7 @@ func main() { if daemon.IsOpen() { decBytes = daemon.GetDec(encBytes) } else { - decBytes, err = wrappers.DecryptAndZeroizePassphrase(encBytes, front.InputHidden("Enter RCW passphrase:")) + decBytes, err = wrappers.DecryptAndZeroizePassword(encBytes, front.InputSecret("Enter RCW password:")) if err != nil { fmt.Println(err) return @@ -81,8 +81,7 @@ func main() { fmt.Println("Daemon already running") return } - err := wrappers.RunSanityCheck(sanityFile, []byte(os.Args[1])) - if err != nil { + if err := wrappers.RunSanityCheck(sanityFile, []byte(os.Args[1])); err != nil { fmt.Println(err) return } @@ -91,8 +90,7 @@ func main() { if os.Args[1] == "init" { // create sanity check file // rcw init - err := wrappers.GenSanityCheckAndZeroizePassphrase(sanityFile, []byte(os.Args[2])) - if err != nil { + if err := wrappers.GenSanityCheckAndZeroizePassword(sanityFile, []byte(os.Args[2])); err != nil { fmt.Println(err) } return @@ -104,13 +102,12 @@ func main() { if daemon.IsOpen() { encBytes = daemon.GetEncAndZeroizeDecBytes(decBytes) } else { - passphrase := front.InputHidden("Enter RCW passphrase: ") - err := wrappers.RunSanityCheck(sanityFile, append([]byte{}, passphrase...)) // pass new slice to avoid zeroizing passphrase) - if err != nil { + password := front.InputSecret("Enter RCW password: ") + if err := wrappers.RunSanityCheck(sanityFile, append([]byte{}, password...)); err != nil { // pass new slice to avoid zeroizing password) fmt.Println(err) return } - encBytes = wrappers.EncryptAndZeroizeDecBytesAndPassphrase(decBytes, passphrase) + encBytes = wrappers.EncryptAndZeroizeDecBytesAndPassword(decBytes, password) } os.WriteFile(outputFile, encBytes, 0600) return diff --git a/go.mod b/go.mod index c4fe945..f54e10f 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.25.7 require ( github.com/Microsoft/go-winio v0.6.2 - github.com/rwinkhart/go-boilerplate v0.2.3-0.20260210031547-48e6abea8b2f + github.com/rwinkhart/go-boilerplate v0.2.3-0.20260211013046-29315c4b764f github.com/rwinkhart/peercred-mini v0.1.3 golang.org/x/crypto v0.48.0 golang.org/x/sys v0.41.0 diff --git a/go.sum b/go.sum index 3485463..0798b29 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/rwinkhart/go-boilerplate v0.2.3-0.20260210031547-48e6abea8b2f h1:QloiAudLU8WAVs+loMU4ZiSd8ve0eiSkGtFhQUibhTw= github.com/rwinkhart/go-boilerplate v0.2.3-0.20260210031547-48e6abea8b2f/go.mod h1:ES13A2r9fnCVfyezwMBgY/RgA4pOIudOUXz3Jk/ikes= +github.com/rwinkhart/go-boilerplate v0.2.3-0.20260211013046-29315c4b764f h1:ITbAql2EG50U0Z9JnPqupvdZ5zp0i44LnqZUsdz6DNc= +github.com/rwinkhart/go-boilerplate v0.2.3-0.20260211013046-29315c4b764f/go.mod h1:ES13A2r9fnCVfyezwMBgY/RgA4pOIudOUXz3Jk/ikes= github.com/rwinkhart/go-winio v0.1.1 h1:kAJKiqneR7cUR01Wn5/doAAV4kOGTEGPug4oinXc5N4= github.com/rwinkhart/go-winio v0.1.1/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0= github.com/rwinkhart/peercred-mini v0.1.3 h1:Pbyk/f4oXVtX38uuZAyxZZ8ZzRImL6WWgCr0pSktCKU= diff --git a/wrappers/highLevel.go b/wrappers/highLevel.go index 9e7eca5..4b43ba1 100644 --- a/wrappers/highLevel.go +++ b/wrappers/highLevel.go @@ -6,15 +6,15 @@ import ( "github.com/rwinkhart/go-boilerplate/security" ) -// DecryptAndZeroizePassphrase decrypts the provided byte slice using the provided passphrase. -func DecryptAndZeroizePassphrase(encBytes, passphrase []byte) ([]byte, error) { +// DecryptAndZeroizePassword decrypts the provided byte slice using the provided password. +func DecryptAndZeroizePassword(encBytes, password []byte) ([]byte, error) { if len(encBytes) < saltSize1 { return nil, errors.New("high-level decrypt: encrypted data is too short (invalid Argon2 salt)") } salt1 := encBytes[:saltSize1] encBytes = encBytes[saltSize1:] - key1 := derivePrimaryKey(passphrase, salt1) - security.ZeroizeBytes(passphrase) + key1 := derivePrimaryKey(password, salt1) + security.ZeroizeBytes(password) security.ZeroizeBytes(salt1) var err error encBytes, err = decryptCha(encBytes, key1) @@ -29,15 +29,15 @@ func DecryptAndZeroizePassphrase(encBytes, passphrase []byte) ([]byte, error) { return encBytes, err } -// EncryptAndZeroizeDecBytesAndPassphrase encrypts the provided byte slice using the provided passphrase. -func EncryptAndZeroizeDecBytesAndPassphrase(decBytes, passphrase []byte) []byte { +// EncryptAndZeroizeDecBytesAndPassword encrypts the provided byte slice using the provided password. +func EncryptAndZeroizeDecBytesAndPassword(decBytes, password []byte) []byte { defer security.ZeroizeBytes(decBytes) salt1 := getRandomBytes(saltSize1) defer security.ZeroizeBytes(salt1) salt2AES := getRandomBytes(saltSize2) salt2Cha := getRandomBytes(saltSize2) - key1 := derivePrimaryKey(passphrase, salt1) - security.ZeroizeBytes(passphrase) + key1 := derivePrimaryKey(password, salt1) + security.ZeroizeBytes(password) key2AES := deriveSecondaryKey(key1, salt2AES, []byte(hkdfInfoAES)) key2Cha := deriveSecondaryKey(key1, salt2Cha, []byte(hkdfInfoCha)) security.ZeroizeBytes(key1) diff --git a/wrappers/keyDeriv.go b/wrappers/keyDeriv.go index a90e43a..ef887c5 100644 --- a/wrappers/keyDeriv.go +++ b/wrappers/keyDeriv.go @@ -21,14 +21,14 @@ const ( saltSize2 = 32 // 256 bits, recommended salt size for HKDF ) -// derivePrimaryKey derives an encryption key from a passphrase using Argon2. +// derivePrimaryKey derives an encryption key from a password using Argon2. // The resulting key is not meant to be used directly for encryption, but rather as a key to derive other keys. -func derivePrimaryKey(passphrase, salt []byte) []byte { - return argon2.IDKey(passphrase, salt, argonTime, argonMemory, argonThreads, keyLen) +func derivePrimaryKey(password, salt []byte) []byte { + return argon2.IDKey(password, salt, argonTime, argonMemory, argonThreads, keyLen) } // deriveSecondaryKey derives a secondary key from the primary key using HKDF. -// It is meant to be an efficient way to derive multiple keys from a single passphrase. +// It is meant to be an efficient way to derive multiple keys from a single password. func deriveSecondaryKey(primaryKey, salt, info []byte) []byte { h := hkdf.New(sha256.New, primaryKey, salt, info) derivedKey := make([]byte, keyLen) diff --git a/wrappers/sanityCheck.go b/wrappers/sanityCheck.go index 7210ecc..af45781 100644 --- a/wrappers/sanityCheck.go +++ b/wrappers/sanityCheck.go @@ -8,28 +8,28 @@ import ( "github.com/rwinkhart/go-boilerplate/security" ) -// GenSanityCheckAndZeroizePassphrase creates an encrypted file containing known plaintext +// GenSanityCheckAndZeroizePassword creates an encrypted file containing known plaintext // to later be used for ensuring the user does not encrypt data with -// an incorrect passphrase. -func GenSanityCheckAndZeroizePassphrase(path string, passphrase []byte) error { - err := os.WriteFile(path, EncryptAndZeroizeDecBytesAndPassphrase([]byte("thx4usin'rcw"), passphrase), 0600) - security.ZeroizeBytes(passphrase) +// an incorrect password. +func GenSanityCheckAndZeroizePassword(path string, password []byte) error { + err := os.WriteFile(path, EncryptAndZeroizeDecBytesAndPassword([]byte("thx4usin'rcw"), password), 0600) + security.ZeroizeBytes(password) return err } // RunSanityCheck should be run before any encryption operation -// to ensure the user does not encrypt data with an incorrect passphrase. +// to ensure the user does not encrypt data with an incorrect password. // Failure to perform this check could result in data loss. -func RunSanityCheck(path string, passphrase []byte) error { +func RunSanityCheck(path string, password []byte) error { encBytes, err := os.ReadFile(path) if err != nil { return errors.New("Failed to read sanity check file (" + path + ")") } - decBytes, err := DecryptAndZeroizePassphrase(encBytes, passphrase) + decBytes, err := DecryptAndZeroizePassword(encBytes, password) if err == nil { if bytes.Equal(decBytes, []byte("thx4usin'rcw")) { return nil } } - return errors.New("sanity check failed (likely due to inconsistent passphrase)") + return errors.New("sanity check failed (likely due to inconsistent password)") }