Encryption
This commit is contained in:
parent
b49f7adbcd
commit
ffca6c9374
126
cryptojs/cryptojs.go
Normal file
126
cryptojs/cryptojs.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package cryptojs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"math/rand"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"git.meow.tf/tyler/go-pastee/evpkdf"
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
keySize = 32
|
||||||
|
)
|
||||||
|
|
||||||
|
func Encrypt(data, passphrase string) (string, error) {
|
||||||
|
salt := make([]byte, 8)
|
||||||
|
|
||||||
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
key := make([]byte, keySize)
|
||||||
|
iv := make([]byte, aes.BlockSize)
|
||||||
|
|
||||||
|
keymat := evpkdf.New(md5.New, []byte(passphrase), salt, keySize + aes.BlockSize, 1)
|
||||||
|
keymatbuf := bytes.NewReader(keymat)
|
||||||
|
|
||||||
|
n, err := keymatbuf.Read(key)
|
||||||
|
|
||||||
|
if n != keySize || err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = keymatbuf.Read(iv)
|
||||||
|
|
||||||
|
if n != aes.BlockSize || err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
padded, err := pkcs7Pad([]byte(data), block.BlockSize())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext := make([]byte, len(padded))
|
||||||
|
|
||||||
|
cbc := cipher.NewCBCEncrypter(block, iv)
|
||||||
|
cbc.CryptBlocks(ciphertext, padded)
|
||||||
|
|
||||||
|
return encode(ciphertext, salt), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decrypt(b64, passphrase string) ([]byte, error) {
|
||||||
|
salt, ct, err := decode(b64)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
key := make([]byte, keySize)
|
||||||
|
iv := make([]byte, aes.BlockSize)
|
||||||
|
|
||||||
|
keymat := evpkdf.New(md5.New, []byte(passphrase), salt, keySize + aes.BlockSize, 1)
|
||||||
|
keymatbuf := bytes.NewReader(keymat)
|
||||||
|
|
||||||
|
n, err := keymatbuf.Read(key)
|
||||||
|
|
||||||
|
if n != keySize || err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = keymatbuf.Read(iv)
|
||||||
|
|
||||||
|
if n != aes.BlockSize || err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cbc := cipher.NewCBCDecrypter(block, iv)
|
||||||
|
cbc.CryptBlocks(ct, ct)
|
||||||
|
|
||||||
|
plain, err := pkcs7Unpad(ct, block.BlockSize())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return plain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(ct []byte, salt []byte) string {
|
||||||
|
b := []byte("Salted__")
|
||||||
|
b = append(b, salt...)
|
||||||
|
b = append(b, ct...)
|
||||||
|
return base64.StdEncoding.EncodeToString(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode(b64 string) ([]byte, []byte, error) {
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(b64)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(decoded[0:8]) != "Salted__" {
|
||||||
|
return nil, nil, errors.New("invalid data")
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoded[8:16], decoded[16:], nil
|
||||||
|
}
|
47
cryptojs/cryptojs_test.go
Normal file
47
cryptojs/cryptojs_test.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package cryptojs
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func Test_EncryptDecrypt(t *testing.T) {
|
||||||
|
enc, err := Encrypt("testing123", "testing")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to encrypt:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := Decrypt(enc, "testing")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to decrypt:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
final := string(b)
|
||||||
|
|
||||||
|
if final != "testing123" {
|
||||||
|
t.Fatal("Final text does not match:", final)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_WebsiteEncrypt(t *testing.T) {
|
||||||
|
enc, err := Encrypt("Testing Paste.ee", "wXBbztgFOsZtTvhK1vcZlR7izK84bmUW")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if enc != "U2FsdGVkX183sL2jzqJiJdhIl0O7R9v46TIkKnORreGpUufYUsqjqYO9b++CSyvV" {
|
||||||
|
t.Fatal("Unexpected output:", enc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_WebsiteDecrypt(t *testing.T) {
|
||||||
|
b, err := Decrypt("U2FsdGVkX183sL2jzqJiJdhIl0O7R9v46TIkKnORreGpUufYUsqjqYO9b++CSyvV", "wXBbztgFOsZtTvhK1vcZlR7izK84bmUW")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Unable to decrypt:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(b) != "Testing Paste.ee" {
|
||||||
|
t.Fatal("Unexpected output:", string(b))
|
||||||
|
}
|
||||||
|
}
|
48
cryptojs/pkcs7.go
Normal file
48
cryptojs/pkcs7.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package cryptojs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Appends padding.
|
||||||
|
func pkcs7Pad(data []byte, blocklen int) ([]byte, error) {
|
||||||
|
if blocklen <= 0 {
|
||||||
|
return nil, fmt.Errorf("invalid blocklen %d", blocklen)
|
||||||
|
}
|
||||||
|
padlen := uint8(1)
|
||||||
|
for ((len(data) + int(padlen)) % blocklen) != 0 {
|
||||||
|
padlen++
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(padlen) > blocklen {
|
||||||
|
panic(fmt.Sprintf("generated invalid padding length %v for block length %v", padlen, blocklen))
|
||||||
|
}
|
||||||
|
pad := bytes.Repeat([]byte{byte(padlen)}, int(padlen))
|
||||||
|
return append(data, pad...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns slice of the original data without padding.
|
||||||
|
func pkcs7Unpad(data []byte, blocklen int) ([]byte, error) {
|
||||||
|
if blocklen <= 0 {
|
||||||
|
return nil, fmt.Errorf("invalid blocklen %d", blocklen)
|
||||||
|
}
|
||||||
|
if len(data)%blocklen != 0 || len(data) == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid data len %d", len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
padlen := int(data[len(data)-1])
|
||||||
|
if padlen > blocklen || padlen == 0 {
|
||||||
|
// Not padded
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
// check padding
|
||||||
|
pad := data[len(data)-padlen:]
|
||||||
|
for i := 0; i < padlen; i++ {
|
||||||
|
if pad[i] != byte(padlen) {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data[:len(data)-padlen], nil
|
||||||
|
}
|
55
evpkdf/evpkdf.go
Normal file
55
evpkdf/evpkdf.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (c) 2016 Tristan Colgate-McFarlane
|
||||||
|
//
|
||||||
|
// This file is part of evpkdf.
|
||||||
|
//
|
||||||
|
// radia is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// radia is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with radia. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package evpkdf implements OpenSSL EVP Key derivation function, aiming to
|
||||||
|
// be compatible with crypto-js default behaviour.
|
||||||
|
package evpkdf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
//New creates a key derivation function that should match the EVP kdf in crypto-js, which, in turn
|
||||||
|
// should be compatible with openssl's EVP kdf
|
||||||
|
func New(hash func() hash.Hash, password []byte, salt []byte, keysize int, iterations int) []byte {
|
||||||
|
hasher := hash()
|
||||||
|
derivedKey := []byte{}
|
||||||
|
block := []byte{}
|
||||||
|
|
||||||
|
// Generate key
|
||||||
|
for len(derivedKey) < keysize {
|
||||||
|
if len(block) != 0 {
|
||||||
|
io.Copy(hasher, bytes.NewBuffer(block))
|
||||||
|
}
|
||||||
|
io.Copy(hasher, bytes.NewBuffer(password))
|
||||||
|
io.Copy(hasher, bytes.NewBuffer(salt))
|
||||||
|
block = hasher.Sum(nil)
|
||||||
|
hasher.Reset()
|
||||||
|
|
||||||
|
// Iterations
|
||||||
|
for i := 1; i < iterations; i++ {
|
||||||
|
io.Copy(hasher, bytes.NewBuffer(block))
|
||||||
|
block = hasher.Sum(nil)
|
||||||
|
hasher.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
derivedKey = append(derivedKey, block...)
|
||||||
|
}
|
||||||
|
return derivedKey[0:keysize]
|
||||||
|
}
|
140
pastee.go
140
pastee.go
@ -1,17 +1,118 @@
|
|||||||
package pastee
|
package GoPastee
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"io"
|
"io"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
"net/url"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"git.meow.tf/tyler/go-pastee/cryptojs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(key string) *Pastee {
|
func New(key string) *Pastee {
|
||||||
return &Pastee{ApiKey: key, Base: "https://api.paste.ee/v1", Client: &http.Client{}}
|
return &Pastee{ApiKey: key, Base: "https://api.paste.ee/v1", Client: &http.Client{Timeout: time.Minute}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pastee) Authenticate(username, password string) (*AuthResponse, error) {
|
||||||
|
body, err := json.Marshal(&authRequest{username, password})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := p.newRequest("POST", "users/authenticate", bytes.NewReader(body))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Content-Length", strconv.Itoa(len(body)))
|
||||||
|
|
||||||
|
res, err := p.Client.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
var response AuthResponse
|
||||||
|
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pastee) List() (*PasteListResponse, error) {
|
||||||
|
q := &url.Values{}
|
||||||
|
q.Set("perpage", "25")
|
||||||
|
q.Set("page", "1")
|
||||||
|
|
||||||
|
req, err := p.newRequest("GET", "pastes?" + q.Encode(), nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := p.Client.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
var response PasteListResponse
|
||||||
|
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pastee) Get(id string) (*Paste, error) {
|
||||||
|
req, err := p.newRequest("GET", "pastes/" + id, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := p.Client.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
var response Paste
|
||||||
|
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pastee) Submit(paste *Paste) (*PasteResponse, error) {
|
func (p *Pastee) Submit(paste *Paste) (*PasteResponse, error) {
|
||||||
|
var key string
|
||||||
|
|
||||||
|
if paste.Encrypted {
|
||||||
|
key = RandStringBytesMaskImprSrc(32)
|
||||||
|
|
||||||
|
for _, section := range paste.Sections {
|
||||||
|
section.Contents = cryptojs.Encrypt(section.Contents, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
body, err := json.Marshal(paste)
|
body, err := json.Marshal(paste)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -25,7 +126,7 @@ func (p *Pastee) Submit(paste *Paste) (*PasteResponse, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("Content-Length", len(body))
|
req.Header.Set("Content-Length", strconv.Itoa(len(body)))
|
||||||
|
|
||||||
res, err := p.Client.Do(req)
|
res, err := p.Client.Do(req)
|
||||||
|
|
||||||
@ -41,11 +142,15 @@ func (p *Pastee) Submit(paste *Paste) (*PasteResponse, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if key != "" {
|
||||||
|
response.Key = key
|
||||||
|
}
|
||||||
|
|
||||||
return &response, nil
|
return &response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pastee) newRequest(method, path string, body io.Reader) (*http.Request, error) {
|
func (p *Pastee) newRequest(method, path string, body io.Reader) (*http.Request, error) {
|
||||||
req, err := http.NewRequest(method, p.Base + path, body)
|
req, err := http.NewRequest(method, p.Base + "/" + path, body)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -54,4 +159,31 @@ func (p *Pastee) newRequest(method, path string, body io.Reader) (*http.Request,
|
|||||||
req.Header.Set("X-Auth-Token", p.ApiKey)
|
req.Header.Set("X-Auth-Token", p.ApiKey)
|
||||||
|
|
||||||
return req, nil
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
const (
|
||||||
|
letterIdxBits = 6 // 6 bits to represent a letter index
|
||||||
|
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
|
||||||
|
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
||||||
|
)
|
||||||
|
|
||||||
|
var src = rand.NewSource(time.Now().UnixNano())
|
||||||
|
|
||||||
|
func RandStringBytesMaskImprSrc(n int) string {
|
||||||
|
b := make([]byte, n)
|
||||||
|
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
|
||||||
|
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
|
||||||
|
if remain == 0 {
|
||||||
|
cache, remain = src.Int63(), letterIdxMax
|
||||||
|
}
|
||||||
|
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
|
||||||
|
b[i] = letterBytes[idx]
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
cache >>= letterIdxBits
|
||||||
|
remain--
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b)
|
||||||
}
|
}
|
32
structs.go
32
structs.go
@ -1,4 +1,4 @@
|
|||||||
package pastee
|
package GoPastee
|
||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
@ -9,6 +9,8 @@ type Pastee struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Paste struct {
|
type Paste struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Views int `json:"views,omitempty"`
|
||||||
Encrypted bool `json:"encrypted,omitempty"`
|
Encrypted bool `json:"encrypted,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
Sections []*Section `json:"sections"`
|
Sections []*Section `json:"sections"`
|
||||||
@ -29,6 +31,34 @@ type Error struct {
|
|||||||
type PasteResponse struct {
|
type PasteResponse struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
Errors []*Error `json:"errors"`
|
Errors []*Error `json:"errors"`
|
||||||
|
Key string `json:"-"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Link string `json:"link"`
|
Link string `json:"link"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type authRequest struct {
|
||||||
|
username string `json:"username"`
|
||||||
|
password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaginationResponse struct {
|
||||||
|
Total int `json:"total"`
|
||||||
|
PerPage int `json:"per_page"`
|
||||||
|
CurrentPage int `json:"current_page"`
|
||||||
|
LastPage int `json:"last_page"`
|
||||||
|
NextPageURL string `json:"next_page_url"`
|
||||||
|
PreviousPageURL string `json:"prev_page_url"`
|
||||||
|
From int `json:"from"`
|
||||||
|
To int `json:"to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PasteListResponse struct {
|
||||||
|
*PaginationResponse
|
||||||
|
|
||||||
|
Data []*Paste `json:"data"`
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user