I've got a json file of AES encrypted secrets. The structure is:
{
"username": "asdf123ASLdf3",
"password": "elisjdvo4etQW"
}
And a struct to hold these values
type Secrets struct {
Username string `json:"username"`
Password string `json:"password"`
}
It's easy to load the encrypted json values into the struct, but what I really want is a struct with the unencrypted values.
So, for each value, I'd like to run it though a function:
aesDecrypt(key string, value string) string
I'm happy to have this done on the first load, or to move everything over into a new struct.
I would like to avoid repeating the json keys or the field names.
What's the best way to do this?
(Also open to other ways to manage encrypted secrets in Go)
One option is to define a custom JSON Unmarshaler. Another is, as you mention, copy it to another struct.
The key insight is knowing that you can override json.Unmarshal
's behaviour by implementing the Unmarshaler interface. In our case, that means defining a function func (ss *Secrets) UnmarshalJSON(bb []byte) error
that will do the AES Decryption when you try to unmarshal any JSON to a Secrets
.
package main
import "fmt"
import "encoding/json"
type Secrets struct {
Username string `json:"username"`
Password string `json:"password"`
}
func main() {
jj := []byte(`{
"username": "asdf123ASLdf3",
"password": "elisjdvo4etQW"
}`)
var ss Secrets
json.Unmarshal(jj, &ss)
fmt.Println(ss)
}
func aesDecrypt(key, value string) string {
return fmt.Sprintf("'%s' decrypted with key '%s'", value, key)
}
func (ss *Secrets) UnmarshalJSON(bb []byte) error {
var objmap map[string]*string
err := json.Unmarshal(bb, &objmap)
ss.Username = aesDecrypt("my key", *objmap["password"])
ss.Password = aesDecrypt("my key", *objmap["username"])
return err
}
This outputs a Secrets
struct:
{'elisjdvo4etQW' decrypted with key 'my key'
'asdf123ASLdf3' decrypted with key 'my key'}
You could simply make a new Secrets
struct every time you need to decrypt the JSON. This could be tedious if you do it alot, or if you have no need for the intermediate state.
package main
import "fmt"
import "encoding/json"
type Secrets struct {
Username string `json:"username"`
Password string `json:"password"`
}
func main() {
jj := []byte(`{
"username": "asdf123ASLdf3",
"password": "elisjdvo4etQW"
}`)
var ss Secrets
json.Unmarshal(jj, &ss)
decoded := Secrets{
aesDecrypt(ss.Username, "my key"),
aesDecrypt(ss.Password, "my key")}
fmt.Println(decoded)
}
func aesDecrypt(key, value string) string {
return fmt.Sprintf("'%s' decrypted with key '%s'", value, key)
}
Check it out at Go Playground.
This has the same output as above:
{'elisjdvo4etQW' decrypted with key 'my key'
'asdf123ASLdf3' decrypted with key 'my key'}
Obviously, you would use a different version of aesDecrypt
, mine's just a dummy. And, as always, you should actually be checking the returned errors in your own code.