10 Commits

Author SHA1 Message Date
b914fdfcdf simplified verbose output and enhanced readme. 2025-01-19 16:23:16 +01:00
f2116f39ef fix name typo 2025-01-18 11:36:56 +01:00
5907c5b2be add badges 2025-01-18 11:35:12 +01:00
8dbdebee46 fix ci builder 2025-01-18 11:33:02 +01:00
daabdc5c9b update dependencies 2025-01-18 11:29:45 +01:00
f4b1ba5863 fixes:
- fix encryption, used the wrong nonce size
- encrypted files were not deleted
- fixed recursion
- fixed linter warnings
2025-01-18 11:27:57 +01:00
eaca5ad181 fix typo 2025-01-18 11:03:18 +01:00
5ca1be594b mv to correct dir 2025-01-18 11:02:26 +01:00
aa03ba5281 add ci pipelines 2025-01-18 11:00:15 +01:00
74c801b914 typo 2023-11-25 14:53:07 +01:00
8 changed files with 181 additions and 86 deletions

35
.github/workflows/ci.yaml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: build-and-test-gowipe
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
version: ['1.21','1.22']
os: [ubuntu-latest, macos-latest, windows-latest]
name: Build
runs-on: ${{ matrix.os }}
steps:
- name: Set up Go ${{ matrix.version }}
uses: actions/setup-go@v5
with:
go-version: '${{ matrix.version }}'
id: go
- name: checkout
uses: actions/checkout@v4
- name: build
run: go build
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v5
with:
go-version: 1.22
- uses: actions/checkout@v4
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
skip-cache: true

32
.github/workflows/release.yaml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: build-and-test
on:
push:
tags:
- "*"
jobs:
release:
name: Build Release Assets
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.22.11
- name: Build the executables
run: ./mkrel.sh gowipe ${{ github.ref_name}}
- name: List the executables
run: ls -l ./releases
- name: Upload the binaries
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref_name }}
file: ./releases/*
file_glob: true

View File

@@ -55,8 +55,8 @@ goupdate:
buildall: buildall:
./mkrel.sh $(tool) $(VERSION) ./mkrel.sh $(tool) $(VERSION)
release: buildall release:
gh release create v$(VERSION) --generate-notes releases/* gh release create v$(VERSION) --generate-notes
show-versions: buildlocal show-versions: buildlocal
@echo "### gowipe version:" @echo "### gowipe version:"

View File

@@ -1,5 +1,56 @@
## gowipe - securely delete files and directories (not for SSD) ## gowipe - securely delete files and directories (not for SSD)
[![Actions](https://github.com/tlinden/gowipe/actions/workflows/ci.yaml/badge.svg)](https://github.com/tlinden/gowipe/actions)
[![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://github.com/tlinden/gowipe/blob/master/LICENSE)
[![Go Report Card](https://goreportcard.com/badge/github.com/tlinden/gowipe)](https://goreportcard.com/report/github.com/tlinden/gowipe)
[![GitHub release](https://img.shields.io/github/v/release/tlinden/gowipe?color=%2300a719)](https://github.com/TLINDEN/gowipe/releases/latest)
## Description
`gowipe` is a simple self contained tool to securely wipe files and
directories. By default it renames and overwrites files and
directories 30 times and uses the `secure` mode, which uses strong
random bytes for the overwriting process. Gowipe writes as much bytes
into a file as its original size.
You can tweak mode and round numbers. Other modes are `zero`, which
uses zeroes for overwriting (not recommended) or `encrypt` which
encrypts the data using AES and a strong random key. This is the most
secure but also to slowest mode.
Although you can use `gowipe` on SSD disks, it doesn't make much
sense. To wipe such a disk you have to resort to other means. But you
can savely use it on magnetic discs or usb drives.
Of course there are many other such tools available, this one is
insofar special as you can download a pre-compiled binary without any
library dependencies. This allows you to wipe files on systems, where
you cannot install a wiper via some package management (such as
appliance systems or vm's).
## Example
Overwrite the directory `vhs` recursively 50 times using strong
encryption and verbose output:
```shell
gowipe -c 50 -E -r -v vhs
Wiped vhs/help.png (355011 bytes)
Wiped vhs/rec.Dockerfile (348 bytes)
Wiped vhs/rec.gif (3533338 bytes)
Wiped vhs/rec.tape (852 bytes)
Wiped vhs (4096 bytes)
Dirs wiped: 1
Files wiped: 5
Bytes deleted: 3889549
Time elapsed: 426.286639ms
Overwritten: 50 times
Wipe mode: encrypt
Recurse dirs: true
```
``` ```
Usage: gowipe [-rcvz] <file|directory>... Usage: gowipe [-rcvz] <file|directory>...
@@ -22,7 +73,7 @@ encrypt Overwrite with ChaCha2Poly1305 encryption (most secure) (-E)
## Getting help ## Getting help
Although I'm happy to hear from tablizer users in private email, Although I'm happy to hear from gowipe users in private email,
that's the best way for me to forget to do something. that's the best way for me to forget to do something.
In order to report a bug, unexpected behavior, feature requests In order to report a bug, unexpected behavior, feature requests

View File

@@ -1,5 +1,5 @@
/* /*
Copyright © 2022 Thomas von Dein Copyright © 2022-2025 Thomas von Dein
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@@ -119,7 +119,7 @@ func GetRandomKey() ([]byte, error) {
return nil, err return nil, err
} }
salt, err := GenerateSecureRandomBytes(chapo.NonceSize) salt, err := GenerateSecureRandomBytes(chapo.NonceSizeX)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -156,11 +156,17 @@ func Encrypt(c *Conf, filename string) error {
for i := 0; i < c.count; i++ { for i := 0; i < c.count; i++ {
for { for {
if size < chunkSize { if size < chunkSize {
EncryptChunk(aead, outfile, size) if err := EncryptChunk(aead, outfile, size); err != nil {
return err
}
break break
} }
EncryptChunk(aead, outfile, chunkSize) if err := EncryptChunk(aead, outfile, chunkSize); err != nil {
return err
}
size = size - chunkSize size = size - chunkSize
if size <= 0 { if size <= 0 {
@@ -174,7 +180,7 @@ func Encrypt(c *Conf, filename string) error {
func EncryptChunk(aead cipher.AEAD, file *os.File, size int64) error { func EncryptChunk(aead cipher.AEAD, file *os.File, size int64) error {
chunk := make([]byte, size) chunk := make([]byte, size)
nonce, err := GenerateSecureRandomBytes(int(chapo.NonceSize)) nonce, err := GenerateSecureRandomBytes(int(chapo.NonceSizeX))
if err != nil { if err != nil {
return err return err
} }
@@ -192,58 +198,3 @@ func EncryptChunk(aead cipher.AEAD, file *os.File, size int64) error {
return nil 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)
}
}
}
*/

9
go.mod
View File

@@ -3,9 +3,12 @@ module gowipe
go 1.20 go 1.20
require ( require (
github.com/JojiiOfficial/shred v1.2.1 // indirect github.com/JojiiOfficial/shred v1.2.1
github.com/spf13/pflag v1.0.5
golang.org/x/crypto v0.15.0
)
require (
github.com/lu4p/shred v0.0.0-20201211173428-0347b645d724 // 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 golang.org/x/sys v0.14.0 // indirect
) )

65
main.go
View File

@@ -1,5 +1,5 @@
/* /*
Copyright © 2022 Thomas von Dein Copyright © 2022-2025 Thomas von Dein
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@@ -18,17 +18,17 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/JojiiOfficial/shred" "github.com/JojiiOfficial/shred"
flag "github.com/spf13/pflag" flag "github.com/spf13/pflag"
) )
const VERSION string = "0.0.2" const VERSION string = "0.0.4"
const Usage string = `This is gowipe - destruct files in a non-recoverable way. const Usage string = `This is gowipe - destruct files in a non-recoverable way.
Usage: gowipe [-rcvz] <file|directory>... Usage: gowipe [-rcvz] <file|directory>...
@@ -56,6 +56,9 @@ type Conf struct {
nodelete bool nodelete bool
norename bool norename bool
verbose bool verbose bool
files int
dirs int
size int64
} }
func main() { func main() {
@@ -84,7 +87,7 @@ func main() {
flag.BoolVarP(&optzero, "zero", "Z", optzero, "zero mode") flag.BoolVarP(&optzero, "zero", "Z", optzero, "zero mode")
flag.BoolVarP(&optsecure, "secure", "S", optsecure, "secure mode") flag.BoolVarP(&optsecure, "secure", "S", optsecure, "secure mode")
flag.BoolVarP(&optmath, "math", "M", optmath, "math mode") flag.BoolVarP(&optmath, "math", "M", optmath, "math mode")
flag.BoolVarP(&optmath, "encrypt", "E", optmath, "encrypt mode") flag.BoolVarP(&optencrypt, "encrypt", "E", optmath, "encrypt mode")
flag.BoolVarP(&c.recurse, "recursive", "r", c.recurse, "recursive") flag.BoolVarP(&c.recurse, "recursive", "r", c.recurse, "recursive")
flag.BoolVarP(&c.nodelete, "nodelete", "n", c.nodelete, "don't delete") flag.BoolVarP(&c.nodelete, "nodelete", "n", c.nodelete, "don't delete")
@@ -139,21 +142,33 @@ func main() {
shredder := shred.Shredder{} shredder := shred.Shredder{}
shredconf := shred.NewShredderConf(&shredder, option, c.count, !c.nodelete) shredconf := shred.NewShredderConf(&shredder, option, c.count, !c.nodelete)
start := time.Now()
for _, file := range flag.Args() { for _, file := range flag.Args() {
Wipe(file, &c, shredconf) Wipe(file, &c, shredconf)
} }
if c.verbose {
fmt.Println()
fmt.Printf(" Dirs wiped: %d\n", c.dirs)
fmt.Printf(" Files wiped: %d\n", c.files)
fmt.Printf("Bytes deleted: %d\n", c.size)
fmt.Printf(" Time elapsed: %s\n", time.Since(start))
fmt.Printf(" Overwritten: %d times\n", c.count)
fmt.Printf(" Wipe mode: %s\n", c.mode)
fmt.Printf(" Recurse dirs: %t\n", c.recurse)
}
} }
func Wipe(file string, c *Conf, wiper *shred.ShredderConf) { func Wipe(file string, c *Conf, wiper *shred.ShredderConf) {
if info, err := os.Stat(file); err == nil { if info, err := os.Stat(file); err == nil {
if info.IsDir() { if info.IsDir() {
if !c.recurse { if !c.recurse {
fmt.Printf("-r not set, ignoring directory %s\n", file) fmt.Printf("-r not set, ignoring directory %s\n", file)
return return
} }
files, err := ioutil.ReadDir(file) files, err := os.ReadDir(file)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -162,6 +177,22 @@ func Wipe(file string, c *Conf, wiper *shred.ShredderConf) {
Wipe(filepath.Join(file, entry.Name()), c, wiper) Wipe(filepath.Join(file, entry.Name()), c, wiper)
} }
// delete dir
if !c.nodelete {
err = os.Remove(Rename(file, c))
if err != nil {
log.Fatal(err)
}
c.dirs++
}
} else {
if c.mode == "encrypt" {
if err := Encrypt(c, file); err != nil {
log.Fatal(err)
}
// delete encrypted file
if !c.nodelete { if !c.nodelete {
err = os.Remove(Rename(file, c)) err = os.Remove(Rename(file, c))
if err != nil { if err != nil {
@@ -169,21 +200,19 @@ func Wipe(file string, c *Conf, wiper *shred.ShredderConf) {
} }
} }
} else { } else {
if c.mode == "encrypt" { if err := wiper.ShredFile(Rename(file, c)); err != nil {
err := Encrypt(c, file)
if err != nil {
log.Fatal(err) log.Fatal(err)
} }
Rename(file, c)
} else {
wiper.ShredFile(Rename(file, c))
} }
c.size += info.Size()
} }
if c.verbose { if c.verbose {
fmt.Printf("Wiped %d times: %s\n", c.count, file) fmt.Printf("Wiped %s (%d bytes)\n", file, info.Size())
} }
c.files++
} else { } else {
if os.IsNotExist(err) { if os.IsNotExist(err) {
fmt.Printf("No such file or directory: %s\n", file) fmt.Printf("No such file or directory: %s\n", file)
@@ -204,7 +233,7 @@ func Rename(file string, c *Conf) string {
for i := 0; i < c.count; i++ { for i := 0; i < c.count; i++ {
for { for {
switch c.mode { switch c.mode {
case `secure`: case `secure`, `encrypt`:
new, err := GenerateSecureRandomString(length) new, err := GenerateSecureRandomString(length)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@@ -220,12 +249,6 @@ func Rename(file string, c *Conf) string {
} }
} }
/*
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)) err := os.Rename(filepath.Join(dir, base), filepath.Join(dir, newname))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# Copyright © 2022 Thomas von Dein # Copyright © 2022-2025 Thomas von Dein
# This program is free software: you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@@ -46,7 +46,7 @@ for D in $DIST; do
GOOS=${os} GOARCH=${arch} go build -o ${binfile} -ldflags "-X 'github.com/tlinden/tablizer/cfg.VERSION=${version}'" GOOS=${os} GOARCH=${arch} go build -o ${binfile} -ldflags "-X 'github.com/tlinden/tablizer/cfg.VERSION=${version}'"
mkdir -p ${tardir} mkdir -p ${tardir}
cp ${binfile} README.md LICENSE ${tardir}/ cp ${binfile} README.md LICENSE ${tardir}/
echo 'tool = tablizer echo 'tool = gowipe
PREFIX = /usr/local PREFIX = /usr/local
UID = root UID = root
GID = 0 GID = 0