mirror of
https://github.com/mgerb/mywebsite
synced 2026-01-09 01:12:52 +00:00
new post - mgcrypt
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,4 +8,5 @@ npm-debug.log
|
|||||||
mywebsite
|
mywebsite
|
||||||
tls.key
|
tls.key
|
||||||
tls.crt
|
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