github.com/wfusion/gofusion@v1.1.14/config/crypto.go (about) 1 package config 2 3 import ( 4 "context" 5 "encoding/base64" 6 "hash/crc64" 7 "math/rand" 8 "reflect" 9 10 "github.com/pkg/errors" 11 "github.com/spf13/cast" 12 "github.com/spf13/pflag" 13 14 "github.com/wfusion/gofusion/common/utils" 15 "github.com/wfusion/gofusion/common/utils/cipher" 16 "github.com/wfusion/gofusion/common/utils/compress" 17 "github.com/wfusion/gofusion/common/utils/encode" 18 ) 19 20 const ( 21 cryptoTagKey = "encrypted" 22 ) 23 24 var ( 25 cryptoFlagString string 26 ) 27 28 func init() { 29 pflag.StringVarP(&cryptoFlagString, "crypto-config", "", "", "json string for crypto config") 30 } 31 32 type CryptoConf struct { 33 Config *cryptoConf `yaml:"config" json:"config" toml:"config"` 34 Custom map[string]*cryptoConf `yaml:"custom" json:"custom" toml:"custom"` 35 } 36 37 func (c *CryptoConf) ToOptionMap() (result map[string][]utils.OptionExtender) { 38 result = make(map[string][]utils.OptionExtender) 39 if c.Config != nil { 40 result[""] = c.Config.ToOptions() 41 } 42 for name, cfg := range c.Custom { 43 result[name] = cfg.ToOptions() 44 } 45 return 46 } 47 48 // cryptoConf 49 //nolint: revive // struct tag too long issue 50 type cryptoConf struct { 51 Key []byte `yaml:"-" json:"-" toml:"-"` 52 KeyBase64 string `yaml:"key_base64" json:"key_base64" toml:"key_base64"` 53 ConfuseKey bool `yaml:"confuse_key" json:"confuse_key" toml:"confuse_key"` 54 55 IV []byte `yaml:"-" json:"-" toml:"-"` 56 IVBase64 string `yaml:"iv_base64" json:"iv_base64" toml:"iv_base64"` 57 58 Algorithm cipher.Algorithm `yaml:"-" json:"-" toml:"-"` 59 AlgorithmString string `yaml:"algorithm" json:"algorithm" toml:"algorithm"` 60 61 Mode cipher.Mode `yaml:"-" json:"-" toml:"-"` 62 ModeString string `yaml:"mode" json:"mode" toml:"mode"` 63 64 CompressAlgorithm compress.Algorithm `yaml:"-" json:"-" toml:"-"` 65 CompressAlgorithmString *string `yaml:"compress_algorithm" json:"compress_algorithm" toml:"compress_algorithm"` 66 67 OutputAlgorithm encode.Algorithm `yaml:"-" json:"-" toml:"-"` 68 OutputAlgorithmString *string `yaml:"output_algorithm" json:"output_algorithm" toml:"output_algorithm"` 69 } 70 71 func (c *cryptoConf) ToOptions() (opts []utils.OptionExtender) { 72 if !c.Algorithm.IsValid() { 73 return nil 74 } 75 76 if c.ConfuseKey { 77 c.Key = c.cryptoConfuseKey(c.Key) 78 } 79 80 opts = make([]utils.OptionExtender, 0, 3) 81 opts = append(opts, encode.Cipher(c.Algorithm, c.Mode, c.Key, c.IV)) 82 if c.CompressAlgorithm.IsValid() { 83 opts = append(opts, encode.Compress(c.CompressAlgorithm)) 84 } 85 if c.OutputAlgorithm.IsValid() { 86 opts = append(opts, encode.Encode(c.OutputAlgorithm)) 87 } 88 return 89 } 90 91 func (c *cryptoConf) cryptoConfuseKey(key []byte) (confused []byte) { 92 var ( 93 k1 = make([]byte, len(key)) 94 k2 = make([]byte, len(key)) 95 k3 = make([]byte, len(key)) 96 ) 97 rndSeed := int64(crc64.Checksum(key, crc64.MakeTable(crc64.ISO))) 98 utils.Must(rand.New(rand.NewSource(cipher.RndSeed ^ compress.RndSeed ^ rndSeed)).Read(k1)) 99 utils.Must(rand.New(rand.NewSource(cipher.RndSeed ^ encode.RndSeed ^ rndSeed)).Read(k2)) 100 utils.Must(rand.New(rand.NewSource(compress.RndSeed ^ encode.RndSeed ^ rndSeed)).Read(k3)) 101 102 confused = make([]byte, len(key)) 103 utils.Must(rand.New(rand.NewSource(cipher.RndSeed ^ compress.RndSeed ^ encode.RndSeed)).Read(confused)) 104 for i := 0; i < len(confused); i++ { 105 confused[i] ^= k1[i] ^ k2[i] ^ k3[i] 106 } 107 return 108 } 109 110 func CryptoConstruct(ctx context.Context, c CryptoConf, _ ...utils.OptionExtender) func() { 111 if c.Config != nil { 112 checkCryptoConf("", c.Config) 113 } 114 for name, cfg := range c.Custom { 115 if cfg != nil { 116 checkCryptoConf(name, cfg) 117 } 118 } 119 120 return func() { 121 122 } 123 } 124 125 func checkCryptoConf(name string, c *cryptoConf) { 126 // cipher 127 if c.Algorithm = cipher.ParseAlgorithm(c.AlgorithmString); !c.Algorithm.IsValid() { 128 panic(errors.Errorf("unknown config %s algorithm: %s", name, c.AlgorithmString)) 129 } 130 c.Mode = cipher.ParseMode(c.ModeString) 131 if !c.Mode.IsValid() { 132 panic(errors.Errorf("unknown config %s mode: %s", name, c.ModeString)) 133 } 134 if utils.IsStrBlank(c.KeyBase64) { 135 panic(errors.Errorf("%s not found crypto key", name)) 136 } 137 c.Key = utils.Must(base64.StdEncoding.DecodeString(c.KeyBase64)) 138 if c.Mode.NeedIV() && utils.IsStrBlank(c.IVBase64) { 139 panic(errors.Errorf("%s not found crypto iv", name)) 140 } 141 c.IV = utils.Must(base64.StdEncoding.DecodeString(c.IVBase64)) 142 143 // compress 144 if utils.IsStrPtrNotBlank(c.CompressAlgorithmString) { 145 c.CompressAlgorithm = compress.ParseAlgorithm(*c.CompressAlgorithmString) 146 if !c.CompressAlgorithm.IsValid() { 147 panic(errors.Errorf("unknown config %s compress algorithm: %s", name, *c.CompressAlgorithmString)) 148 } 149 } 150 151 // output 152 if utils.IsStrPtrNotBlank(c.OutputAlgorithmString) { 153 c.OutputAlgorithm = encode.ParseAlgorithm(*c.OutputAlgorithmString) 154 if !c.OutputAlgorithm.IsValid() { 155 panic(errors.Errorf("unknown config %s output algorithm: %s", name, *c.OutputAlgorithmString)) 156 } 157 } 158 } 159 160 type cryptoConfigOption struct { 161 name string 162 } 163 164 func CryptoConfigName(name string) utils.OptionFunc[cryptoConfigOption] { 165 return func(o *cryptoConfigOption) { 166 o.name = name 167 } 168 } 169 170 func CryptoEncryptFunc(opts ...utils.OptionExtender) func(src string) (dst string) { 171 o := utils.ApplyOptions[InitOption](opts...) 172 opt := utils.ApplyOptions[cryptoConfigOption](opts...) 173 optsMap := Use(o.AppName).(*registry).cryptoConfig().ToOptionMap() 174 opts = optsMap[opt.name] 175 return func(src string) (dst string) { 176 return utils.Must(encode.From(src).Encode(opts...).ToString()) 177 } 178 } 179 180 func CryptoDecryptFunc(opts ...utils.OptionExtender) func(src string) (dst string) { 181 o := utils.ApplyOptions[InitOption](opts...) 182 opt := utils.ApplyOptions[cryptoConfigOption](opts...) 183 optsMap := Use(o.AppName).(*registry).cryptoConfig().ToOptionMap() 184 for _, opts := range optsMap { 185 utils.SliceReverse(opts) 186 } 187 opts = optsMap[opt.name] 188 return func(src string) (dst string) { 189 return utils.Must(encode.From(src).Decode(opts...).ToString()) 190 } 191 } 192 193 func CryptoDecryptByTag(data any, opts ...utils.OptionExtender) { 194 o := utils.ApplyOptions[InitOption](opts...) 195 optsMap := Use(o.AppName).(*registry).cryptoConfig().ToOptionMap() 196 for _, opts := range optsMap { 197 utils.SliceReverse(opts) 198 } 199 200 supportedFields := utils.NewSet(reflect.Struct, reflect.Array, reflect.Slice, reflect.Map) 201 utils.TraverseValue(data, false, func(field reflect.StructField, value reflect.Value) (end, stepIn bool) { 202 if !value.IsValid() || !value.CanInterface() || !value.CanSet() { 203 return 204 } 205 206 vk := value.Kind() 207 stepIn = supportedFields.Contains(vk) || 208 (vk == reflect.Ptr && value.Elem().IsValid() && value.Elem().Kind() == reflect.Struct) 209 210 configName, ok := field.Tag.Lookup(cryptoTagKey) 211 if !ok { 212 return 213 } 214 opts, ok := optsMap[configName] 215 if !ok { 216 return 217 } 218 src := cast.ToString(value.Interface()) 219 if utils.IsStrBlank(src) { 220 return 221 } 222 223 dst := utils.Must(encode.From(src).Decode(opts...).ToString()) 224 value.SetString(dst) 225 return 226 }) 227 }