Add Encrypt() and Decrypt() functionality
This commit is contained in:
19
pkg/transcrypt/cipherSuite.go
Normal file
19
pkg/transcrypt/cipherSuite.go
Normal 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
87
pkg/transcrypt/convert.go
Normal 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
113
pkg/transcrypt/crypto.go
Normal 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
47
pkg/transcrypt/decrypt.go
Normal 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
55
pkg/transcrypt/encrypt.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user