From ab7f90b7cc33ddc5617c5663a7b7e82ac5910178 Mon Sep 17 00:00:00 2001 From: YenchangChan Date: Fri, 14 Jun 2024 08:23:10 +0800 Subject: [PATCH] feat: encrypt password --- cmd/clickhouse_sinker/main.go | 10 +++ go.mod | 2 +- task/sinker.go | 4 + util/aes.go | 71 ++++++++++++++++ util/common.go | 1 + util/gosypt.go | 149 ++++++++++++++++++++++++++++++++++ 6 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 util/aes.go create mode 100644 util/gosypt.go diff --git a/cmd/clickhouse_sinker/main.go b/cmd/clickhouse_sinker/main.go index d74d72c6..6e471f50 100644 --- a/cmd/clickhouse_sinker/main.go +++ b/cmd/clickhouse_sinker/main.go @@ -60,10 +60,12 @@ func initCmdOptions() { NacosGroup: "DEFAULT_GROUP", NacosUsername: "nacos", NacosPassword: "nacos", + Encrypt: "", } // 2. Replace options with the corresponding env variable if present. util.EnvBoolVar(&cmdOps.ShowVer, "v") + util.EnvStringVar(&cmdOps.Encrypt, "e") util.EnvStringVar(&cmdOps.LogLevel, "log-level") util.EnvStringVar(&cmdOps.LogPaths, "log-paths") util.EnvIntVar(&cmdOps.HTTPPort, "http-port") @@ -89,6 +91,7 @@ func initCmdOptions() { // 3. Replace options with the corresponding CLI parameter if present. flag.BoolVar(&cmdOps.ShowVer, "v", cmdOps.ShowVer, "show build version and quit") + flag.StringVar(&cmdOps.Encrypt, "e", cmdOps.Encrypt, "encrypt password") flag.StringVar(&cmdOps.LogLevel, "log-level", cmdOps.LogLevel, "one of debug, info, warn, error, dpanic, panic, fatal") flag.StringVar(&cmdOps.LogPaths, "log-paths", cmdOps.LogPaths, "a list of comma-separated log file path. stdout means the console stdout") flag.IntVar(&cmdOps.HTTPPort, "http-port", cmdOps.HTTPPort, "http listen port") @@ -114,6 +117,9 @@ func initCmdOptions() { flag.StringVar(&cmdOps.KafkaGSSAPIPassword, "kafka-gssapi-password", cmdOps.KafkaGSSAPIPassword, "kafka GSSAPI password") flag.Parse() + if err := util.Gsypt.Unmarshal(&cmdOps); err != nil { + util.Logger.Fatal("failed to decrypt password", zap.Error(err)) + } } func getVersion() string { @@ -122,6 +128,10 @@ func getVersion() string { func init() { initCmdOptions() + if cmdOps.Encrypt != "" { + fmt.Println(util.AesEncryptECB(cmdOps.Encrypt)) + os.Exit(0) + } logPaths := strings.Split(cmdOps.LogPaths, ",") util.InitLogger(logPaths) util.SetLogLevel(cmdOps.LogLevel) diff --git a/go.mod b/go.mod index 16fabec4..62238dfb 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/jinzhu/copier v0.4.0 github.com/matoous/go-nanoid/v2 v2.0.0 github.com/nacos-group/nacos-sdk-go v1.1.4 + github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.18.0 github.com/prometheus/common v0.45.0 github.com/shopspring/decimal v1.3.1 @@ -64,7 +65,6 @@ require ( github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect github.com/paulmach/orb v0.11.1 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect diff --git a/task/sinker.go b/task/sinker.go index e2b76db1..c5760d09 100644 --- a/task/sinker.go +++ b/task/sinker.go @@ -285,6 +285,10 @@ func (s *Sinker) applyConfig(newCfg *config.Config) (err error) { util.SetLogTrace(newCfg.LogTrace) util.Rs.SetPoolSize(newCfg.RecordPoolSize) util.Rs.Reset() + if err := util.Gsypt.Unmarshal(&newCfg.Clickhouse); err != nil { + util.Logger.Error("failed to decrypt config password", zap.Error(err)) + return err + } if s.curCfg == nil { // The first time invoking of applyConfig err = s.applyFirstConfig(newCfg) diff --git a/util/aes.go b/util/aes.go new file mode 100644 index 00000000..2bef763a --- /dev/null +++ b/util/aes.go @@ -0,0 +1,71 @@ +package util + +import ( + "crypto/aes" + "encoding/hex" + "strings" +) + +var salt = "656f6974656b" + +// select hex(aes_encrypt("123456", unhex("656f6974656b"))); => E310E892E56801CED9ED98AA177F18E6 +func AesEncryptECB(origData string) string { + if origData == "" { + return origData + } + var encrypted []byte + var o = []byte(origData) + s, _ := hex.DecodeString(salt) + cipher, _ := aes.NewCipher(generateKey(s)) + length := (len(o) + aes.BlockSize) / aes.BlockSize + plain := make([]byte, length*aes.BlockSize) + copy(plain, o) + pad := byte(len(plain) - len(o)) + for i := len(o); i < len(plain); i++ { + plain[i] = pad + } + encrypted = make([]byte, len(plain)) + for bs, be := 0, cipher.BlockSize(); bs <= len(o); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() { + cipher.Encrypt(encrypted[bs:be], plain[bs:be]) + } + return strings.ToUpper(hex.EncodeToString(encrypted)) +} + +// select aes_decrypt(unhex("E310E892E56801CED9ED98AA177F18E6"), unhex("656f6974656b")); => 123456 +func AesDecryptECB(encrypted string) string { + if encrypted == "" { + return encrypted + } + var decrypted []byte + h, _ := hex.DecodeString(encrypted) + s, _ := hex.DecodeString(salt) + cipher, _ := aes.NewCipher(generateKey(s)) + decrypted = make([]byte, len(h)) + + for bs, be := 0, cipher.BlockSize(); bs < len(h); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() { + cipher.Decrypt(decrypted[bs:be], h[bs:be]) + } + + bEnd := searchByteSliceIndex(decrypted, 32) + return string(decrypted[:bEnd]) +} +func generateKey(key []byte) (genKey []byte) { + genKey = make([]byte, 16) + copy(genKey, key) + for i := 16; i < len(key); { + for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 { + genKey[j] ^= key[i] + } + } + return genKey +} + +func searchByteSliceIndex(bSrc []byte, b byte) int { + for i := 0; i < len(bSrc); i++ { + if bSrc[i] < b { + return i + } + } + + return len(bSrc) +} diff --git a/util/common.go b/util/common.go index c21be1d9..9ca5a34e 100644 --- a/util/common.go +++ b/util/common.go @@ -62,6 +62,7 @@ type CmdOptions struct { NacosPassword string NacosDataID string NacosServiceName string // participate in assignment management if not empty + Encrypt string Credentials } diff --git a/util/gosypt.go b/util/gosypt.go new file mode 100644 index 00000000..9d9ef829 --- /dev/null +++ b/util/gosypt.go @@ -0,0 +1,149 @@ +package util + +import ( + "reflect" + "strings" + + "github.com/pkg/errors" +) + +const ( + GosyptPrefixDefault = "ENC(" + GosyptSuffxiDefault = ")" + GosyptAlgorithm = "AESWITHHEXANDBASE64" +) + +/* Golang Simple Encrypt, simulate jasypt */ +type Gosypt struct { + prefix string + suffix string + algorithm string +} + +var Gsypt = &Gosypt{ + prefix: GosyptPrefixDefault, + suffix: GosyptSuffxiDefault, + algorithm: GosyptAlgorithm, +} + +func (gsypt *Gosypt) ensurePassword(password string) string { + if !strings.HasPrefix(password, gsypt.prefix) || !strings.HasSuffix(password, gsypt.suffix) { + return password + } + passwd := strings.TrimSuffix(strings.TrimPrefix(password, gsypt.prefix), gsypt.suffix) + if gsypt.algorithm == GosyptAlgorithm { + return AesDecryptECB(passwd) + } + return password +} + +func (gsypt *Gosypt) SetAttribution(prefix, suffix, algorithm string) { + gsypt.prefix = prefix + gsypt.suffix = suffix + gsypt.algorithm = algorithm +} + +func (gsypt *Gosypt) Unmarshal(v interface{}) error { + rt := reflect.TypeOf(v) + rv := reflect.ValueOf(v) + + if rt.Kind() != reflect.Ptr { + return errors.Wrap(nil, "invalid args, expect ptr") + } + + for rt.Kind() == reflect.Ptr { + rt = rt.Elem() + rv = rv.Elem() + } + + if rt.Kind() == reflect.Struct { + v, err := gsypt.structHandle(rt, rv) + if err != nil { + return err + } + rv.Set(v) + } else if rt.Kind() == reflect.Slice || rt.Kind() == reflect.Array { + v, err := gsypt.sliceHandle(rt, rv) + if err != nil { + return err + } + rv.Set(v) + } else if rt.Kind() == reflect.Map { + v, err := gsypt.mapHandle(rt, rv) + if err != nil { + return err + } + rv.Set(v) + } else if rt.Kind() == reflect.Interface { + v, err := gsypt.interfaceHandle(rt, rv) + if err != nil { + return err + } + rv.Set(v) + } else if rt.Kind() == reflect.String { + rv.Set(gsypt.stringHandle(rv)) + } + + return nil +} + +func (gsypt *Gosypt) sliceHandle(rt reflect.Type, rv reflect.Value) (reflect.Value, error) { + for j := 0; j < rv.Len(); j++ { + if rt.Elem().Kind() == reflect.String { + rv.Index(j).Set(gsypt.stringHandle(rv.Index(j))) + } else { + if err := gsypt.Unmarshal(rv.Index(j).Addr().Interface()); err != nil { + return rv, err + } + } + } + return rv, nil +} + +func (gsypt *Gosypt) mapHandle(rt reflect.Type, rv reflect.Value) (reflect.Value, error) { + for _, k := range rv.MapKeys() { + key := k.Convert(rv.Type().Key()) + if rt.Elem().Kind() == reflect.String { + v := gsypt.ensurePassword(rv.MapIndex(key).String()) + rv.SetMapIndex(key, reflect.ValueOf(v)) + } else { + v := rv.MapIndex(key).Interface() + if err := gsypt.Unmarshal(&v); err != nil { + return rv, err + } + rv.SetMapIndex(key, reflect.ValueOf(v)) + } + } + return rv, nil +} + +func (gsypt *Gosypt) interfaceHandle(rt reflect.Type, rv reflect.Value) (reflect.Value, error) { + //todo + return rv, nil +} + +func (gsypt *Gosypt) structHandle(rt reflect.Type, rv reflect.Value) (reflect.Value, error) { + for i := 0; i < rt.NumField(); i++ { + rtf := rt.Field(i) + rvf := rv.Field(i) + + rtt := rtf.Type + for rtt.Kind() == reflect.Ptr { + rtt = rtt.Elem() + } + + if rtt.Kind() == reflect.String { + rv.Field(i).Set(gsypt.stringHandle(rvf)) + } else { + if err := gsypt.Unmarshal(rvf.Addr().Interface()); err != nil { + return rv, err + } + } + } + return rv, nil +} + +func (gsypt *Gosypt) stringHandle(rv reflect.Value) reflect.Value { + rv.SetString(gsypt.ensurePassword(rv.String())) + return rv +}