feat(argon2id cost param): enable user to modify the cost param
feat(hint): embed argon2 custom param as hint feat(aes): add AES-GCM as algo fix(stdin/stdout): fixed STDIN/STDOUT issue
This commit is contained in:
parent
2508b4c822
commit
64e4ca400f
|
@ -1,3 +1,5 @@
|
|||
/.idea/
|
||||
/simple-privacy-tool
|
||||
/spt
|
||||
/spt
|
||||
/vendor
|
||||
/test-payload*
|
23
README.md
23
README.md
|
@ -1,18 +1,20 @@
|
|||
# simple-privacy-tool
|
||||
|
||||
Simple Privacy Tool is a simple tool to encrypt and decrypt files. It uses the symmetric algorithm XChaCha-Poly1305 and
|
||||
Argon2id for the key derivation function.
|
||||
Simple Privacy Tool is a simple tool to encrypt and decrypt files. It uses the symmetric algorithm XChaCha20-Poly1305 and
|
||||
AES-GCM, and Argon2id for the key derivation function.
|
||||
|
||||
Since this tool uses a symmetric algorithm, the level of privacy hinges solely on the password's strength. So, make sure
|
||||
to choose your password carefully.
|
||||
|
||||
### Build
|
||||
|
||||
### Build
|
||||
```shell
|
||||
go build
|
||||
```
|
||||
or install with `go install gitea.suyono.dev/suyono/simple-privacy-tool`
|
||||
|
||||
### Usage
|
||||
By default simple-privacy-tool uses XChaCha20-Poly1305.
|
||||
|
||||
#### Encrypt
|
||||
Encrypting `plainfile` to `cryptedfile`
|
||||
|
@ -36,3 +38,18 @@ Special usage, just omit both file paths to use `STDIN` and `STDOUT`.
|
|||
```shell
|
||||
tar -zcf - dir | simple-privacy-tool encrypt | another-command
|
||||
```
|
||||
|
||||
#### Customize Argon2id parameter
|
||||
The simple-privacy-tool accepts several flags to tweak Argon2id parameters. There are three parameters that user can
|
||||
adjust: time, memory, and threads. Example
|
||||
```shell
|
||||
simple-privacy-tool encrypt --kdf argon2 --argon2id-time 2 --argon2id-mem 65536 --argon2id-thread 4 --hint inputFile outputFile
|
||||
```
|
||||
The user has to include `--kdf` flag to be able to customize the parameter. Optionally, user can add `--hint` flag to embed
|
||||
the custom parameter in the encrypted file as a hint. Warning: the hint in the encrypted file is not protected (authenticated)
|
||||
and the decryption process doesn't use the hint.
|
||||
|
||||
The purpose of the hint is as human reminder. User can print the embedded hint by using command
|
||||
```shell
|
||||
simple-privacy-tool hint encryptedFile
|
||||
```
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package spt
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"gitea.suyono.dev/suyono/simple-privacy-tool/privacy"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type decryptApp struct {
|
||||
|
@ -12,16 +12,6 @@ type decryptApp struct {
|
|||
r *privacy.Reader
|
||||
}
|
||||
|
||||
type stdoutWrapper struct {
|
||||
file *os.File
|
||||
}
|
||||
|
||||
func newDecryptApp(a *cApp) *decryptApp {
|
||||
return &decryptApp{
|
||||
cApp: *a,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decryptApp) GetPassphrase() (err error) {
|
||||
var (
|
||||
passphrase string
|
||||
|
@ -37,8 +27,25 @@ func (d *decryptApp) GetPassphrase() (err error) {
|
|||
}
|
||||
|
||||
func (d *decryptApp) ProcessFiles() (err error) {
|
||||
d.r = privacy.NewPrivacyReader(d.srcFile)
|
||||
var (
|
||||
src io.Reader
|
||||
)
|
||||
|
||||
if f.IsBase64() {
|
||||
src = base64.NewDecoder(base64.StdEncoding, d.srcFile)
|
||||
} else {
|
||||
src = d.srcFile
|
||||
}
|
||||
|
||||
d.r = privacy.NewPrivacyReaderWithKeyGen(src, f.KeyGen())
|
||||
redo:
|
||||
if err = d.r.ReadMagic(); err != nil {
|
||||
if h, ok := err.(privacy.InvalidCipherMethod); ok {
|
||||
if err = SkipHint(h, src); err != nil {
|
||||
return fmt.Errorf("reading magic bytes: %w", err)
|
||||
}
|
||||
goto redo
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -46,17 +53,8 @@ func (d *decryptApp) ProcessFiles() (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
if d.dstPath == "-" || d.srcPath == "-" {
|
||||
w := stdoutWrapper{
|
||||
file: d.dstFile,
|
||||
}
|
||||
if _, err = io.Copy(w, d.r); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if _, err = io.Copy(d.dstFile, d.r); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = io.Copy(d.dstFile, d.r); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = d.dstFile.Close(); err != nil {
|
||||
|
@ -69,45 +67,3 @@ func (d *decryptApp) ProcessFiles() (err error) {
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
func (sw stdoutWrapper) ReadFrom(reader io.Reader) (n int64, err error) {
|
||||
var (
|
||||
nr int
|
||||
rErr error
|
||||
)
|
||||
buf := make([]byte, 32768)
|
||||
|
||||
for {
|
||||
if nr, err = reader.Read(buf); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
n += int64(nr)
|
||||
if _, err = sw.file.Write(buf[:nr]); err != nil {
|
||||
return n, fmt.Errorf("readfrom internal write: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
rErr = err
|
||||
if nr > 0 {
|
||||
n += int64(nr)
|
||||
if nr > 32768 {
|
||||
return n, fmt.Errorf("last piece length %d: %w", nr, err)
|
||||
}
|
||||
if _, err = sw.file.Write(buf[:nr]); err != nil {
|
||||
return n, fmt.Errorf("readfrom internal write: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if rErr == io.EOF || rErr == nil {
|
||||
err = nil
|
||||
} else {
|
||||
err = rErr
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (sw stdoutWrapper) Write(b []byte) (n int, err error) {
|
||||
return sw.file.Write(b)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package spt
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"gitea.suyono.dev/suyono/simple-privacy-tool/privacy"
|
||||
"io"
|
||||
)
|
||||
|
@ -10,12 +11,6 @@ type encryptApp struct {
|
|||
wc *privacy.WriteCloser
|
||||
}
|
||||
|
||||
func newEncryptApp(a *cApp) *encryptApp {
|
||||
return &encryptApp{
|
||||
cApp: *a,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encryptApp) GetPassphrase() (err error) {
|
||||
var (
|
||||
passphrase string
|
||||
|
@ -40,7 +35,21 @@ func (e *encryptApp) GetPassphrase() (err error) {
|
|||
}
|
||||
|
||||
func (e *encryptApp) ProcessFiles() (err error) {
|
||||
e.wc = privacy.NewPrivacyWriteCloser(e.dstFile, privacy.DefaultCipherMethod) //TODO: need to handle when custom keygen accepted
|
||||
var dst io.WriteCloser
|
||||
|
||||
if f.IsBase64() {
|
||||
dst = base64.NewEncoder(base64.StdEncoding, e.dstFile)
|
||||
} else {
|
||||
dst = e.dstFile
|
||||
}
|
||||
|
||||
if f.IncludeHint() {
|
||||
if err = WriteHint(f.KeyGen(), dst); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
e.wc = privacy.NewPrivacyWriteCloserWithKeyGen(dst, f.CipherMethod(), f.KeyGen())
|
||||
if err = e.wc.NewSalt(); err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
package spt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gitea.suyono.dev/suyono/simple-privacy-tool/privacy"
|
||||
)
|
||||
|
||||
type flags struct {
|
||||
base64Encoding bool
|
||||
algo string
|
||||
cmType privacy.CipherMethodType
|
||||
kdf string
|
||||
argon2idTime int
|
||||
argon2idMemory int
|
||||
argon2idThreads int
|
||||
keygen privacy.KeyGen
|
||||
hint bool
|
||||
}
|
||||
|
||||
const (
|
||||
defaultAlgo = ""
|
||||
chacha20Algo = "chacha"
|
||||
aesAlgo = "aes"
|
||||
|
||||
defaultKdf = defaultAlgo
|
||||
argon2idKdf = "argon2"
|
||||
|
||||
argon2idTime = 1
|
||||
argon2idMemory = 64 * 1024
|
||||
argon2idThreads = 4
|
||||
)
|
||||
|
||||
var (
|
||||
f flags
|
||||
)
|
||||
|
||||
func initFlags() {
|
||||
encryptCmd.PersistentFlags().BoolVar(&f.hint, "hint", false, "include hint in the output file")
|
||||
encryptCmd.PersistentFlags().StringVar(&f.algo, "algo", defaultAlgo, "encryption algorithm, valid values: chacha and aes. Default algo is chacha")
|
||||
rootCmd.PersistentFlags().StringVar(&f.kdf, "kdf", defaultKdf, "Key Derivation Function, valid values: argon2")
|
||||
rootCmd.PersistentFlags().IntVar(&f.argon2idTime, "argon2id-time", argon2idTime, "sets argon2id time cost-parameter")
|
||||
rootCmd.PersistentFlags().IntVar(&f.argon2idMemory, "argon2id-mem", argon2idMemory, "sets argon2id memory cost-parameter (in KB)")
|
||||
rootCmd.PersistentFlags().IntVar(&f.argon2idThreads, "argon2id-thread", argon2idThreads, "sets argon2id thread cost-parameter")
|
||||
rootCmd.PersistentFlags().BoolVar(&f.base64Encoding, "base64", false, "the file is encoded in Base64")
|
||||
}
|
||||
|
||||
func processFlags() (err error) {
|
||||
if err = processAlgoFlags(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = processKeyGenFlags(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func processAlgoFlags() (err error) {
|
||||
switch f.algo {
|
||||
case defaultAlgo, chacha20Algo:
|
||||
f.cmType = privacy.XChaCha20Simple
|
||||
case aesAlgo:
|
||||
f.cmType = privacy.AES256GCMSimple
|
||||
default:
|
||||
return errors.New("invalid algo")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func processKeyGenFlags() (err error) {
|
||||
switch f.kdf {
|
||||
case defaultKdf:
|
||||
f.keygen = privacy.NewArgon2()
|
||||
case argon2idKdf:
|
||||
if f.argon2idTime < 0 || f.argon2idThreads < 0 || f.argon2idMemory < 0 {
|
||||
return errors.New("invalid argon2id parameter")
|
||||
}
|
||||
f.keygen, err = privacy.NewArgon2WithParams(uint32(f.argon2idTime), uint32(f.argon2idMemory), uint8(argon2idThreads))
|
||||
default:
|
||||
return errors.New("invalid KDF")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (f flags) IsBase64() bool {
|
||||
return f.base64Encoding
|
||||
}
|
||||
|
||||
func (f flags) CipherMethod() privacy.CipherMethodType {
|
||||
return f.cmType
|
||||
}
|
||||
|
||||
func (f flags) KeyGen() privacy.KeyGen {
|
||||
return f.keygen
|
||||
}
|
||||
|
||||
func (f flags) IncludeHint() bool {
|
||||
return f.hint
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package spt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitea.suyono.dev/suyono/simple-privacy-tool/privacy"
|
||||
"github.com/spf13/cobra"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotHint = errors.New("not a hint")
|
||||
)
|
||||
|
||||
func SkipHint(b []byte, r io.Reader) (err error) {
|
||||
if b[0] != 0xFF {
|
||||
return ErrNotHint
|
||||
}
|
||||
hintLen := int(binary.LittleEndian.Uint16(b[1:3])) - 13 // 13 is hint minimum length, prevent overlapping with magic
|
||||
if hintLen > 0 {
|
||||
skip := make([]byte, hintLen)
|
||||
|
||||
var (
|
||||
n, total int
|
||||
)
|
||||
for total < hintLen {
|
||||
if n, err = r.Read(skip); err != nil {
|
||||
return
|
||||
}
|
||||
total += n
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func WriteHint(k privacy.KeyGen, w io.Writer) (err error) {
|
||||
var (
|
||||
b []byte
|
||||
bb *bytes.Buffer
|
||||
total int
|
||||
n int
|
||||
)
|
||||
|
||||
if b, err = k.MarshalJSON(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wb := make([]byte, 3)
|
||||
wb[0] = 0xFF
|
||||
if len(b) >= 13 {
|
||||
binary.LittleEndian.PutUint16(wb[1:], uint16(len(b)))
|
||||
} else {
|
||||
binary.LittleEndian.PutUint16(wb[1:], uint16(13))
|
||||
}
|
||||
bb = bytes.NewBuffer(make([]byte, 0))
|
||||
bb.Write(wb)
|
||||
bb.Write(b)
|
||||
|
||||
if len(b) < 13 {
|
||||
bb.Write(make([]byte, 13-len(b)))
|
||||
}
|
||||
|
||||
b = bb.Bytes()
|
||||
for total < len(b) {
|
||||
if n, err = w.Write(b[total:]); err != nil {
|
||||
return
|
||||
}
|
||||
total += n
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CmdReadHint(cmd *cobra.Command, args []string) (err error) {
|
||||
var (
|
||||
f *os.File
|
||||
tb []byte
|
||||
hLen int
|
||||
)
|
||||
|
||||
if f, err = os.Open(args[0]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
tb = make([]byte, 3)
|
||||
if _, err = f.Read(tb); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if tb[0] != 0xFF {
|
||||
return ErrNotHint
|
||||
}
|
||||
|
||||
hLen = int(binary.LittleEndian.Uint16(tb[1:]))
|
||||
tb = make([]byte, hLen)
|
||||
if _, err = f.Read(tb); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if hLen == 13 {
|
||||
for hLen > 0 {
|
||||
if tb[hLen-1] != 0 {
|
||||
break
|
||||
}
|
||||
hLen--
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("hint:", string(tb[:hLen]))
|
||||
|
||||
return
|
||||
}
|
|
@ -2,9 +2,11 @@ package spt
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
tw "gitea.suyono.dev/suyono/terminal_wrapper"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"log"
|
||||
)
|
||||
|
||||
type cApp struct {
|
||||
|
@ -21,20 +23,39 @@ var (
|
|||
ErrPassphraseMismatch = errors.New("mismatch passphrase")
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "spt",
|
||||
Use: "simple-privacy-tool",
|
||||
Short: "a simple tool to encrypt and decrypt file",
|
||||
}
|
||||
|
||||
hintCmd = &cobra.Command{
|
||||
Use: "hint file",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: CmdReadHint,
|
||||
Short: "extract and print hint from encrypted file",
|
||||
}
|
||||
|
||||
encryptCmd = &cobra.Command{
|
||||
Use: "encrypt",
|
||||
Use: "encrypt srcFile dstFile",
|
||||
Args: validatePositionalArgs,
|
||||
RunE: encrypt,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := processFlags(); err != nil {
|
||||
return err
|
||||
}
|
||||
return encrypt(cmd, args)
|
||||
},
|
||||
Short: "encrypt srcFile, output to dstFile",
|
||||
}
|
||||
|
||||
decryptCmd = &cobra.Command{
|
||||
Use: "decrypt",
|
||||
RunE: decrypt,
|
||||
Args: validatePositionalArgs,
|
||||
Use: "decrypt srcFile dstFile",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := processFlags(); err != nil {
|
||||
return err
|
||||
}
|
||||
return decrypt(cmd, args)
|
||||
},
|
||||
Args: validatePositionalArgs,
|
||||
Short: "decrypt srcFile, output to dstFile",
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -43,7 +64,8 @@ func Execute() error {
|
|||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(encryptCmd, decryptCmd)
|
||||
initFlags()
|
||||
rootCmd.AddCommand(encryptCmd, decryptCmd, hintCmd)
|
||||
}
|
||||
|
||||
func validatePositionalArgs(cmd *cobra.Command, args []string) error {
|
||||
|
@ -65,11 +87,13 @@ func encrypt(cmd *cobra.Command, args []string) (err error) {
|
|||
if terminal, err = tw.MakeTerminal(os.Stderr); err != nil {
|
||||
return
|
||||
}
|
||||
log.SetOutput(terminal)
|
||||
defer func() {
|
||||
existingErr := err
|
||||
if err = terminal.Restore(); err == nil && existingErr != nil {
|
||||
err = existingErr
|
||||
}
|
||||
log.SetOutput(os.Stderr)
|
||||
}()
|
||||
|
||||
app = &cApp{
|
||||
|
@ -80,14 +104,14 @@ func encrypt(cmd *cobra.Command, args []string) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
eApp = newEncryptApp(app)
|
||||
eApp = &encryptApp{
|
||||
cApp: *app,
|
||||
}
|
||||
|
||||
if err = eApp.GetPassphrase(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO: process additional flags
|
||||
|
||||
if err = eApp.ProcessFiles(); err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -105,11 +129,13 @@ func decrypt(cmd *cobra.Command, args []string) (err error) {
|
|||
if terminal, err = tw.MakeTerminal(os.Stderr); err != nil {
|
||||
return
|
||||
}
|
||||
log.SetOutput(terminal)
|
||||
defer func() {
|
||||
existingErr := err
|
||||
if err = terminal.Restore(); err == nil && existingErr != nil {
|
||||
err = existingErr
|
||||
}
|
||||
log.SetOutput(os.Stderr)
|
||||
}()
|
||||
|
||||
app = &cApp{
|
||||
|
@ -120,14 +146,14 @@ func decrypt(cmd *cobra.Command, args []string) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
dApp = newDecryptApp(app)
|
||||
dApp = &decryptApp{
|
||||
cApp: *app,
|
||||
}
|
||||
|
||||
if err = dApp.GetPassphrase(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO: process additional flags
|
||||
|
||||
if err = dApp.ProcessFiles(); err != nil {
|
||||
return
|
||||
}
|
||||
|
|
7
main.go
7
main.go
|
@ -1,14 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.suyono.dev/suyono/simple-privacy-tool/cmd/spt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := spt.Execute()
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
//panic(err)
|
||||
}
|
||||
_ = spt.Execute()
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package privacy
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"io"
|
||||
)
|
||||
|
@ -20,9 +22,9 @@ type KeyGen interface {
|
|||
const (
|
||||
segmentSizeBytesLen int = 4
|
||||
|
||||
Uninitialised CipherMethodType = 0
|
||||
XChaCha20Simple CipherMethodType = 1
|
||||
AES256GCMSimple CipherMethodType = 2
|
||||
Uninitialised CipherMethodType = 0x00
|
||||
XChaCha20Simple CipherMethodType = 0x01
|
||||
AES256GCMSimple CipherMethodType = 0x02
|
||||
|
||||
DefaultCipherMethod = XChaCha20Simple
|
||||
)
|
||||
|
@ -31,7 +33,6 @@ var (
|
|||
ErrInvalidSaltLen = errors.New("invalid salt length")
|
||||
ErrUninitialisedSalt = errors.New("uninitialised salt")
|
||||
ErrUninitialisedMethod = errors.New("cipher method type uninitialised")
|
||||
ErrInvalidCipherMethod = errors.New("invalid cipher method type")
|
||||
//ErrCannotReadMagicBytes = errors.New("cannot read magic bytes") //no usage for now
|
||||
ErrInvalidReadFlow = errors.New("func ReadMagic should be called before calling Read")
|
||||
ErrInvalidKeyState = errors.New("func GenerateKey should be called first")
|
||||
|
@ -39,6 +40,12 @@ var (
|
|||
segmentLenBytes = make([]byte, segmentSizeBytesLen)
|
||||
)
|
||||
|
||||
type InvalidCipherMethod []byte
|
||||
|
||||
func (i InvalidCipherMethod) Error() string {
|
||||
return "invalid cipher method type"
|
||||
}
|
||||
|
||||
type Reader struct {
|
||||
*Privacy
|
||||
reader io.Reader
|
||||
|
@ -158,12 +165,19 @@ func (p *Privacy) GenerateKey(passphrase string) error {
|
|||
key = p.keygen.GenerateKey([]byte(passphrase), p.salt)
|
||||
switch p.cmType {
|
||||
case XChaCha20Simple:
|
||||
p.aead, err = chacha20poly1305.NewX(key)
|
||||
if p.aead, err = chacha20poly1305.NewX(key); err != nil {
|
||||
return err
|
||||
}
|
||||
case AES256GCMSimple:
|
||||
var block cipher.Block
|
||||
if block, err = aes.NewCipher(key); err != nil {
|
||||
return err
|
||||
}
|
||||
if p.aead, err = cipher.NewGCM(block); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return ErrInvalidCipherMethod
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
return InvalidCipherMethod([]byte{})
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -187,7 +201,7 @@ func (wc *WriteCloser) Write(b []byte) (n int, err error) {
|
|||
}
|
||||
|
||||
if !wc.magicWritten {
|
||||
n, err = wc.writeCloser.Write(wc.salt)
|
||||
n, err = wc.writeUp(wc.salt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -229,7 +243,7 @@ func (wc *WriteCloser) writeSegment() (n int, err error) {
|
|||
|
||||
written = len(wc.bufSlice)
|
||||
binary.LittleEndian.PutUint32(segmentLenBytes, uint32(written))
|
||||
n, err = wc.writeCloser.Write(segmentLenBytes)
|
||||
n, err = wc.writeUp(segmentLenBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -243,7 +257,7 @@ func (wc *WriteCloser) writeSegment() (n int, err error) {
|
|||
ciphertext = plaintext[:0]
|
||||
|
||||
wc.aead.Seal(ciphertext, nonce, plaintext, segmentLenBytes)
|
||||
n, err = wc.writeCloser.Write(wc.buf[:written+wc.aead.NonceSize()+wc.aead.Overhead()])
|
||||
n, err = wc.writeUp(wc.buf[:written+wc.aead.NonceSize()+wc.aead.Overhead()])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -252,6 +266,25 @@ func (wc *WriteCloser) writeSegment() (n int, err error) {
|
|||
return written, nil
|
||||
}
|
||||
|
||||
func (wc *WriteCloser) writeUp(b []byte) (n int, err error) {
|
||||
var (
|
||||
total int
|
||||
)
|
||||
|
||||
for {
|
||||
if n, err = wc.writeCloser.Write(b[total:]); err != nil {
|
||||
return n + total, err
|
||||
}
|
||||
|
||||
total += n
|
||||
if total == len(b) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (wc *WriteCloser) Close() (err error) {
|
||||
if len(wc.bufSlice) > 0 {
|
||||
_, err = wc.writeSegment()
|
||||
|
@ -265,32 +298,46 @@ func (wc *WriteCloser) Close() (err error) {
|
|||
func (r *Reader) ReadMagic() (err error) {
|
||||
if r.cmType == Uninitialised {
|
||||
magic := make([]byte, 16)
|
||||
_, err = r.reader.Read(magic[:1])
|
||||
if err != nil {
|
||||
if _, err = r.readUp(magic); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch CipherMethodType(magic[0]) {
|
||||
case XChaCha20Simple:
|
||||
r.cmType = XChaCha20Simple
|
||||
_, err = r.reader.Read(magic[1:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = r.SetSalt(magic)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case AES256GCMSimple:
|
||||
r.cmType = AES256GCMSimple
|
||||
default:
|
||||
return ErrInvalidCipherMethod
|
||||
return InvalidCipherMethod(magic)
|
||||
}
|
||||
|
||||
if err = r.SetSalt(magic); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reader) readUp(b []byte) (n int, err error) {
|
||||
var (
|
||||
total int
|
||||
)
|
||||
|
||||
for {
|
||||
if n, err = r.reader.Read(b[total:]); err != nil {
|
||||
return n + total, err
|
||||
}
|
||||
|
||||
total += n
|
||||
if total == len(b) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (r *Reader) Read(b []byte) (n int, err error) {
|
||||
var (
|
||||
segmentLen uint32
|
||||
|
@ -316,10 +363,11 @@ func (r *Reader) Read(b []byte) (n int, err error) {
|
|||
r.buf = make([]byte, int(r.segmentSize)+r.aead.Overhead()+r.aead.NonceSize())
|
||||
}
|
||||
|
||||
//log.Printf("Read for %d bytes\n", len(b))
|
||||
copied = 0
|
||||
for copied < len(b) {
|
||||
if len(r.bufSlice) == 0 {
|
||||
n, err = r.reader.Read(segmentLenBytes)
|
||||
n, err = r.readUp(segmentLenBytes)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
if copied > 0 {
|
||||
|
@ -338,7 +386,7 @@ func (r *Reader) Read(b []byte) (n int, err error) {
|
|||
return 0, ErrInvalidSegmentLength
|
||||
}
|
||||
|
||||
n, err = r.reader.Read(r.buf[:int(segmentLen)+r.aead.Overhead()+r.aead.NonceSize()])
|
||||
n, err = r.readUp(r.buf[:int(segmentLen)+r.aead.Overhead()+r.aead.NonceSize()])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -348,7 +396,7 @@ func (r *Reader) Read(b []byte) (n int, err error) {
|
|||
plaintext = ciphertext[:0]
|
||||
|
||||
if _, err = r.aead.Open(plaintext, nonce, ciphertext, segmentLenBytes); err != nil {
|
||||
return
|
||||
return copied, fmt.Errorf("decrypt Read: %w", err)
|
||||
}
|
||||
plaintext = plaintext[:int(segmentLen)]
|
||||
r.bufSlice = plaintext
|
||||
|
@ -364,5 +412,6 @@ func (r *Reader) Read(b []byte) (n int, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
return copied, nil
|
||||
n = copied
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue