diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8e805f3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+releases
+a
+t
+gowipe
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d25b893
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,89 @@
+# Copyright © 2023 Thomas von Dein
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+
+#
+# no need to modify anything below
+tool = gowipe
+VERSION = $(shell grep VERSION main.go | head -1 | cut -d '"' -f2)
+archs = darwin freebsd linux windows
+PREFIX = /usr/local
+UID = root
+GID = 0
+HAVE_POD :=
+
+all: $(tool) buildlocal
+
+buildlocal:
+ CGO_LDFLAGS='-static' go build -tags osusergo,netgo -ldflags "-extldflags=-static" -o $(tool)
+
+install: buildlocal
+ install -d -o $(UID) -g $(GID) $(PREFIX)/bin
+ install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
+ install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
+ install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/
+
+clean:
+ rm -rf $(tool) coverage.out
+
+test:
+ go test -v ./...
+
+singletest:
+ @echo "Call like this: ''make singletest TEST=TestPrepareColumns"
+ go test -run $(TEST)
+
+cover-report:
+ go test ./... -cover -coverprofile=coverage.out
+ go tool cover -html=coverage.out
+
+goupdate:
+ go get -t -u=patch ./...
+
+buildall:
+ ./mkrel.sh $(tool) $(VERSION)
+
+release: buildall
+ gh release create v$(VERSION) --generate-notes releases/*
+
+show-versions: buildlocal
+ @echo "### gowipe version:"
+ @./gowipe -v
+
+ @echo
+ @echo "### go module versions:"
+ @go list -m all
+
+ @echo
+ @echo "### go version used for building:"
+ @grep -m 1 go go.mod
+
+
+dir:
+ rm -rf a
+ mkdir -p a/b/c
+ date > a/filea
+ date > a/b/fileb
+ date > a/b/c/filec
+
+bench: all
+ dd if=/dev/zero of=t/fileZ bs=1024 count=200000
+ dd if=/dev/zero of=t/fileM bs=1024 count=200000
+ dd if=/dev/zero of=t/fileS bs=1024 count=200000
+ dd if=/dev/zero of=t/fileE bs=1024 count=200000
+ /usr/bin/time -f "%S" ./gowipe -Z t/fileZ
+ /usr/bin/time -f "%S" ./gowipe -M t/fileM
+ /usr/bin/time -f "%S" ./gowipe -S t/fileS
+ /usr/bin/time -f "%S" ./gowipe -E t/fileE
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c72f28f
Binary files /dev/null and b/README.md differ
diff --git a/crypto.go b/crypto.go
new file mode 100644
index 0000000..a977af5
--- /dev/null
+++ b/crypto.go
@@ -0,0 +1,244 @@
+/*
+Copyright © 2022 Thomas von Dein
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+package main
+
+import (
+ "crypto/cipher"
+ cryptorand "crypto/rand"
+ "errors"
+ "fmt"
+ "io"
+ "math/big"
+ mathrand "math/rand"
+ "os"
+ "time"
+ "unsafe"
+
+ "golang.org/x/crypto/argon2"
+ chapo "golang.org/x/crypto/chacha20poly1305"
+)
+
+const (
+ SaltSize = 32 // in bytes
+ NonceSize = 24 // in bytes. taken from aead.NonceSize()
+ KeySize = uint32(32) // KeySize is 32 bytes (256 bits).
+ KeyTime = uint32(5)
+ KeyMemory = uint32(1024 * 64) // KeyMemory in KiB. here, 64 MiB.
+ KeyThreads = uint8(4)
+ chunkSize = 1024 * 32 // chunkSize in bytes. here, 32 KiB.
+
+ letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-"
+
+ letterIdxBits = 6 // 6 bits to represent a letter index
+ letterIdxMask = 1<= 0; {
+ if remain == 0 {
+ cache, remain = src.Int63(), letterIdxMax
+ }
+ if idx := int(cache & letterIdxMask); idx < len(letters) {
+ b[i] = letters[idx]
+ i--
+ }
+ cache >>= letterIdxBits
+ remain--
+ }
+
+ return *(*string)(unsafe.Pointer(&b))
+}
+
+func GetRandomKey() ([]byte, error) {
+ password, err := GenerateSecureRandomBytes(int(chapo.KeySize))
+ if err != nil {
+ return nil, err
+ }
+
+ salt, err := GenerateSecureRandomBytes(chapo.NonceSize)
+ if err != nil {
+ return nil, err
+ }
+
+ key := argon2.IDKey(password, salt, KeyTime, KeyMemory, KeyThreads, chapo.KeySize)
+
+ return key, nil
+}
+
+func Encrypt(c *Conf, filename string) error {
+ info, err := os.Stat(filename)
+ if err != nil {
+ return err
+ }
+
+ size := info.Size()
+
+ outfile, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0666)
+ if err != nil {
+ return err
+ }
+ defer outfile.Close()
+
+ key, err := GetRandomKey()
+ if err != nil {
+ return err
+ }
+
+ aead, err := chapo.NewX(key)
+ if err != nil {
+ return err
+ }
+
+ for i := 0; i < c.count; i++ {
+ for {
+ EncryptChunk(aead, outfile, size)
+ size = size - chunkSize
+
+ if size <= 0 {
+ break
+ }
+ }
+ }
+
+ return nil
+}
+
+func EncryptChunk(aead cipher.AEAD, file *os.File, size int64) error {
+ chunk := make([]byte, size)
+ nonce, err := GenerateSecureRandomBytes(int(chapo.NonceSize))
+ if err != nil {
+ return err
+ }
+
+ cipher := aead.Seal(nil, nonce, chunk, nil)
+
+ n, err := file.Write(cipher[:size])
+ if err != nil {
+ return err
+ }
+
+ if int64(n) != size {
+ return errors.New("invalid number of bytes written")
+ }
+
+ return nil
+}
+
+/*
+func Encrypt(c *Conf, filename string) error {
+ salt, err := GetRand(KeySize)
+ if err != nil {
+ return err
+ }
+
+ salt1, err := GetRand(KeySize)
+ if err != nil {
+ return err
+ }
+
+ outfile, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0666)
+ if err != nil {
+ return err
+ }
+ defer outfile.Close()
+
+ key := argon2.IDKey(salt1, salt, KeyTime, KeyMemory, KeyThreads, KeySize)
+
+ aead, err := chacha20poly1305.NewX(key)
+ if err != nil {
+ return err
+ }
+
+ buf := make([]byte, chunkSize)
+ ad_counter := 0 // associated data is a counter
+
+ for {
+ if n > 0 {
+ // Select a random nonce, and leave capacity for the ciphertext.
+ nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+n+aead.Overhead())
+ if m, err := cryptorand.Read(nonce); err != nil || m != aead.NonceSize() {
+ return err
+ }
+
+ msg := buf[:n]
+ // Encrypt the message and append the ciphertext to the nonce.
+ encryptedMsg := aead.Seal(nonce, nonce, msg, []byte(string(ad_counter)))
+ outfile.Write(encryptedMsg)
+ ad_counter += 1
+ }
+
+ if err == io.EOF {
+ break
+ }
+
+ if err != nil {
+ log.Println("Error when reading input file chunk :", err)
+ panic(err)
+ }
+ }
+}
+*/
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..02fb021
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,11 @@
+module gowipe
+
+go 1.20
+
+require (
+ github.com/JojiiOfficial/shred v1.2.1 // indirect
+ github.com/lu4p/shred v0.0.0-20201211173428-0347b645d724 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ golang.org/x/crypto v0.15.0 // indirect
+ golang.org/x/sys v0.14.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..049ed97
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,10 @@
+github.com/JojiiOfficial/shred v1.2.1 h1:658CFVTqcAkYVg815vW+guYnyJTLOIoS15tMyPTYhNo=
+github.com/JojiiOfficial/shred v1.2.1/go.mod h1:/OAxd6eYOhrXb3KW+2wmDog2BiFlUld8oJEKa+xblxU=
+github.com/lu4p/shred v0.0.0-20201211173428-0347b645d724 h1:nLJRUakdvy2j7JsefrtAUqGRbfJUKKqRu/3BCRA9mIQ=
+github.com/lu4p/shred v0.0.0-20201211173428-0347b645d724/go.mod h1:6b1kEKx7IPBboPSTnoJZE5sbSDjcNkHHO3Hii8TU8XY=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
+golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
+golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
+golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..b9ed8fe
--- /dev/null
+++ b/main.go
@@ -0,0 +1,238 @@
+/*
+Copyright © 2022 Thomas von Dein
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/JojiiOfficial/shred"
+ flag "github.com/spf13/pflag"
+)
+
+const VERSION string = "0.0.2"
+const Usage string = `This is gowipe - destruct files in a non-recoverable way.
+
+Usage: gowipe [-rcvz] ...
+
+Options:
+-r --recursive Delete recursively
+-c --count Overwrite files times
+-m --mode Use for overwriting (or use -E, -S, -M, -Z)
+-n --nodelete Do not delete files after overwriting
+-N --norename Do not rename the files
+-v --verbose Verbose output
+-V --version Show program version
+-h --help Show usage
+
+Available modes:
+zero Overwrite with zeroes (-Z)
+math Overwrite with math random bytes (-M)
+secure Overwrite with secure random bytes (default) (-S)
+encrypt Overwrite with ChaCha2Poly1305 encryption (most secure) (-E)`
+
+type Conf struct {
+ mode string
+ count int
+ recurse bool
+ nodelete bool
+ norename bool
+ verbose bool
+}
+
+func main() {
+ showversion := false
+ showhelp := false
+ optzero := false
+ optsecure := false
+ optmath := false
+ optencrypt := false
+
+ c := Conf{
+ verbose: false,
+ mode: `secure`,
+ count: 30,
+ recurse: false,
+ nodelete: false,
+ norename: false,
+ }
+
+ flag.BoolVarP(&showversion, "version", "V", showversion, "show version")
+ flag.BoolVarP(&showhelp, "help", "h", showversion, "show help")
+ flag.BoolVarP(&c.verbose, "verbose", "v", c.verbose, "verbose")
+
+ flag.StringVarP(&c.mode, "mode", "m", c.mode, "overwrite mode")
+
+ flag.BoolVarP(&optzero, "zero", "Z", optzero, "zero mode")
+ flag.BoolVarP(&optsecure, "secure", "S", optsecure, "secure mode")
+ flag.BoolVarP(&optmath, "math", "M", optmath, "math mode")
+ flag.BoolVarP(&optmath, "encrypt", "E", optmath, "encrypt mode")
+
+ flag.BoolVarP(&c.recurse, "recursive", "r", c.recurse, "recursive")
+ flag.BoolVarP(&c.nodelete, "nodelete", "n", c.nodelete, "don't delete")
+ flag.BoolVarP(&c.norename, "norename", "N", c.norename, "don't rename")
+ flag.IntVarP(&c.count, "count", "c", c.count, "overwrite count")
+
+ flag.Parse()
+
+ if showversion {
+ fmt.Printf("This is gowipe version %s\n", VERSION)
+ os.Exit(0)
+ }
+
+ if showhelp {
+ fmt.Println(Usage)
+ os.Exit(0)
+ }
+
+ if len(flag.Args()) == 0 {
+ fmt.Println(Usage)
+ os.Exit(0)
+ }
+
+ var option shred.WriteOptions
+
+ if optzero {
+ option = shred.WriteZeros
+ }
+ if optmath {
+ option = shred.WriteRand
+ }
+ if optsecure {
+ option = shred.WriteRandSecure
+ }
+ if optencrypt {
+ c.mode = "encrypt"
+ }
+
+ switch c.mode {
+ case `secure`:
+ option = shred.WriteRandSecure
+ case `math`:
+ option = shred.WriteRand
+ case `zero`:
+ option = shred.WriteZeros
+ case `encrypt`:
+ optencrypt = true
+ default:
+ option = shred.WriteRandSecure
+ }
+
+ shredder := shred.Shredder{}
+ shredconf := shred.NewShredderConf(&shredder, option, c.count, !c.nodelete)
+
+ for _, file := range flag.Args() {
+ Wipe(file, &c, shredconf)
+ }
+}
+
+func Wipe(file string, c *Conf, wiper *shred.ShredderConf) {
+ if info, err := os.Stat(file); err == nil {
+
+ if info.IsDir() {
+ if !c.recurse {
+ fmt.Printf("-r not set, ignoring directory %s\n", file)
+ return
+ }
+
+ files, err := ioutil.ReadDir(file)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ for _, entry := range files {
+ Wipe(filepath.Join(file, entry.Name()), c, wiper)
+ }
+
+ if !c.nodelete {
+ err = os.Remove(Rename(file, c))
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+ } else {
+ if c.mode == "encrypt" {
+ err := Encrypt(c, file)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ Rename(file, c)
+ } else {
+ wiper.ShredFile(Rename(file, c))
+ }
+ }
+
+ if c.verbose {
+ fmt.Printf("Wiped %d times: %s\n", c.count, file)
+ }
+ } else {
+ if os.IsNotExist(err) {
+ fmt.Printf("No such file or directory: %s\n", file)
+ } else {
+ fmt.Println(err)
+ }
+
+ os.Exit(1)
+ }
+}
+
+func Rename(file string, c *Conf) string {
+ var newname string
+ dir := filepath.Dir(file)
+ base := filepath.Base(file)
+ length := len(base)
+
+ for i := 0; i < c.count; i++ {
+ for {
+ switch c.mode {
+ case `secure`:
+ new, err := GenerateSecureRandomString(length)
+ if err != nil {
+ log.Fatal(err)
+ }
+ newname = new
+ case `math`:
+ newname = GenerateMathRandomString(length)
+ case `zero`:
+ newname = strings.Repeat("0", length)
+ }
+ if newname != base {
+ break
+ }
+ }
+
+ /*
+ if c.verbose {
+ fmt.Printf("renaming %s/%s => %s/%s\n", dir, base, dir, newname)
+ }
+ */
+
+ err := os.Rename(filepath.Join(dir, base), filepath.Join(dir, newname))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ base = newname
+ }
+
+ return filepath.Join(dir, newname)
+}
diff --git a/mkrel.sh b/mkrel.sh
new file mode 100755
index 0000000..894deaf
--- /dev/null
+++ b/mkrel.sh
@@ -0,0 +1,65 @@
+#!/bin/bash
+
+# Copyright © 2022 Thomas von Dein
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+
+# get list with: go tool dist list
+DIST="darwin/amd64
+freebsd/amd64
+linux/amd64
+netbsd/amd64
+openbsd/amd64
+windows/amd64"
+
+tool="$1"
+version="$2"
+
+if test -z "$version"; then
+ echo "Usage: $0 "
+ exit 1
+fi
+
+rm -rf releases
+mkdir -p releases
+
+
+for D in $DIST; do
+ os=${D/\/*/}
+ arch=${D/*\//}
+ binfile="releases/${tool}-${os}-${arch}-${version}"
+ tardir="${tool}-${os}-${arch}-${version}"
+ tarfile="releases/${tool}-${os}-${arch}-${version}.tar.gz"
+ set -x
+ GOOS=${os} GOARCH=${arch} go build -o ${binfile} -ldflags "-X 'github.com/tlinden/tablizer/cfg.VERSION=${version}'"
+ mkdir -p ${tardir}
+ cp ${binfile} README.md LICENSE ${tardir}/
+ echo 'tool = tablizer
+PREFIX = /usr/local
+UID = root
+GID = 0
+
+install:
+ install -d -o $(UID) -g $(GID) $(PREFIX)/bin
+ install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
+ install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
+ install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/' > ${tardir}/Makefile
+ tar cpzf ${tarfile} ${tardir}
+ sha256sum ${binfile} | cut -d' ' -f1 > ${binfile}.sha256
+ sha256sum ${tarfile} | cut -d' ' -f1 > ${tarfile}.sha256
+ rm -rf ${tardir}
+ set +x
+done
+