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
159
160
161
162
163
164
165
166
167
168
169
170
|
func printUsage() {
fmt.Printf(os.Args[0] + `
Encrypt or decrypt a file using AES with a 256-bit key file.
This program can also generate 256-bit keys.
Usage:
` + os.Args[0] + ` [-h|--help]
` + os.Args[0] + ` [-g|--genkey]
` + os.Args[0] + ` <keyFile> <file> [-d|--decrypt]
Examples:
# Generate a 32-byte (256-bit) key
` + os.Args[0] + ` --genkey
# Encrypt with secret key. Output to STDOUT
` + os.Args[0] + ` --genkey > secret.key
# Encrypt message using secret key. Output to ciphertext.dat
` + os.Args[0] + ` secret.key message.txt > ciphertext.dat
# Decrypt message using secret key. Output to STDOUT
` + os.Args[0] + ` secret.key ciphertext.dat -d
# Decrypt message using secret key. Output to message.txt
` + os.Args[0] + ` secret.key ciphertext.dat -d > cleartext.txt
`)
}
// Check command-line arguments.
// If the help or generate key functions are chosen
// they are run and then the program exits
// otherwise it returns keyFile, file, decryptFlag.
func checkArgs() (string, string, bool) {
if len(os.Args) < 2 || len(os.Args) > 4 {
printUsage()
os.Exit(1)
}
// One arg provided
if len(os.Args) == 2 {
// Only -h, --help and --genkey are valid one-argument uses
if os.Args[1] == "-h" || os.Args[1] == "--help" {
printUsage() // Print help text
os.Exit(0) // Exit gracefully no error
}
if os.Args[1] == "-g" || os.Args[1] == "--genkey" {
// Generate a key and print to STDOUT
// User should redirect output to a file if needed
key := generateKey()
fmt.Printf(string(key[:])) // No newline
os.Exit(0) // Exit gracefully
}
}
// The only use options left is
// encrypt <keyFile> <file> [-d|--decrypt]
// If there are only 2 args provided, they must be the
// keyFile and file without a decrypt flag.
if len(os.Args) == 3 {
return os.Args[1], os.Args[2], false // keyFile, file, decryptFlag
}
// If 3 args are provided, check that the last one is -d or --decrypt
if len(os.Args) == 4 {
if os.Args[3] != "-d" && os.Args[3] != "--decrypt" {
fmt.Println("Error: Unknown usage.")
printUsage()
os.Exit(1) // Exit with error code
}
return os.Args[1], os.Args[2], true
}
return "", "", false // Default blank return
}
func generateKey() []byte {
randomBytes := make([]byte, 32) // 32 bytes, 256 bit
numBytesRead, err := rand.Read(randomBytes)
if err != nil {
log.Fatal("Error generating random key.", err)
}
if numBytesRead != 32 {
log.Fatal("Error generating 32 random bytes for key.")
}
return randomBytes
}
// AES encryption
func encrypt(key, message []byte) ([]byte, error) {
// Initialize block cipher
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// Create the byte slice that will hold encrypted message
cipherText := make([]byte, aes.BlockSize+len(message))
// Generate the Initialization Vector (IV) nonce
// which is stored at the beginning of the byte slice
// The IV is the same length as the AES blocksize
iv := cipherText[:aes.BlockSize]
_, err = io.ReadFull(rand.Reader, iv)
if err != nil {
return nil, err
}
// Choose the block cipher mode of operation
// Using the cipher feedback (CFB) mode here.
// CBCEncrypter also available.
cfb := cipher.NewCFBEncrypter(block, iv)
// Generate the encrypted message and store it
// in the remaining bytes after the IV nonce
cfb.XORKeyStream(cipherText[aes.BlockSize:], message)
return cipherText, nil
}
// AES decryption
func decrypt(key, cipherText []byte) ([]byte, error) {
// Initialize block cipher
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// Separate the IV nonce from the encrypted message bytes
iv := cipherText[:aes.BlockSize]
cipherText = cipherText[aes.BlockSize:]
// Decrypt the message using the CFB block mode
cfb := cipher.NewCFBDecrypter(block, iv)
cfb.XORKeyStream(cipherText, cipherText)
return cipherText, nil
}
func main() {
// if generate key flag, just output a key to stdout and exit
keyFile, file, decryptFlag := checkArgs()
// Load key from file
keyFileData, err := ioutil.ReadFile(keyFile)
if err != nil {
log.Fatal("Unable to read key file contents.", err)
}
// Load file to be encrypted or decrypted
fileData, err := ioutil.ReadFile(file)
if err != nil {
log.Fatal("Unable to read key file contents.", err)
}
// Perform encryption unless the decryptFlag was provided
// Outputs to STDOUT. User can redirect output to file.
if decryptFlag {
message, err := decrypt(keyFileData, fileData)
if err != nil {
log.Fatal("Error decrypting. ", err)
}
fmt.Printf("%s", message)
} else {
cipherText, err := encrypt(keyFileData, fileData)
if err != nil {
log.Fatal("Error encrypting. ", err)
}
fmt.Printf("%s", cipherText)
}
}
|