From a4c740642bfa9f112751a02171cc67124aafa7c7 Mon Sep 17 00:00:00 2001 From: Shibly Meeran Date: Sat, 20 Jan 2024 19:22:58 +0530 Subject: [PATCH] adding byte flags for keytype in lib. password policy, command aliases and prefix for key and ciphertext in CLI. --- .goreleaser.yml | 2 +- README.md | 56 +++++---- cli/internal/commands/const.go | 28 ++--- cli/internal/commands/decrypt.go | 126 ++++++++------------ cli/internal/commands/encrypt.go | 155 ++++++++++--------------- cli/internal/commands/keygen.go | 40 ++----- cli/internal/commands/password.go | 30 ----- cli/internal/commands/userinput.go | 74 ++++++++++++ cli/internal/commands/xipher.go | 4 +- const.go | 24 ++-- crypto.go | 85 +++----------- internal/ecc/crypto.go | 4 +- internal/ecc/keys.go | 6 +- internal/{symcipher => xcp}/crypto.go | 8 +- internal/{symcipher => xcp}/key.go | 7 +- kdf.go | 27 +++-- keys.go | 95 ++++++++------- xipher_test.go | 160 ++++++++------------------ 18 files changed, 397 insertions(+), 534 deletions(-) delete mode 100644 cli/internal/commands/password.go create mode 100644 cli/internal/commands/userinput.go rename internal/{symcipher => xcp}/crypto.go (95%) rename internal/{symcipher => xcp}/key.go (79%) diff --git a/.goreleaser.yml b/.goreleaser.yml index 42dd4bb..57d0a44 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -31,7 +31,7 @@ nfpms: brews: - name: xipher homepage: "https://dev.shib.me/xipher" - description: "Xipher is a curated collection of cryptographic primitives written in Go to encrypt and decrypt data with optional compression." + description: "Xipher is a curated collection of cryptographic primitives put together to perform password-based asymmetric encryption. It is written in Go and can be used as a library or a CLI tool." license: "MIT" commit_author: name: Shibly Meeran diff --git a/README.md b/README.md index 353580f..8ccaa06 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,11 @@ [![Release Status](https://github.com/shibme/xipher/actions/workflows/release.yml/badge.svg)](https://github.com/shibme/xipher/actions/workflows/release.yml) [![License](https://img.shields.io/github/license/shibme/xipher)](https://github.com/shibme/xipher/blob/main/LICENSE) -Xipher is a curated collection of cryptographic primitives written in Go to encrypt and decrypt data with optional compression. +Xipher is a curated collection of cryptographic primitives put together to perform password-based asymmetric encryption. It is written in Go and can be used as a library or a CLI tool. ### Features - Password based public key generation. -- Encrypt data with public key generated from a password. -- Encrypt data with password or a generated private key. -- Decrypt data with password or a a given private key (works on all combinations of encryption). +- Encryption of data with the public key generated from a password. - Supports stream cipher along with stream compression thereby keeping a low memory footprint. Makes it handy for encrypting large files or data streams. ### Under the hood @@ -21,12 +19,38 @@ Xipher uses the following cryptographic primitives and libraries to encrypt/decr - [XChaCha20-Poly1305](https://en.wikipedia.org/wiki/ChaCha20-Poly1305) for encryption and decryption. - [Zlib](https://en.wikipedia.org/wiki/Zlib) for compression and decompression. -### Install or Update -```bash +### CLI + +#### Install/Update (CLI) +Download the latest binary from the [releases](https://github.com/shibme/xipher/releases/latest) page and add it to your path. + +You can also install with brew using the following command +```sh +brew install shibme/beta/xipher +``` +Alternatively try it out using docker +```sh +docker run --rm -v $(pwd):/data/ -it shibme/xipher help +``` + +#### Usage Example (CLI) +- Generate a new public key +```sh +xipher keygen +``` +- Use the public key to encrypt text/file +```sh +xipher encrypt text -k +``` + +### Go Package + +#### Install/Update (Go Package) +```sh go get -u dev.shib.me/xipher ``` -### Usage Example +#### Usage Example (Go Package) ```go package main @@ -67,22 +91,4 @@ func main() { } fmt.Println("Decrypted:", string(plainText)) } -``` -The output of the above code looks something like this: -```sh -PublicKey: MS5JUG7ZVJLETJA7WE2XKFHRE4PP6LKYCTEF2FTNUJ5QEZJIGZPCAIABX7UMDM7DPZX6WNOXICBUBBPPKE -Encrypted: AQQCAAN75DA3HY36N7VTLV2AQNAIL32RBWA2GJY7JNFA7QNOBT4CFXFBZMKY36DGE6FBKPATB7EJTD5SCNXI6URGO4WERYXYHYTL4RCXLRMYBG4K4UDZ5HGE7APXUVZ4RNESUV3EWVDJBHUAT5F7U5BFNUMTXB72Q3AZBGZDN3KXBWLUX23Q -Decrypted: Hello World! -``` - -### Basic CLI -Download the latest binary from the [releases](https://github.com/shibme/xipher/releases/latest) page and add it to your path. - -You can also install with brew using the following command -```sh -brew install shibme/beta/xipher -``` -Alternatively try it out using docker by mounting to `/data` directory inside the container -```sh -docker run --rm -v $(pwd):/data/ -it shibme/xipher help ``` \ No newline at end of file diff --git a/cli/internal/commands/const.go b/cli/internal/commands/const.go index bdd1a99..26f0294 100644 --- a/cli/internal/commands/const.go +++ b/cli/internal/commands/const.go @@ -3,7 +3,9 @@ package commands import "github.com/spf13/cobra" const ( - appName = "xipher" + appName = "xipher" + xipherKeyPrefix = "XK_" + xipherTxtPrefix = "XT_" ) var ( @@ -20,7 +22,7 @@ var ( encryptCmd *cobra.Command // Encrypt String Command - encryptStrCmd *cobra.Command + encryptTxtCmd *cobra.Command // Encrypt File Command encryptFileCmd *cobra.Command @@ -29,7 +31,7 @@ var ( decryptCmd *cobra.Command // Decrypt String Command - decryptStrCmd *cobra.Command + decryptTxtCmd *cobra.Command // Decrypt File Command decryptFileCmd *cobra.Command @@ -50,11 +52,11 @@ var ( usage: "Shows version info", } - // Password Flag - passwordFlag = flagDef{ - name: "password", - shorthand: "p", - usage: "Specify a password", + // Ignore Password Policy Check Flag + ignorePasswordCheckFlag = flagDef{ + name: "ignore", + shorthand: "i", + usage: "Ignores the password policy check", } // Key Flag @@ -64,11 +66,11 @@ var ( usage: "Specify a key string", } - // String Flag - stringFlag = flagDef{ - name: "string", - shorthand: "s", - usage: "Specify a string", + // Ciphertext Flag + ciphertextFlag = flagDef{ + name: "ciphertext", + shorthand: "c", + usage: "Specify the ciphertext", } // File Flag diff --git a/cli/internal/commands/decrypt.go b/cli/internal/commands/decrypt.go index 67ec568..c32c451 100644 --- a/cli/internal/commands/decrypt.go +++ b/cli/internal/commands/decrypt.go @@ -6,77 +6,68 @@ import ( "os" "dev.shib.me/xipher" + "github.com/fatih/color" "github.com/spf13/cobra" ) +func fromXipherText(xipherText string) ([]byte, error) { + if len(xipherText) < len(xipherTxtPrefix) || xipherText[:len(xipherTxtPrefix)] != xipherTxtPrefix { + return nil, fmt.Errorf("invalid xipher text") + } + return decode(xipherText[len(xipherTxtPrefix):]) +} + func decryptCommand() *cobra.Command { if decryptCmd != nil { return decryptCmd } decryptCmd = &cobra.Command{ - Use: "decrypt", - Short: "Decrypts the data", + Use: "decrypt", + Aliases: []string{"decr", "dec", "de", "d"}, + Short: "Decrypts the encrypted data", Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, } - decryptCmd.AddCommand(decryptStringCommand()) + decryptCmd.AddCommand(decryptTextCommand()) decryptCmd.AddCommand(decryptFileCommand()) return decryptCmd } -func decryptStringCommand() *cobra.Command { - if decryptStrCmd != nil { - return decryptStrCmd +func decryptTextCommand() *cobra.Command { + if decryptTxtCmd != nil { + return decryptTxtCmd } - decryptStrCmd = &cobra.Command{ - Use: "string", - Aliases: []string{"str"}, - Short: "Decrypts a xipher encrypted string", + decryptTxtCmd = &cobra.Command{ + Use: "text", + Aliases: []string{"txt", "t", "string", "str", "s"}, + Short: "Decrypts a xipher encrypted text", Run: func(cmd *cobra.Command, args []string) { - cipheredStr, err := decode(cmd.Flag(stringFlag.name).Value.String()) + xipherText, err := fromXipherText(cmd.Flag(ciphertextFlag.name).Value.String()) if err != nil { exitOnError(err) } var src, dst bytes.Buffer - src.Write(cipheredStr) - keyStr := cmd.Flag(keyFlag.name).Value.String() - if keyStr != "" { - keyBytes, err := decode(keyStr) - if err != nil { - exitOnError(err) - } - privKey, err := xipher.ParsePrivateKey(keyBytes) - if err != nil { - exitOnError(err) - } - err = privKey.DecryptStream(&dst, &src) - if err != nil { - exitOnError(err) - } - } else { - // Get password from user - password, err := getPasswordFromUser(false) - if err != nil { - exitOnError(err) - } - privKey, err := xipher.NewPrivateKeyForPassword(password) - if err != nil { - exitOnError(err) - } - err = privKey.DecryptStream(&dst, &src) - if err != nil { - exitOnError(err) - } + src.Write(xipherText) + password, err := getPasswordFromUser(false, true) + if err != nil { + exitOnError(err) } - fmt.Println(dst.String()) + privKey, err := xipher.NewPrivateKeyForPassword(password) + if err != nil { + exitOnError(err) + } + err = privKey.DecryptStream(&dst, &src) + if err != nil { + exitOnError(err) + } + fmt.Println(color.GreenString(dst.String())) safeExit() }, } - decryptStrCmd.Flags().StringP(stringFlag.name, stringFlag.shorthand, "", stringFlag.usage) - decryptStrCmd.Flags().StringP(keyFlag.name, keyFlag.shorthand, "", keyFlag.usage) - decryptStrCmd.MarkFlagRequired(stringFlag.name) - return decryptStrCmd + decryptTxtCmd.Flags().StringP(ciphertextFlag.name, ciphertextFlag.shorthand, "", ciphertextFlag.usage) + decryptTxtCmd.MarkFlagRequired(ciphertextFlag.name) + return decryptTxtCmd } func decryptFileCommand() *cobra.Command { @@ -84,8 +75,9 @@ func decryptFileCommand() *cobra.Command { return decryptFileCmd } decryptFileCmd = &cobra.Command{ - Use: "file", - Short: "Decrypts a xipher encrypted file", + Use: "file", + Aliases: []string{"f"}, + Short: "Decrypts a xipher encrypted file", Run: func(cmd *cobra.Command, args []string) { srcPath := cmd.Flag(fileFlag.name).Value.String() dstPath := cmd.Flag(outFlag.name).Value.String() @@ -101,41 +93,23 @@ func decryptFileCommand() *cobra.Command { if err != nil { exitOnError(err) } - keyStr := cmd.Flag(keyFlag.name).Value.String() - if keyStr != "" { - keyBytes, err := decode(keyStr) - if err != nil { - exitOnError(err) - } - privKey, err := xipher.ParsePrivateKey(keyBytes) - if err != nil { - exitOnError(err) - } - err = privKey.DecryptStream(dst, src) - if err != nil { - exitOnError(err) - } - } else { - // Get password from user - password, err := getPasswordFromUser(false) - if err != nil { - exitOnError(err) - } - privKey, err := xipher.NewPrivateKeyForPassword(password) - if err != nil { - exitOnError(err) - } - err = privKey.DecryptStream(dst, src) - if err != nil { - exitOnError(err) - } + password, err := getPasswordFromUser(false, true) + if err != nil { + exitOnError(err) + } + privKey, err := xipher.NewPrivateKeyForPassword(password) + if err != nil { + exitOnError(err) + } + err = privKey.DecryptStream(dst, src) + if err != nil { + exitOnError(err) } safeExit() }, } decryptFileCmd.Flags().StringP(fileFlag.name, fileFlag.shorthand, "", fileFlag.usage) decryptFileCmd.Flags().StringP(outFlag.name, outFlag.shorthand, "", outFlag.usage) - decryptFileCmd.Flags().StringP(keyFlag.name, keyFlag.shorthand, "", keyFlag.usage) decryptFileCmd.MarkFlagRequired(fileFlag.name) decryptFileCmd.MarkFlagRequired(outFlag.name) return decryptFileCmd diff --git a/cli/internal/commands/encrypt.go b/cli/internal/commands/encrypt.go index 2ac75ad..d5c8a70 100644 --- a/cli/internal/commands/encrypt.go +++ b/cli/internal/commands/encrypt.go @@ -6,85 +6,72 @@ import ( "os" "dev.shib.me/xipher" + "github.com/fatih/color" "github.com/spf13/cobra" ) +func toXipherText(data []byte) string { + return xipherTxtPrefix + encode(data) +} + +func fromXipherKey(xipherKey string) ([]byte, error) { + if len(xipherKey) < len(xipherKeyPrefix) || xipherKey[:len(xipherKeyPrefix)] != xipherKeyPrefix { + return nil, fmt.Errorf("invalid xipher key") + } + return decode(xipherKey[len(xipherKeyPrefix):]) +} + func encryptCommand() *cobra.Command { if encryptCmd != nil { return encryptCmd } encryptCmd = &cobra.Command{ - Use: "encrypt", - Short: "Encrypts the data", + Use: "encrypt", + Aliases: []string{"encr", "enc", "en", "e"}, + Short: "Encrypts data", Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, } - encryptCmd.AddCommand(encryptStringCommand()) + encryptCmd.AddCommand(encryptTextCommand()) encryptCmd.AddCommand(encryptFileCommand()) return encryptCmd } -func encryptStringCommand() *cobra.Command { - if encryptStrCmd != nil { - return encryptStrCmd +func encryptTextCommand() *cobra.Command { + if encryptTxtCmd != nil { + return encryptTxtCmd } - encryptStrCmd = &cobra.Command{ - Use: "string", - Aliases: []string{"str"}, - Short: "Encrypts a given string", + encryptTxtCmd = &cobra.Command{ + Use: "text", + Aliases: []string{"txt", "t", "string", "str", "s"}, + Short: "Encrypts a given text", Run: func(cmd *cobra.Command, args []string) { - inputStr := cmd.Flag(stringFlag.name).Value.String() + keyBytes, err := fromXipherKey(cmd.Flag(keyFlag.name).Value.String()) + if err != nil { + exitOnError(err) + } + pubKey, err := xipher.ParsePublicKey(keyBytes) + if err != nil { + exitOnError(err) + } + input, err := getHiddenInputFromUser("Enter text to encrypt: ") + if err != nil { + exitOnError(err) + } var src, dst bytes.Buffer - src.WriteString(inputStr) - keyStr := cmd.Flag(keyFlag.name).Value.String() - if keyStr != "" { - keyBytes, err := decode(keyStr) - if err != nil { - exitOnError(err) - } - if len(keyBytes) == xipher.PrivateKeyLength { - privKey, err := xipher.ParsePrivateKey(keyBytes) - if err != nil { - exitOnError(err) - } - err = privKey.EncryptStream(&dst, &src, true) - if err != nil { - exitOnError(err) - } - } else { - pubKey, err := xipher.ParsePublicKey(keyBytes) - if err != nil { - exitOnError(err) - } - err = pubKey.EncryptStream(&dst, &src, true) - if err != nil { - exitOnError(err) - } - } - } else { - // Get password from user - password, err := getPasswordFromUser(true) - if err != nil { - exitOnError(err) - } - privKey, err := xipher.NewPrivateKeyForPassword(password) - if err != nil { - exitOnError(err) - } - err = privKey.EncryptStream(&dst, &src, true) - if err != nil { - exitOnError(err) - } + src.Write(input) + err = pubKey.EncryptStream(&dst, &src, true) + if err != nil { + exitOnError(err) } - fmt.Println(encode(dst.Bytes())) + fmt.Println(color.GreenString(toXipherText(dst.Bytes()))) safeExit() }, } - encryptStrCmd.Flags().StringP(stringFlag.name, stringFlag.shorthand, "", stringFlag.usage) - encryptStrCmd.Flags().StringP(keyFlag.name, keyFlag.shorthand, "", keyFlag.usage) - encryptStrCmd.MarkFlagRequired(stringFlag.name) - return encryptStrCmd + encryptTxtCmd.Flags().StringP(keyFlag.name, keyFlag.shorthand, "", keyFlag.usage) + encryptTxtCmd.MarkFlagRequired(keyFlag.name) + return encryptTxtCmd } func encryptFileCommand() *cobra.Command { @@ -92,8 +79,9 @@ func encryptFileCommand() *cobra.Command { return encryptFileCmd } encryptFileCmd = &cobra.Command{ - Use: "file", - Short: "Encrypts a given file", + Use: "file", + Aliases: []string{"f"}, + Short: "Encrypts a given file", Run: func(cmd *cobra.Command, args []string) { srcPath := cmd.Flag(fileFlag.name).Value.String() src, err := os.Open(srcPath) @@ -110,45 +98,19 @@ func encryptFileCommand() *cobra.Command { } keyStr := cmd.Flag(keyFlag.name).Value.String() compress, _ := cmd.Flags().GetBool(compressFlag.name) - if keyStr != "" { - keyBytes, err := decode(keyStr) - if err != nil { - exitOnError(err) - } - if len(keyBytes) == xipher.PrivateKeyLength { - privKey, err := xipher.ParsePrivateKey(keyBytes) - if err != nil { - exitOnError(err) - } - err = privKey.EncryptStream(dst, src, compress) - if err != nil { - exitOnError(err) - } - } else { - pubKey, err := xipher.ParsePublicKey(keyBytes) - if err != nil { - exitOnError(err) - } - err = pubKey.EncryptStream(dst, src, compress) - if err != nil { - exitOnError(err) - } - } - } else { - // Get password from user - password, err := getPasswordFromUser(true) - if err != nil { - exitOnError(err) - } - privKey, err := xipher.NewPrivateKeyForPassword(password) - if err != nil { - exitOnError(err) - } - err = privKey.EncryptStream(dst, src, compress) - if err != nil { - exitOnError(err) - } + keyBytes, err := fromXipherKey(keyStr) + if err != nil { + exitOnError(err) + } + pubKey, err := xipher.ParsePublicKey(keyBytes) + if err != nil { + exitOnError(err) + } + err = pubKey.EncryptStream(dst, src, compress) + if err != nil { + exitOnError(err) } + fmt.Println("Encrypted file:", color.GreenString(dstPath)) safeExit() }, } @@ -157,5 +119,6 @@ func encryptFileCommand() *cobra.Command { encryptFileCmd.Flags().StringP(keyFlag.name, keyFlag.shorthand, "", keyFlag.usage) encryptFileCmd.Flags().BoolP(compressFlag.name, compressFlag.shorthand, false, compressFlag.usage) encryptFileCmd.MarkFlagRequired(fileFlag.name) + encryptFileCmd.MarkFlagRequired(keyFlag.name) return encryptFileCmd } diff --git a/cli/internal/commands/keygen.go b/cli/internal/commands/keygen.go index 2aaeeb3..c848dae 100644 --- a/cli/internal/commands/keygen.go +++ b/cli/internal/commands/keygen.go @@ -2,12 +2,16 @@ package commands import ( "fmt" - "os" "dev.shib.me/xipher" + "github.com/fatih/color" "github.com/spf13/cobra" ) +func toXipherKey(pubKey []byte) string { + return xipherKeyPrefix + encode(pubKey) +} + func keygenCommand() *cobra.Command { if keygenCmd != nil { return keygenCmd @@ -16,42 +20,22 @@ func keygenCommand() *cobra.Command { Use: "keygen", Short: "Generate a new key pair or public key based on a password", Run: func(cmd *cobra.Command, args []string) { - pwdFlag, err := cmd.Flags().GetBool(passwordFlag.name) + ignoreFlag, _ := cmd.Flags().GetBool(ignorePasswordCheckFlag.name) + password, err := getPasswordFromUser(true, ignoreFlag) if err != nil { exitOnError(err) } - var privKey *xipher.PrivateKey - if pwdFlag { - // Get password from user - password, err := getPasswordFromUser(true) - if err != nil { - exitOnError(err) - } - privKey, err = xipher.NewPrivateKeyForPassword(password) - if err != nil { - exitOnError(err) - } - } else { - privKey, err = xipher.NewPrivateKey() - if err != nil { - exitOnError(err) - } + privKey, err := xipher.NewPrivateKeyForPassword(password) + if err != nil { + exitOnError(err) } pubKey, err := privKey.PublicKey() if err != nil { exitOnError(err) } - pubKeyStr := encode(pubKey.Bytes()) - fmt.Println("Public Key:", pubKeyStr) - if privKeyBytes, err := privKey.Bytes(); err == nil { - fileName := fmt.Sprintf("xipher_%s.privkey", pubKeyStr) - if err := os.WriteFile(fileName, []byte(encode(privKeyBytes)), 0644); err != nil { - exitOnError(err) - } - fmt.Println("Private Key file:", fileName) - } + fmt.Println("Public Key:", color.HiWhiteString(toXipherKey(pubKey.Bytes()))) }, } - keygenCmd.Flags().BoolP(passwordFlag.name, passwordFlag.shorthand, false, passwordFlag.usage) + keygenCmd.Flags().BoolP(ignorePasswordCheckFlag.name, ignorePasswordCheckFlag.shorthand, false, ignorePasswordCheckFlag.usage) return keygenCmd } diff --git a/cli/internal/commands/password.go b/cli/internal/commands/password.go deleted file mode 100644 index af37f60..0000000 --- a/cli/internal/commands/password.go +++ /dev/null @@ -1,30 +0,0 @@ -package commands - -import ( - "bytes" - "fmt" - "syscall" - - "golang.org/x/term" -) - -func getPasswordFromUser(confirm bool) ([]byte, error) { - fmt.Print("Enter Password:\t") - password, err := term.ReadPassword(int(syscall.Stdin)) - if err != nil { - return nil, err - } - fmt.Println() - if confirm { - fmt.Print("Confirm Password:\t") - confirmPassword, err := term.ReadPassword(int(syscall.Stdin)) - if err != nil { - return nil, err - } - fmt.Println() - if !bytes.Equal(password, confirmPassword) { - return nil, fmt.Errorf("passwords do not match") - } - } - return password, nil -} diff --git a/cli/internal/commands/userinput.go b/cli/internal/commands/userinput.go new file mode 100644 index 0000000..99811ee --- /dev/null +++ b/cli/internal/commands/userinput.go @@ -0,0 +1,74 @@ +package commands + +import ( + "bytes" + "fmt" + "strings" + "syscall" + "unicode" + + "golang.org/x/term" +) + +const ( + pwdSpecialChars = "!@#$%^&*()_+=" + pwdLength = 10 +) + +var errInvalidPassword = fmt.Errorf("xipher: please set a decent password with at least %d characters, including at least one uppercase letter, one lowercase letter, one number, and one of the following special characters: %s", pwdLength, pwdSpecialChars) + +func pwdCheck(password string) error { + var ( + upp, low, num, sym bool + tot uint32 + ) + for _, char := range password { + switch { + case unicode.IsUpper(char): + upp = true + tot++ + case unicode.IsLower(char): + low = true + tot++ + case unicode.IsNumber(char): + num = true + tot++ + case strings.ContainsRune(pwdSpecialChars, char): + sym = true + tot++ + default: + return errInvalidPassword + } + } + if !(upp && low && num && sym && tot >= pwdLength) { + return errInvalidPassword + } + return nil +} + +func getHiddenInputFromUser(prompt string) ([]byte, error) { + fmt.Print(prompt) + input, err := term.ReadPassword(int(syscall.Stdin)) + fmt.Println() + return input, err +} + +func getPasswordFromUser(confirm, ignorePolicyCheck bool) ([]byte, error) { + password, err := getHiddenInputFromUser("Enter a Password: ") + if err != nil { + return nil, err + } + if !ignorePolicyCheck { + if err = pwdCheck(string(password)); err != nil { + return nil, err + } + } + if confirm { + if confirmPassword, err := getHiddenInputFromUser("Confirm Password: "); err != nil { + return nil, err + } else if !bytes.Equal(password, confirmPassword) { + return nil, fmt.Errorf("passwords do not match") + } + } + return password, nil +} diff --git a/cli/internal/commands/xipher.go b/cli/internal/commands/xipher.go index 9323010..0cc16cb 100644 --- a/cli/internal/commands/xipher.go +++ b/cli/internal/commands/xipher.go @@ -7,8 +7,8 @@ func XipherCommand() *cobra.Command { return xipherCmd } xipherCmd = &cobra.Command{ - Use: "xipher", - Short: "Xipher is a curated collection of cryptographic primitives written in Go to encrypt and decrypt data with optional compression.", + Use: appName, + Short: "Xipher is a curated collection of cryptographic primitives put together to perform password-based asymmetric encryption. It is written in Go and can be used as a library or a CLI tool.", Run: func(cmd *cobra.Command, args []string) { version, _ := cmd.Flags().GetBool(versionFlag.name) if version { diff --git a/const.go b/const.go index b42b713..ea05c9b 100644 --- a/const.go +++ b/const.go @@ -3,24 +3,18 @@ package xipher import ( "fmt" - "dev.shib.me/xipher/internal/symcipher" + "dev.shib.me/xipher/internal/xcp" ) const ( - cipherKeyLength = 32 + keyLength = 32 - // PrivateKeyLength is the length of a private key. - PrivateKeyLength = cipherKeyLength - // PublicKeyLength is the length of a public key. - PublicKeyLength = cipherKeyLength + kdfSpecLength + // PrivateKeyMinLength is the minimum length of a private key. + PrivateKeyMinLength = 1 + keyLength + // PublicKeyMinLength is the minimum length of a public key. + PublicKeyMinLength = 1 + keyLength // CipherTextMinLength is the minimum length of a ciphertext. - CipherTextMinLength = symcipher.CipherTextMinLength - - // Ciphertext Types - ctKeySymmetric byte = 1 - ctKeyAsymmetric byte = 2 - ctPwdSymmetric byte = 3 - ctPwdAsymmetric byte = 4 + CipherTextMinLength = xcp.CipherTextMinLength // Argon2 Default Spec argon2Iterations uint8 = 16 @@ -31,7 +25,9 @@ const ( kdfSaltLength = 16 kdfSpecLength = kdfParamsLenth + kdfSaltLength - zero byte = 0 + // Key Types + keyTypeEccDirect uint8 = 0 + keyTypeEccPwd uint8 = 1 ) var ( diff --git a/crypto.go b/crypto.go index 2379859..fdb5b87 100644 --- a/crypto.go +++ b/crypto.go @@ -5,63 +5,16 @@ import ( "io" "dev.shib.me/xipher/internal/ecc" - "dev.shib.me/xipher/internal/symcipher" ) -func (privateKey *PrivateKey) NewEncryptingWriter(dst io.Writer, compression bool) (writer io.WriteCloser, err error) { - if privateKey.isPwdBased() { - if _, err := dst.Write([]byte{ctPwdSymmetric}); err != nil { - return nil, err - } - if _, err = dst.Write(privateKey.spec.bytes()); err != nil { - return nil, err - } - } else { - if _, err := dst.Write([]byte{ctKeySymmetric}); err != nil { - return nil, err - } - } - if privateKey.symEncrypter == nil { - if privateKey.symEncrypter, err = symcipher.New(privateKey.key); err != nil { - return nil, err - } - } - return privateKey.symEncrypter.NewEncryptingWriter(dst, compression) -} - -// EncryptStream encrypts src with the private key treating it as a symmetric key and writes to dst. -func (privateKey *PrivateKey) EncryptStream(dst io.Writer, src io.Reader, compression bool) (err error) { - encryptedWriter, err := privateKey.NewEncryptingWriter(dst, compression) - if err != nil { - return err - } - if _, err = io.Copy(encryptedWriter, src); err != nil { - return err - } - return encryptedWriter.Close() -} - -// Encrypt encrypts data with the private key treating it as a symmetric key. -func (privateKey *PrivateKey) Encrypt(data []byte, compression bool) (ciphertext []byte, err error) { - var buf bytes.Buffer - if err = privateKey.EncryptStream(&buf, bytes.NewReader(data), compression); err != nil { +func (publicKey *PublicKey) NewEncryptingWriter(dst io.Writer, compression bool) (writer io.WriteCloser, err error) { + if _, err := dst.Write([]byte{publicKey.keyType}); err != nil { return nil, err } - return buf.Bytes(), nil -} - -func (publicKey *PublicKey) NewEncryptingWriter(dst io.Writer, compression bool) (writer io.WriteCloser, err error) { if publicKey.isPwdBased() { - if _, err := dst.Write([]byte{ctPwdAsymmetric}); err != nil { - return nil, err - } if _, err = dst.Write(publicKey.spec.bytes()); err != nil { return nil, err } - } else { - if _, err := dst.Write([]byte{ctKeyAsymmetric}); err != nil { - return nil, err - } } return publicKey.publicKey.NewEncryptingWriter(dst, compression) } @@ -98,13 +51,18 @@ func (privateKey *PrivateKey) getKeyForPwdSpec(spec kdfSpec) (key []byte, err er } func (privateKey *PrivateKey) NewDecryptingReader(src io.Reader) (io.ReadCloser, error) { - ctTypeBytes := make([]byte, 1) - if _, err := io.ReadFull(src, ctTypeBytes); err != nil { + keyTypeBytes := make([]byte, 1) + if _, err := io.ReadFull(src, keyTypeBytes); err != nil { return nil, err } - ctType := ctTypeBytes[0] + var keyType uint8 = keyTypeBytes[0] key := privateKey.key - if ctType == ctPwdSymmetric || ctType == ctPwdAsymmetric { + switch keyType { + case keyTypeEccDirect: + if privateKey.isPwdBased() { + return nil, errDecryptionFailedKeyRequired + } + case keyTypeEccPwd: if !privateKey.isPwdBased() { return nil, errDecryptionFailedPwdRequired } @@ -119,25 +77,14 @@ func (privateKey *PrivateKey) NewDecryptingReader(src io.Reader) (io.ReadCloser, if key, err = privateKey.getKeyForPwdSpec(*spec); err != nil { return nil, err } - } else if (ctType == ctKeySymmetric || ctType == ctKeyAsymmetric) && privateKey.isPwdBased() { - return nil, errDecryptionFailedKeyRequired - } - switch ctType { - case ctKeySymmetric, ctPwdSymmetric: - decrypter, err := symcipher.New(key) - if err != nil { - return nil, err - } - return decrypter.NewDecryptingReader(src) - case ctKeyAsymmetric, ctPwdAsymmetric: - eccPrivKey, err := ecc.GetPrivateKey(key) - if err != nil { - return nil, err - } - return eccPrivKey.NewDecryptingReader(src) default: return nil, errInvalidCiphertext } + eccPrivKey, err := ecc.GetPrivateKey(key) + if err != nil { + return nil, err + } + return eccPrivKey.NewDecryptingReader(src) } // DecryptStream decrypts src and writes to dst. diff --git a/internal/ecc/crypto.go b/internal/ecc/crypto.go index f33be3e..2ac241b 100644 --- a/internal/ecc/crypto.go +++ b/internal/ecc/crypto.go @@ -3,7 +3,7 @@ package ecc import ( "io" - "dev.shib.me/xipher/internal/symcipher" + "dev.shib.me/xipher/internal/xcp" "golang.org/x/crypto/curve25519" ) @@ -29,7 +29,7 @@ func (privateKey *PrivateKey) NewDecryptingReader(src io.Reader) (io.ReadCloser, if err != nil { return nil, err } - decrypter, err := symcipher.New(sharedKey) + decrypter, err := xcp.New(sharedKey) if err != nil { return nil, err } diff --git a/internal/ecc/keys.go b/internal/ecc/keys.go index e2724a7..48805bb 100644 --- a/internal/ecc/keys.go +++ b/internal/ecc/keys.go @@ -4,7 +4,7 @@ import ( "crypto/rand" "fmt" - "dev.shib.me/xipher/internal/symcipher" + "dev.shib.me/xipher/internal/xcp" "golang.org/x/crypto/curve25519" ) @@ -27,7 +27,7 @@ type PublicKey struct { type encrypter struct { ephPubKey []byte - cipher *symcipher.SymmetricCipher + cipher *xcp.SymmetricCipher } // Bytes returns the bytes of the private key. @@ -97,7 +97,7 @@ func (publicKey *PublicKey) getEncrypter() (*encrypter, error) { if err != nil { return nil, err } - cipher, err := symcipher.New(sharedKey) + cipher, err := xcp.New(sharedKey) if err != nil { return nil, err } diff --git a/internal/symcipher/crypto.go b/internal/xcp/crypto.go similarity index 95% rename from internal/symcipher/crypto.go rename to internal/xcp/crypto.go index 1b31b0c..43f891b 100644 --- a/internal/symcipher/crypto.go +++ b/internal/xcp/crypto.go @@ -1,4 +1,4 @@ -package symcipher +package xcp import ( "bytes" @@ -6,8 +6,6 @@ import ( "crypto/cipher" "crypto/rand" "io" - - "golang.org/x/crypto/chacha20poly1305" ) type Writer struct { @@ -20,7 +18,7 @@ type Writer struct { // NewEncryptingWriter returns a new io.WriteCloser that encrypts data with the cipher and writes to dst. func (cipher *SymmetricCipher) NewEncryptingWriter(dst io.Writer, compress bool) (io.WriteCloser, error) { - nonce := make([]byte, chacha20poly1305.NonceSize) + nonce := make([]byte, nonceLength) if _, err := rand.Read(nonce); err != nil { return nil, err } @@ -96,7 +94,7 @@ type Reader struct { // NewDecryptingReader returns a new io.ReadCloser that decrypts src with the cipher func (cipher *SymmetricCipher) NewDecryptingReader(src io.Reader) (io.ReadCloser, error) { - nonce := make([]byte, chacha20poly1305.NonceSize) + nonce := make([]byte, nonceLength) if _, err := io.ReadFull(src, nonce); err != nil { return nil, err } diff --git a/internal/symcipher/key.go b/internal/xcp/key.go similarity index 79% rename from internal/symcipher/key.go rename to internal/xcp/key.go index efb3b70..91c9f12 100644 --- a/internal/symcipher/key.go +++ b/internal/xcp/key.go @@ -1,4 +1,4 @@ -package symcipher +package xcp import ( "crypto/cipher" @@ -8,7 +8,8 @@ import ( const ( KeyLength = chacha20poly1305.KeySize - CipherTextMinLength = chacha20poly1305.NonceSize + chacha20poly1305.Overhead + nonceLength = chacha20poly1305.NonceSizeX + CipherTextMinLength = nonceLength + chacha20poly1305.Overhead ptBlockSize = 64 * 1024 ctBlockSize = ptBlockSize + chacha20poly1305.Overhead ) @@ -20,7 +21,7 @@ type SymmetricCipher struct { // New returns a new Cipher instance. If a Cipher instance with the same key has already been created, it will be returned instead. func New(key []byte) (*SymmetricCipher, error) { - aead, err := chacha20poly1305.New(key) + aead, err := chacha20poly1305.NewX(key) if err != nil { return nil, err } diff --git a/kdf.go b/kdf.go index 57055c5..6bd3561 100644 --- a/kdf.go +++ b/kdf.go @@ -65,31 +65,38 @@ func (s *kdfSpec) getThreads() uint32 { } func (s *kdfSpec) getCipherKey(pwd []byte) []byte { - return argon2.IDKey(pwd, s.getSalt(), s.getIterations(), s.getMemory(), uint8(s.getThreads()), cipherKeyLength) + return argon2.IDKey(pwd, s.getSalt(), s.getIterations(), s.getMemory(), uint8(s.getThreads()), keyLength) } func (s *kdfSpec) bytes() []byte { return append([]byte{s.iterations, s.memory, s.threads}, s.salt...) } -func parseKdfSpec(bytes []byte) (*kdfSpec, error) { - if bytes == nil || len(bytes) != kdfSpecLength { +var kdfSpecMap = make(map[string]*kdfSpec) + +func parseKdfSpec(kdfBytes []byte) (*kdfSpec, error) { + if kdfBytes == nil || len(kdfBytes) != kdfSpecLength { return nil, errInvalidKDFSpec } - if [kdfSpecLength]byte(bytes) == [kdfSpecLength]byte{} { + if [kdfSpecLength]byte(kdfBytes) == [kdfSpecLength]byte{} { return nil, nil } - iterations := bytes[0] - memory := bytes[1] - threads := bytes[2] - salt := bytes[kdfParamsLenth:] + if spec, ok := kdfSpecMap[string(kdfBytes)]; ok { + return spec, nil + } + iterations := kdfBytes[0] + memory := kdfBytes[1] + threads := kdfBytes[2] + salt := kdfBytes[kdfParamsLenth:] if iterations == 0 || memory == 0 || threads == 0 || salt == nil { return nil, errInvalidKDFSpec } - return &kdfSpec{ + spec := &kdfSpec{ iterations: iterations, memory: memory, threads: threads, salt: salt, - }, nil + } + kdfSpecMap[string(kdfBytes)] = spec + return spec, nil } diff --git a/keys.go b/keys.go index fc3637e..9ba5ebb 100644 --- a/keys.go +++ b/keys.go @@ -1,22 +1,19 @@ package xipher import ( - "bytes" "crypto/rand" - "crypto/sha1" "fmt" "dev.shib.me/xipher/internal/ecc" - "dev.shib.me/xipher/internal/symcipher" ) type PrivateKey struct { - password *[]byte - spec *kdfSpec - key []byte - symEncrypter *symcipher.SymmetricCipher - publicKey *PublicKey - specKeyMap map[string][]byte + keyType uint8 + password *[]byte + spec *kdfSpec + key []byte + publicKey *PublicKey + specKeyMap map[string][]byte } // NewPrivateKeyForPassword creates a new private key for the given password. @@ -43,6 +40,7 @@ func newPrivateKeyForPwdAndSpec(password []byte, spec *kdfSpec) (*PrivateKey, er return nil, errInvalidPassword } privateKey := &PrivateKey{ + keyType: keyTypeEccPwd, password: &password, spec: spec, key: spec.getCipherKey(password), @@ -54,35 +52,37 @@ func newPrivateKeyForPwdAndSpec(password []byte, spec *kdfSpec) (*PrivateKey, er // NewPrivateKey creates a new random private key. func NewPrivateKey() (*PrivateKey, error) { - key := make([]byte, cipherKeyLength) + key := make([]byte, keyLength) if _, err := rand.Read(key); err != nil { return nil, err } return &PrivateKey{ - key: key, + keyType: keyTypeEccDirect, + key: key, }, nil } -// ParsePrivateKey parses the given bytes and returns a corresponding private key. the given bytes must be 32 bytes long. +// ParsePrivateKey parses the given bytes and returns a corresponding private key. the given bytes must be 33 bytes long. func ParsePrivateKey(key []byte) (*PrivateKey, error) { - if len(key) != PrivateKeyLength { - return nil, fmt.Errorf("%s: invalid private key length: expected %d, got %d", "xipher", PrivateKeyLength, len(key)) + if len(key) < PrivateKeyMinLength || key[0] != keyTypeEccDirect { + return nil, fmt.Errorf("%s: invalid private key length: expected %d, got %d", "xipher", PrivateKeyMinLength, len(key)) } return &PrivateKey{ - key: key, + keyType: keyTypeEccDirect, + key: key, }, nil } func (privateKey *PrivateKey) isPwdBased() bool { - return privateKey.password != nil && privateKey.spec != nil + return privateKey.keyType%2 == 1 } // Bytes returns the private key as bytes only if it is not password based. func (privateKey *PrivateKey) Bytes() ([]byte, error) { - if privateKey.password != nil || privateKey.spec != nil { + if privateKey.isPwdBased() { return nil, errPrivKeyUnavailableForPwd } - return privateKey.key, nil + return append([]byte{privateKey.keyType}, privateKey.key...), nil } // PublicKey returns the public key corresponding to the private key. @@ -97,6 +97,7 @@ func (privateKey *PrivateKey) PublicKey() (*PublicKey, error) { return nil, err } privateKey.publicKey = &PublicKey{ + keyType: privateKey.keyType, publicKey: eccPubKey, spec: privateKey.spec, } @@ -105,49 +106,57 @@ func (privateKey *PrivateKey) PublicKey() (*PublicKey, error) { } type PublicKey struct { + keyType uint8 publicKey *ecc.PublicKey spec *kdfSpec } -// ParsePublicKey parses the given bytes and returns a corresponding public key. the given bytes must be 51 bytes long. +// ParsePublicKey parses the given bytes and returns a corresponding public key. the given bytes must be at least 33 bytes long. func ParsePublicKey(pubKeyBytes []byte) (*PublicKey, error) { - if len(pubKeyBytes) != PublicKeyLength { - return nil, fmt.Errorf("%s: invalid public key length: expected %d, got %d", "xipher", PublicKeyLength, len(pubKeyBytes)) + if len(pubKeyBytes) < PublicKeyMinLength { + return nil, errInvalidPublicKey } - keyBytes := pubKeyBytes[:cipherKeyLength] - eccPubKey, err := ecc.GetPublicKey(keyBytes) - if err != nil { - return nil, err - } - publicKey := &PublicKey{ - publicKey: eccPubKey, - } - specBytes := pubKeyBytes[cipherKeyLength:] - if specBytes[0] == zero { - sum := sha1.Sum(keyBytes) - if !bytes.Equal(sum[:kdfSpecLength-1], specBytes[1:]) { - return nil, errInvalidPublicKey + keyType := pubKeyBytes[0] + switch keyType { + case keyTypeEccDirect | keyTypeEccPwd: + keyBytes := pubKeyBytes[1:] + var spec *kdfSpec + if keyType == keyTypeEccPwd { + specBytes := keyBytes[keyLength:] + var err error + if spec, err = parseKdfSpec(specBytes); err != nil { + return nil, err + } + keyBytes = keyBytes[:keyLength] } - } else { - publicKey.spec, err = parseKdfSpec(specBytes) + eccPubKey, err := ecc.GetPublicKey(keyBytes) if err != nil { return nil, err } + publicKey := &PublicKey{ + keyType: keyType, + publicKey: eccPubKey, + spec: spec, + } + return publicKey, nil + default: + return nil, errInvalidPublicKey } - return publicKey, nil } func (publicKey *PublicKey) isPwdBased() bool { - return publicKey.spec != nil + return publicKey.keyType%2 == 1 +} + +func (publicKey *PublicKey) keyBytesWithType() []byte { + return append([]byte{publicKey.keyType}, publicKey.publicKey.Bytes()...) } // Bytes returns the public key as bytes. func (publicKey *PublicKey) Bytes() []byte { - pubKeyBytes := publicKey.publicKey.Bytes() - if publicKey.spec != nil { - return append(pubKeyBytes, publicKey.spec.bytes()...) + if publicKey.isPwdBased() { + return append(publicKey.keyBytesWithType(), publicKey.spec.bytes()...) } else { - sum := sha1.Sum(pubKeyBytes) - return append(pubKeyBytes, append([]byte{zero}, sum[:kdfSpecLength-1]...)...) + return publicKey.keyBytesWithType() } } diff --git a/xipher_test.go b/xipher_test.go index 7cdab48..fcc5a89 100644 --- a/xipher_test.go +++ b/xipher_test.go @@ -20,84 +20,11 @@ func getMemoryStats() string { return sb.String() } -func TestPasswordSymmetricCipher(t *testing.T) { - password := make([]byte, 14) - if _, err := rand.Read(password); err != nil { - t.Error("Error generating random password", err) - } - privKey, err := NewPrivateKeyForPassword(password) - if err != nil { - t.Error("Error generating private key for password", err) - } - data := []byte("Hello World!") - uncompressedCiphertext, err := privKey.Encrypt(data, false) - if err != nil { - t.Error("Error encrypting data", err) - } - plaintext, err := privKey.Decrypt(uncompressedCiphertext) - if err != nil { - t.Error("Error decrypting data", err) - } - if string(plaintext) != string(data) { - t.Errorf("Plaintext was incorrect, got: %s, want: %s.", string(plaintext), string(data)) - } - compressedCiphertext, err := privKey.Encrypt(data, true) - if err != nil { - t.Error("Error encrypting data", err) - } - plaintext, err = privKey.Decrypt(compressedCiphertext) - if err != nil { - t.Error("Error decrypting data", err) - } - if string(plaintext) != string(data) { - t.Errorf("Plaintext was incorrect, got: %s, want: %s.", string(plaintext), string(data)) - } - t.Log(getMemoryStats()) -} - -func TestKeySymmetricCipher(t *testing.T) { - key := make([]byte, 32) - if _, err := rand.Read(key); err != nil { - t.Error("Error generating random key", err) - } - privKey, err := ParsePrivateKey(key) +func TestKeyXipher(t *testing.T) { + privKey, err := NewPrivateKey() if err != nil { t.Error("Error parsing private key", err) } - data := []byte("Hello World!") - uncompressedCiphertext, err := privKey.Encrypt(data, false) - if err != nil { - t.Error("Error encrypting data", err) - } - plaintext, err := privKey.Decrypt(uncompressedCiphertext) - if err != nil { - t.Error("Error decrypting data", err) - } - if string(plaintext) != string(data) { - t.Errorf("Plaintext was incorrect, got: %s, want: %s.", string(plaintext), string(data)) - } - compressedCiphertext, err := privKey.Encrypt(data, true) - if err != nil { - t.Error("Error encrypting data", err) - } - plaintext, err = privKey.Decrypt(compressedCiphertext) - if err != nil { - t.Error("Error decrypting data", err) - } - if string(plaintext) != string(data) { - t.Errorf("Plaintext was incorrect, got: %s, want: %s.", string(plaintext), string(data)) - } - t.Log(getMemoryStats()) -} -func TestPasswordAsymmetricCipher(t *testing.T) { - password := make([]byte, 14) - if _, err := rand.Read(password); err != nil { - t.Error("Error generating random password", err) - } - privKey, err := NewPrivateKeyForPassword(password) - if err != nil { - t.Error("Error generating private key for password", err) - } pubKey, err := privKey.PublicKey() if err != nil { t.Error("Error generating public key", err) @@ -128,50 +55,16 @@ func TestPasswordAsymmetricCipher(t *testing.T) { t.Log(getMemoryStats()) } -func TestKeyAsymmetricCipher(t *testing.T) { - key := make([]byte, 32) - if _, err := rand.Read(key); err != nil { - t.Error("Error generating random key", err) - } - privKey, err := ParsePrivateKey(key) +func TestFileEncryption(t *testing.T) { + privKey, err := NewPrivateKey() if err != nil { - t.Error("Error parsing private key", err) + t.Error("Error generating private key", err) } + pubKey, err := privKey.PublicKey() if err != nil { t.Error("Error generating public key", err) } - data := []byte("Hello World!") - uncompressedCiphertext, err := pubKey.Encrypt(data, false) - if err != nil { - t.Error("Error encrypting data", err) - } - plaintext, err := privKey.Decrypt(uncompressedCiphertext) - if err != nil { - t.Error("Error decrypting data", err) - } - if string(plaintext) != string(data) { - t.Errorf("Plaintext was incorrect, got: %s, want: %s.", string(plaintext), string(data)) - } - compressedCiphertext, err := pubKey.Encrypt(data, true) - if err != nil { - t.Error("Error encrypting data", err) - } - plaintext, err = privKey.Decrypt(compressedCiphertext) - if err != nil { - t.Error("Error decrypting data", err) - } - if string(plaintext) != string(data) { - t.Errorf("Plaintext was incorrect, got: %s, want: %s.", string(plaintext), string(data)) - } - t.Log(getMemoryStats()) -} - -func TestFileEncryption(t *testing.T) { - privKey, err := NewPrivateKey() - if err != nil { - t.Error("Error generating private key", err) - } // Generating Test Data ptFile, err := os.CreateTemp("", "xipher") @@ -197,7 +90,7 @@ func TestFileEncryption(t *testing.T) { if err != nil { t.Error("Error creating temp file for CT", err) } - if err := privKey.EncryptStream(ctFile, ptFile, false); err != nil { + if err := pubKey.EncryptStream(ctFile, ptFile, false); err != nil { t.Error("Error encrypting data", err) } ctFile.Close() @@ -220,3 +113,42 @@ func TestFileEncryption(t *testing.T) { t.Log(getMemoryStats()) } + +func TestPasswordXipher(t *testing.T) { + password := make([]byte, 14) + if _, err := rand.Read(password); err != nil { + t.Error("Error generating random password", err) + } + privKey, err := NewPrivateKeyForPassword(password) + if err != nil { + t.Error("Error generating private key for password", err) + } + pubKey, err := privKey.PublicKey() + if err != nil { + t.Error("Error generating public key", err) + } + data := []byte("Hello World!") + uncompressedCiphertext, err := pubKey.Encrypt(data, false) + if err != nil { + t.Error("Error encrypting data", err) + } + plaintext, err := privKey.Decrypt(uncompressedCiphertext) + if err != nil { + t.Error("Error decrypting data", err) + } + if string(plaintext) != string(data) { + t.Errorf("Plaintext was incorrect, got: %s, want: %s.", string(plaintext), string(data)) + } + compressedCiphertext, err := pubKey.Encrypt(data, true) + if err != nil { + t.Error("Error encrypting data", err) + } + plaintext, err = privKey.Decrypt(compressedCiphertext) + if err != nil { + t.Error("Error decrypting data", err) + } + if string(plaintext) != string(data) { + t.Errorf("Plaintext was incorrect, got: %s, want: %s.", string(plaintext), string(data)) + } + t.Log(getMemoryStats()) +}