/go-mask

go-mask is a simple, customizable Go library for masking sensitive information.

Primary LanguageGoMIT LicenseMIT

go-mask

Documentation Go Tests codecov Go Report Card

go-mask is a simple, customizable Go library for masking sensitive information.

Features

  • You can mask any field of a structure using the struct's tags. (example → How to use)
  • It is also possible to mask using field names or map keys without using tags. (example → field name / map key)
  • Users can make use of their own custom-created masking functions. (example → custom mask function)
  • The masked object is a copied object, so it does not overwrite the original data before masking(although it's not perfect...)
    • Private fields are not copied
    • It is moderately fast in performing deep copies.

Installation

go get github.com/showa-93/go-mask

Mask Tags

go-mask does not provide many tags by default.
This is because it is believed that users should create their own necessary masking functions.

tag type description
mask:"filled" string Masks the string with the same number of masking characters.
mask:"filledXXX" string XXX = number of masking characters. Masks with a fixed number of characters. mask:"filled3"***
mask:"fixed" string Masks with a fixed number of characters. *******
mask:"hash" string Masks the string by converting it to a value using sha1.
mask:"randomXXX" int / float64 XXX = numeric value. Masks with a random value in the range of 0 to the XXX.
mask:"zero" any It can be applied to any type, masking it with the zero value of that type.

How to use

string

package main

import (
	"fmt"

	mask "github.com/showa-93/go-mask"
)

func main() {
	{
		maskValue, _ := mask.String(mask.MaskTypeFixed, "Hello World!!")
		fmt.Println(maskValue)
	}
	{
		value := struct {
			Title string   `mask:"filled"`
			Casts []string `mask:"fixed"`
		}{
			Title: "Catch Me If You Can",
			Casts: []string{
				"Thomas Jeffrey \"Tom\" Hanks",
				"Leonardo Wilhelm DiCaprio",
			},
		}
		masker := mask.NewMasker()
		masker.SetMaskChar("-")
		masker.RegisterMaskStringFunc(mask.MaskTypeFilled, masker.MaskFilledString)
		masker.RegisterMaskStringFunc(mask.MaskTypeFixed, masker.MaskFixedString)

		maskValue, _ := mask.Mask(value)
		maskValue2, _ := masker.Mask(value)

		fmt.Printf("%+v\n", value)
		fmt.Printf("%+v\n", maskValue)
		fmt.Printf("%+v\n", maskValue2)
	}
}
********
{Title:Catch Me If You Can Casts:[Thomas Jeffrey "Tom" Hanks Leonardo Wilhelm DiCaprio]}
{Title:******************* Casts:[******** ********]}
{Title:------------------- Casts:[-------- --------]}

int / float64 / uint

package main

import (
	"fmt"

	mask "github.com/showa-93/go-mask"
)

func main() {
	{
		maskValue, _ := mask.Int("random100", 10)
		fmt.Println(maskValue)
	}
	{
		maskValue, _ := mask.Float64("random100.2", 12.3)
		fmt.Println(maskValue)
	}

	{
		value := struct {
			Price   int     `mask:"random1000"`
			Percent float64 `mask:"random1.3"`
		}{
			Price:   300,
			Percent: 0.80,
		}
		masker := mask.NewMasker()
		masker.RegisterMaskIntFunc(mask.MaskTypeRandom, masker.MaskRandomInt)
		masker.RegisterMaskFloat64Func(mask.MaskTypeRandom, masker.MaskRandomFloat64)

		maskValue, _ := mask.Mask(value)
		maskValue2, _ := masker.Mask(value)

		fmt.Printf("%+v\n", maskValue)
		fmt.Printf("%+v\n", maskValue2)
	}
}
29
50.45
{Price:917 Percent:0.183}
{Price:733 Percent:0.241}

slice / array

package main

import (
	"fmt"

	"github.com/showa-93/go-mask"
)

type Value struct {
	Name string `mask:"filled"`
	Type int    `mask:"random10"`
}

func main() {
	values := []Value{
		{
			Name: "Thomas Jeffrey \"Tom\" Hanks",
			Type: 1,
		},
		{
			Name: "Leonardo Wilhelm DiCaprio",
			Type: 2,
		},
	}
	masker := mask.NewMasker()
	masker.SetMaskChar("+")
	masker.RegisterMaskStringFunc(mask.MaskTypeFilled, masker.MaskFilledString)
	masker.RegisterMaskIntFunc(mask.MaskTypeRandom, masker.MaskRandomInt)

	maskValues, _ := mask.Mask(values)
	maskValues2, _ := masker.Mask(values)

	fmt.Printf("%+v\n", values)
	fmt.Printf("%+v\n", maskValues)
	fmt.Printf("%+v\n", maskValues2)
}
[{Name:Thomas Jeffrey "Tom" Hanks Type:1} {Name:Leonardo Wilhelm DiCaprio Type:2}]
[{Name:************************** Type:8} {Name:************************* Type:9}]
[{Name:++++++++++++++++++++++++++ Type:4} {Name:+++++++++++++++++++++++++ Type:8}]

map

package main

import (
	"fmt"

	"github.com/showa-93/go-mask"
)

type Value struct {
	Name string `mask:"filled"`
	Type int    `mask:"random10"`
}

func main() {
	values := map[string]Value{
		"one": {
			Name: "Thomas Jeffrey \"Tom\" Hanks",
			Type: 1,
		},
		"two": {
			Name: "Leonardo Wilhelm DiCaprio",
			Type: 2,
		},
	}
	masker := mask.NewMasker()
	masker.SetMaskChar("")
	masker.RegisterMaskStringFunc(mask.MaskTypeFilled, masker.MaskFilledString)
	masker.RegisterMaskIntFunc(mask.MaskTypeRandom, masker.MaskRandomInt)

	maskValues, _ := mask.Mask(values)
	maskValues2, _ := masker.Mask(values)

	fmt.Printf("%+v\n", values)
	fmt.Printf("%+v\n", maskValues)
	fmt.Printf("%+v\n", maskValues2)
}
map[one:{Name:Thomas Jeffrey "Tom" Hanks Type:1} two:{Name:Leonardo Wilhelm DiCaprio Type:2}]
map[one:{Name:************************** Type:8} two:{Name:************************* Type:6}]
map[one:{Name: Type:6} two:{Name: Type:2}]

JSON

package main

import (
	"encoding/json"
	"fmt"

	mask "github.com/showa-93/go-mask"
)

func main() {
	masker := mask.NewMasker()
	masker.RegisterMaskStringFunc(mask.MaskTypeFilled, masker.MaskFilledString)
	masker.RegisterMaskField("S", "filled4")

	v := `{
		"S": "Hello world",
		"I": 1,
		"O": {
			"S": "Second",
			"S2": "豚汁"
		}
	}`
	var target any
	json.Unmarshal([]byte(v), &target)
	masked, _ := masker.Mask(target)
	mv, _ := json.Marshal(masked)
	fmt.Println(string(mv))
}
{"I":1,"O":{"S":"****","S2":"豚汁"},"S":"****"}

nested struct

package main

import (
	"fmt"

	"github.com/showa-93/go-mask"
)

type Node struct {
	Value string `mask:"filled"`
	Next  *Node
}

func main() {
	node := Node{
		Value: "first",
		Next: &Node{
			Value: "second",
			Next: &Node{
				Value: "third",
			},
		},
	}

	masker := mask.NewMasker()
	masker.SetMaskChar("🤗")
	masker.RegisterMaskStringFunc(mask.MaskTypeFilled, masker.MaskFilledString)

	maskNode, _ := mask.Mask(node)
	maskNode2, _ := masker.Mask(node)

	fmt.Printf("first=%+v,second=%+v,third=%+v\n", node, node.Next, node.Next.Next)
	fmt.Printf("first=%+v,second=%+v,third=%+v\n", maskNode, maskNode.Next, maskNode.Next.Next)
	fmt.Printf("first=%+v,second=%+v,third=%+v\n", maskNode2.(Node), maskNode2.(Node).Next, maskNode2.(Node).Next.Next)
}
first={Value:first Next:0xc000010048},second=&{Value:second Next:0xc000010060},third=&{Value:third Next:<nil>}
first={Value:***** Next:0xc0000100a8},second=&{Value:****** Next:0xc0000100c0},third=&{Value:***** Next:<nil>}
first={Value:🤗🤗🤗🤗🤗 Next:0xc000010120},second=&{Value:🤗🤗🤗🤗🤗🤗 Next:0xc000010138},third=&{Value:🤗🤗🤗🤗🤗 Next:<nil>}

field name / map key

package main

import (
	"fmt"

	mask "github.com/showa-93/go-mask"
)

type User struct {
	ID      string // no tag
	Name    string
	Gender  string
	Age     int
	ExtData map[string]string
}

func main() {
	masker := mask.NewMasker()

	masker.RegisterMaskStringFunc(mask.MaskTypeFilled, masker.MaskFilledString)
	masker.RegisterMaskIntFunc(mask.MaskTypeRandom, masker.MaskRandomInt)

	// registered field name
	masker.RegisterMaskField("Name", "filled4")
	masker.RegisterMaskField("Animal", "filled6")
	masker.RegisterMaskField("Age", mask.MaskTypeRandom+"100")

	u := User{
		ID:     "1",
		Name:   "タマ",
		Gender: "Male",
		Age:    4,
		ExtData: map[string]string{
			"Animal": "Cat",
		},
	}
	maskedUser, _ := masker.Mask(u)
	fmt.Printf("%+v", maskedUser)
}
{ID:1 Name:**** Gender:Male Age:10 ExtData:map[Animal:******]}

custom mask function

package main

import (
	"fmt"
	"regexp"
	"strings"

	mask "github.com/showa-93/go-mask"
)

func init() {
	maskTypeRegExp := "regexp"
	mask.RegisterMaskStringFunc(maskTypeRegExp, MaskRegExp)
}

// MaskRegExp is sample to add a custom mask function
func MaskRegExp(arg, value string) (string, error) {
	var (
		reg *regexp.Regexp
		err error
	)
	reg, err = regexp.Compile(arg)
	if err != nil {
		return "", err
	}

	indexes := reg.FindStringSubmatchIndex(value)
	if len(indexes) >= 4 && indexes[2] >= 0 && indexes[3] >= 0 {
		var sb strings.Builder
		sb.WriteString(value[:indexes[2]])
		sb.WriteString(mask.MaskChar())
		sb.WriteString(value[indexes[3]:])
		return sb.String(), nil
	}

	return value, nil
}

func main() {
	mask.SetMaskChar("cat")
	type Hachiware struct {
		Message string `mask:"regexp(gopher)."`
	}

	input := Hachiware{Message: "I love gopher!"}
	got, _ := mask.Mask(input)
	fmt.Printf("%+v\n", input)
	fmt.Printf("%+v\n", got)

	// The Masker initialized with NewMasker does not have
	// any custom masking functions registered, so no masking will occur
	masker := mask.NewMasker()
	got2, _ := masker.Mask(input)
	fmt.Printf("%+v\n", got2)
}
{Message:I love gopher!}
{Message:I love cat!}
{Message:I love gopher!}