wip: debugging issue
This commit is contained in:
parent
7b29d8f6cc
commit
6c4b48873b
|
@ -1,2 +1,3 @@
|
|||
/.idea/
|
||||
/simple-privacy-tool
|
||||
/simple-privacy-tool
|
||||
/spt
|
36
README.md
36
README.md
|
@ -1,2 +1,38 @@
|
|||
# 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.
|
||||
|
||||
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
|
||||
|
||||
```shell
|
||||
go build
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
#### Encrypt
|
||||
Encrypting `plainfile` to `cryptedfile`
|
||||
```shell
|
||||
simple-privacy-tool encrypt plainfile cryptedfile
|
||||
```
|
||||
|
||||
#### Decrypt
|
||||
Decrypting `cryptedfile` back to `plainfile`
|
||||
```shell
|
||||
simple-privacy-tool decrypt cryptedfile plainfile
|
||||
```
|
||||
|
||||
#### Using STDIN/STDOUT
|
||||
`simple-privacy-tool` can operate on `STDIN` or `STDOUT`. Just replace the file path with `-`
|
||||
```shell
|
||||
tar -zcf - dir | simple-privacy-tool encrypt - - | another-command
|
||||
```
|
||||
|
||||
Special usage, just omit both file paths to use `STDIN` and `STDOUT`.
|
||||
```shell
|
||||
tar -zcf - dir | simple-privacy-tool encrypt | another-command
|
||||
```
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
package spt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.suyono.dev/suyono/simple-privacy-tool/privacy"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type decryptApp struct {
|
||||
cApp
|
||||
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
|
||||
)
|
||||
|
||||
if passphrase, err = d.term.ReadPassword("input passphrase: "); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
d.passphrase = passphrase
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *decryptApp) ProcessFiles() (err error) {
|
||||
d.r = privacy.NewPrivacyReader(d.srcFile)
|
||||
if err = d.r.ReadMagic(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = d.r.GenerateKey(d.passphrase); err != nil {
|
||||
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 = d.dstFile.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = d.srcFile.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package spt
|
||||
|
||||
import (
|
||||
"gitea.suyono.dev/suyono/simple-privacy-tool/privacy"
|
||||
"io"
|
||||
)
|
||||
|
||||
type encryptApp struct {
|
||||
cApp
|
||||
wc *privacy.WriteCloser
|
||||
}
|
||||
|
||||
func newEncryptApp(a *cApp) *encryptApp {
|
||||
return &encryptApp{
|
||||
cApp: *a,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encryptApp) GetPassphrase() (err error) {
|
||||
var (
|
||||
passphrase string
|
||||
verify string
|
||||
)
|
||||
|
||||
if passphrase, err = e.term.ReadPassword("input passphrase: "); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if verify, err = e.term.ReadPassword("verify - input passphrase: "); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if passphrase != verify {
|
||||
return ErrPassphraseMismatch
|
||||
}
|
||||
|
||||
e.passphrase = passphrase
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *encryptApp) ProcessFiles() (err error) {
|
||||
e.wc = privacy.NewPrivacyWriteCloser(e.dstFile, privacy.DefaultCipherMethod) //TODO: need to handle when custom keygen accepted
|
||||
if err = e.wc.NewSalt(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = e.wc.GenerateKey(e.passphrase); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = io.Copy(e.wc, e.srcFile); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = e.wc.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = e.srcFile.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
140
cmd/spt/spt.go
140
cmd/spt/spt.go
|
@ -1,10 +1,25 @@
|
|||
package spt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
tw "gitea.suyono.dev/suyono/terminal_wrapper"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
)
|
||||
|
||||
type cApp struct {
|
||||
term *tw.Terminal
|
||||
srcPath string
|
||||
dstPath string
|
||||
srcFile *os.File
|
||||
dstFile *os.File
|
||||
passphrase string
|
||||
}
|
||||
|
||||
var (
|
||||
//ErrFatalError = errors.New("fatal error occurred")
|
||||
ErrPassphraseMismatch = errors.New("mismatch passphrase")
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "spt",
|
||||
Short: "a simple tool to encrypt and decrypt file",
|
||||
|
@ -12,12 +27,14 @@ var (
|
|||
|
||||
encryptCmd = &cobra.Command{
|
||||
Use: "encrypt",
|
||||
Args: validatePositionalArgs,
|
||||
RunE: encrypt,
|
||||
}
|
||||
|
||||
decryptCmd = &cobra.Command{
|
||||
Use: "decrypt",
|
||||
RunE: decrypt,
|
||||
Args: validatePositionalArgs,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -29,24 +46,117 @@ func init() {
|
|||
rootCmd.AddCommand(encryptCmd, decryptCmd)
|
||||
}
|
||||
|
||||
func encrypt(cmd *cobra.Command, args []string) error {
|
||||
//TODO: implementation
|
||||
|
||||
//// this is the sample of reading password
|
||||
//fmt.Print("input passphrase: ")
|
||||
//passwd, err := privacy.ReadPassword()
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//fmt.Println()
|
||||
//
|
||||
//fmt.Printf("password: %s\n", passwd)
|
||||
//// end of sample
|
||||
func validatePositionalArgs(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 0 && len(args) != 2 {
|
||||
//TODO: improve the error message
|
||||
return errors.New("invalid arguments")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decrypt(cmd *cobra.Command, args []string) error {
|
||||
//TODO: implementation
|
||||
func encrypt(cmd *cobra.Command, args []string) (err error) {
|
||||
var (
|
||||
terminal *tw.Terminal
|
||||
app *cApp
|
||||
eApp *encryptApp
|
||||
)
|
||||
|
||||
if terminal, err = tw.MakeTerminal(os.Stderr); err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
existingErr := err
|
||||
if err = terminal.Restore(); err == nil && existingErr != nil {
|
||||
err = existingErr
|
||||
}
|
||||
}()
|
||||
|
||||
app = &cApp{
|
||||
term: terminal,
|
||||
}
|
||||
|
||||
if err = app.ProcessArgs(args); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
eApp = newEncryptApp(app)
|
||||
|
||||
if err = eApp.GetPassphrase(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO: process additional flags
|
||||
|
||||
if err = eApp.ProcessFiles(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decrypt(cmd *cobra.Command, args []string) (err error) {
|
||||
var (
|
||||
terminal *tw.Terminal
|
||||
app *cApp
|
||||
dApp *decryptApp
|
||||
)
|
||||
|
||||
if terminal, err = tw.MakeTerminal(os.Stderr); err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
existingErr := err
|
||||
if err = terminal.Restore(); err == nil && existingErr != nil {
|
||||
err = existingErr
|
||||
}
|
||||
}()
|
||||
|
||||
app = &cApp{
|
||||
term: terminal,
|
||||
}
|
||||
|
||||
if err = app.ProcessArgs(args); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dApp = newDecryptApp(app)
|
||||
|
||||
if err = dApp.GetPassphrase(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO: process additional flags
|
||||
|
||||
if err = dApp.ProcessFiles(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *cApp) ProcessArgs(args []string) (err error) {
|
||||
if len(args) == 0 {
|
||||
a.srcFile = os.Stdin
|
||||
a.dstFile = os.Stdout
|
||||
} else {
|
||||
a.srcPath = args[0]
|
||||
a.dstPath = args[1]
|
||||
if a.srcPath == "-" {
|
||||
a.srcFile = os.Stdin
|
||||
} else {
|
||||
if a.srcFile, err = os.Open(a.srcPath); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if a.dstPath == "-" {
|
||||
a.dstFile = os.Stdout
|
||||
} else {
|
||||
if a.dstFile, err = os.OpenFile(a.dstPath, os.O_CREATE|os.O_WRONLY, 0640); err != nil { //TODO: allow user to define the destination file permission
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
3
go.mod
3
go.mod
|
@ -3,13 +3,14 @@ module gitea.suyono.dev/suyono/simple-privacy-tool
|
|||
go 1.20
|
||||
|
||||
require (
|
||||
gitea.suyono.dev/suyono/terminal_wrapper v0.0.0-20230722101024-a3e50949f40f
|
||||
github.com/spf13/cobra v1.7.0
|
||||
golang.org/x/crypto v0.11.0
|
||||
golang.org/x/term v0.10.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/term v0.10.0 // indirect
|
||||
)
|
||||
|
|
2
go.sum
2
go.sum
|
@ -1,3 +1,5 @@
|
|||
gitea.suyono.dev/suyono/terminal_wrapper v0.0.0-20230722101024-a3e50949f40f h1:yPac52dWHSutQH99FUhqz9rt2FHKaHr9srdJuDI9pkg=
|
||||
gitea.suyono.dev/suyono/terminal_wrapper v0.0.0-20230722101024-a3e50949f40f/go.mod h1:I8qBJ8oaj+RwbLggXWNRrvRCDD+/bnRoQne0V4xsBNU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
|
|
1
main.go
1
main.go
|
@ -9,5 +9,6 @@ func main() {
|
|||
err := spt.Execute()
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
//panic(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,8 @@ import (
|
|||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/term"
|
||||
"io"
|
||||
)
|
||||
|
||||
type CipherMethodType byte
|
||||
|
@ -369,17 +366,3 @@ func (r *Reader) Read(b []byte) (n int, err error) {
|
|||
|
||||
return copied, nil
|
||||
}
|
||||
|
||||
func ReadPassphraseFromTerminal() (string, error) {
|
||||
var inputFd = int(os.Stdin.Fd())
|
||||
if !term.IsTerminal(inputFd) {
|
||||
return "", errors.New("not a terminal")
|
||||
}
|
||||
|
||||
passwd, err := term.ReadPassword(inputFd)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(passwd), nil
|
||||
}
|
||||
|
|
|
@ -91,14 +91,25 @@ func TestReadWriteClose(t *testing.T) {
|
|||
ur := mr.New(mr.NewSource(1))
|
||||
bb := make([]byte, 1048)
|
||||
|
||||
var bar int
|
||||
var (
|
||||
//bar int
|
||||
n int
|
||||
wl int
|
||||
rl int
|
||||
)
|
||||
for i := 0; i < 63; i++ {
|
||||
bar = 1000 + ur.Intn(49)
|
||||
ur.Read(bb[:bar])
|
||||
sha.Write(bb[:bar])
|
||||
if _, err = writer.Write(bb[:bar]); err != nil {
|
||||
//bar = 1000 + ur.Intn(49)
|
||||
//ur.Read(bb[:bar])
|
||||
//sha.Write(bb[:bar])
|
||||
//if n, err = writer.Write(bb[:bar]); err != nil {
|
||||
// t.Fatal("unexpected: Write failed", err)
|
||||
//}
|
||||
ur.Read(bb)
|
||||
sha.Write(bb)
|
||||
if n, err = writer.Write(bb); err != nil {
|
||||
t.Fatal("unexpected: Write failed", err)
|
||||
}
|
||||
wl += n
|
||||
}
|
||||
|
||||
if err = writer.Close(); err != nil {
|
||||
|
@ -120,25 +131,29 @@ func TestReadWriteClose(t *testing.T) {
|
|||
|
||||
sha.Reset()
|
||||
err = nil
|
||||
n = 0
|
||||
for err == nil {
|
||||
if _, err = reader.Read(bb); err != nil {
|
||||
if n, err = reader.Read(bb); err != nil {
|
||||
if err == io.EOF {
|
||||
continue
|
||||
} else {
|
||||
t.Fatal("unexpected: Read failed", err)
|
||||
}
|
||||
}
|
||||
rl += n
|
||||
sha.Write(bb)
|
||||
}
|
||||
|
||||
readHash := sha.Sum(nil)
|
||||
t.Log("read hash:", hex.EncodeToString(readHash))
|
||||
|
||||
//for i := range writeHash {
|
||||
// if readHash[i] != writeHash[i] {
|
||||
// t.Fatal("unexpected: mismatch hash")
|
||||
// }
|
||||
//}
|
||||
t.Log("wl", wl)
|
||||
t.Log("rl", rl)
|
||||
for i := range writeHash {
|
||||
if readHash[i] != writeHash[i] {
|
||||
t.Fatal("unexpected: mismatch hash")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrial(t *testing.T) {
|
||||
|
|
Loading…
Reference in New Issue