aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backend/api/routes.go8
-rw-r--r--backend/cache/cache.go17
-rw-r--r--backend/db/db.go21
-rw-r--r--backend/db/schemas.go1
-rw-r--r--backend/security/encrypt.go81
-rw-r--r--backend/security/hash.go (renamed from backend/hashing/hash.go)2
6 files changed, 124 insertions, 6 deletions
diff --git a/backend/api/routes.go b/backend/api/routes.go
index ff43714..474fdda 100644
--- a/backend/api/routes.go
+++ b/backend/api/routes.go
@@ -3,6 +3,7 @@ package api
import (
"encoding/json"
"fmt"
+ "github.com/jackyzha0/ctrl-v/security"
"net/http"
"time"
@@ -87,6 +88,13 @@ func handleGetPaste(w http.ResponseWriter, r *http.Request, parsedPassword strin
return
}
+ // if internal error with encryption
+ if err == security.EncryptionError {
+ w.WriteHeader(http.StatusInternalServerError)
+ fmt.Fprintf(w, "%s", err)
+ return
+ }
+
// otherwise, return paste content, title, and current time
w.Header().Set("Content-Type", "application/json")
pasteMap := map[string]interface{}{
diff --git a/backend/cache/cache.go b/backend/cache/cache.go
index 71007e5..43e615a 100644
--- a/backend/cache/cache.go
+++ b/backend/cache/cache.go
@@ -2,7 +2,7 @@ package cache
import (
"errors"
- "github.com/jackyzha0/ctrl-v/hashing"
+ "github.com/jackyzha0/ctrl-v/security"
"sync"
"github.com/jackyzha0/ctrl-v/db"
@@ -46,9 +46,22 @@ func (c *Cache) Get(hash, userPassword string) (db.Paste, error) {
// if there is a password, check the provided one against it
if p.Password != "" {
// if passwords do not match, the user is unauthorized
- if !hashing.PasswordsEqual(p.Password, userPassword) {
+ if !security.PasswordsEqual(p.Password, userPassword) {
return db.Paste{}, UserUnauthorized
}
+
+ // if password matches, decrypt content
+ key, _, err := security.DeriveKey(userPassword, p.Salt)
+ if err != nil {
+ return db.Paste{}, security.EncryptionError
+ }
+
+ decryptedContent, err := security.Decrypt(key, p.Content)
+ if err != nil {
+ return db.Paste{}, security.EncryptionError
+ }
+
+ p.Content = decryptedContent
}
return p, nil
diff --git a/backend/db/db.go b/backend/db/db.go
index 4e58188..df112d0 100644
--- a/backend/db/db.go
+++ b/backend/db/db.go
@@ -5,7 +5,7 @@ import (
"os"
"time"
- "github.com/jackyzha0/ctrl-v/hashing"
+ "github.com/jackyzha0/ctrl-v/security"
"github.com/joho/godotenv"
log "github.com/sirupsen/logrus"
)
@@ -30,7 +30,7 @@ const ContentLimit = 100000
// creates a new paste with title, content and hash, returns the hash of the created paste
func New(ip, content, expiry, title, password string) (string, error) {
// generate hash from ip
- hash := hashing.GenerateURI(ip)
+ hash := security.GenerateURI(ip)
// check for size of title and content
errs := checkLengths(title, content)
@@ -45,9 +45,24 @@ func New(ip, content, expiry, title, password string) (string, error) {
Title: title,
}
+ // if there is a password, encrypt content and hash the password
if password != "" {
+ // use pass to encrypt content
+ key, salt, err := security.DeriveKey(password, nil)
+ if err != nil {
+ return "", fmt.Errorf("could not generate key: %s", err.Error())
+ }
+ new.Salt = salt
+
+ encryptedContent, err := security.Encrypt(key, new.Content)
+ if err != nil {
+ return "", fmt.Errorf("could not encrypt content: %s", err.Error())
+ }
+
+ new.Content = encryptedContent
+
// hash given password
- hashedPass, err := hashing.HashPassword(password)
+ hashedPass, err := security.HashPassword(password)
if err != nil {
return "", fmt.Errorf("could not hash password: %s", err.Error())
}
diff --git a/backend/db/schemas.go b/backend/db/schemas.go
index 4c73f82..d3551fc 100644
--- a/backend/db/schemas.go
+++ b/backend/db/schemas.go
@@ -14,4 +14,5 @@ type Paste struct {
Expiry time.Time `bson:"expiry"`
Title string
Password string
+ Salt []byte
}
diff --git a/backend/security/encrypt.go b/backend/security/encrypt.go
new file mode 100644
index 0000000..4af5e3c
--- /dev/null
+++ b/backend/security/encrypt.go
@@ -0,0 +1,81 @@
+package security
+
+import (
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rand"
+ "errors"
+ "golang.org/x/crypto/scrypt"
+)
+
+var EncryptionError = errors.New("could not encrypt the given content")
+
+func Encrypt(key, data string) (string, error) {
+ // initialize aes block cipher with given key
+ blockCipher, err := aes.NewCipher([]byte(key))
+ if err != nil {
+ return "", err
+ }
+
+ // wrap block cipher with Galois Counter Mode and standard nonce length
+ gcm, err := cipher.NewGCM(blockCipher)
+ if err != nil {
+ return "", err
+ }
+
+ // generate nonce (number once used) unique to the given key
+ nonce := make([]byte, gcm.NonceSize())
+ if _, err = rand.Read(nonce); err != nil {
+ return "", err
+ }
+
+ // seal nonce with data to use during decryption
+ cipherText := gcm.Seal(nonce, nonce, []byte(data), nil)
+
+ return string(cipherText), nil
+}
+
+func Decrypt(key, data string) (string, error) {
+ // similar to encrypt, create cipher and wrap with GCM
+ blockCipher, err := aes.NewCipher([]byte(key))
+ if err != nil {
+ return "", err
+ }
+
+ gcm, err := cipher.NewGCM(blockCipher)
+ if err != nil {
+ return "", err
+ }
+
+ // extract the nonce from the data
+ nonce, cipherText := data[:gcm.NonceSize()], data[gcm.NonceSize():]
+
+ // use nonce to decrypt the data
+ plaintext, err := gcm.Open(nil, []byte(nonce), []byte(cipherText), nil)
+ if err != nil {
+ return "", err
+ }
+
+ return string(plaintext), nil
+}
+
+const keyBytes = 16
+const iterations = 16384
+const relativeMemoryCost = 8
+const relativeCPUCost = 1
+
+func DeriveKey(password string, salt []byte) (string, []byte, error) {
+ if salt == nil {
+ salt = make([]byte, keyBytes)
+ if _, err := rand.Read(salt); err != nil {
+ return "", nil, err
+ }
+ }
+
+ key, err := scrypt.Key([]byte(password), salt, iterations, relativeMemoryCost, relativeCPUCost, keyBytes)
+ if err != nil {
+ return "", nil, err
+ }
+
+ return string(key), salt, nil
+}
diff --git a/backend/hashing/hash.go b/backend/security/hash.go
index e944fbe..b6ce167 100644
--- a/backend/hashing/hash.go
+++ b/backend/security/hash.go
@@ -1,4 +1,4 @@
-package hashing
+package security
import (
"crypto/md5"