Add Encrypt() and Decrypt() functionality

This commit is contained in:
Jan Tytgat
2024-10-25 17:11:27 +02:00
parent 7821020da7
commit df500c6f1c
8 changed files with 413 additions and 0 deletions

View File

@ -0,0 +1,19 @@
package transcrypt
type CipherSuite byte
const (
AES_256_GCM CipherSuite = iota
CHACHA20_POLY1305
)
func GetCipherSuite(s string) CipherSuite {
switch s {
case "AES_256_GCM":
return AES_256_GCM
case "CHACHA20_POLY1305":
return CHACHA20_POLY1305
default:
return CHACHA20_POLY1305
}
}

87
pkg/transcrypt/convert.go Normal file
View File

@ -0,0 +1,87 @@
package transcrypt
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"reflect"
"regexp"
"strings"
"github.com/minio/sio"
)
var regexEncryptedString = regexp.MustCompile(`\d{2}:[\w\d]{24}:[\w\d]*:[\w\d]*`)
func convertValueToHexString(v reflect.Value) (string, error) {
var err error
switch v.Kind() {
case reflect.Int:
buf := make([]byte, 0)
bufWriter := bytes.NewBuffer(buf)
err = binary.Write(bufWriter, binary.BigEndian, v.Int())
if err != nil {
return "", err
}
return hex.EncodeToString(bufWriter.Bytes()), nil
default:
return hex.EncodeToString([]byte(v.String())), nil
}
}
func convertBytesToValue(d []byte, k reflect.Kind) (reflect.Value, error) {
switch k {
case reflect.Int:
decodedInt := binary.BigEndian.Uint64(d)
return reflect.ValueOf(int(decodedInt)), nil
default:
return reflect.ValueOf(string(d)), nil
}
}
func decodeHexString(key string, data string) ([]byte, reflect.Kind, sio.Config, error) {
if data == "" {
return nil, reflect.Invalid, sio.Config{}, fmt.Errorf("value is empty")
}
if !regexEncryptedString.MatchString(data) {
return nil, reflect.Invalid, sio.Config{}, fmt.Errorf("value is not valid")
}
var split []string
split = strings.Split(data, ":")
var err error
var cipherSuiteBytes []byte
if cipherSuiteBytes, err = hex.DecodeString(split[0]); err != nil {
return nil, reflect.Invalid, sio.Config{}, fmt.Errorf("cannot decode cipersuite: %w", err)
}
var nonce []byte
if nonce, err = hex.DecodeString(split[1]); err != nil {
return nil, reflect.Invalid, sio.Config{}, fmt.Errorf("cannot decode nonce: %w", err)
}
var encryptedBytes []byte
if encryptedBytes, err = hex.DecodeString(split[2]); err != nil {
return nil, reflect.Invalid, sio.Config{}, fmt.Errorf("cannot decode encrypted data: %w", err)
}
var kindBytes []byte
if kindBytes, err = hex.DecodeString(split[3]); err != nil {
return nil, reflect.Invalid, sio.Config{}, fmt.Errorf("cannot decode kindBytes: %w", err)
}
var kind reflect.Kind
if kind = getKindForString(string(kindBytes)); kind == reflect.Invalid {
return nil, reflect.Invalid, sio.Config{}, fmt.Errorf("cannot decode kind: %w", err)
}
var cryptoConfig sio.Config
if cryptoConfig, err = createCryptoConfig(key, cipherSuiteBytes, nonce); err != nil {
return nil, reflect.Invalid, sio.Config{}, fmt.Errorf("cannot create crypto config: %w", err)
}
return encryptedBytes, kind, cryptoConfig, nil
}

113
pkg/transcrypt/crypto.go Normal file
View File

@ -0,0 +1,113 @@
package transcrypt
import (
"crypto/rand"
"crypto/sha256"
"errors"
"fmt"
"io"
"reflect"
"github.com/minio/sio"
"golang.org/x/crypto/hkdf"
)
func createCryptoConfig(key string, cipher []byte, salt []byte) (sio.Config, error) {
if key == "" {
return sio.Config{}, errors.New("key is empty")
}
if cipher == nil {
return sio.Config{}, errors.New("cipher is empty")
}
var err error
// If salt is nil, create a new salt that can be used for encryption
if salt == nil {
if salt, err = createSalt(); err != nil {
return sio.Config{}, fmt.Errorf("could not create salt: %w", err)
}
}
// Create encryption key
kdf := hkdf.New(sha256.New, []byte(key), salt[:12], nil)
var encKey [32]byte
if _, err = io.ReadFull(kdf, encKey[:]); err != nil {
return sio.Config{}, fmt.Errorf("failed to derive encryption encKey: %w", err)
}
return sio.Config{
CipherSuites: cipher,
Key: encKey[:],
Nonce: (*[12]byte)(salt[:]),
}, nil
}
// createSalt creates a random salt for use with the encrypt/decrypt functionality
func createSalt() ([]byte, error) {
var nonce [12]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
return nil, fmt.Errorf("failed to read random data for nonce: %w", err)
}
return nonce[:], nil
}
func getKindForString(s string) reflect.Kind {
switch s {
case "bool":
return reflect.Bool
case "int":
return reflect.Int
case "int8":
return reflect.Int8
case "int16":
return reflect.Int16
case "int32":
return reflect.Int32
case "int64":
return reflect.Int64
case "uint":
return reflect.Uint
case "uint8":
return reflect.Uint8
case "uint16":
return reflect.Uint16
case "uint32":
return reflect.Uint32
case "uint64":
return reflect.Uint64
case "uintptr":
return reflect.Uintptr
case "float32":
return reflect.Float32
case "float64":
return reflect.Float64
case "complex64":
return reflect.Complex64
case "complex128":
return reflect.Complex128
case "array":
return reflect.Array
case "chan":
return reflect.Chan
case "func":
return reflect.Func
case "interface":
return reflect.Interface
case "map":
return reflect.Map
case "pointer":
return reflect.Pointer
case "slice":
return reflect.Slice
case "string":
return reflect.String
case "struct":
return reflect.Struct
case "unsafepointer":
return reflect.UnsafePointer
default:
return reflect.Invalid
}
}

47
pkg/transcrypt/decrypt.go Normal file
View File

@ -0,0 +1,47 @@
package transcrypt
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"reflect"
"github.com/minio/sio"
)
func Decrypt(key string, data string) (any, error) {
if key == "" {
return nil, errors.New("key is empty")
}
if data == "" {
return nil, errors.New("data is nil")
}
var err error
var encryptedData []byte
var kind reflect.Kind
var cryptoConfig sio.Config
if encryptedData, kind, cryptoConfig, err = decodeHexString(key, data); err != nil {
return nil, err
}
var decryptedHexData *bytes.Buffer
decryptedHexData = bytes.NewBuffer(make([]byte, 0))
if _, err = sio.Decrypt(decryptedHexData, bytes.NewBuffer(encryptedData), cryptoConfig); err != nil {
return nil, fmt.Errorf("decrypt failed: %w", err)
}
var decryptedData []byte
if decryptedData, err = hex.DecodeString(string(decryptedHexData.Bytes())); err != nil {
return nil, fmt.Errorf("decode decrypted hex data failed: %w", err)
}
var outputValue reflect.Value
if outputValue, err = convertBytesToValue(decryptedData, kind); err != nil {
return nil, err
}
return outputValue.Interface(), nil
}

55
pkg/transcrypt/encrypt.go Normal file
View File

@ -0,0 +1,55 @@
package transcrypt
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"reflect"
"strings"
"github.com/minio/sio"
)
func Encrypt(key string, cipherSuite CipherSuite, d any) (string, error) {
if key == "" {
return "", errors.New("key is empty")
}
if d == nil {
return "", errors.New("data is nil")
}
var err error
var data string
// Convert input data to reflect.Value before serialization
if data, err = convertValueToHexString(reflect.ValueOf(d)); err != nil {
return "", err
}
var cryptoConfig sio.Config
if cryptoConfig, err = createCryptoConfig(key, []byte{byte(cipherSuite)}, nil); err != nil {
return "", err
}
encryptedData := bytes.NewBuffer(make([]byte, 0))
if _, err = sio.Encrypt(encryptedData, bytes.NewBuffer([]byte(data)), cryptoConfig); err != nil {
return "", err
}
// Encode all details in hex before joining together
encryptedString := strings.Join(
[]string{
hex.EncodeToString([]byte{byte(cipherSuite)}),
hex.EncodeToString(cryptoConfig.Nonce[:]),
hex.EncodeToString(encryptedData.Bytes()),
hex.EncodeToString([]byte(reflect.TypeOf(d).Kind().String())),
}, ":",
)
if !regexEncryptedString.MatchString(encryptedString) {
return "", fmt.Errorf("could not validate encrypted data")
}
return encryptedString, nil
}