-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathcrypt.go
158 lines (127 loc) · 3.19 KB
/
crypt.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Crypt.go is a file encryption/decryption utility that uses AES-GCM to
// encrypt a file. The AES key is derived from a user provided passphrase using
// Argon2.
//
// Usage of crypt:
// -a string
// Action to perform. (default "encrypt")
// -i string
// Input file.
// -o string
// Output file.
package main
import (
"crypto/aes"
"crypto/cipher"
"flag"
"fmt"
"io/ioutil"
"os"
"syscall"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/ssh/terminal"
)
// Minimum password length.
const passLen = 16
// Static salt used to seed the Argon2 KDF. Generated with:
// head -c 32 /dev/urandom | base64
const salt = "AKatmtgdkMKq5SFYLt8tBlUxuwLccdCjFfFNi2b3o9A"
// The Cryptor struct manages the encryption and decryption of data.
type Cryptor struct {
Aead cipher.AEAD
Nonce []byte
}
func (c *Cryptor) Decrypt(ciphertext []byte) ([]byte, error) {
return c.Aead.Open(nil, c.Nonce, ciphertext, nil)
}
func (c *Cryptor) Encrypt(plaintext []byte) []byte {
return c.Aead.Seal(nil, c.Nonce, plaintext, nil)
}
// Create a new Cryptor struct.
// Both the encryption key and the nonce are derived from the passphrase.
func NewCryptor(passphrase string) (*Cryptor, error) {
c := new(Cryptor)
kdf := argon2.Key([]byte(passphrase), []byte(salt), 4, 32*1024, 4, 44)
c.Nonce = kdf[32:]
block, err := aes.NewCipher(kdf[:32])
if err != nil {
return c, err
}
aead, err := cipher.NewGCM(block)
if err != nil {
return c, err
}
c.Aead = aead
return c, nil
}
// Get the encryption password from the user.
// Ask the user to enter the password twice and verify the passwords match.
// Also verify the password has at least passLen characters.
func getEncPass() string {
pass1 := getPass("Enter password: ")
pass2 := getPass("Enter password again: ")
if pass1 != pass2 {
fmt.Println("Passwords do not match.")
os.Exit(0)
}
if len(pass1) < passLen {
fmt.Printf("Password must be at least %d characters.\n", passLen)
os.Exit(0)
}
return pass1
}
// Use x/crypto/ssh/terminal to get the user password without echoing to the
// screen.
func getPass(prompt string) string {
fmt.Print(prompt)
pass, err := terminal.ReadPassword(int(syscall.Stdin))
if err != nil {
fmt.Println("\nCould not read password.")
os.Exit(0)
}
fmt.Println("")
return string(pass)
}
func save(name string, data []byte) {
err := ioutil.WriteFile(name, data, 0644)
check(err)
}
func check(e error) {
if e != nil {
fmt.Printf("Error: %s\n", e.Error())
os.Exit(0)
}
}
func main() {
var action string
var inFile string
var outFile string
var password string
var inData []byte
var outData []byte
flag.StringVar(&action, "a", "encrypt", "Action to perform.")
flag.StringVar(&inFile, "i", "", "Input file.")
flag.StringVar(&outFile, "o", "", "Output file.")
flag.Parse()
switch action {
case "encrypt":
password = getEncPass()
case "decrypt":
password = getPass("Enter password: ")
default:
flag.Usage()
}
cryptor, err := NewCryptor(password)
check(err)
inData, err = ioutil.ReadFile(inFile)
check(err)
switch action {
case "encrypt":
outData = cryptor.Encrypt(inData)
save(outFile, outData)
case "decrypt":
outData, err := cryptor.Decrypt(inData)
check(err)
save(outFile, outData)
}
}