mirror of
https://github.com/mgerb/mywebsite
synced 2026-01-08 09:02:47 +00:00
new post - mgcrypt
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,4 +8,5 @@ npm-debug.log
|
||||
mywebsite
|
||||
tls.key
|
||||
tls.crt
|
||||
stats.html
|
||||
stats.html
|
||||
/vendor
|
||||
|
||||
42
Gopkg.lock
generated
Normal file
42
Gopkg.lock
generated
Normal 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
46
Gopkg.toml
Normal 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
|
||||
185
posts/Projects/2018-12-12-mgcrypt.md
Normal file
185
posts/Projects/2018-12-12-mgcrypt.md
Normal 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
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user