Skip to content

Instantly share code, notes, and snippets.

@vinsonzou
Created May 19, 2021 12:51
Show Gist options
  • Select an option

  • Save vinsonzou/3576a4d20d3247ec0a1953d488e7d446 to your computer and use it in GitHub Desktop.

Select an option

Save vinsonzou/3576a4d20d3247ec0a1953d488e7d446 to your computer and use it in GitHub Desktop.
encrypt/decrypt by MacOS keychain
package main
import (
"flag"
"fmt"
"os"
"os/signal"
"runtime"
"syscall"
"github.com/minio/sio"
"github.com/zalando/go-keyring"
"golang.org/x/crypto/scrypt"
)
const (
codeOK int = iota // exit successfully
codeError // exit because of error
codeCancel // exit because of interrupt
)
var (
cleanChan chan<- int // initialized in init
cleanFn = make([]func(int), 0, 3)
)
var (
listFlag bool
decryptFlag bool
)
func init() {
// 设置flag参数 (变量指针,参数名,默认值,帮助信息)
flag.BoolVar(&decryptFlag, "d", false, fmt.Sprintf("%-8s Decrypt", ""))
flag.Usage = func() {
printFlag := func(f *flag.Flag) {
fmt.Fprintf(os.Stderr, " -%-6s %s\n", f.Name, f.Usage)
}
fmt.Fprintf(os.Stderr, "Usage: %s [FLAGS] [ARGUMENTS...]\n\n", os.Args[0])
flag.VisitAll(printFlag)
os.Exit(codeOK)
}
cleanCh := make(chan int, 1)
cleanChan = cleanCh
go func() {
code := <-cleanCh
for _, f := range cleanFn {
f(code)
}
os.Exit(code)
}()
// handle user termination
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigChan
cleanChan <- codeCancel // try to exit gracefully
runtime.Goexit()
}()
}
func main() {
flag.Parse()
ciphersuite := []byte{sio.AES_256_GCM}
in, out := parseIOArgs()
key := deriveKey(out, in)
cfg := sio.Config{Key: key, CipherSuites: ciphersuite}
if decryptFlag {
decrypt(out, in, cfg)
} else {
encrypt(out, in, cfg)
}
}
func exit(code int) {
cleanChan <- code
runtime.Goexit()
}
func parseIOArgs() (*os.File, *os.File) {
switch args := flag.Args(); len(args) {
default:
fmt.Fprintf(os.Stderr, "Unknown arguments: %s\n", args[2:])
exit(codeError)
return nil, nil // make compiler happy
case 0:
return os.Stdin, os.Stdout
case 1:
in, err := os.Open(args[0])
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to open '%s': %v\n", args[0], err)
exit(codeError)
}
cleanFn = append(cleanFn, func(code int) { in.Close() })
return in, os.Stdout
case 2:
in, err := os.Open(args[0])
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to open '%s': %v\n", args[0], err)
exit(codeError)
}
out, err := os.Create(args[1])
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create '%s': %v\n", args[1], err)
exit(codeError)
}
cleanFn = append(cleanFn, func(code int) {
out.Close()
if code != codeOK { // remove file on error
os.Remove(out.Name())
}
})
return in, out
}
}
func deriveKey(dst, src *os.File) []byte {
service := "go-app"
user := "go-app"
secret, err := keyring.Get(service, user)
if err != nil {
fmt.Fprintln(os.Stderr, "Failed to get secret from keychain")
}
password := []byte(secret)
salt := make([]byte, 32)
key, err := scrypt.Key(password, salt, 32768, 16, 1, 32)
if err != nil {
fmt.Fprintln(os.Stderr, "Failed to derive key from password and salt")
exit(codeError)
}
return key
}
func encrypt(dst, src *os.File, cfg sio.Config) {
if _, err := sio.Encrypt(dst, src, cfg); err != nil {
fmt.Fprintf(os.Stderr, "Failed to encrypt: '%s'\n", src.Name())
exit(codeError)
}
}
func decrypt(dst, src *os.File, cfg sio.Config) {
if _, err := sio.Decrypt(dst, src, cfg); err != nil {
fmt.Fprintf(os.Stderr, "Failed to decrypt: '%s'\n", src.Name())
exit(codeError)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment