package encryptedstring import ( "encoding/hex" "encoding/xml" "fmt" "strings" ) // IPotentiallyUnencrypted describes an interface for a struct which was potentially not encrypted type IPotentiallyUnencrypted interface { // WasUnencrypted determines wheter the value was set from data marked as unencrypted WasUnencrypted() bool } // EncryptedString is a string wrapper which will en-/decrypt the actual value upon (un)marshalling type EncryptedString struct { data string } // New returns a new EncryptedString instance with a specific value func New(value string) EncryptedString { es := EncryptedString{} es.encrypt(value) return es } // Value returns the current value func (es EncryptedString) Value() string { if decrypted, err := es.decrypt(); err == nil { return decrypted } return "" } // String implements the fmt.Stringer interface func (es EncryptedString) String() string { if es.WasUnencrypted() { if data, err := getEncryptionProvider().Encrypt([]byte(es.data)); err == nil { return defaultIdentifier + hex.EncodeToString(data) } } else if strings.HasPrefix(es.data, defaultIdentifier) { return es.data } return "" } // SetValue sets the current value func (es *EncryptedString) SetValue(newValue string) { es.encrypt(newValue) } // WasUnencrypted determines wheter the value was set from data marked as unencrypted func (es EncryptedString) WasUnencrypted() bool { return !strings.HasPrefix(es.data, defaultIdentifier) } // MarshalJSON satisfies the json.Marshaler interface func (es EncryptedString) MarshalJSON() ([]byte, error) { return []byte(fmt.Sprintf("\"%s\"", es.String())), nil } // UnmarshalJSON satisfies the json.Unmarshaler interface func (es *EncryptedString) UnmarshalJSON(data []byte) error { cleanData, ok := unquoteBytes(data) if ok { es.data = string(cleanData) if _, err := es.decrypt(); err == nil { return nil } } return fmt.Errorf("'%s' cannot be unmarshalled from JSON", string(data)) } // MarshalXML satisfies the xml.Marshaler interface func (es EncryptedString) MarshalXML(e *xml.Encoder, start xml.StartElement) error { err := e.EncodeToken(start) if nil != err { return err } stringValue := es.String() err = e.EncodeToken(xml.CharData([]byte(stringValue))) if nil != err { return err } err = e.EncodeToken(xml.EndElement{ Name: start.Name, }) if nil != err { return err } return e.Flush() } // UnmarshalXML satisfies the xml.Unmarshaler interface func (es *EncryptedString) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { var ( token xml.Token err error ) Loop: for { if token, err = d.Token(); err != nil { return err } if token == nil { break } switch se := token.(type) { case xml.StartElement: return fmt.Errorf("bad XML syntax") case xml.CharData: stringData := strings.Trim(string(se), " \t\r\n") if 0 < len(stringData) { es.data = stringData if _, err = es.decrypt(); err != nil { return err } } case xml.EndElement: break Loop } } return nil } // MarshalText satisfies the TextMarshaler interface func (es EncryptedString) MarshalText() (text []byte, err error) { return []byte(es.String()), nil } // UnmarshalText satisfies the TextUnmarshaler interface func (es *EncryptedString) UnmarshalText(text []byte) error { es.data = string(text) if _, err := es.decrypt(); err != nil { return err } return nil } func (es *EncryptedString) encrypt(value string) { payload := []byte(value) additionalBytes := len(payload) % 16 for i := 0; i < additionalBytes; i++ { payload = append(payload, 0) } if encrypted, err := getEncryptionProvider().Encrypt(payload); err == nil { es.data = defaultIdentifier + hex.EncodeToString(encrypted) } } func (es *EncryptedString) decrypt() (decrypted string, err error) { if strings.HasPrefix(es.data, defaultIdentifier) { var payload []byte payload, err = hex.DecodeString(es.data[len(defaultIdentifier):]) if err == nil { if unencrypted, err := getEncryptionProvider().Decrypt(payload); err == nil { for len(unencrypted) > 0 { if unencrypted[len(unencrypted)-1] == 0 { unencrypted = unencrypted[:len(unencrypted)-1] } else { break } } decrypted = string(unencrypted) } } } else { decrypted = es.data } return }