Skip to content
This repository was archived by the owner on Oct 4, 2021. It is now read-only.

Commit de7912c

Browse files
authored
Merge pull request #3 from TomWright/main
Refactor and simplify codebase
2 parents d63e0ae + 6400bb0 commit de7912c

File tree

13 files changed

+361
-367
lines changed

13 files changed

+361
-367
lines changed

.github/workflows/codeql-analysis.yml

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,34 +35,34 @@ jobs:
3535
# https://docs.github.com/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
3636

3737
steps:
38-
- name: Checkout repository
39-
uses: actions/checkout@v2
38+
- name: Checkout repository
39+
uses: actions/checkout@v2
4040

41-
# Initializes the CodeQL tools for scanning.
42-
- name: Initialize CodeQL
43-
uses: github/codeql-action/init@v1
44-
with:
45-
languages: ${{ matrix.language }}
46-
# If you wish to specify custom queries, you can do so here or in a config file.
47-
# By default, queries listed here will override any specified in a config file.
48-
# Prefix the list here with "+" to use these queries and those in the config file.
49-
# queries: ./path/to/local/query, your-org/your-repo/queries@main
41+
# Initializes the CodeQL tools for scanning.
42+
- name: Initialize CodeQL
43+
uses: github/codeql-action/init@v1
44+
with:
45+
languages: ${{ matrix.language }}
46+
# If you wish to specify custom queries, you can do so here or in a config file.
47+
# By default, queries listed here will override any specified in a config file.
48+
# Prefix the list here with "+" to use these queries and those in the config file.
49+
# queries: ./path/to/local/query, your-org/your-repo/queries@main
5050

51-
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
52-
# If this step fails, then you should remove it and run the build manually (see below)
53-
- name: Autobuild
54-
uses: github/codeql-action/autobuild@v1
51+
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
52+
# If this step fails, then you should remove it and run the build manually (see below)
53+
- name: Autobuild
54+
uses: github/codeql-action/autobuild@v1
5555

56-
# ℹ️ Command-line programs to run using the OS shell.
57-
# 📚 https://git.io/JvXDl
56+
# ℹ️ Command-line programs to run using the OS shell.
57+
# 📚 https://git.io/JvXDl
5858

59-
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
60-
# and modify them (or add more) to build your code if your project
61-
# uses a compiled language
59+
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
60+
# and modify them (or add more) to build your code if your project
61+
# uses a compiled language
6262

63-
#- run: |
64-
# make bootstrap
65-
# make release
63+
#- run: |
64+
# make bootstrap
65+
# make release
6666

67-
- name: Perform CodeQL Analysis
68-
uses: github/codeql-action/analyze@v1
67+
- name: Perform CodeQL Analysis
68+
uses: github/codeql-action/analyze@v1

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# filecrypt
22
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/12610588667845698ee710628e313c0b)](https://www.codacy.com/gh/flew-software/filecrypt/dashboard?utm_source=github.com&utm_medium=referral&utm_content=flew-software/filecrypt&utm_campaign=Badge_Grade)
3+
[![Go Report Card](https://goreportcard.com/badge/github.com/flew-software/filecrypt)](https://goreportcard.com/report/github.com/flew-software/filecrypt)
34
[![](https://tokei.rs/b1/github/flew-software/filecrypt?category=lines)](https://github.com/flew-software/filecrypt)
45
![](https://img.shields.io/badge/Binaries%20under-800kb-succes)
56
![GitHub repo size](https://img.shields.io/github/repo-size/flew-software/filecrypt)

app.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package filecrypt
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"os"
7+
"strings"
8+
)
9+
10+
// App contains the actual application logic.
11+
type App struct {
12+
// FileCryptExtension is the file extension of encrypted files.
13+
FileCryptExtension string
14+
// Overwrite defines whether or not to overwrite existing files.
15+
Overwrite bool
16+
}
17+
18+
// Encrypt encrypts the given file and returns the path the an encrypted file.
19+
func (a *App) Encrypt(fileLocation string, password Passphrase) (string, error) {
20+
if err := password.validate(); err != nil {
21+
return "", err
22+
}
23+
24+
outputFilepath := fmt.Sprintf("%s%s", fileLocation, a.FileCryptExtension)
25+
26+
if fileExists(outputFilepath) && !a.Overwrite {
27+
return "", fmt.Errorf("file already exists [%s]. use --force flag to overwrite", outputFilepath)
28+
}
29+
30+
fileData, err := ioutil.ReadFile(fileLocation)
31+
if err != nil {
32+
return "", fmt.Errorf("could not read file [%s]: %w", fileLocation, err)
33+
}
34+
35+
encryptedFileData, err := encrypt(fileData, password)
36+
if err != nil {
37+
return "", fmt.Errorf("could not encrypt data: %w", err)
38+
}
39+
40+
// creates a file
41+
nf, err := os.Create(outputFilepath)
42+
if err != nil {
43+
return "", fmt.Errorf("could not create output file [%s]: %w", outputFilepath, err)
44+
}
45+
defer nf.Close()
46+
47+
// writes the encrypted data slice to the file
48+
_, writeErr := nf.Write(encryptedFileData)
49+
if writeErr != nil {
50+
return "", fmt.Errorf("could not write encrypted data to file: %w", err)
51+
}
52+
53+
return outputFilepath, nil
54+
}
55+
56+
// Decrypt decrypts the given file and returns the path to an unencrypted file.
57+
func (a *App) Decrypt(fileLocation string, password Passphrase) (string, error) {
58+
realFile := strings.Replace(fileLocation, a.FileCryptExtension, "", -1)
59+
if fileLocation == realFile {
60+
return "", fmt.Errorf("input file does not contain a valid extension: expected %s", a.FileCryptExtension)
61+
}
62+
63+
// Checks if file exists
64+
if fileExists(realFile) && !a.Overwrite {
65+
return "", fmt.Errorf("file already exists [%s]. use --force flag to overwrite", realFile)
66+
}
67+
68+
// reads the file
69+
b, readErr := ioutil.ReadFile(fileLocation)
70+
if readErr != nil {
71+
return "", fmt.Errorf("could not read input file [%s]: %w", fileLocation, readErr)
72+
}
73+
74+
// decrypted byte slice
75+
clearText, err := decrypt(b, password)
76+
if err != nil {
77+
return "", fmt.Errorf("could not decrypt file: %w", err)
78+
}
79+
80+
// creates a file
81+
nf, err := os.Create(realFile)
82+
if err != nil {
83+
return "", fmt.Errorf("could not create unencrypted file [%s]: %w", realFile, err)
84+
}
85+
defer nf.Close()
86+
87+
// writes decrypted buffer to the file
88+
_, writeErr := nf.Write(clearText)
89+
if writeErr != nil {
90+
return "", fmt.Errorf("could not write to unencrypted file: %w", err)
91+
}
92+
93+
return realFile, nil
94+
}

app_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package filecrypt
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
)
7+
8+
func TestCreateHash(t *testing.T) {
9+
var tests = []struct {
10+
input string
11+
expectedOutput string
12+
}{
13+
{"test", "098f6bcd4621d373cade4e832627b4f6"},
14+
{"log", "dc1d71bbb5c4d2a5e936db79ef10c19f"},
15+
{"log", "dc1d71bbb5c4d2a5e936db79ef10c19f"},
16+
}
17+
for _, test := range tests {
18+
if output := createHash(test.input); string(output) != test.expectedOutput {
19+
t.Error("Test failed: input:{} expectedOutput:{} output:{}", test.input, test.expectedOutput, output)
20+
}
21+
}
22+
}
23+
24+
func TestEncryptDecrypt(t *testing.T) {
25+
var tests = []struct {
26+
input string
27+
passphrase Passphrase
28+
}{
29+
{"log", Passphrase("popcorn")},
30+
{"log", Passphrase("pa$$word")},
31+
{"log", Passphrase("pdsasdas")},
32+
{"log", Passphrase("[asdsdasdmasn]")},
33+
{"log", Passphrase("popwas")},
34+
{"log", Passphrase("oi0isas")},
35+
{"log", Passphrase("0=2oasdaj")},
36+
{"test", Passphrase("testPass")},
37+
{"test", Passphrase("asdasdasd")},
38+
{"test", Passphrase("testP[ss")},
39+
{"test", Passphrase("195s2f5")},
40+
{"test", Passphrase("%%%%%%%s")},
41+
{"test", Passphrase("#491k2@")},
42+
{"test", Passphrase("[]'fd;fo;hkaf")},
43+
}
44+
for k, test := range tests {
45+
testCase := test
46+
t.Run(fmt.Sprint(k), func(t *testing.T) {
47+
encryptedResults, err := encrypt([]byte(testCase.input), testCase.passphrase)
48+
if err != nil {
49+
t.Errorf("unexpected error: %s", err)
50+
return
51+
}
52+
53+
decryptedResults, err := decrypt(encryptedResults, testCase.passphrase)
54+
if err != nil {
55+
t.Errorf("unexpected error: %s", err)
56+
return
57+
}
58+
59+
if exp, got := string(decryptedResults), testCase.input; exp != got {
60+
t.Errorf("expected result %s, got %s", exp, got)
61+
}
62+
})
63+
}
64+
}
65+
66+
func TestPassphraseValidate(t *testing.T) {
67+
var tests = []struct {
68+
passphrase Passphrase
69+
valid bool
70+
}{
71+
// valid tests
72+
{Passphrase("popcorn"), true},
73+
{Passphrase("pa$$word"), true},
74+
{Passphrase("pdsasdas"), true},
75+
{Passphrase("[asdsdasdmasn]"), true},
76+
{Passphrase("popwas"), true},
77+
{Passphrase("oi0isas"), true},
78+
{Passphrase("0=2oasdaj"), true},
79+
{Passphrase("testPass"), true},
80+
{Passphrase("asdasdasd"), true},
81+
{Passphrase("testP[ss"), true},
82+
{Passphrase("195s2f5"), true},
83+
{Passphrase("%%%%%%%s"), true},
84+
{Passphrase("#491k2@"), true},
85+
86+
// invalid tests
87+
{Passphrase("#49"), false},
88+
{Passphrase("#f9"), false},
89+
{Passphrase("as9"), false},
90+
{Passphrase("bn9"), false},
91+
{Passphrase("pop"), false},
92+
{Passphrase("lol"), false},
93+
{Passphrase("123"), false},
94+
{Passphrase("default"), false},
95+
}
96+
for k, test := range tests {
97+
testCase := test
98+
t.Run(fmt.Sprint(k), func(t *testing.T) {
99+
if exp, got := testCase.valid, testCase.passphrase.validate() == nil; exp != got {
100+
t.Errorf("expected result %v, got %v", exp, got)
101+
}
102+
})
103+
}
104+
}

cmd/filecrypt/main.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"github.com/flew-software/filecrypt"
7+
"os"
8+
"strings"
9+
)
10+
11+
// file crypt encrypted file
12+
const fileCryptExtension string = ".fcef"
13+
14+
func main() {
15+
// flag vars
16+
var (
17+
fileLocation string
18+
password string
19+
mode string
20+
force bool
21+
)
22+
23+
// flags
24+
flag.StringVar(&fileLocation, "location", "./", "Location of the file")
25+
flag.StringVar(&password, "password", "default", "Password to use while encrypting/decrypting")
26+
flag.StringVar(&mode, "mode", "undefined", "FileCrypt mode (encrypt/decrypt)")
27+
flag.BoolVar(&force, "force", false, "Force write even if a file exists with that name (overwrite)")
28+
flag.Parse()
29+
30+
app := filecrypt.App{
31+
FileCryptExtension: fileCryptExtension,
32+
Overwrite: force,
33+
}
34+
35+
switch strings.ToLower(mode) {
36+
case "encrypt", "e":
37+
newFile, err := app.Encrypt(fileLocation, filecrypt.Passphrase(password))
38+
if err != nil {
39+
fmt.Printf("could not encrypt file: %s\n", err)
40+
os.Exit(1)
41+
}
42+
fmt.Printf("file encrypted:\n%s\n", newFile)
43+
44+
case "decrypt", "d":
45+
newFile, err := app.Decrypt(fileLocation, filecrypt.Passphrase(password))
46+
if err != nil {
47+
fmt.Printf("could not decrypt file: %s\n", err)
48+
os.Exit(1)
49+
}
50+
fmt.Printf("file decrypted:\n%s\n", newFile)
51+
default:
52+
fmt.Printf("unhandled mode: %s\n", mode)
53+
os.Exit(1)
54+
}
55+
}

encryption.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package filecrypt
2+
3+
import (
4+
"crypto/aes"
5+
"crypto/cipher"
6+
"crypto/md5"
7+
"crypto/rand"
8+
"encoding/hex"
9+
"fmt"
10+
"io"
11+
)
12+
13+
type encryptedData []byte
14+
type decryptedData []byte
15+
type hash string
16+
17+
func createHash(key string) hash {
18+
hasher := md5.New()
19+
hasher.Write([]byte(key))
20+
return hash(hex.EncodeToString(hasher.Sum(nil)))
21+
}
22+
23+
func encrypt(data []byte, p Passphrase) (encryptedData, error) {
24+
block, _ := aes.NewCipher([]byte(createHash(string(p))))
25+
gcm, _ := cipher.NewGCM(block)
26+
nonce := make([]byte, gcm.NonceSize())
27+
_, err := io.ReadFull(rand.Reader, nonce)
28+
if err != nil {
29+
return nil, fmt.Errorf("could not get nonce: %w", err)
30+
}
31+
cipherText := gcm.Seal(nonce, nonce, data, nil)
32+
return cipherText, nil
33+
}
34+
35+
func decrypt(data []byte, p Passphrase) (decryptedData, error) {
36+
key := []byte(createHash(string(p)))
37+
block, _ := aes.NewCipher(key)
38+
gcm, _ := cipher.NewGCM(block)
39+
nonceSize := gcm.NonceSize()
40+
nonce, cipherText := data[:nonceSize], data[nonceSize:]
41+
plainText, err := gcm.Open(nil, nonce, cipherText, nil)
42+
return plainText, err
43+
}

file.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package filecrypt
2+
3+
import (
4+
"os"
5+
)
6+
7+
func fileExists(filename string) bool {
8+
info, err := os.Stat(filename)
9+
if os.IsNotExist(err) {
10+
return false
11+
}
12+
return !info.IsDir()
13+
}

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/flew-software/filecrypt
2+
3+
go 1.15

0 commit comments

Comments
 (0)