Add Encrypt() and Decrypt() functionality
This commit is contained in:
76
.github/workflows/codeql.yml
vendored
Normal file
76
.github/workflows/codeql.yml
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ "main" ]
|
||||||
|
schedule:
|
||||||
|
- cron: '17 21 * * 0'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'go' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||||
|
# Use only 'java' to analyze code written in Java, Kotlin or both
|
||||||
|
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
|
||||||
|
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
|
||||||
|
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||||
|
# queries: security-extended,security-and-quality
|
||||||
|
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
|
|
||||||
|
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||||
|
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||||
|
|
||||||
|
# - run: |
|
||||||
|
# echo "Run, Build Application using script"
|
||||||
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v2
|
||||||
|
with:
|
||||||
|
category: "/language:${{matrix.language}}"
|
6
go.mod
6
go.mod
@ -1,3 +1,9 @@
|
|||||||
module github.com/jantytgat/go-transcrypt
|
module github.com/jantytgat/go-transcrypt
|
||||||
|
|
||||||
go 1.23.2
|
go 1.23.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/minio/sio v0.4.1 // indirect
|
||||||
|
golang.org/x/crypto v0.28.0 // indirect
|
||||||
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
|
)
|
||||||
|
10
go.sum
Normal file
10
go.sum
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
github.com/minio/sio v0.4.1 h1:EMe3YBC1nf+sRQia65Rutxi+Z554XPV0dt8BIBA+a/0=
|
||||||
|
github.com/minio/sio v0.4.1/go.mod h1:oBSjJeGbBdRMZZwna07sX9EFzZy+ywu5aofRiV1g79I=
|
||||||
|
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||||
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
|
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||||
|
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||||
|
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
|
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
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