16 Commits
v0.0.2 ... main

Author SHA1 Message Date
f7aabc75be Merge branch 'main' of ssh://codeberg.org/scip/gowipe 2025-11-25 22:20:33 +01:00
88febcf664 clean up 2025-11-25 22:19:48 +01:00
T. von Dein
8dc372a222 move to codeberg (#1) 2025-11-25 22:15:08 +01:00
a90d6e5c3c fix algo 2025-02-05 18:03:52 +01:00
ed4ad2340b add changelog builder, update release builder 2025-02-05 17:56:05 +01:00
f7b0cfa905 fix heading 2025-01-19 16:27:03 +01:00
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
10 changed files with 257 additions and 90 deletions

65
.goreleaser.yaml Normal file
View File

@@ -0,0 +1,65 @@
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
version: 2
before:
hooks:
- go mod tidy
gitea_urls:
api: https://codeberg.org/api/v1
download: https://codeberg.org
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- freebsd
archives:
- formats: [tar.gz]
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}_{{ .Tag }}
# use zip for windows archives
format_overrides:
- goos: windows
formats: [zip]
- goos: linux
formats: [tar.gz,binary]
files:
- src: "*.md"
strip_parent: true
- src: Makefile.dist
dst: Makefile
wrap_in_directory: true
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
groups:
- title: Improved
regexp: '^.*?(feat|add|new)(\([[:word:]]+\))??!?:.+$'
order: 0
- title: Fixed
regexp: '^.*?(bug|fix)(\([[:word:]]+\))??!?:.+$'
order: 1
- title: Changed
order: 999
release:
header: "# Release Notes"
footer: >-
---
Full Changelog: [{{ .PreviousTag }}...{{ .Tag }}](https://codeberg.org/scip/gowipe/compare/{{ .PreviousTag }}...{{ .Tag }})

36
.woodpecker/build.yaml Normal file
View File

@@ -0,0 +1,36 @@
matrix:
platform:
- linux/amd64
goversion:
- 1.24
labels:
platform: ${platform}
steps:
build:
when:
event: [push]
image: golang:${goversion}
commands:
- go get
- go build
linter:
when:
event: [push]
image: golang:${goversion}
commands:
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.5.0
- golangci-lint --version
- golangci-lint run ./...
depends_on: [build]
test:
when:
event: [push]
image: golang:${goversion}
commands:
- go get
- go test -v -cover
depends_on: [build,linter]

15
.woodpecker/release.yaml Normal file
View File

@@ -0,0 +1,15 @@
# build release
labels:
platform: linux/amd64
steps:
goreleaser:
image: goreleaser/goreleaser
when:
event: [tag]
environment:
GITEA_TOKEN:
from_secret: DEPLOY_TOKEN
commands:
- goreleaser release --clean --verbose

View File

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

18
Makefile.dist Normal file
View File

@@ -0,0 +1,18 @@
# -*-make-*-
.PHONY: install all
tool = rpn
PREFIX = /usr/local
UID = root
GID = 0
all:
@echo "Type 'sudo make install' to install the tool."
@echo "To change prefix, type 'sudo make install PREFIX=/opt'"
install:
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
install -d -o $(UID) -g $(GID) $(PREFIX)/share/doc
install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
install -o $(UID) -g $(GID) -m 444 *.md $(PREFIX)/share/doc/

View File

@@ -1,5 +1,56 @@
## gowipe - securely delete files and directories (not for SSD)
[![status-badge](https://ci.codeberg.org/api/badges/15612/status.svg)](https://ci.codeberg.org/repos/15612)
[![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://codeberg.org/scip/gowipe/raw/branch/main/LICENSE)
[![Go Report Card](https://goreportcard.com/badge/codeberg.org/scip/gowipe)](https://goreportcard.com/report/codeberg.org/scip/gowipe)
## 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 ChaCha20Poly1305 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
```
Usage: gowipe [-rcvz] <file|directory>...
@@ -22,12 +73,12 @@ encrypt Overwrite with ChaCha2Poly1305 encryption (most secure) (-E)
## 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.
In order to report a bug, unexpected behavior, feature requests
or to submit a patch, please open an issue on github:
https://github.com/TLINDEN/gowipe/issues.
https://codeberg.org/scip/gowipe/issues.
## Copyright and license
@@ -39,4 +90,4 @@ T.v.Dein <tom AT vondein DOT org>
## Project homepage
https://github.com/TLINDEN/gowipe
https://codeberg.org/scip/gowipe

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
it under the terms of the GNU General Public License as published by
@@ -22,6 +22,7 @@ import (
"errors"
"fmt"
"io"
"log"
"math/big"
mathrand "math/rand"
"os"
@@ -119,7 +120,7 @@ func GetRandomKey() ([]byte, error) {
return nil, err
}
salt, err := GenerateSecureRandomBytes(chapo.NonceSize)
salt, err := GenerateSecureRandomBytes(chapo.NonceSizeX)
if err != nil {
return nil, err
}
@@ -141,7 +142,11 @@ func Encrypt(c *Conf, filename string) error {
if err != nil {
return err
}
defer outfile.Close()
defer func() {
if err := outfile.Close(); err != nil {
log.Fatal(err)
}
}()
key, err := GetRandomKey()
if err != nil {
@@ -156,11 +161,17 @@ func Encrypt(c *Conf, filename string) error {
for i := 0; i < c.count; i++ {
for {
if size < chunkSize {
EncryptChunk(aead, outfile, size)
if err := EncryptChunk(aead, outfile, size); err != nil {
return err
}
break
}
EncryptChunk(aead, outfile, chunkSize)
if err := EncryptChunk(aead, outfile, chunkSize); err != nil {
return err
}
size = size - chunkSize
if size <= 0 {
@@ -174,7 +185,7 @@ func Encrypt(c *Conf, filename string) error {
func EncryptChunk(aead cipher.AEAD, file *os.File, size int64) error {
chunk := make([]byte, size)
nonce, err := GenerateSecureRandomBytes(int(chapo.NonceSize))
nonce, err := GenerateSecureRandomBytes(int(chapo.NonceSizeX))
if err != nil {
return err
}
@@ -192,58 +203,3 @@ func EncryptChunk(aead cipher.AEAD, file *os.File, size int64) error {
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
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/spf13/pflag v1.0.5 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/sys v0.14.0 // indirect
)

59
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
it under the terms of the GNU General Public License as published by
@@ -18,17 +18,17 @@ package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/JojiiOfficial/shred"
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.
Usage: gowipe [-rcvz] <file|directory>...
@@ -56,6 +56,9 @@ type Conf struct {
nodelete bool
norename bool
verbose bool
files int
dirs int
size int64
}
func main() {
@@ -84,7 +87,7 @@ func main() {
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(&optencrypt, "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")
@@ -139,21 +142,33 @@ func main() {
shredder := shred.Shredder{}
shredconf := shred.NewShredderConf(&shredder, option, c.count, !c.nodelete)
start := time.Now()
for _, file := range flag.Args() {
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) {
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)
files, err := os.ReadDir(file)
if err != nil {
log.Fatal(err)
}
@@ -162,28 +177,42 @@ func Wipe(file string, c *Conf, wiper *shred.ShredderConf) {
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" {
err := Encrypt(c, file)
if err != nil {
if err := Encrypt(c, file); err != nil {
log.Fatal(err)
}
Rename(file, c)
// delete encrypted file
if !c.nodelete {
err = os.Remove(Rename(file, c))
if err != nil {
log.Fatal(err)
}
}
} else {
wiper.ShredFile(Rename(file, c))
if err := wiper.ShredFile(Rename(file, c)); err != nil {
log.Fatal(err)
}
}
c.size += info.Size()
}
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 {
if os.IsNotExist(err) {
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 {
switch c.mode {
case `secure`:
case `secure`, `encrypt`:
new, err := GenerateSecureRandomString(length)
if err != nil {
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))
if err != nil {
log.Fatal(err)

View File

@@ -1,6 +1,6 @@
#!/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
# it under the terms of the GNU General Public License as published by
@@ -43,10 +43,10 @@ for D in $DIST; do
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}'"
GOOS=${os} GOARCH=${arch} go build -o ${binfile} -ldflags "-X 'codeberg.org/scip/tablizer/cfg.VERSION=${version}'"
mkdir -p ${tardir}
cp ${binfile} README.md LICENSE ${tardir}/
echo 'tool = tablizer
echo 'tool = gowipe
PREFIX = /usr/local
UID = root
GID = 0