2021-01-09 02:51:36 +00:00
|
|
|
/*
|
|
|
|
Copyright © 2021 Arsen Musayelyan
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2020-12-03 10:12:43 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2020-12-06 00:47:04 +00:00
|
|
|
"bytes"
|
2020-12-03 10:12:43 +00:00
|
|
|
"crypto/aes"
|
|
|
|
"crypto/cipher"
|
|
|
|
"crypto/md5"
|
|
|
|
"crypto/rand"
|
|
|
|
"encoding/hex"
|
2020-12-06 00:47:04 +00:00
|
|
|
"github.com/klauspost/compress/zstd"
|
2020-12-03 10:12:43 +00:00
|
|
|
"github.com/rs/zerolog"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Encrypt given file using the shared key
|
2020-12-06 00:47:04 +00:00
|
|
|
func CompressAndEncryptFile(filePath string, newFilePath string, sharedKey string) {
|
2020-12-03 10:12:43 +00:00
|
|
|
// Use ConsoleWriter logger
|
2020-12-03 17:32:03 +00:00
|
|
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
|
2020-12-03 10:12:43 +00:00
|
|
|
// Read data from file
|
2020-12-06 00:47:04 +00:00
|
|
|
file, err := os.Open(filePath)
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("Error opening file")
|
|
|
|
}
|
2020-12-06 00:55:22 +00:00
|
|
|
// Create buffer for compressed data
|
2020-12-06 00:47:04 +00:00
|
|
|
compressedBuffer := new(bytes.Buffer)
|
2020-12-06 00:55:22 +00:00
|
|
|
// Create Zstd encoder
|
2020-12-06 00:47:04 +00:00
|
|
|
zstdEncoder, err := zstd.NewWriter(compressedBuffer)
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("Error creating Zstd encoder")
|
|
|
|
}
|
2020-12-06 00:55:22 +00:00
|
|
|
// Copy file data to Zstd encoder
|
2020-12-06 00:47:04 +00:00
|
|
|
_, err = io.Copy(zstdEncoder, file)
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("Error reading file")
|
|
|
|
}
|
2020-12-06 00:55:22 +00:00
|
|
|
// Close Zstd encoder
|
2020-12-06 00:47:04 +00:00
|
|
|
zstdEncoder.Close()
|
2020-12-06 00:55:22 +00:00
|
|
|
// Read compressed data into data variable
|
2020-12-06 00:47:04 +00:00
|
|
|
data, err := ioutil.ReadAll(compressedBuffer)
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("Error reading compressed buffer")
|
|
|
|
}
|
2020-12-03 10:12:43 +00:00
|
|
|
// Create md5 hash of password in order to make it the required size
|
|
|
|
md5Hash := md5.New()
|
|
|
|
md5Hash.Write([]byte(sharedKey))
|
|
|
|
// Encode md5 hash bytes into hexadecimal
|
|
|
|
hashedKey := hex.EncodeToString(md5Hash.Sum(nil))
|
|
|
|
// Create new AES cipher
|
2020-12-05 06:45:38 +00:00
|
|
|
block, err := aes.NewCipher([]byte(hashedKey))
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("Error creating AES cipher")
|
|
|
|
}
|
2020-12-03 10:12:43 +00:00
|
|
|
// Create GCM for AES cipher
|
|
|
|
gcm, err := cipher.NewGCM(block)
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("Error creating GCM")
|
|
|
|
}
|
2020-12-03 10:12:43 +00:00
|
|
|
// Make byte slice for nonce
|
|
|
|
nonce := make([]byte, gcm.NonceSize())
|
|
|
|
// Read random bytes into nonce slice
|
|
|
|
_, err = io.ReadFull(rand.Reader, nonce)
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("Error creating nonce")
|
|
|
|
}
|
2020-12-03 10:12:43 +00:00
|
|
|
// Encrypt data
|
|
|
|
ciphertext := gcm.Seal(nonce, nonce, data, nil)
|
|
|
|
// Create new file
|
|
|
|
newFile, err := os.Create(newFilePath)
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("Error creating file")
|
|
|
|
}
|
2020-12-03 10:12:43 +00:00
|
|
|
// Defer file close
|
|
|
|
defer newFile.Close()
|
|
|
|
// Write ciphertext to new file
|
|
|
|
bytesWritten, err := newFile.Write(ciphertext)
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("Error writing to file")
|
|
|
|
}
|
2020-12-03 10:12:43 +00:00
|
|
|
// Log bytes written and to which file
|
|
|
|
log.Info().Str("file", filepath.Base(newFilePath)).Msg("Wrote " + strconv.Itoa(bytesWritten) + " bytes")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decrypt given file using the shared key
|
2020-12-06 00:47:04 +00:00
|
|
|
func DecryptAndDecompressFile(filePath string, newFilePath string, sharedKey string) {
|
2020-12-03 10:12:43 +00:00
|
|
|
// Use ConsoleWriter logger
|
2020-12-03 17:32:03 +00:00
|
|
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
|
2020-12-03 10:12:43 +00:00
|
|
|
// Read data from file
|
|
|
|
data, err := ioutil.ReadFile(filePath)
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("Error reading file")
|
|
|
|
}
|
2020-12-03 10:12:43 +00:00
|
|
|
// Create md5 hash of password in order to make it the required size
|
|
|
|
md5Hash := md5.New()
|
|
|
|
md5Hash.Write([]byte(sharedKey))
|
|
|
|
hashedKey := hex.EncodeToString(md5Hash.Sum(nil))
|
|
|
|
// Create new AES cipher
|
|
|
|
block, _ := aes.NewCipher([]byte(hashedKey))
|
|
|
|
// Create GCM for AES cipher
|
|
|
|
gcm, err := cipher.NewGCM(block)
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("Error creating GCM")
|
|
|
|
}
|
2020-12-03 10:12:43 +00:00
|
|
|
// Get standard GCM nonce size
|
|
|
|
nonceSize := gcm.NonceSize()
|
|
|
|
// Get nonce and ciphertext from data
|
|
|
|
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
|
|
|
|
// Decrypt data
|
|
|
|
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("Error decrypting data")
|
|
|
|
}
|
2020-12-06 00:55:22 +00:00
|
|
|
// Create new Zstd decoder
|
2020-12-06 00:47:04 +00:00
|
|
|
zstdDecoder, err := zstd.NewReader(bytes.NewBuffer(plaintext))
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("Error creating Zstd decoder")
|
|
|
|
}
|
2020-12-03 10:12:43 +00:00
|
|
|
// Create new file
|
|
|
|
newFile, err := os.Create(newFilePath)
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("Error creating file")
|
|
|
|
}
|
2020-12-06 00:55:22 +00:00
|
|
|
// Close new file at the end of this function
|
2020-12-03 10:12:43 +00:00
|
|
|
defer newFile.Close()
|
2020-12-06 00:55:22 +00:00
|
|
|
// Write decompressed plaintext to new file
|
2020-12-06 00:47:04 +00:00
|
|
|
bytesWritten, err := io.Copy(newFile, zstdDecoder)
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("Error writing to file")
|
|
|
|
}
|
2020-12-06 00:47:04 +00:00
|
|
|
zstdDecoder.Close()
|
2020-12-03 10:12:43 +00:00
|
|
|
// Log bytes written and to which file
|
2020-12-06 00:47:04 +00:00
|
|
|
log.Info().Str("file", filepath.Base(newFilePath)).Msg("Wrote " + strconv.Itoa(int(bytesWritten)) + " bytes")
|
2020-12-03 10:12:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Encrypt files in given directory using shared key
|
|
|
|
func EncryptFiles(dir string, sharedKey string) {
|
|
|
|
// Use ConsoleWriter logger
|
2020-12-03 17:32:03 +00:00
|
|
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
|
2020-12-03 10:12:43 +00:00
|
|
|
// Walk given directory
|
|
|
|
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
|
|
// If error reading, return err
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-12-03 10:12:43 +00:00
|
|
|
// If file is not a directory and is not the key
|
2020-12-21 07:18:42 +00:00
|
|
|
if !info.IsDir() && !strings.Contains(path, "key.aes") {
|
2020-12-06 00:55:22 +00:00
|
|
|
// Compress and Encrypt the file using shared key, appending .zst.enc
|
2020-12-21 07:18:42 +00:00
|
|
|
CompressAndEncryptFile(path, path+".zst.enc", sharedKey)
|
2020-12-03 10:12:43 +00:00
|
|
|
// Remove unencrypted file
|
|
|
|
err := os.Remove(path)
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-12-03 10:12:43 +00:00
|
|
|
}
|
|
|
|
// Return nil if no error occurs
|
|
|
|
return nil
|
|
|
|
})
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("Error encrypting files")
|
|
|
|
}
|
2020-12-03 10:12:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Decrypt files in given directory using shared key
|
|
|
|
func DecryptFiles(dir string, sharedKey string) {
|
|
|
|
// Use ConsoleWriter logger
|
2020-12-03 17:32:03 +00:00
|
|
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
|
2020-12-03 10:12:43 +00:00
|
|
|
// Walk given directory
|
|
|
|
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
|
|
// If error reading, return err
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-12-03 10:12:43 +00:00
|
|
|
// If file is not a directory and is encrypted
|
|
|
|
if !info.IsDir() && strings.Contains(path, ".enc") {
|
2020-12-06 00:55:22 +00:00
|
|
|
// Decrypt and decompress the file using the shared key, removing .zst.enc
|
2020-12-06 00:47:04 +00:00
|
|
|
DecryptAndDecompressFile(path, strings.TrimSuffix(path, ".zst.enc"), sharedKey)
|
2020-12-03 10:12:43 +00:00
|
|
|
}
|
|
|
|
// Return nil if no errors occurred
|
|
|
|
return nil
|
|
|
|
})
|
2020-12-21 07:18:42 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal().Err(err).Msg("Error decrypting files")
|
|
|
|
}
|
|
|
|
}
|