Skip to content

Instantly share code, notes, and snippets.

@chmouel
Last active November 1, 2023 18:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chmouel/a43cb6ba018e16b6e2935da9e43e8152 to your computer and use it in GitHub Desktop.
Save chmouel/a43cb6ba018e16b6e2935da9e43e8152 to your computer and use it in GitHub Desktop.
hide-password-multi-writer.go

This will hide passwords implemented as a writer.

the example in main will create two writer one to stdout and one to a file, and hide a list of passwords

It tries to be a bit smart to print the characters as soon as possible

% cat test.txt
hello this is a password that string should be hidden now!!
I hope the string anotherpassword2 will be kept secret
 
and this is my secret word it's called: helloworld
% cat test.txt|go run hidepasswordwriter.go
hello this is a XXXXXX that string should be hidden now!!
I hope the string XXXXXX will be kept secret

and this is my secret word it's called: XXXXXX
module github.com/chmouel/hidepasswordwriter
go 1.21.3
package main
import (
"fmt"
"io"
"os"
"sort"
"strings"
)
// ByLength implements the sort.Interface for []string based on the string length.
type ByLength []string
func (s ByLength) Len() int {
return len(s)
}
func (s ByLength) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s ByLength) Less(i, j int) bool {
return len(s[i]) > len(s[j]) // Sort in descending order (longest first)
}
type HidePasswordsWriter struct {
buffer []byte
writer io.Writer
passwords []string
longest int
hideString string
}
func NewHidePasswordsWriter(writer io.Writer, passwords []string, hideString string) *HidePasswordsWriter {
c := &HidePasswordsWriter{
passwords: passwords,
hideString: hideString,
writer: writer,
}
c.createBuffer()
return c
}
func (cw *HidePasswordsWriter) Write(p []byte) (n int, err error) {
for _, char := range p {
cw.buffer = append(cw.buffer, []byte(string(char))...)
pq := false
for _, v := range cw.passwords {
if string(cw.buffer) == v {
cw.buffer = []byte(cw.hideString)
pq = true
break
}
if strings.HasPrefix(v, string(cw.buffer)) {
pq = false
break
}
pq = true
}
if len(cw.buffer) >= cw.longest {
pq = true
}
if pq {
cw.Flush()
}
}
return len(p), nil
}
func keepLongestOfSamePrefix(allstrings []string) []string {
ret := []string{}
sort.Strings(allstrings)
for k, v := range allstrings {
if k < len(allstrings)-1 && strings.HasPrefix(allstrings[k+1], v) {
continue
}
ret = append(ret, v)
}
return ret
}
func (cw *HidePasswordsWriter) createBuffer() {
cw.longest = 0
cw.passwords = keepLongestOfSamePrefix(cw.passwords)
cw.longest = len(cw.passwords[0])
cw.buffer = make([]byte, 0, cw.longest)
}
func (cw *HidePasswordsWriter) Flush() {
if len(cw.buffer) > 0 {
cw.writer.Write(cw.buffer)
cw.buffer = cw.buffer[:0] // Clear the buffer
}
}
func test() {
passwords := []string{"foofofofofoofofofoaofoofaofao", "password", "password2", "helloworld", "anotherpassword2"}
// stdout writer
stdoutWriter := NewHidePasswordsWriter(os.Stdout, passwords, "XXXXXX")
// output to a file writer
outputFile, err := os.Create("/tmp/output.txt")
defer outputFile.Close()
fileWriter := NewHidePasswordsWriter(outputFile, passwords, "XXXXXX")
// write stdin to both
multiWriter := io.MultiWriter(fileWriter, stdoutWriter)
_, err = io.Copy(multiWriter, os.Stdin)
if err != nil {
fmt.Println(err)
}
// flush the rest if needed
fileWriter.Flush()
stdoutWriter.Flush()
}
func main() {
test()
}
hello this is a password that string should be hidden now!!
I hope the string anotherpassword2 will be kept secret
this is hidden as well password2
pass me the wine and don't hide the word pass
and this is my secret word it's called: helloworld
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment