diff --git a/README.md b/README.md index a2173bd..05450ea 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,32 @@ Listing the contents of an archive: $ aar list -f archive.aarch ``` +Encrypting an archive: + +```bash +$ aar encrypt -f archive.aarch +Password: +``` + +Where `` is the password you want to use to encrypt the archive, with a minimum length of 8 characters. +It removes the original _.aarch_ file and creates a new one with the encrypted data, with extension _.aarch.enc_. + +> [!NOTE] +> The encryption is done using the AES-256-GCM algorithm, and it only works for angel archives. + +Decrypting an archive: + +```bash +$ aar decrypt -f archive.aarch.enc +Password: +``` + +Where `` is the password you used to encrypt the archive. +It removes the encrypted _.aarch.enc_ file and creates a new one with the decrypted data, with extension _.aarch_. + +> [!NOTE] +> The decryption is done using the AES-256-GCM algorithm, and it only works for encrypted angel archives. + ## File Format ### Archive Header diff --git a/cmd/aar/main.go b/cmd/aar/main.go index c27bd26..1db0d0c 100644 --- a/cmd/aar/main.go +++ b/cmd/aar/main.go @@ -4,8 +4,10 @@ import ( "flag" "fmt" "os" + "syscall" "github.com/angelsolaorbaiceta/aar/cmd" + "golang.org/x/term" ) func main() { @@ -19,6 +21,12 @@ func main() { listCmd = flag.NewFlagSet("list", flag.ExitOnError) listFileNameFlag = listCmd.String("f", "", "Filename of the archive to list") + + encryptCmd = flag.NewFlagSet("encrypt", flag.ExitOnError) + encryptFileNameFlag = encryptCmd.String("f", "", "Filename of the archive to encrypt") + + decryptCmd = flag.NewFlagSet("decrypt", flag.ExitOnError) + decryptFileNameFlag = decryptCmd.String("f", "", "Filename of the archive to decrypt") ) if len(os.Args) < 2 { @@ -48,6 +56,20 @@ func main() { validateFileName(*listFileNameFlag) cmd.ListArchive(*listFileNameFlag) + case "encrypt": + encryptCmd.Parse(os.Args[2:]) + validateFileName(*encryptFileNameFlag) + password := promptPassword() + + cmd.EncryptArchive(*encryptFileNameFlag, password) + + case "decrypt": + decryptCmd.Parse(os.Args[2:]) + validateFileName(*decryptFileNameFlag) + password := promptPassword() + + cmd.DecryptArchive(*decryptFileNameFlag, password) + default: fmt.Fprintf(os.Stderr, "Usage: aar [options]\n") os.Exit(1) @@ -69,3 +91,28 @@ func createArchive(fileName string, fileNames []string) { cmd.CreateArchive(fileName, fileNames) } + +func promptPassword() string { + fmt.Print("Password: ") + + // Disable input echoing + passwordBytes, err := term.ReadPassword(int(syscall.Stdin)) + if err != nil { + fmt.Fprintf(os.Stderr, "Error reading password: %v\n", err) + os.Exit(1) + } + + fmt.Println() // Move to the next line after password input + + password := string(passwordBytes) + validatePassword(password) + + return password +} + +func validatePassword(password string) { + if len(password) < 8 { + fmt.Fprintf(os.Stderr, "The password must be at least 8 characters long.\n") + os.Exit(1) + } +} diff --git a/cmd/cypt.go b/cmd/cypt.go new file mode 100644 index 0000000..33bc334 --- /dev/null +++ b/cmd/cypt.go @@ -0,0 +1,107 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/angelsolaorbaiceta/aar/archive" +) + +func EncryptArchive(fileName, password string) { + // Read the archive + reader, err := os.OpenFile(fileName, os.O_RDONLY, 0) + if err != nil { + fmt.Fprintf(os.Stderr, "Error opening archive file: %v\n", err) + os.Exit(1) + } + + arch, err := archive.ReadArchive(reader) + if err != nil { + fmt.Fprintf(os.Stderr, "Error reading archive: %v\n", err) + os.Exit(1) + } + + // Encrypt the archive + encArch, err := arch.Encrypt(password) + if err != nil { + fmt.Fprintf(os.Stderr, "Error encrypting archive: %v\n", err) + os.Exit(1) + } + + // Write the encrypted archive to disk + encFileName := fileName + ".enc" + encFile, err := os.Create(encFileName) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating encrypted archive file: %v\n", err) + os.Exit(1) + } + defer encFile.Close() + + if err := encArch.Write(encFile); err != nil { + fmt.Fprintf(os.Stderr, "Error writing encrypted archive file: %v\n", err) + os.Exit(1) + } + + fmt.Fprintf(os.Stderr, "Archive encrypted successfully to %s\n", encFileName) + + // Remove the original archive + if err := os.Remove(fileName); err != nil { + fmt.Fprintf(os.Stderr, "Error removing original archive: %v\n", err) + os.Exit(1) + } +} + +func DecryptArchive(fileName, password string) { + // Read the encrypted archive + reader, err := os.OpenFile(fileName, os.O_RDONLY, 0) + if err != nil { + fmt.Fprintf(os.Stderr, "Error opening encrypted archive file: %v\n", err) + os.Exit(1) + } + + encArch, err := archive.ReadEncryptedArchive(reader) + if err != nil { + fmt.Fprintf(os.Stderr, "Error reading encrypted archive: %v\n", err) + os.Exit(1) + } + + // Decrypt the archive + arch, err := encArch.Decrypt(password) + if err != nil { + fmt.Fprintf(os.Stderr, "Error decrypting archive: %v\n", err) + os.Exit(1) + } + + // Write the decrypted archive to disk + decFileName := decryptFileName(fileName) + decFile, err := os.Create(decFileName) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating decrypted archive file: %v\n", err) + os.Exit(1) + } + defer decFile.Close() + + if err := arch.Write(decFile); err != nil { + fmt.Fprintf(os.Stderr, "Error writing decrypted archive file: %v\n", err) + os.Exit(1) + } + + fmt.Fprintf(os.Stderr, "Archive decrypted successfully to %s\n", decFileName) + + // Remove the encrypted archive + if err := os.Remove(fileName); err != nil { + fmt.Fprintf(os.Stderr, "Error removing encrypted archive: %v\n", err) + os.Exit(1) + } +} + +// decryptFileName returns the decrypted file name from the encrypted file name. +// If the file name doesn't end with ".enc", it appends ".dec" to the file name. +// Otherwise, it removes the ".enc" extension. +func decryptFileName(fileName string) string { + if len(fileName) < 4 || fileName[len(fileName)-4:] != ".enc" { + return fileName + ".dec" + } + + return fileName[:len(fileName)-4] +} diff --git a/go.mod b/go.mod index 13ab8eb..678c409 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,12 @@ require ( github.com/stretchr/testify v1.9.0 github.com/ulikunitz/xz v0.5.12 golang.org/x/crypto v0.26.0 + golang.org/x/term v0.23.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.23.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index f3924b9..86283c5 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,10 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=