tsafe.go - safe-go - Unnamed repository; edit this file 'description' to name the repository.
HTML git clone git://git.z3bra.org/safe-go.git
DIR Log
DIR Files
DIR Refs
---
tsafe.go (3872B)
---
1 package main
2
3 import (
4 "crypto/rand"
5 "flag"
6 "fmt"
7 "io"
8 "log"
9 "os"
10
11 "github.com/netfoundry/secretstream"
12 "golang.org/x/crypto/argon2"
13 "golang.org/x/term"
14 )
15
16 const (
17 BUFSIZ = 8192
18
19 // default key derivation values in libsodium
20 argon2id_time_cost = 2
21 argon2id_memory_cost = 67108864 / 1024
22 argon2id_threads = 1
23 argon2id_salt_len = 16
24 xchacha20poly1305_key_len = 32
25 xchacha20poly1305_iv_len = 16
26 )
27
28 func usage() {
29 fmt.Printf("usage: %s [-hed] [-s salt] [-f file]\n", os.Args[0])
30 os.Exit(2)
31 }
32
33 func readsalt(f *os.File, salt *[]byte) {
34 _, err := f.Read(*salt)
35 if err != nil {
36 log.Fatal(err)
37 }
38 }
39
40 func deriv(pw []byte, key *[]byte, salt []byte) {
41 *key = argon2.IDKey(pw, salt,
42 argon2id_time_cost,
43 argon2id_memory_cost,
44 argon2id_threads,
45 xchacha20poly1305_key_len)
46 }
47
48 func encrypt(in *os.File, out *os.File, key []byte, salt []byte) {
49 var tag byte
50 buf := make([]byte, BUFSIZ)
51
52 enc, nonce, err := secretstream.NewEncryptor(key)
53 if err != nil {
54 log.Fatal(err)
55 }
56
57 out.Write(salt)
58 if err != nil {
59 log.Fatal(err)
60 }
61
62 out.Write(nonce)
63 if err != nil {
64 log.Fatal(err)
65 }
66
67 loop := 1
68 tag = secretstream.TagMessage
69 for loop > 0 {
70 n, err := in.Read(buf)
71 if err == io.EOF || n < len(buf) {
72 tag = secretstream.TagFinal
73 loop = 0
74 } else if err != nil {
75 log.Fatal(err)
76 }
77 cipher, err := enc.Push(buf[:n], tag)
78 if err != nil {
79 log.Fatal(err)
80 }
81
82 out.Write(cipher)
83 if err != nil {
84 log.Fatal(err)
85 }
86 }
87 }
88
89 func decrypt(in *os.File, out *os.File, key []byte) {
90 buf := make([]byte, BUFSIZ+secretstream.StreamABytes)
91 header := make([]byte, secretstream.StreamHeaderBytes)
92
93 // Skip beginning of file which (supposedly) contains the salt for the key
94 in.Seek(argon2id_salt_len, os.SEEK_SET)
95 _, err := in.Read(header)
96 if err != nil {
97 log.Fatal(err)
98 }
99
100 dec, err := secretstream.NewDecryptor(key, header)
101 if err != nil {
102 log.Fatal(err)
103 }
104
105 loop := 1
106 for loop > 0 {
107 n, err := in.Read(buf)
108 if err != nil && err != io.EOF {
109 log.Fatal(err)
110 }
111
112 plain, tag, err := dec.Pull(buf[:n])
113 if err != nil {
114 log.Fatal(err)
115 }
116
117 if tag == secretstream.TagFinal {
118 loop = 0
119 }
120 out.Write(plain)
121 }
122 }
123
124 func main() {
125 var err error
126 var key, salt, pass []byte
127 var filename, saltfile string
128 var dflag, eflag bool
129
130 in := os.Stdin
131 out := os.Stdout
132
133 salt = make([]byte, argon2id_salt_len)
134 key = make([]byte, xchacha20poly1305_key_len)
135
136 flag.StringVar(&filename, "f", "", "Encrypt/decrypt to/from file name")
137 flag.StringVar(&saltfile, "s", "", "Read salt from file (encrypt-only)")
138 flag.BoolVar(&eflag, "e", false, "encrypt input (default)")
139 flag.BoolVar(&dflag, "d", false, "decrypt input")
140 flag.Usage = usage
141 flag.Parse()
142
143 if eflag && dflag {
144 log.Fatal("Cannot use encryption and decryption at the same time")
145 }
146
147 args := flag.Args()
148 if len(args) > 0 {
149 usage()
150 }
151
152 // Prompt user for password
153 tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0644)
154 if err != nil {
155 log.Fatal(err)
156 }
157 defer tty.Close()
158 fmt.Fprint(tty, "password:")
159 pass, _ = term.ReadPassword(int(tty.Fd()))
160 fmt.Fprintln(tty, "")
161
162 if dflag {
163 if len(filename) > 0 {
164 in, err = os.Open(filename)
165 if err != nil {
166 log.Fatal(err)
167 }
168 defer in.Close()
169 }
170
171 readsalt(in, &salt)
172 deriv(pass, &key, salt)
173 decrypt(in, out, key)
174 } else {
175 if len(saltfile) > 0 {
176 // Read salt from any manually specified file…
177 f, err := os.Open(saltfile)
178 if err != nil {
179 log.Fatal(err)
180 }
181 readsalt(f, &salt)
182 f.Close()
183 } else {
184 // … or generate a random one
185 _, err = rand.Read(salt)
186 if err != nil {
187 log.Fatal(err)
188 }
189 }
190 deriv(pass, &key, salt)
191 if len(filename) > 0 {
192 out, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
193 if err != nil {
194 log.Fatal(err)
195 }
196 defer out.Close()
197 }
198 encrypt(in, out, key, salt)
199 }
200 }