1
0
mirror of https://github.com/mgerb/mywebsite synced 2026-01-08 09:02:47 +00:00

new post - mgcrypt

This commit is contained in:
2018-12-12 22:54:31 -06:00
parent 1ec2ed2e10
commit d2a81f0c9d
4 changed files with 275 additions and 1 deletions

3
.gitignore vendored
View File

@@ -8,4 +8,5 @@ npm-debug.log
mywebsite
tls.key
tls.crt
stats.html
stats.html
/vendor

42
Gopkg.lock generated Normal file
View File

@@ -0,0 +1,42 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/NYTimes/gziphandler"
packages = ["."]
revision = "2600fb119af974220d3916a5916d6e31176aac1b"
version = "v1.0.1"
[[projects]]
name = "github.com/julienschmidt/httprouter"
packages = ["."]
revision = "348b672cd90d8190f8240323e372ecd1e66b59dc"
version = "v1.2.0"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = [
"acme",
"acme/autocert"
]
revision = "505ab145d0a99da450461ae2c1a9f6cd10d1f447"
[[projects]]
branch = "v2"
name = "gopkg.in/mgo.v2"
packages = [
".",
"bson",
"internal/json",
"internal/sasl",
"internal/scram"
]
revision = "9856a29383ce1c59f308dd1cf0363a79b5bef6b5"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "9162cd6548eca5cba13aa4966fdb690196fa0ad08b56779e18448b9d94c0985c"
solver-name = "gps-cdcl"
solver-version = 1

46
Gopkg.toml Normal file
View File

@@ -0,0 +1,46 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/NYTimes/gziphandler"
version = "1.0.1"
[[constraint]]
name = "github.com/julienschmidt/httprouter"
version = "1.2.0"
[[constraint]]
branch = "master"
name = "golang.org/x/crypto"
[[constraint]]
branch = "v2"
name = "gopkg.in/mgo.v2"
[prune]
go-tests = true
unused-packages = true

View File

@@ -0,0 +1,185 @@
# MGCrypt
A dead simple [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) file encryption tool I wrote.
[Check out the source code here.](https://github.com/mgerb/mgcrypt)
***
## Why?
I've been wanting a simple tool to encrypt files on the fly. There are various tools
that already do this, but I thought this would be a perfect learning opportunity.
## What language?
Go! Of course I picked Go for this because it is perfect.
- it's fast
- compiles cross platform executables
- has crypto libraries built in
- my favorite language :)
## How does [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) work?
To be honest I don't know the nitty gritty details of how this algorithm encrypts files,
but it has been thoroughly tested and is secure enough. AES is a symmetric encryption algorithm.
This means that the same key can be used to both encrypt and decrypt. This is perfect for
securing files.
## Generating keys
AES has a key size of 128, 192 or 256 bits. This means I would need to have a 16 character
long password just to reach the 128 bits. I first starting thinking how I would get around this.
Maybe I could just take the first 128 bits of the password, but what if the password wasn't
longer than 16 characters? Add more characters to it?
This seems a little iffy if we are going to mess with passwords directly like this. Remember
that I just need 128 bits and they do not need to be characters. What if we hash the password
and take the first 128 bits of it? This is actually a potential solution and is known as a
truncated hash.
### PBKDF2
I was thinking of using SHA256 to hash the password and use the truncated version bits. This
should work just fine, but SHA256 is a fairly fast hashing algorithm, which would open us
up to dictionary attacks.
It turns out there is an algorithm designed for exactly what we need here; converting a password
to a 128 bit AES key. It's known as [PBKDF2](https://en.wikipedia.org/wiki/PBKDF2). This algorithm
works perfectly because we can tune the cost it takes to generate a password. The algorithm also
allows us to supply a salt.
### What's the point of the salt?
The concept of a salt was a little confusing to me at first. I wondered why this arbitrary
data added to a password would increase security. A salt is just extra bits added to the
password to generate a unique AES key for each encrypted file. Keep in mind here we are
generating an AES key from a password. The key will then be used to encrypt/decrypt the file.
Why would we store the salt in plain text on the file? Of course we could store it in a
secret database somewhere but this just isn't practical. The purpose of the salt is just
to make brute forcing a group of files much more difficult.
Let's say we have a directory of files all encrypted with the same AES key. This means
that a hacker would only have to brute force one key and then they would have access to everything.
Using a salt along with the password to generate our AES key means that we produce a
different key for each individual file. This increases the time substantially that it will
take to crack every file.
### Generating the salt
Good thing PBKDF2 takes care of generating the AES key and all we need to provide is the
salt and the passphrase. We are going to use a 128 bit salt. This salt will then be stored on
the encrypted file. We can just take the first 128 bits of the file and append it to the
encrypted file. Then, we can just peel that salt off when we are decrypting!
```Go
// generate AES key based on password input and salt from file
func generateKey(password, salt []byte) []byte {
return pbkdf2.Key(password, salt, 4096, 32, sha256.New)
}
```
There's a problem with this. This would mean that the first 128 bits of the encrypted file,
contain 128 bits of the unencrypted portion of the file. I easily solved this by grabbing
the first 128 bits of the SHA256 checksum of the original file and used that as a salt.
```Go
// get the first 128 bits of the sha256 checksum of the file
func getEncryptionSalt(filedata []byte) []byte {
sum := sha256.Sum256(filedata)
return sum[:16]
}
```
## Encrypting
The encrypt function should seem pretty straight forward. You may notice something called the IV,
or initialization vector. This is almost like the salt, but it doesn't have anything to do with
our passphrase or AES key. It just adds some extra randomness to the encrypted file. Like the salt,
these random bits are stored with the encrypted file. The AES tools included in Go provide this automatically.
The IV is actually stored at the beginning of the encrypted file after our salt.
You can see at the return statement we are appending the salt to the beginning of the encrypted file.
```Go
func encrypt(inputKey, data []byte) ([]byte, error) {
salt := getEncryptionSalt(data)
key := generateKey(inputKey, salt)
// Empty array of 16 + data length
// Include the IV at the beginning
ciphertext := make([]byte, aes.BlockSize+len(data))
// Create the AES cipher
block, err := aes.NewCipher(key)
if err != nil {
return ciphertext, err
}
// Slice of first 16 bytes
iv := ciphertext[:aes.BlockSize]
// Write 16 rand bytes to fill iv
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return ciphertext, err
}
// Return an encrypted stream
stream := cipher.NewCFBEncrypter(block, iv)
// Encrypt bytes from plaintext to ciphertext
stream.XORKeyStream(ciphertext[aes.BlockSize:], data)
// add the salt to the beginning of the ciphertext
return append(salt, ciphertext...), nil
}
```
## Decrypting
The decrypt function looks very similar to the encrypt function, except we are peeling off
the salt right away. You can see the IV bits being removed as well before the file is
finally decrypted.
```Go
// get the first 128 bits from the file as the salt
func getDecriptionSalt(filedata []byte) []byte {
return filedata[:16]
}
func decrypt(inputKey, data []byte) ([]byte, error) {
salt := getDecriptionSalt(data)
key := generateKey(inputKey, salt)
// strip the salt from the file
data = data[16:]
// Create the AES cipher
block, err := aes.NewCipher(key)
if err != nil {
return data, err
}
// Before even testing the decryption,
// if the text is too small, then it is incorrect
if len(data) < aes.BlockSize {
return data, errors.New("Text is too short")
}
// Get the 16 byte IV
iv := data[:aes.BlockSize]
// Remove the IV from the ciphertext
data = data[aes.BlockSize:]
// Return a decrypted stream
stream := cipher.NewCFBDecrypter(block, iv)
// Decrypt bytes from ciphertext
stream.XORKeyStream(data, data)
return data, nil
}
```