From 28a0e9b2c3bd53437b1cbd1f60f04e606b7311e3 Mon Sep 17 00:00:00 2001 From: Evan Su <48808396+HACKERALERT@users.noreply.github.com> Date: Fri, 12 Nov 2021 19:03:21 -0500 Subject: [PATCH] Finalize v1.20 --- src/Picocrypt.go | 4984 +++++++++++++++++++++++----------------------- 1 file changed, 2510 insertions(+), 2474 deletions(-) diff --git a/src/Picocrypt.go b/src/Picocrypt.go index 68302d0..528a0f1 100644 --- a/src/Picocrypt.go +++ b/src/Picocrypt.go @@ -1,2474 +1,2510 @@ -package main - -/* - -Picocrypt v1.19 -Copyright (c) Evan Su (https://evansu.cc) -Released under a GNU GPL v3 License -https://github.com/HACKERALERT/Picocrypt - -~ In cryptography we trust ~ - -*/ - -import ( - _ "embed" - - // Generic - "archive/zip" - "bytes" - "encoding/hex" - "encoding/json" - "fmt" - "hash" - "image" - "image/color" - "image/png" - "io" - "io/ioutil" - "math" - "math/big" - "net/http" - "os" - "os/exec" - "path/filepath" - "regexp" - "runtime" - "runtime/debug" - "strconv" - "strings" - "sync" - "syscall" - "time" - - // Cryptography - "crypto/cipher" - "crypto/hmac" - "crypto/md5" - "crypto/rand" - "crypto/sha1" - "crypto/sha256" - "crypto/subtle" - - "github.com/HACKERALERT/serpent" - "golang.org/x/crypto/argon2" - "golang.org/x/crypto/blake2b" - "golang.org/x/crypto/blake2s" - "golang.org/x/crypto/chacha20" - "golang.org/x/crypto/hkdf" - "golang.org/x/crypto/sha3" - - // UI - "github.com/HACKERALERT/giu" - - // Reed-Solomon - "github.com/HACKERALERT/infectious" - - // Helpers - "github.com/HACKERALERT/clipboard" - "github.com/HACKERALERT/dialog" - "github.com/HACKERALERT/jibber_jabber" - "github.com/HACKERALERT/zxcvbn-go" -) - -//go:embed icon.png -var icon []byte - -//go:embed font.ttf -var font []byte - -//go:embed sdelete64.exe -var sdelete64bytes []byte - -//go:embed strings.json -var localeBytes []byte - -// Localization -type locale struct { - iso string - data []string -} - -var locales []locale -var selectedLocale = "en" -var allLocales = []string{ - "en", -} -var languages = []string{ - "English", -} -var languageSelected int32 - -// Generic variables -var version = "v1.19" -var dpi float32 -var tab = 0 -var mode string -var working bool -var recombine bool -var fill float32 = -0.0000001 -var sdelete64path string - -// Three variables store the input files -var onlyFiles []string -var onlyFolders []string -var allFiles []string - -// Input file variables -var inputLabel = "Drop files and folders into this window." -var inputFile string - -// Password variables -var password string -var cPassword string -var passwordStrength int -var passwordState = giu.InputTextFlagsPassword -var passwordStateLabel = "Show" - -// Password generator variables -var showGenpass = false -var genpassCopy = true -var genpassLength int32 = 32 -var genpassUpper = true -var genpassLower = true -var genpassNums = true -var genpassSymbols = true - -// Keyfile variables -var keyfile bool -var keyfiles []string -var keyfileOrderMatters bool -var keyfilePrompt = "None selected." -var showKeyfile bool - -// Metadata variables -var metadata string -var metadataPrompt = "Metadata:" -var metadataDisabled bool - -// Advanced options -var shredTemp bool -var fast bool -var paranoid bool -var reedsolo bool -var deleteWhenDone bool -var split bool -var splitSize string -var splitUnits = []string{ - "KiB", - "MiB", - "GiB", -} -var splitSelected int32 = 1 -var compress bool -var encryptFilename bool -var keep bool -var kept bool - -// Output file variables -var outputFile string - -// Status variables -var mainStatus = "Ready." -var mainStatusColor = color.RGBA{0xff, 0xff, 0xff, 0xff} -var popupStatus string - -// Progress variables -var progress float32 -var progressInfo string -var showProgress bool - -// Confirm overwrite variables -var showConfirmation bool - -// Reed-Solomon encoders -var rs1, _ = infectious.NewFEC(1, 3) // 1 data shard, 3 total -> 2 parity shards -var rs5, _ = infectious.NewFEC(5, 15) -var rs6, _ = infectious.NewFEC(6, 18) -var rs16, _ = infectious.NewFEC(16, 48) -var rs24, _ = infectious.NewFEC(24, 72) -var rs32, _ = infectious.NewFEC(32, 96) -var rs64, _ = infectious.NewFEC(64, 192) -var rs128, _ = infectious.NewFEC(128, 136) - -// File checksum generator variables -var csMd5 string -var csSha1 string -var csSha256 string -var csSha3 string -var csBlake2b string -var csBlake2s string -var csValidate string -var md5Color = color.RGBA{0x00, 0x00, 0x00, 0x00} -var sha1Color = color.RGBA{0x00, 0x00, 0x00, 0x00} -var sha256Color = color.RGBA{0x00, 0x00, 0x00, 0x00} -var sha3Color = color.RGBA{0x00, 0x00, 0x00, 0x00} -var blake2bColor = color.RGBA{0x00, 0x00, 0x00, 0x00} -var blake2sColor = color.RGBA{0x00, 0x00, 0x00, 0x00} -var csProgress float32 = 0 -var md5Selected = true -var sha1Selected = true -var sha256Selected = true -var sha3Selected = false -var blake2bSelected = false -var blake2sSelected = false - -// Shredder variables -var shredding string = "Ready." -var shredPasses int32 = 4 -var stopShredding bool -var shredProgress float32 -var shredDone float32 -var shredTotal float32 -var shredOverlay string - -func draw() { - giu.SingleWindow().Layout( - giu.Custom(func() { - pos := giu.GetCursorPos() - w, _ := giu.CalcTextSize(languages[languageSelected]) - giu.Row( - giu.Dummy(-w/dpi-34, 0), - giu.Combo("##language", languages[languageSelected], languages, &languageSelected).OnChange(func() { - selectedLocale = allLocales[languageSelected] - shredding = s(shredding) - }).Size(w/dpi+26), - ).Build() - giu.SetCursorPos(pos) - - giu.TabBar().TabItems( - giu.TabItem(s("Encryption")).Layout( - giu.Custom(func() { - if giu.IsItemActive() { - tab = 0 - } - }), - - giu.Custom(func() { - if showGenpass { - giu.PopupModal(s("Generate password:")). - Flags(giu.WindowFlagsNoMove|giu.WindowFlagsNoResize).Layout( - giu.Row( - giu.Label(s("Length:")), - giu.SliderInt(&genpassLength, 4, 64).Size(fill), - ), - giu.Checkbox(s("Uppercase"), &genpassUpper), - giu.Checkbox(s("Lowercase"), &genpassLower), - giu.Checkbox(s("Numbers"), &genpassNums), - giu.Checkbox(s("Symbols"), &genpassSymbols), - giu.Checkbox(s("Copy to clipboard"), &genpassCopy), - giu.Row( - giu.Button(s("Cancel")).Size(100, 0).OnClick(func() { - giu.CloseCurrentPopup() - showGenpass = false - }), - giu.Button(s("Generate")).Size(100, 0).OnClick(func() { - tmp := genPassword() - password = tmp - cPassword = tmp - passwordStrength = zxcvbn.PasswordStrength(password, nil).Score - giu.CloseCurrentPopup() - showGenpass = false - giu.Update() - }), - ), - ).Build() - giu.OpenPopup(s("Generate password:")) - giu.Update() - } - }), - - giu.Custom(func() { - if showKeyfile { - giu.PopupModal(s("Manage keyfiles:")). - Flags(giu.WindowFlagsNoMove|giu.WindowFlagsNoResize).Layout( - giu.Row( - giu.Label(s("Drop and drop your keyfiles.")), - ), - giu.Custom(func() { - if mode != "decrypt" { - giu.Checkbox(s("Require correct keyfile order"), &keyfileOrderMatters).Build() - giu.Tooltip(s("If checked, you will need to drop keyfiles in the correct order.")).Build() - } else if keyfileOrderMatters { - giu.Label(s("The correct order of keyfiles is required.")).Build() - } - }), - - giu.Custom(func() { - for _, i := range keyfiles { - giu.Row( - giu.Label(filepath.Base(i)), - giu.Button("Remove").OnClick(func() { - var tmp []string - for _, j := range keyfiles { - if j != i { - tmp = append(tmp, j) - } - } - keyfiles = tmp - }), - ).Build() - - } - }), - giu.Dummy(0, 200), - giu.Row( - giu.Button(s("Clear")).Size(150, 0).OnClick(func() { - keyfiles = nil - }), - giu.Tooltip(s("Remove all keyfiles.")), - giu.Button(s("Done")).Size(150, 0).OnClick(func() { - giu.CloseCurrentPopup() - showKeyfile = false - }), - ), - ).Build() - giu.OpenPopup(s("Manage keyfiles:")) - giu.Update() - } - }), - - giu.Custom(func() { - if showConfirmation { - giu.PopupModal(s("Warning:")). - Flags(giu.WindowFlagsNoMove|giu.WindowFlagsNoResize).Layout( - giu.Label(s("Output already exists. Overwrite?")), - giu.Row( - giu.Button(s("No")).Size(100, 0).OnClick(func() { - giu.CloseCurrentPopup() - showConfirmation = false - }), - giu.Button(s("Yes")).Size(100, 0).OnClick(func() { - giu.CloseCurrentPopup() - showConfirmation = false - showProgress = true - giu.Update() - go func() { - work() - working = false - showProgress = false - debug.FreeOSMemory() - giu.Update() - }() - }), - ), - ).Build() - giu.OpenPopup(s("Warning:")) - giu.Update() - } - }), - - giu.Custom(func() { - if showProgress { - giu.PopupModal(" "). - Flags(giu.WindowFlagsNoMove|giu.WindowFlagsNoResize|giu.WindowFlagsNoTitleBar).Layout( - giu.Custom(func() { - if !working { - giu.CloseCurrentPopup() - } - }), - giu.Row( - giu.ProgressBar(progress).Size(280, 0).Overlay(progressInfo), - giu.Button(s("Cancel")).Size(58, 0).OnClick(func() { - working = false - }), - ), - giu.Label(popupStatus), - ).Build() - giu.OpenPopup(" ") - giu.Update() - } - }), - - giu.Row( - giu.Label(inputLabel), - giu.Custom(func() { - w, _ := giu.GetAvailableRegion() - bw, _ := giu.CalcTextSize(s("Clear")) - p, _ := giu.GetWindowPadding() - bw += p * 2 - dw := w - bw - p - giu.Dummy(float32(math.Max(float64(dw/dpi), float64(-bw/dpi-p))), 0).Build() - giu.SameLine() - giu.Style().SetDisabled(len(allFiles) == 0 && len(onlyFiles) == 0).To( - giu.Button(s("Clear")).Size(bw/dpi, 0).OnClick(resetUI), - giu.Tooltip(s("Clear all input files and reset UI state.")), - ).Build() - }), - ), - - giu.Separator(), - - giu.Style().SetDisabled(len(allFiles) == 0 && len(onlyFiles) == 0).To( - giu.Row( - giu.Label(s("Password:")), - giu.Dummy(-124, 0), - giu.Style().SetDisabled(mode == "decrypt" && !keyfile).To( - giu.Label(s("Keyfiles:")), - ), - ), - giu.Row( - giu.Button(s(passwordStateLabel)).Size(54, 0).OnClick(func() { - if passwordState == giu.InputTextFlagsPassword { - passwordState = giu.InputTextFlagsNone - passwordStateLabel = "Hide" - } else { - passwordState = giu.InputTextFlagsPassword - passwordStateLabel = "Show" - } - }), - - giu.Button(s("Clear")).Size(54, 0).OnClick(func() { - password = "" - cPassword = "" - }), - - giu.Button(s("Copy")).Size(54, 0).OnClick(func() { - clipboard.WriteAll(password) - }), - - giu.Button(s("Paste")).Size(54, 0).OnClick(func() { - tmp, _ := clipboard.ReadAll() - password = tmp - if mode != "decrypt" { - cPassword = tmp - } - passwordStrength = zxcvbn.PasswordStrength(password, nil).Score - giu.Update() - }), - - giu.Style().SetDisabled(mode == "decrypt").To( - giu.Button(s("Create")).Size(54, 0).OnClick(func() { - showGenpass = true - }), - ), - - giu.Style().SetDisabled(mode == "decrypt" && !keyfile).To( - giu.Row( - giu.Button(s("Edit")).Size(54, 0).OnClick(func() { - showKeyfile = true - }), - giu.Style().SetDisabled(mode == "decrypt").To( - giu.Button(s("Create")).Size(54, 0).OnClick(func() { - file, _ := dialog.File().Title(s("Save keyfile as:")).Save() - if file == "" { - return - } - fout, _ := os.Create(file) - data := make([]byte, 1048576) - rand.Read(data) - fout.Write(data) - fout.Close() - }), - ), - ), - ), - ), - giu.Row( - giu.InputText(&password).Flags(passwordState).Size(302/dpi).OnChange(func() { - passwordStrength = zxcvbn.PasswordStrength(password, nil).Score - }), - giu.Custom(func() { - c := giu.GetCanvas() - p := giu.GetCursorScreenPos() - - var col color.RGBA - switch passwordStrength { - case 0: - col = color.RGBA{0xc8, 0x4c, 0x4b, 0xff} - case 1: - col = color.RGBA{0xa9, 0x6b, 0x4b, 0xff} - case 2: - col = color.RGBA{0x8a, 0x8a, 0x4b, 0xff} - case 3: - col = color.RGBA{0x6b, 0xa9, 0x4b, 0xff} - case 4: - col = color.RGBA{0x4c, 0xc8, 0x4b, 0xff} - } - if password == "" || mode == "decrypt" { - col = color.RGBA{0xff, 0xff, 0xff, 0x00} - } - - path := p.Add(image.Pt( - int(math.Round(float64(-20*dpi))), - int(math.Round(float64(12*dpi))), - )) - c.PathArcTo(path, 6*dpi, -math.Pi/2, float32(passwordStrength+1)/5*2*math.Pi-math.Pi/2, -1) - c.PathStroke(col, false, 2) - }), - giu.Style().SetDisabled(true).To( - giu.InputText(&keyfilePrompt).Size(fill), - ), - ), - ), - - giu.Style().SetDisabled(password == "").To( - giu.Row( - giu.Style().SetDisabled(mode == "decrypt").To( - giu.Label(s("Confirm password:")), - ), - giu.Dummy(-124, 0), - giu.Style().SetDisabled(true).To( - giu.Label(s("Custom Argon2:")), - ), - ), - ), - giu.Style().SetDisabled(password == "").To( - giu.Row( - giu.Style().SetDisabled(mode == "decrypt").To( - giu.Row( - giu.InputText(&cPassword).Flags(passwordState).Size(302/dpi), - giu.Custom(func() { - c := giu.GetCanvas() - p := giu.GetCursorScreenPos() - col := color.RGBA{0x4c, 0xc8, 0x4b, 0xff} - - if cPassword != password { - col = color.RGBA{0xc8, 0x4c, 0x4b, 0xff} - } - if password == "" || cPassword == "" || mode == "decrypt" { - col = color.RGBA{0xff, 0xff, 0xff, 0x00} - } - - path := p.Add(image.Pt( - int(math.Round(float64(-20*dpi))), - int(math.Round(float64(12*dpi))), - )) - c.PathArcTo(path, 6*dpi, 0, 2*math.Pi, -1) - c.PathStroke(col, false, 2) - }), - ), - ), - giu.Style().SetDisabled(true).To( - giu.Button(s("W.I.P")).Size(fill, 0), - ), - ), - ), - - giu.Dummy(0, 3), - giu.Separator(), - giu.Dummy(0, 0), - - giu.Style().SetDisabled(password == "" || (password != cPassword && mode == "encrypt")).To( - giu.Label(s(metadataPrompt)), - giu.Style().SetDisabled(metadataDisabled).To( - giu.InputText(&metadata).Size(fill), - ), - - giu.Label(s("Advanced:")), - giu.Custom(func() { - if mode != "decrypt" { - giu.Row( - giu.Checkbox(s("Shred temporary files"), &shredTemp), - giu.Dummy(-221, 0), - giu.Checkbox(s("Encode with Reed-Solomon"), &reedsolo), - ).Build() - giu.Row( - giu.Checkbox(s("Use fast mode"), &fast), - giu.Dummy(-221, 0), - giu.Checkbox(s("Delete files when complete"), &deleteWhenDone), - ).Build() - giu.Row( - giu.Checkbox(s("Use paranoid mode"), ¶noid), - giu.Dummy(-221, 0), - giu.Style().SetDisabled(!(len(allFiles) > 1 || len(onlyFolders) > 0)).To( - giu.Checkbox(s("Compress files"), &compress), - ), - ).Build() - giu.Row( - giu.Style().SetDisabled(true).To( - giu.Checkbox(s("Encrypt filename (W.I.P)"), &encryptFilename), - ), - giu.Dummy(-221, 0), - giu.Checkbox(s("Split every"), &split), - giu.InputText(&splitSize).Size(55/dpi).Flags(giu.InputTextFlagsCharsHexadecimal).OnChange(func() { - split = splitSize != "" - }), - giu.Combo("##splitter", splitUnits[splitSelected], splitUnits, &splitSelected).Size(52), - ).Build() - } else { - giu.Checkbox(s("Keep decrypted output even if it's corrupted or modified"), &keep).Build() - giu.Checkbox(s("Delete the encrypted files after a successful decryption"), &deleteWhenDone).Build() - giu.Dummy(0, 52).Build() - } - }), - - giu.Label(s("Save output as:")), - giu.Custom(func() { - w, _ := giu.GetAvailableRegion() - bw, _ := giu.CalcTextSize(s("Change")) - p, _ := giu.GetWindowPadding() - bw += p * 2 - dw := w - bw - p - giu.Style().SetDisabled(true).To( - giu.InputText(&outputFile).Size(dw / dpi / dpi).Flags(giu.InputTextFlagsReadOnly), - ).Build() - giu.SameLine() - giu.Button(s("Change")).Size(bw/dpi, 0).OnClick(func() { - file, _ := dialog.File().Title(s("Save as:")).Save() - if file == "" { - return - } - - if mode == "encrypt" { - if len(allFiles) > 1 || len(onlyFolders) > 0 { - file = strings.TrimSuffix(file, ".zip.pcv") - file = strings.TrimSuffix(file, ".pcv") - if !strings.HasSuffix(file, ".zip.pcv") { - file += ".zip.pcv" - } - } else { - file = strings.TrimSuffix(file, ".pcv") - ind := strings.Index(inputFile, ".") - file += inputFile[ind:] - if !strings.HasSuffix(file, ".pcv") { - file += ".pcv" - } - } - } else { - ind := strings.Index(file, ".") - if ind != -1 { - file = file[:ind] - } - if strings.HasSuffix(inputFile, ".zip.pcv") { - file += ".zip" - } else { - tmp := strings.TrimSuffix(filepath.Base(inputFile), ".pcv") - tmp = tmp[strings.Index(tmp, "."):] - file += tmp - } - } - - outputFile = file - }).Build() - giu.Tooltip(s("Save the output with a custom path and name.")).Build() - }), - - giu.Dummy(0, 2), - giu.Separator(), - giu.Dummy(0, 3), - - giu.Button(s("Start")).Size(fill, 34).OnClick(func() { - if keyfile && keyfiles == nil { - mainStatus = "Please select your keyfiles." - mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} - return - } - _, err := os.Stat(outputFile) - if err == nil { - showConfirmation = true - giu.Update() - } else { - showProgress = true - giu.Update() - go func() { - work() - working = false - showProgress = false - debug.FreeOSMemory() - giu.Update() - }() - } - }), - giu.Style().SetColor(giu.StyleColorText, mainStatusColor).To( - giu.Label(s(mainStatus)), - ), - ), - ), - giu.TabItem(s("Checksum")).Layout( - giu.Custom(func() { - if giu.IsItemActive() { - tab = 1 - } - }), - giu.Label(s("Toggle the hashes you would like to generate and drop a file here.")), - - // MD5 - giu.Custom(func() { - giu.Checkbox("MD5:", &md5Selected).OnChange(func() { - csMd5 = "" - }).Build() - giu.SameLine() - w, _ := giu.GetAvailableRegion() - bw, _ := giu.CalcTextSize(s("Copy")) - padding, _ := giu.GetWindowPadding() - bw += 2 * padding - size := w - bw - padding - giu.Dummy(size/dpi, 0).Build() - giu.SameLine() - giu.Button(s("Copy")+"##md5").Size(bw/dpi, 0).OnClick(func() { - clipboard.WriteAll(csMd5) - }).Build() - }), - giu.Style().SetColor(giu.StyleColorBorder, md5Color).To( - giu.Style().SetDisabled(true).To( - giu.InputText(&csMd5).Size(fill).Flags(giu.InputTextFlagsReadOnly), - ), - ), - - // SHA1 - giu.Custom(func() { - giu.Checkbox("SHA1:", &sha1Selected).OnChange(func() { - csSha1 = "" - }).Build() - giu.SameLine() - w, _ := giu.GetAvailableRegion() - bw, _ := giu.CalcTextSize(s("Copy")) - padding, _ := giu.GetWindowPadding() - bw += 2 * padding - size := w - bw - padding - giu.Dummy(size/dpi, 0).Build() - giu.SameLine() - giu.Button(s("Copy")+"##sha1").Size(bw/dpi, 0).OnClick(func() { - clipboard.WriteAll(csSha1) - }).Build() - }), - giu.Style().SetColor(giu.StyleColorBorder, sha1Color).To( - giu.Style().SetDisabled(true).To( - giu.InputText(&csSha1).Size(fill).Flags(giu.InputTextFlagsReadOnly), - ), - ), - - // SHA256 - giu.Custom(func() { - giu.Checkbox("SHA256:", &sha256Selected).OnChange(func() { - csSha256 = "" - }).Build() - giu.SameLine() - w, _ := giu.GetAvailableRegion() - bw, _ := giu.CalcTextSize(s("Copy")) - padding, _ := giu.GetWindowPadding() - bw += 2 * padding - size := w - bw - padding - giu.Dummy(size/dpi, 0).Build() - giu.SameLine() - giu.Button(s("Copy")+"##sha256").Size(bw/dpi, 0).OnClick(func() { - clipboard.WriteAll(csSha256) - }).Build() - }), - giu.Style().SetColor(giu.StyleColorBorder, sha256Color).To( - giu.Style().SetDisabled(true).To( - giu.InputText(&csSha256).Size(fill).Flags(giu.InputTextFlagsReadOnly), - ), - ), - - // SHA3-256 - giu.Custom(func() { - giu.Checkbox("SHA3:", &sha3Selected).OnChange(func() { - csSha3 = "" - }).Build() - giu.SameLine() - w, _ := giu.GetAvailableRegion() - bw, _ := giu.CalcTextSize(s("Copy")) - padding, _ := giu.GetWindowPadding() - bw += 2 * padding - size := w - bw - padding - giu.Dummy(size/dpi, 0).Build() - giu.SameLine() - giu.Button(s("Copy")+"##sha3").Size(bw/dpi, 0).OnClick(func() { - clipboard.WriteAll(csSha3) - }).Build() - }), - giu.Style().SetColor(giu.StyleColorBorder, sha3Color).To( - giu.Style().SetDisabled(true).To( - giu.InputText(&csSha3).Size(fill).Flags(giu.InputTextFlagsReadOnly), - ), - ), - - // BLAKE2b - giu.Custom(func() { - giu.Checkbox("BLAKE2b:", &blake2bSelected).OnChange(func() { - csBlake2b = "" - }).Build() - giu.SameLine() - w, _ := giu.GetAvailableRegion() - bw, _ := giu.CalcTextSize(s("Copy")) - padding, _ := giu.GetWindowPadding() - bw += 2 * padding - size := w - bw - padding - giu.Dummy(size/dpi, 0).Build() - giu.SameLine() - giu.Button(s("Copy")+"##blake2b").Size(bw/dpi, 0).OnClick(func() { - clipboard.WriteAll(csBlake2b) - }).Build() - }), - giu.Style().SetColor(giu.StyleColorBorder, blake2bColor).To( - giu.Style().SetDisabled(true).To( - giu.InputText(&csBlake2b).Size(fill).Flags(giu.InputTextFlagsReadOnly), - ), - ), - - // BLAKE2s - giu.Custom(func() { - giu.Checkbox("BLAKE2s:", &blake2sSelected).OnChange(func() { - csBlake2s = "" - }).Build() - giu.SameLine() - w, _ := giu.GetAvailableRegion() - bw, _ := giu.CalcTextSize(s("Copy")) - padding, _ := giu.GetWindowPadding() - bw += 2 * padding - size := w - bw - padding - giu.Dummy(size/dpi, 0).Build() - giu.SameLine() - giu.Button(s("Copy")+"##blake2s").Size(bw/dpi, 0).OnClick(func() { - clipboard.WriteAll(csBlake2s) - }).Build() - }), - giu.Style().SetColor(giu.StyleColorBorder, blake2sColor).To( - giu.Style().SetDisabled(true).To( - giu.InputText(&csBlake2s).Size(fill).Flags(giu.InputTextFlagsReadOnly), - ), - ), - - giu.Dummy(0, 23), - // Input entry for validating a checksum - giu.Row( - giu.Label(s("Validate a checksum:")), - giu.Custom(func() { - bw, _ := giu.CalcTextSize(s("Paste")) - padding, _ := giu.GetWindowPadding() - bw += 2 * padding - giu.Button(s("Paste")).Size(bw/dpi, 0).OnClick(func() { - tmp, _ := clipboard.ReadAll() - csValidate = tmp - md5Color = color.RGBA{0x00, 0x00, 0x00, 0x00} - sha1Color = color.RGBA{0x00, 0x00, 0x00, 0x00} - sha256Color = color.RGBA{0x00, 0x00, 0x00, 0x00} - sha3Color = color.RGBA{0x00, 0x00, 0x00, 0x00} - blake2bColor = color.RGBA{0x00, 0x00, 0x00, 0x00} - blake2sColor = color.RGBA{0x00, 0x00, 0x00, 0x00} - if csValidate == "" { - return - } - csValidate = strings.ToLower(csValidate) - if csValidate == csMd5 { - md5Color = color.RGBA{0x00, 0xff, 0x00, 0xff} - } else if csValidate == csSha1 { - sha1Color = color.RGBA{0x00, 0xff, 0x00, 0xff} - } else if csValidate == csSha256 { - sha256Color = color.RGBA{0x00, 0xff, 0x00, 0xff} - } else if csValidate == csSha3 { - sha3Color = color.RGBA{0x00, 0xff, 0x00, 0xff} - } else if csValidate == csBlake2b { - blake2bColor = color.RGBA{0x00, 0xff, 0x00, 0xff} - } else if csValidate == csBlake2s { - blake2sColor = color.RGBA{0x00, 0xff, 0x00, 0xff} - } - giu.Update() - }).Build() - }), - giu.Custom(func() { - bw, _ := giu.CalcTextSize(s("Paste")) - padding, _ := giu.GetWindowPadding() - bw += 2 * padding - giu.Button(s("Clear")).Size(bw/dpi, 0).OnClick(func() { - csValidate = "" - md5Color = color.RGBA{0x00, 0x00, 0x00, 0x00} - sha1Color = color.RGBA{0x00, 0x00, 0x00, 0x00} - sha256Color = color.RGBA{0x00, 0x00, 0x00, 0x00} - sha3Color = color.RGBA{0x00, 0x00, 0x00, 0x00} - blake2bColor = color.RGBA{0x00, 0x00, 0x00, 0x00} - blake2sColor = color.RGBA{0x00, 0x00, 0x00, 0x00} - }).Build() - }), - ), - giu.Style().SetDisabled(true).To( - giu.InputText(&csValidate).Size(fill), - ), - - // Progress bar - giu.Label(s("Progress:")), - giu.ProgressBar(csProgress).Size(fill, 0), - ), - giu.TabItem(s("Shredder")).Layout( - giu.Custom(func() { - if giu.IsItemActive() { - tab = 2 - } - }), - - giu.Label(s("Drop files and folders here to shred them.")), - giu.Custom(func() { - if runtime.GOOS == "darwin" { - giu.Label(s("Number of passes: Not supported on macOS")).Build() - } else { - giu.Row( - giu.Label(s("Number of passes:")), - giu.SliderInt(&shredPasses, 1, 32).Size(fill), - ).Build() - } - }), - giu.Dummy(0, -50), - giu.Custom(func() { - w, _ := giu.GetAvailableRegion() - bw, _ := giu.CalcTextSize(s("Cancel")) - padding, _ := giu.GetWindowPadding() - bw += 2 * padding - size := w - bw - padding - giu.Row( - giu.ProgressBar(shredProgress).Overlay(shredOverlay).Size(size/dpi, 0), - giu.Button(s("Cancel")).Size(bw/dpi, 0).OnClick(func() { - stopShredding = true - shredding = s("Ready.") - shredProgress = 0 - shredOverlay = "" - }), - ).Build() - }), - giu.Custom(func() { - if len(shredding) > 60 { - shredding = "....." + shredding[len(shredding)-50:] - } - giu.Label(shredding).Wrapped(true).Build() - }), - ), - giu.TabItem(s("About")).Layout( - giu.Custom(func() { - if giu.IsItemActive() { - tab = 3 - } - }), - giu.Label(fmt.Sprintf(s("Picocrypt %s, created by Evan Su (https://evansu.cc/)."), version)), - giu.Label(s("Released under a GNU GPL v3 License.")), - giu.Label(s("A warm thank you to all the people listed below.")), - giu.Label(s("Patrons:")), - giu.Label(" - Frederick Doe"), - giu.Label(s("Donators:")), - giu.Label(" - jp26"), - giu.Label(" - W.Graham"), - giu.Label(" - N. Chin"), - giu.Label(" - Manjot"), - giu.Label(" - Phil P."), - giu.Label(" - E. Zahard"), - giu.Label(s("Translators:")), - giu.Label("umitseyhan75, digitalblossom, zeeaall, francirc, kurpau"), - giu.Label(s("Other:")), - giu.Label("Fuderal, u/greenreddits, u/Tall_Escape, u/NSABackdoors"), - ), - ).Build() - }), - ) -} - -func onDrop(names []string) { - if tab == 1 { - go generateChecksums(names[0]) - return - } - if tab == 2 { - go shred(names, true) - return - } - - if showKeyfile { - keyfiles = append(keyfiles, names...) - tmp := []string{} - for _, i := range keyfiles { - duplicate := false - for _, j := range tmp { - if i == j { - duplicate = true - } - } - stat, _ := os.Stat(i) - if !duplicate && !stat.IsDir() { - tmp = append(tmp, i) - } - } - keyfiles = tmp - if len(keyfiles) == 1 { - keyfilePrompt = s("Using 1 keyfile.") - } - keyfilePrompt = fmt.Sprintf(s("Using %d keyfiles."), len(keyfiles)) - return - } - - // Clear variables - recombine = false - onlyFiles = nil - onlyFolders = nil - allFiles = nil - files, folders := 0, 0 - resetUI() - - if len(names) == 1 { - stat, _ := os.Stat(names[0]) - if stat.IsDir() { - // Update variables - mode = "encrypt" - folders++ - inputLabel = s("1 folder selected.") - - // Add the folder - onlyFolders = append(onlyFolders, names[0]) - - // Set the input and output paths - inputFile = filepath.Join(filepath.Dir(names[0]), s("Encrypted")) + ".zip" - outputFile = filepath.Join(filepath.Dir(names[0]), s("Encrypted")) + ".zip.pcv" - } else { - files++ - name := filepath.Base(names[0]) - nums := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} - endsNum := false - for _, i := range nums { - if strings.HasSuffix(names[0], i) { - endsNum = true - } - } - isSplit := strings.Contains(names[0], ".pcv.") && endsNum - - // Decide if encrypting or decrypting - if strings.HasSuffix(names[0], ".pcv") || isSplit { - //var err error - mode = "decrypt" - inputLabel = name + s(" (will decrypt)") - metadataPrompt = s("Metadata (read-only):") - metadataDisabled = true - - if isSplit { - inputLabel = name + s(" (will recombine and decrypt)") - ind := strings.Index(names[0], ".pcv") - names[0] = names[0][:ind+4] - inputFile = names[0] - outputFile = names[0][:ind] - recombine = true - } else { - outputFile = names[0][:len(names[0])-4] - } - - // Open input file in read-only mode - var fin *os.File - if isSplit { - fin, _ = os.Open(names[0] + ".0") - } else { - fin, _ = os.Open(names[0]) - } - - // Use regex to test if input is a valid Picocrypt volume - tmp := make([]byte, 30) - fin.Read(tmp) - if string(tmp[:5]) == "v1.13" { - resetUI() - mainStatus = "Please use Picocrypt v1.13 to decrypt this file." - mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} - fin.Close() - return - } - if valid, _ := regexp.Match(`^v\d\.\d{2}.{10}0?\d+`, tmp); !valid && !isSplit { - resetUI() - mainStatus = "This doesn't seem to be a Picocrypt volume." - mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} - fin.Close() - return - } - fin.Seek(0, 0) - - // Read metadata and insert into box - var err error - tmp = make([]byte, 15) - fin.Read(tmp) - tmp, _ = rsDecode(rs5, tmp) - if string(tmp) == "v1.14" || string(tmp) == "v1.15" || string(tmp) == "v1.16" { - resetUI() - mainStatus = "Please use Picocrypt v1.16 to decrypt this file." - mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} - fin.Close() - return - } - tmp = make([]byte, 15) - fin.Read(tmp) - tmp, err = rsDecode(rs5, tmp) - - if err == nil { - metadataLength, _ := strconv.Atoi(string(tmp)) - tmp = make([]byte, metadataLength*3) - fin.Read(tmp) - metadata = "" - - for i := 0; i < metadataLength*3; i += 3 { - t, err := rsDecode(rs1, tmp[i:i+3]) - if err != nil { - metadata = s("Metadata is corrupted.") - break - } - metadata += string(t) - } - } else { - metadata = s("Metadata is corrupted.") - } - - flags := make([]byte, 18) - fin.Read(flags) - fin.Close() - flags, err = rsDecode(rs6, flags) - if err != nil { - mainStatus = "Input file is corrupt and cannot be decrypted." - mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} - return - } - - if flags[2] == 1 { - keyfile = true - keyfilePrompt = s("Keyfiles required.") - } else { - keyfilePrompt = s("Not applicable.") - } - if flags[5] == 1 { - keyfileOrderMatters = true - } - } else { - mode = "encrypt" - inputLabel = name + s(" (will encrypt)") - inputFile = names[0] - outputFile = names[0] + ".pcv" - } - - // Add the file - onlyFiles = append(onlyFiles, names[0]) - inputFile = names[0] - } - } else { - mode = "encrypt" - - // There are multiple dropped items, check each one - for _, name := range names { - stat, _ := os.Stat(name) - - // Check if item is a file or a directory - if stat.IsDir() { - folders++ - onlyFolders = append(onlyFolders, name) - } else { - files++ - onlyFiles = append(onlyFiles, name) - allFiles = append(allFiles, name) - } - } - - if folders == 0 { - inputLabel = fmt.Sprintf(s("%d files selected."), files) - } else if files == 0 { - inputLabel = fmt.Sprintf(s("%d folders selected."), files) - } else { - if files == 1 && folders > 1 { - inputLabel = fmt.Sprintf(s("1 file and %d folders selected."), folders) - } else if folders == 1 && files > 1 { - inputLabel = fmt.Sprintf(s("%d files and 1 folder selected."), files) - } else if folders == 1 && files == 1 { - inputLabel = s("1 file and 1 folder selected.") - } else { - inputLabel = fmt.Sprintf(s("%d files and %d folders selected."), files, folders) - } - } - - // Set the input and output paths - inputFile = filepath.Join(filepath.Dir(names[0]), s("Encrypted")) + ".zip" - outputFile = filepath.Join(filepath.Dir(names[0]), s("Encrypted")) + ".zip.pcv" - } - // Recursively add all files to 'allFiles' - if folders > 0 { - for _, name := range onlyFolders { - filepath.Walk(name, func(path string, _ os.FileInfo, _ error) error { - stat, _ := os.Stat(path) - if !stat.IsDir() { - allFiles = append(allFiles, path) - } - return nil - }) - } - } -} - -func work() { - popupStatus = s("Starting...") - mainStatus = "Working..." - mainStatusColor = color.RGBA{0xff, 0xff, 0xff, 0xff} - working = true - padded := false - - var salt []byte - var hkdfSalt []byte - var serpentSalt []byte - var nonce []byte - var keyHash []byte - var _keyHash []byte - var keyfileKey []byte - var keyfileHash []byte = make([]byte, 32) - var _keyfileHash []byte - var dataMac []byte - - if mode == "encrypt" { - if compress { - popupStatus = s("Compressing files...") - } else { - popupStatus = s("Combining files...") - } - - // "Tar" files together (a .zip file with no compression) - if len(allFiles) > 1 || len(onlyFolders) > 0 { - var rootDir string - if len(onlyFolders) > 0 { - rootDir = filepath.Dir(onlyFolders[0]) - } else { - rootDir = filepath.Dir(onlyFiles[0]) - } - - file, err := os.Create(inputFile) - if err != nil { - mainStatus = "Access denied by operating system." - mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} - return - } - - w := zip.NewWriter(file) - for i, path := range allFiles { - if !working { - w.Close() - file.Close() - os.Remove(inputFile) - mainStatus = "Operation cancelled by user." - mainStatusColor = color.RGBA{0xff, 0xff, 0xff, 0xff} - return - } - progressInfo = fmt.Sprintf("%d/%d", i, len(allFiles)) - progress = float32(i) / float32(len(allFiles)) - giu.Update() - if path == inputFile { - continue - } - - stat, _ := os.Stat(path) - header, _ := zip.FileInfoHeader(stat) - header.Name = strings.TrimPrefix(path, rootDir) - - // Windows requires forward slashes in a .zip file - if runtime.GOOS == "windows" { - header.Name = strings.ReplaceAll(header.Name, "\\", "/") - header.Name = strings.TrimPrefix(header.Name, "/") - } - - if compress { - header.Method = zip.Deflate - } else { - header.Method = zip.Store - } - writer, _ := w.CreateHeader(header) - file, _ := os.Open(path) - io.Copy(writer, file) - file.Close() - } - w.Flush() - w.Close() - file.Close() - } - } - - if recombine { - popupStatus = s("Recombining file...") - total := 0 - - for { - _, err := os.Stat(fmt.Sprintf("%s.%d", inputFile, total)) - if err != nil { - break - } - total++ - } - - fout, _ := os.Create(inputFile) - for i := 0; i < total; i++ { - fin, _ := os.Open(fmt.Sprintf("%s.%d", inputFile, i)) - for { - data := make([]byte, 1048576) - read, err := fin.Read(data) - if err != nil { - break - } - data = data[:read] - fout.Write(data) - } - fin.Close() - progressInfo = fmt.Sprintf("%d/%d", i, total) - progress = float32(i) / float32(total) - giu.Update() - } - fout.Close() - progressInfo = "" - } - - stat, _ := os.Stat(inputFile) - total := stat.Size() - if mode == "decrypt" { - total -= 789 - } - - // XChaCha20's max message size is 256 GiB - if total > 256*1073741824 { - mainStatus = "Total size is larger than 256 GiB, XChaCha20's limit." - mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} - return - } - - // Open input file in read-only mode - fin, err := os.Open(inputFile) - if err != nil { - mainStatus = "Access denied by operating system." - mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} - return - } - - var fout *os.File - - // If encrypting, generate values; if decrypting, read values from file - if mode == "encrypt" { - popupStatus = s("Generating values...") - giu.Update() - - var err error - fout, err = os.Create(outputFile) - if err != nil { - mainStatus = "Access denied by operating system." - mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} - return - } - - // Generate random cryptography values - salt = make([]byte, 16) - hkdfSalt = make([]byte, 32) - serpentSalt = make([]byte, 16) - nonce = make([]byte, 24) - - // Write version to file - fout.Write(rsEncode(rs5, []byte(version))) - - // Encode the length of the metadata with Reed-Solomon - metadataLength := []byte(fmt.Sprintf("%05d", len(metadata))) - metadataLength = rsEncode(rs5, metadataLength) - - // Write the length of the metadata to file - fout.Write(metadataLength) - - // Reed-Solomon-encode the metadata and write to file - for _, i := range []byte(metadata) { - fout.Write(rsEncode(rs1, []byte{i})) - } - - flags := make([]byte, 6) - if fast { - flags[0] = 1 - } - if paranoid { - flags[1] = 1 - } - if len(keyfiles) > 0 { - flags[2] = 1 - } - if reedsolo { - flags[3] = 1 - } - if total%1048576 >= 1048448 { - flags[4] = 1 - } - if keyfileOrderMatters { - flags[5] = 1 - } - flags = rsEncode(rs6, flags) - fout.Write(flags) - - // Fill salts and nonce with Go's CSPRNG - rand.Read(salt) - rand.Read(hkdfSalt) - rand.Read(serpentSalt) - rand.Read(nonce) - - // Encode salt with Reed-Solomon and write to file - _salt := rsEncode(rs16, salt) - fout.Write(_salt) - - // Encode HKDF salt with Reed-Solomon and write to file - _hkdfSalt := rsEncode(rs32, hkdfSalt) - fout.Write(_hkdfSalt) - - // Encode Serpent salt with Reed-Solomon and write to file - _serpentSalt := rsEncode(rs16, serpentSalt) - fout.Write(_serpentSalt) - - // Encode nonce with Reed-Solomon and write to file - _nonce := rsEncode(rs24, nonce) - fout.Write(_nonce) - - // Write placeholder for hash of key - fout.Write(make([]byte, 192)) - - // Write placeholder for hash of hash of keyfile - fout.Write(make([]byte, 96)) - - // Write placeholder for HMAC-BLAKE2b/HMAC-SHA3 of file - fout.Write(make([]byte, 192)) - } else { - var err1 error - var err2 error - var err3 error - var err4 error - var err5 error - var err6 error - var err7 error - var err8 error - var err9 error - var err10 error - - popupStatus = s("Reading values...") - giu.Update() - - version := make([]byte, 15) - fin.Read(version) - _, err1 = rsDecode(rs5, version) - - tmp := make([]byte, 15) - fin.Read(tmp) - tmp, err2 = rsDecode(rs5, tmp) - metadataLength, _ := strconv.Atoi(string(tmp)) - - fin.Read(make([]byte, metadataLength*3)) - - flags := make([]byte, 18) - fin.Read(flags) - flags, err3 = rsDecode(rs6, flags) - fast = flags[0] == 1 - paranoid = flags[1] == 1 - reedsolo = flags[3] == 1 - padded = flags[4] == 1 - - salt = make([]byte, 48) - fin.Read(salt) - salt, err4 = rsDecode(rs16, salt) - - hkdfSalt = make([]byte, 96) - fin.Read(hkdfSalt) - hkdfSalt, err5 = rsDecode(rs32, hkdfSalt) - - serpentSalt = make([]byte, 48) - fin.Read(serpentSalt) - serpentSalt, err6 = rsDecode(rs16, serpentSalt) - - nonce = make([]byte, 72) - fin.Read(nonce) - nonce, err7 = rsDecode(rs24, nonce) - - _keyHash = make([]byte, 192) - fin.Read(_keyHash) - _keyHash, err8 = rsDecode(rs64, _keyHash) - - _keyfileHash = make([]byte, 96) - fin.Read(_keyfileHash) - _keyfileHash, err9 = rsDecode(rs32, _keyfileHash) - - dataMac = make([]byte, 192) - fin.Read(dataMac) - dataMac, err10 = rsDecode(rs64, dataMac) - - // Is there a better way? - if err1 != nil || err2 != nil || err3 != nil || err4 != nil || err5 != nil || - err6 != nil || err7 != nil || err8 != nil || err9 != nil || err10 != nil { - if keep { - kept = true - } else { - mainStatus = "The header is corrupt and the input file cannot be decrypted." - mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} - fin.Close() - return - } - } - } - - popupStatus = s("Deriving key...") - progress = 0 - progressInfo = "" - giu.Update() - - // Derive encryption/decryption keys and subkeys - var key []byte - if fast { - key = argon2.IDKey( - []byte(password), - salt, - 4, - 131072, - 4, - 32, - ) - } else if paranoid { - key = argon2.IDKey( - []byte(password), - salt, - 8, - 1048576, - 8, - 32, - ) - } else { - key = argon2.IDKey( - []byte(password), - salt, - 4, - 1048576, - 4, - 32, - ) - } - - if !working { - mainStatus = "Operation cancelled by user." - mainStatusColor = color.RGBA{0xff, 0xff, 0xff, 0xff} - if mode == "encrypt" && (len(allFiles) > 1 || len(onlyFolders) > 0) { - os.Remove(outputFile) - } - if recombine { - os.Remove(inputFile) - } - os.Remove(outputFile) - return - } - - if len(keyfiles) > 0 || keyfile { - if keyfileOrderMatters { - var keysum = sha3.New256() - for _, path := range keyfiles { - kin, _ := os.Open(path) - kstat, _ := os.Stat(path) - kbytes := make([]byte, kstat.Size()) - kin.Read(kbytes) - kin.Close() - keysum.Write(kbytes) - } - keyfileKey = keysum.Sum(nil) - keyfileSha3 := sha3.New256() - keyfileSha3.Write(keyfileKey) - keyfileHash = keyfileSha3.Sum(nil) - } else { - var keysum []byte - for _, path := range keyfiles { - kin, _ := os.Open(path) - kstat, _ := os.Stat(path) - kbytes := make([]byte, kstat.Size()) - kin.Read(kbytes) - kin.Close() - ksha3 := sha3.New256() - ksha3.Write(kbytes) - keyfileKey := ksha3.Sum(nil) - if keysum == nil { - keysum = keyfileKey - } else { - for i, j := range keyfileKey { - keysum[i] ^= j - } - } - } - keyfileKey = keysum - keyfileSha3 := sha3.New256() - keyfileSha3.Write(keysum) - keyfileHash = keyfileSha3.Sum(nil) - } - } - - sha3_512 := sha3.New512() - sha3_512.Write(key) - keyHash = sha3_512.Sum(nil) - - // Validate password and/or keyfiles - if mode == "decrypt" { - keyCorrect := true - keyfileCorrect := true - var tmp bool - - keyCorrect = !(subtle.ConstantTimeCompare(keyHash, _keyHash) == 0) - if keyfile { - keyfileCorrect = !(subtle.ConstantTimeCompare(keyfileHash, _keyfileHash) == 0) - tmp = !keyCorrect || !keyfileCorrect - } else { - tmp = !keyCorrect - } - - if tmp || keep { - if keep { - kept = true - } else { - fin.Close() - if !keyCorrect { - mainStatus = "The provided password is incorrect." - } else { - if keyfileOrderMatters { - mainStatus = "Incorrect keyfiles and/or order." - } else { - mainStatus = "Incorrect keyfiles." - } - } - mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} - key = nil - if recombine { - os.Remove(inputFile) - } - return - } - } - - var err error - fout, err = os.Create(outputFile) - if err != nil { - mainStatus = "Access denied by operating system." - mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} - return - } - } - - if len(keyfiles) > 0 || keyfile { - // XOR key and keyfile - tmp := key - key = make([]byte, 32) - for i := range key { - key[i] = tmp[i] ^ keyfileKey[i] - } - } - - done := 0 - counter := 0 - startTime := time.Now() - chacha20, _ := chacha20.NewUnauthenticatedCipher(key, nonce) - - // Use HKDF-SHA3 to generate a subkey - var mac hash.Hash - subkey := make([]byte, 32) - hkdf := hkdf.New(sha3.New256, key, hkdfSalt, nil) - hkdf.Read(subkey) - if fast { - // Keyed BLAKE2b - mac, _ = blake2b.New512(subkey) - } else { - // HMAC-SHA3 - mac = hmac.New(sha3.New512, subkey) - } - - // Generate another subkey and cipher (not used unless paranoid mode is checked) - serpentKey := make([]byte, 32) - hkdf.Read(serpentKey) - _serpent, _ := serpent.NewCipher(serpentKey) - serpentCTR := cipher.NewCTR(_serpent, serpentSalt) - - for { - if !working { - mainStatus = "Operation cancelled by user." - mainStatusColor = color.RGBA{0xff, 0xff, 0xff, 0xff} - fin.Close() - fout.Close() - if mode == "encrypt" && (len(allFiles) > 1 || len(onlyFolders) > 0) { - os.Remove(outputFile) - } - if recombine { - os.Remove(inputFile) - } - os.Remove(outputFile) - return - } - - var data []byte - if mode == "decrypt" && reedsolo { - data = make([]byte, 1114112) - } else { - data = make([]byte, 1048576) - } - - size, err := fin.Read(data) - if err != nil { - break - } - data = data[:size] - _data := make([]byte, len(data)) - - // "Actual" encryption is done in the next couple of lines - if mode == "encrypt" { - if paranoid { - serpentCTR.XORKeyStream(_data, data) - copy(data, _data) - } - - chacha20.XORKeyStream(_data, data) - mac.Write(_data) - - if reedsolo { - copy(data, _data) - _data = nil - if len(data) == 1048576 { - for i := 0; i < 1048576; i += 128 { - tmp := data[i : i+128] - tmp = rsEncode(rs128, tmp) - _data = append(_data, tmp...) - } - } else { - chunks := math.Floor(float64(len(data)) / 128) - for i := 0; float64(i) < chunks; i++ { - tmp := data[i*128 : (i+1)*128] - tmp = rsEncode(rs128, tmp) - _data = append(_data, tmp...) - } - tmp := data[int(chunks*128):] - _data = append(_data, rsEncode(rs128, pad(tmp))...) - } - } - } else { - if reedsolo { - copy(_data, data) - data = nil - if len(_data) == 1114112 { - for i := 0; i < 1114112; i += 136 { - tmp := _data[i : i+136] - tmp, err = rsDecode(rs128, tmp) - if err != nil { - if keep { - kept = true - } else { - mainStatus = "The input file is too corrupted to decrypt." - mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} - fin.Close() - fout.Close() - broken() - return - } - } - if i == 1113976 && done+1114112 >= int(total) && padded { - tmp = unpad(tmp) - } - data = append(data, tmp...) - } - } else { - chunks := len(_data)/136 - 1 - for i := 0; i < chunks; i++ { - tmp := _data[i*136 : (i+1)*136] - tmp, err = rsDecode(rs128, tmp) - if err != nil { - if keep { - kept = true - } else { - mainStatus = "The input file is too corrupted to decrypt." - mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} - fin.Close() - fout.Close() - broken() - return - } - } - data = append(data, tmp...) - } - tmp := _data[int(chunks)*136:] - tmp, err = rsDecode(rs128, tmp) - if err != nil { - if keep { - kept = true - } else { - mainStatus = "The input file is too corrupted to decrypt." - mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} - fin.Close() - fout.Close() - broken() - return - } - } - tmp = unpad(tmp) - data = append(data, tmp...) - } - _data = make([]byte, len(data)) - } - - mac.Write(data) - chacha20.XORKeyStream(_data, data) - - if paranoid { - copy(data, _data) - serpentCTR.XORKeyStream(_data, data) - } - } - fout.Write(_data) - - // Update stats - if mode == "decrypt" && reedsolo { - done += 1114112 - } else { - done += 1048576 - } - counter++ - progress = float32(done) / float32(total) - elapsed := float64(time.Since(startTime)) / math.Pow(10, 9) - speed := float64(done) / elapsed / math.Pow(10, 6) - eta := int(math.Floor(float64(total-int64(done)) / (speed * math.Pow(10, 6)))) - - if progress > 1 { - progress = 1 - } - - progressInfo = fmt.Sprintf("%.2f%%", progress*100) - popupStatus = fmt.Sprintf(s("Working at %.2f MB/s (ETA: %s)"), speed, humanize(eta)) - giu.Update() - } - - if mode == "encrypt" { - // Seek back to header and write important data - fout.Seek(int64(312+len(metadata)*3), 0) - fout.Write(rsEncode(rs64, keyHash)) - fout.Write(rsEncode(rs32, keyfileHash)) - fout.Write(rsEncode(rs64, mac.Sum(nil))) - } else { - // Validate the authenticity of decrypted data - if subtle.ConstantTimeCompare(mac.Sum(nil), dataMac) == 0 { - if keep { - kept = true - } else { - fin.Close() - fout.Close() - broken() - return - } - } - } - - fin.Close() - fout.Close() - - // Split files into chunks - if split { - var splitted []string - popupStatus = s("Splitting file...") - stat, _ := os.Stat(outputFile) - size := stat.Size() - finished := 0 - chunkSize, _ := strconv.Atoi(splitSize) - - // User can choose KiB, MiB, and GiB - if splitSelected == 0 { - chunkSize *= 1024 - } else if splitSelected == 1 { - chunkSize *= 1048576 - } else { - chunkSize *= 1073741824 - } - chunks := int(math.Ceil(float64(size) / float64(chunkSize))) - fin, _ := os.Open(outputFile) - - for i := 0; i < chunks; i++ { - fout, _ := os.Create(fmt.Sprintf("%s.%d", outputFile, i)) - done := 0 - for { - data := make([]byte, 1048576) - read, err := fin.Read(data) - if err != nil { - break - } - if !working { - fin.Close() - fout.Close() - mainStatus = "Operation cancelled by user." - mainStatusColor = color.RGBA{0xff, 0xff, 0xff, 0xff} - - // If user cancels, remove the unfinished files - for _, j := range splitted { - os.Remove(j) - } - os.Remove(fmt.Sprintf("%s.%d", outputFile, i)) - os.Remove(outputFile) - return - } - data = data[:read] - fout.Write(data) - done += read - if done >= chunkSize { - break - } - } - fout.Close() - finished++ - splitted = append(splitted, fmt.Sprintf("%s.%d", outputFile, i)) - progress = float32(finished) / float32(chunks) - progressInfo = fmt.Sprintf("%d/%d", finished, chunks) - giu.Update() - } - fin.Close() - if shredTemp { - progressInfo = "" - popupStatus = s("Shredding temporary files...") - shred([]string{inputFile + ".pcv"}, false) - } else { - os.Remove(inputFile + ".pcv") - } - } - - // Remove the temporary file used to combine a splitted Picocrypt volume - if recombine { - os.Remove(inputFile) - } - - // Delete the temporary zip file if user wishes - if len(allFiles) > 1 || len(onlyFolders) > 0 { - if shredTemp { - progressInfo = "" - popupStatus = s("Shredding temporary files...") - giu.Update() - shred([]string{inputFile}, false) - } else { - os.Remove(inputFile) - } - } - - if deleteWhenDone { - progressInfo = "" - popupStatus = s("Deleted files...") - giu.Update() - if mode == "decrypt" { - if recombine { - total := 0 - for { - _, err := os.Stat(fmt.Sprintf("%s.%d", inputFile, total)) - if err != nil { - break - } - os.Remove(fmt.Sprintf("%s.%d", inputFile, total)) - total++ - } - } else { - os.Remove(inputFile) - } - } else { - for _, i := range onlyFiles { - os.Remove(i) - } - for _, i := range onlyFolders { - os.RemoveAll(i) - } - } - } - - resetUI() - - // If user chose to keep a corrupted/modified file, let them know - if kept { - mainStatus = "The input file is corrupted and/or modified. Please be careful." - mainStatusColor = color.RGBA{0xff, 0xff, 0x00, 0xff} - } else { - mainStatus = "Completed." - mainStatusColor = color.RGBA{0x00, 0xff, 0x00, 0xff} - } - - // Clear UI state - working = false - kept = false - key = nil - popupStatus = s("Ready.") -} - -// This function is run if an issue occurs during decryption -func broken() { - mainStatus = "The input file is either corrupted or intentionally modified." - mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} - if recombine { - os.Remove(inputFile) - } - os.Remove(outputFile) - giu.Update() -} - -// Generate file checksums -func generateChecksums(file string) { - fin, _ := os.Open(file) - - // Clear UI state - csMd5 = "" - csSha1 = "" - csSha256 = "" - csSha3 = "" - csBlake2b = "" - csBlake2s = "" - md5Color = color.RGBA{0x00, 0x00, 0x00, 0x00} - sha1Color = color.RGBA{0x00, 0x00, 0x00, 0x00} - sha256Color = color.RGBA{0x00, 0x00, 0x00, 0x00} - sha3Color = color.RGBA{0x00, 0x00, 0x00, 0x00} - blake2bColor = color.RGBA{0x00, 0x00, 0x00, 0x00} - blake2sColor = color.RGBA{0x00, 0x00, 0x00, 0x00} - csValidate = "" - - if md5Selected { - csMd5 = s("Calculating...") - } - if sha1Selected { - csSha1 = s("Calculating...") - } - if sha256Selected { - csSha256 = s("Calculating...") - } - if sha3Selected { - csSha3 = s("Calculating...") - } - if blake2bSelected { - csBlake2b = s("Calculating...") - } - if blake2sSelected { - csBlake2s = s("Calculating...") - } - - // Create the checksum objects - crcMd5 := md5.New() - crcSha1 := sha1.New() - crcSha256 := sha256.New() - crcSha3 := sha3.New256() - crcBlake2b, _ := blake2b.New256(nil) - crcBlake2s, _ := blake2s.New256(nil) - - stat, _ := os.Stat(file) - total := stat.Size() - var done int64 = 0 - for { - var data []byte - _data := make([]byte, 1048576) - size, err := fin.Read(_data) - if err != nil { - break - } - data = _data[:size] - - if md5Selected { - crcMd5.Write(data) - } - if sha1Selected { - crcSha1.Write(data) - } - if sha256Selected { - crcSha256.Write(data) - } - if sha3Selected { - crcSha3.Write(data) - } - if blake2bSelected { - crcBlake2b.Write(data) - } - if blake2sSelected { - crcBlake2s.Write(data) - } - - done += int64(size) - csProgress = float32(done) / float32(total) - giu.Update() - } - csProgress = 0 - if md5Selected { - csMd5 = hex.EncodeToString(crcMd5.Sum(nil)) - } - if sha1Selected { - csSha1 = hex.EncodeToString(crcSha1.Sum(nil)) - } - if sha256Selected { - csSha256 = hex.EncodeToString(crcSha256.Sum(nil)) - } - if sha3Selected { - csSha3 = hex.EncodeToString(crcSha3.Sum(nil)) - } - if blake2bSelected { - csBlake2b = hex.EncodeToString(crcBlake2b.Sum(nil)) - } - if blake2sSelected { - csBlake2s = hex.EncodeToString(crcBlake2s.Sum(nil)) - } - - fin.Close() - giu.Update() -} - -// Recursively shred all file(s) and folder(s) passed in as 'names' -func shred(names []string, separate bool) { - stopShredding = false - shredTotal = 0 - shredDone = 0 - - // 'separate' is true if this function is being called from the encryption/decryption tab - if separate { - shredOverlay = s("Shredding...") - } - - // Walk through directories to get the total number of files for statistics - for _, name := range names { - filepath.Walk(name, func(path string, _ os.FileInfo, err error) error { - if err != nil { - return nil - } - stat, _ := os.Stat(path) - if !stat.IsDir() { - shredTotal++ - } - return nil - }) - } - - for _, name := range names { - shredding = name - - // Linux and macOS need a command with similar syntax and usage, so they're combined - if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { - stat, _ := os.Stat(name) - if stat.IsDir() { - var coming []string - - // Walk the folder recursively - filepath.Walk(name, func(path string, _ os.FileInfo, err error) error { - if err != nil { - return nil - } - if stopShredding { - return nil - } - stat, _ := os.Stat(path) - if !stat.IsDir() { - if len(coming) == 128 { - // Use a WaitGroup to parallelize shredding - var wg sync.WaitGroup - for i, j := range coming { - wg.Add(1) - go func(wg *sync.WaitGroup, id int, j string) { - defer wg.Done() - shredding = j - var cmd *exec.Cmd - if runtime.GOOS == "linux" { - cmd = exec.Command("shred", "-ufvz", "-n", strconv.Itoa(int(shredPasses)), j) - } else { - cmd = exec.Command("rm", "-rfP", j) - } - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - cmd.Run() - shredDone++ - shredUpdate(separate) - giu.Update() - }(&wg, i, j) - } - wg.Wait() - coming = nil - } else { - coming = append(coming, path) - } - } - return nil - }) - for _, i := range coming { - if stopShredding { - break - } - go func(i string) { - shredding = i - var cmd *exec.Cmd - if runtime.GOOS == "linux" { - cmd = exec.Command("shred", "-ufvz", "-n", strconv.Itoa(int(shredPasses)), i) - } else { - cmd = exec.Command("rm", "-rfP", i) - } - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - cmd.Run() - shredDone++ - shredUpdate(separate) - giu.Update() - }(i) - } - if !stopShredding { - os.RemoveAll(name) - } - } else { // The path is a file, not a directory, so just shred it - shredding = name - var cmd *exec.Cmd - if runtime.GOOS == "linux" { - cmd = exec.Command("shred", "-ufvz", "-n", strconv.Itoa(int(shredPasses)), name) - } else { - cmd = exec.Command("rm", "-rfP", name) - } - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - cmd.Run() - shredDone++ - shredUpdate(separate) - } - } else if runtime.GOOS == "windows" { - stat, _ := os.Stat(name) - if stat.IsDir() { - // Walk the folder recursively - filepath.Walk(name, func(path string, _ os.FileInfo, err error) error { - if err != nil { - return nil - } - stat, _ := os.Stat(path) - if stat.IsDir() { - if stopShredding { - return nil - } - - t := 0 - files, _ := ioutil.ReadDir(path) - for _, f := range files { - if !f.IsDir() { - t++ - } - } - shredDone += float32(t) - shredUpdate(separate) - shredding = strings.ReplaceAll(path, "\\", "/") + "/*" - cmd := exec.Command(sdelete64path, "*", "-p", strconv.Itoa(int(shredPasses))) - cmd.Dir = path - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - cmd.Run() - giu.Update() - } - return nil - }) - - if !stopShredding { - // sdelete64 doesn't delete the empty folder, so I'll do it manually - os.RemoveAll(name) - } - } else { - shredding = name - cmd := exec.Command(sdelete64path, name, "-p", strconv.Itoa(int(shredPasses))) - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - cmd.Run() - shredDone++ - shredUpdate(separate) - } - } - giu.Update() - if stopShredding { - return - } - } - - // Clear UI state - shredding = s("Completed.") - shredProgress = 0 - shredOverlay = "" -} - -// Update shredding statistics -func shredUpdate(separate bool) { - if separate { - shredOverlay = fmt.Sprintf("%d/%d", int(shredDone), int(shredTotal)) - shredProgress = shredDone / shredTotal - } else { - popupStatus = fmt.Sprintf("%d/%d", int(shredDone), int(shredTotal)) - progress = shredDone / shredTotal - } - giu.Update() -} - -// Reset the UI to a clean state with nothing selected or checked -func resetUI() { - mode = "" - onlyFiles = nil - onlyFolders = nil - allFiles = nil - inputLabel = s("Drop files and folders into this window.") - password = "" - cPassword = "" - keyfiles = nil - keyfile = false - keyfileOrderMatters = false - keyfilePrompt = s("None selected.") - metadata = "" - metadataPrompt = "Metadata:" - metadataDisabled = false - shredTemp = false - keep = false - reedsolo = false - split = false - splitSize = "" - splitSelected = 1 - fast = false - deleteWhenDone = false - paranoid = false - compress = false - encryptFilename = false - inputFile = "" - outputFile = "" - progress = 0 - progressInfo = "" - mainStatus = "Ready." - mainStatusColor = color.RGBA{0xff, 0xff, 0xff, 0xff} - giu.Update() -} - -// Reed-Solomon encoder -func rsEncode(rs *infectious.FEC, data []byte) []byte { - var res []byte - rs.Encode(data, func(s infectious.Share) { - res = append(res, s.DeepCopy().Data[0]) - }) - return res -} - -// Reed-Solomon decoder -func rsDecode(rs *infectious.FEC, data []byte) ([]byte, error) { - tmp := make([]infectious.Share, rs.Total()) - for i := 0; i < rs.Total(); i++ { - tmp[i] = infectious.Share{ - Number: i, - Data: []byte{data[i]}, - } - } - res, err := rs.Decode(nil, tmp) - if err != nil { - if rs.Total() == 136 { - return data[:128], err - } - return data[:rs.Total()/3], err - } - return res, nil -} - -// PKCS7 Pad (for use with Reed-Solomon, not for cryptographic purposes) -func pad(data []byte) []byte { - padLen := 128 - len(data)%128 - padding := bytes.Repeat([]byte{byte(padLen)}, padLen) - return append(data, padding...) -} - -// PKCS7 Unpad -func unpad(data []byte) []byte { - length := len(data) - padLen := int(data[length-1]) - return data[:length-padLen] -} - -func genPassword() string { - chars := "" - if genpassUpper { - chars += "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - } - if genpassLower { - chars += "abcdefghijklmnopqrstuvwxyz" - } - if genpassNums { - chars += "1234567890" - } - if genpassSymbols { - chars += "-=!@#$^&()_+?" - } - if chars == "" { - return chars - } - tmp := make([]byte, genpassLength) - for i := 0; i < int(genpassLength); i++ { - j, _ := rand.Int(rand.Reader, new(big.Int).SetUint64(uint64(len(chars)))) - tmp[i] = chars[j.Int64()] - } - if genpassCopy { - clipboard.WriteAll(string(tmp)) - } - return string(tmp) -} - -// Convert seconds to HH:MM:SS -func humanize(seconds int) string { - hours := int(math.Floor(float64(seconds) / 3600)) - seconds %= 3600 - minutes := int(math.Floor(float64(seconds) / 60)) - seconds %= 60 - hours = int(math.Max(float64(hours), 0)) - minutes = int(math.Max(float64(minutes), 0)) - seconds = int(math.Max(float64(seconds), 0)) - return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) -} - -func s(term string) string { - for _, i := range locales { - if i.iso == selectedLocale { - for _, j := range locales { - if j.iso == "en" { - for k, l := range j.data { - if l == term { - return i.data[k] - } - } - } - } - - } - } - return term -} - -func main() { - // Parse locales - var obj map[string]json.RawMessage - json.Unmarshal(localeBytes, &obj) - for i := range obj { - var tmp []string - json.Unmarshal(obj[i], &tmp) - locales = append(locales, locale{ - iso: i, - data: tmp, - }) - } - - // Check system locale - for _, i := range locales { - tmp, err := jibber_jabber.DetectIETF() - if err == nil { - if strings.HasPrefix(tmp, i.iso) { - selectedLocale = i.iso - for j, k := range allLocales { - if k == i.iso { - languageSelected = int32(j) - } - } - } - } - } - - // Create a temporary file to store sdelete64.exe - sdelete64, _ := os.CreateTemp("", "sdelete64.*.exe") - sdelete64path = sdelete64.Name() - sdelete64.Write(sdelete64bytes) - sdelete64.Close() - cmd := exec.Command(sdelete64path, "/accepteula") - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - cmd.Run() - - // Set a universal font - giu.SetDefaultFontFromBytes(font, 18) - - // Create the master window - window := giu.NewMasterWindow("Picocrypt", 442, 532, giu.MasterWindowFlagsNotResizable) - dialog.Init() - - // Set window icon - reader := bytes.NewReader(icon) - decoded, _ := png.Decode(reader) - window.SetIcon([]image.Image{decoded}) - - // Set callbacks - window.SetDropCallback(onDrop) - window.SetCloseCallback(func() bool { - return !working - }) - - // Set universal DPI - dpi = giu.Context.GetPlatform().GetContentScale() - - // Start a goroutine to check if a newer version is available - go func() { - v, err := http.Get("https://raw.githubusercontent.com/HACKERALERT/Picocrypt/main/internals/version.txt") - if err == nil { - body, err := io.ReadAll(v.Body) - v.Body.Close() - if err == nil { - if string(body[:5]) != version { - mainStatus = "A newer version is available." - mainStatusColor = color.RGBA{0, 255, 0, 255} - } - } - } - }() - - // Start the UI - window.Run(draw) - - // Window closed, clean up - os.Remove(sdelete64path) -} +package main + +/* + +Picocrypt v1.20 +Copyright (c) Evan Su (https://evansu.cc) +Released under a GNU GPL v3 License +https://github.com/HACKERALERT/Picocrypt + +~ In cryptography we trust ~ + +*/ + +import ( + _ "embed" + + // Generic + "archive/zip" + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "hash" + "image" + "image/color" + "image/png" + "io" + "io/ioutil" + "math" + "math/big" + "net/http" + "os" + "os/exec" + "path/filepath" + "regexp" + "runtime" + "runtime/debug" + "strconv" + "strings" + "sync" + "syscall" + "time" + + // Cryptography + "crypto/cipher" + "crypto/hmac" + "crypto/md5" + "crypto/rand" + "crypto/sha1" + "crypto/sha256" + "crypto/subtle" + + "github.com/HACKERALERT/serpent" + "golang.org/x/crypto/argon2" + "golang.org/x/crypto/blake2b" + "golang.org/x/crypto/blake2s" + "golang.org/x/crypto/chacha20" + "golang.org/x/crypto/hkdf" + "golang.org/x/crypto/sha3" + + // UI + "github.com/HACKERALERT/giu" + + // Reed-Solomon + "github.com/HACKERALERT/infectious" + + // Helpers + "github.com/HACKERALERT/clipboard" + "github.com/HACKERALERT/dialog" + "github.com/HACKERALERT/jibber_jabber" + "github.com/HACKERALERT/zxcvbn-go" +) + +//go:embed icon.png +var icon []byte + +//go:embed font.ttf +var font []byte + +//go:embed sdelete64.exe +var sdelete64bytes []byte + +//go:embed strings.json +var localeBytes []byte + +// Localization +type locale struct { + iso string + data []string +} + +var locales []locale +var selectedLocale = "en" +var allLocales = []string{ + "en", +} +var languages = []string{ + "English", +} +var languageSelected int32 + +// Generic variables +var version = "v1.20" +var window *giu.MasterWindow +var windowOptimized bool +var dpi float32 +var tab = 0 +var mode string +var working bool +var recombine bool +var sdelete64path string + +// Three variables store the input files +var onlyFiles []string +var onlyFolders []string +var allFiles []string + +// Input file variables +var inputLabel = "Drop files and folders into this window." +var inputFile string + +// Password variables +var password string +var cPassword string +var passwordStrength int +var passwordState = giu.InputTextFlagsPassword +var passwordStateLabel = "Show" + +// Password generator variables +var showGenpass = false +var genpassCopy = true +var genpassLength int32 = 32 +var genpassUpper = true +var genpassLower = true +var genpassNums = true +var genpassSymbols = true + +// Keyfile variables +var keyfile bool +var keyfiles []string +var keyfileOrderMatters bool +var keyfilePrompt = "None selected." +var showKeyfile bool + +// Metadata variables +var metadata string +var metadataPrompt = "Metadata:" +var metadataDisabled bool + +// Advanced options +var shredTemp bool +var fast bool +var paranoid bool +var reedsolo bool +var deleteWhenDone bool +var split bool +var splitSize string +var splitUnits = []string{ + "KiB", + "MiB", + "GiB", +} +var splitSelected int32 = 1 +var compress bool +var encryptFilename bool +var keep bool +var kept bool + +// Output file variables +var outputFile string + +// Status variables +var mainStatus = "Ready." +var mainStatusColor = color.RGBA{0xff, 0xff, 0xff, 0xff} +var popupStatus string + +// Progress variables +var progress float32 +var progressInfo string +var showProgress bool + +// Confirm overwrite variables +var showConfirmation bool + +// Reed-Solomon encoders +var rs1, _ = infectious.NewFEC(1, 3) // 1 data shard, 3 total -> 2 parity shards +var rs5, _ = infectious.NewFEC(5, 15) +var rs6, _ = infectious.NewFEC(6, 18) +var rs16, _ = infectious.NewFEC(16, 48) +var rs24, _ = infectious.NewFEC(24, 72) +var rs32, _ = infectious.NewFEC(32, 96) +var rs64, _ = infectious.NewFEC(64, 192) +var rs128, _ = infectious.NewFEC(128, 136) + +// File checksum generator variables +var csMd5 string +var csSha1 string +var csSha256 string +var csSha3 string +var csBlake2b string +var csBlake2s string +var csValidate string +var md5Color = color.RGBA{0x00, 0x00, 0x00, 0x00} +var sha1Color = color.RGBA{0x00, 0x00, 0x00, 0x00} +var sha256Color = color.RGBA{0x00, 0x00, 0x00, 0x00} +var sha3Color = color.RGBA{0x00, 0x00, 0x00, 0x00} +var blake2bColor = color.RGBA{0x00, 0x00, 0x00, 0x00} +var blake2sColor = color.RGBA{0x00, 0x00, 0x00, 0x00} +var csProgress float32 = 0 +var md5Selected = true +var sha1Selected = true +var sha256Selected = true +var sha3Selected = false +var blake2bSelected = false +var blake2sSelected = false + +// Shredder variables +var shredding = "Ready." +var shredPasses int32 = 4 +var stopShredding bool +var shredProgress float32 +var shredDone float32 +var shredTotal float32 +var shredText string +var shredOverlay string + +func draw() { + giu.SingleWindow().Flags(giu.WindowFlagsNoDecoration|giu.WindowFlagsNoNavFocus|giu.WindowFlagsNoMove| + giu.WindowFlagsNoScrollWithMouse).Layout( + giu.Custom(func() { + pos := giu.GetCursorPos() + w, _ := giu.CalcTextSize(languages[languageSelected]) + giu.Row( + giu.Dummy(-w/dpi-34, 0), + giu.Combo("##language", languages[languageSelected], languages, &languageSelected).OnChange(func() { + selectedLocale = allLocales[languageSelected] + }).Size(w/dpi+26), + ).Build() + giu.SetCursorPos(pos) + + giu.TabBar().TabItems( + giu.TabItem(s("Encryption")).Layout( + giu.Custom(func() { + if giu.IsItemActive() { + tab = 0 + } + }), + + giu.Custom(func() { + if showGenpass { + giu.PopupModal(s("Generate password:")). + Flags(giu.WindowFlagsNoMove|giu.WindowFlagsNoResize).Layout( + giu.Row( + giu.Label(s("Length:")), + giu.SliderInt(&genpassLength, 4, 64).Size(giu.Auto), + ), + giu.Checkbox(s("Uppercase"), &genpassUpper), + giu.Checkbox(s("Lowercase"), &genpassLower), + giu.Checkbox(s("Numbers"), &genpassNums), + giu.Checkbox(s("Symbols"), &genpassSymbols), + giu.Checkbox(s("Copy to clipboard"), &genpassCopy), + giu.Row( + giu.Button(s("Cancel")).Size(100, 0).OnClick(func() { + giu.CloseCurrentPopup() + showGenpass = false + }), + giu.Button(s("Generate")).Size(100, 0).OnClick(func() { + tmp := genPassword() + password = tmp + cPassword = tmp + passwordStrength = zxcvbn.PasswordStrength(password, nil).Score + giu.CloseCurrentPopup() + showGenpass = false + giu.Update() + }), + ), + ).Build() + giu.OpenPopup(s("Generate password:")) + giu.Update() + } + }), + + giu.Custom(func() { + if showKeyfile { + giu.PopupModal(s("Manage keyfiles:")). + Flags(giu.WindowFlagsNoMove|giu.WindowFlagsNoResize|giu.WindowFlagsAlwaysAutoResize).Layout( + giu.Label(s("Drag and drop your keyfiles here.")), + giu.Custom(func() { + if mode != "decrypt" { + giu.Checkbox(s("Require correct keyfile order"), &keyfileOrderMatters).Build() + giu.Tooltip(s("If checked, you will need to drop keyfiles in the correct order.")).Build() + } else if keyfileOrderMatters { + giu.Label(s("The correct order of keyfiles is required.")).Build() + } + }), + + giu.Custom(func() { + for _, i := range keyfiles { + giu.Row( + giu.SmallButton("×").OnClick(func() { + var tmp []string + for _, j := range keyfiles { + if j != i { + tmp = append(tmp, j) + } + } + keyfiles = tmp + if len(keyfiles) == 0 { + keyfilePrompt = s("None selected.") + } else if len(keyfiles) == 1 { + keyfilePrompt = s("Using 1 keyfile.") + } else { + keyfilePrompt = fmt.Sprintf(s("Using %d keyfiles."), len(keyfiles)) + } + }), + giu.Label(filepath.Base(i)), + ).Build() + + } + }), + giu.Row( + giu.Button(s("Clear")).Size(150, 0).OnClick(func() { + keyfiles = nil + keyfilePrompt = s("None selected.") + }), + giu.Tooltip(s("Remove all keyfiles.")), + giu.Button(s("Done")).Size(150, 0).OnClick(func() { + giu.CloseCurrentPopup() + showKeyfile = false + }), + ), + ).Build() + giu.OpenPopup(s("Manage keyfiles:")) + giu.Update() + } + }), + + giu.Custom(func() { + if showConfirmation { + giu.PopupModal(s("Warning:")). + Flags(giu.WindowFlagsNoMove|giu.WindowFlagsNoResize).Layout( + giu.Label(s("Output already exists. Overwrite?")), + giu.Row( + giu.Button(s("No")).Size(100, 0).OnClick(func() { + giu.CloseCurrentPopup() + showConfirmation = false + }), + giu.Button(s("Yes")).Size(100, 0).OnClick(func() { + giu.CloseCurrentPopup() + showConfirmation = false + showProgress = true + giu.Update() + go func() { + work() + working = false + showProgress = false + debug.FreeOSMemory() + giu.Update() + }() + }), + ), + ).Build() + giu.OpenPopup(s("Warning:")) + giu.Update() + } + }), + + giu.Custom(func() { + if showProgress { + giu.PopupModal(" "). + Flags(giu.WindowFlagsNoMove|giu.WindowFlagsNoResize).Layout( + giu.Custom(func() { + if !working { + giu.CloseCurrentPopup() + } + }), + giu.Row( + giu.ProgressBar(progress).Size(280, 0).Overlay(progressInfo), + giu.Button(s("Cancel")).Size(58, 0).OnClick(func() { + working = false + }), + ), + giu.Label(popupStatus), + ).Build() + giu.OpenPopup(" ") + giu.Update() + } + }), + + giu.Row( + giu.Label(inputLabel), + giu.Custom(func() { + bw, _ := giu.CalcTextSize(s("Clear")) + p, _ := giu.GetWindowPadding() + bw += p * 2 + giu.Dummy(float32(float64(-(bw+p)/dpi)), 0).Build() + giu.SameLine() + giu.Style().SetDisabled(len(allFiles) == 0 && len(onlyFiles) == 0).To( + giu.Button(s("Clear")).Size(bw/dpi, 0).OnClick(resetUI), + giu.Tooltip(s("Clear all input files and reset UI state.")), + ).Build() + }), + ), + + giu.Separator(), + + giu.Style().SetDisabled(len(allFiles) == 0 && len(onlyFiles) == 0).To( + giu.Row( + giu.Label(s("Password:")), + giu.Dummy(-124, 0), + giu.Style().SetDisabled(mode == "decrypt" && !keyfile).To( + giu.Label(s("Keyfiles:")), + ), + ), + giu.Row( + giu.Button(s(passwordStateLabel)).Size(54, 0).OnClick(func() { + if passwordState == giu.InputTextFlagsPassword { + passwordState = giu.InputTextFlagsNone + passwordStateLabel = "Hide" + } else { + passwordState = giu.InputTextFlagsPassword + passwordStateLabel = "Show" + } + }), + + giu.Button(s("Clear")).Size(54, 0).OnClick(func() { + password = "" + cPassword = "" + }), + + giu.Button(s("Copy")).Size(54, 0).OnClick(func() { + clipboard.WriteAll(password) + }), + + giu.Button(s("Paste")).Size(54, 0).OnClick(func() { + tmp, _ := clipboard.ReadAll() + password = tmp + if mode != "decrypt" { + cPassword = tmp + } + passwordStrength = zxcvbn.PasswordStrength(password, nil).Score + giu.Update() + }), + + giu.Style().SetDisabled(mode == "decrypt").To( + giu.Button(s("Create")).Size(54, 0).OnClick(func() { + showGenpass = true + }), + ), + + giu.Style().SetDisabled(mode == "decrypt" && !keyfile).To( + giu.Row( + giu.Button(s("Edit")).Size(54, 0).OnClick(func() { + showKeyfile = true + }), + giu.Style().SetDisabled(mode == "decrypt").To( + giu.Button(s("Create")).Size(54, 0).OnClick(func() { + file, _ := dialog.File().Title(s("Save keyfile as:")).Save() + if file == "" { + return + } + fout, _ := os.Create(file) + data := make([]byte, 1048576) + rand.Read(data) + fout.Write(data) + fout.Close() + }), + ), + ), + ), + ), + giu.Row( + giu.InputText(&password).Flags(passwordState).Size(302/dpi).OnChange(func() { + passwordStrength = zxcvbn.PasswordStrength(password, nil).Score + }), + giu.Custom(func() { + c := giu.GetCanvas() + p := giu.GetCursorScreenPos() + + var col color.RGBA + switch passwordStrength { + case 0: + col = color.RGBA{0xc8, 0x4c, 0x4b, 0xff} + case 1: + col = color.RGBA{0xa9, 0x6b, 0x4b, 0xff} + case 2: + col = color.RGBA{0x8a, 0x8a, 0x4b, 0xff} + case 3: + col = color.RGBA{0x6b, 0xa9, 0x4b, 0xff} + case 4: + col = color.RGBA{0x4c, 0xc8, 0x4b, 0xff} + } + if password == "" || mode == "decrypt" { + col = color.RGBA{0xff, 0xff, 0xff, 0x00} + } + + path := p.Add(image.Pt( + int(math.Round(float64(-20*dpi))), + int(math.Round(float64(12*dpi))), + )) + c.PathArcTo(path, 6*dpi, -math.Pi/2, float32(passwordStrength+1)/5*2*math.Pi-math.Pi/2, -1) + c.PathStroke(col, false, 2) + }), + giu.Style().SetDisabled(true).To( + giu.InputText(&keyfilePrompt).Size(giu.Auto), + ), + ), + ), + + giu.Style().SetDisabled(password == "").To( + giu.Row( + giu.Style().SetDisabled(mode == "decrypt").To( + giu.Label(s("Confirm password:")), + ), + giu.Dummy(-124, 0), + giu.Style().SetDisabled(true).To( + giu.Label(s("Custom Argon2:")), + ), + ), + ), + giu.Style().SetDisabled(password == "").To( + giu.Row( + giu.Style().SetDisabled(mode == "decrypt").To( + giu.Row( + giu.InputText(&cPassword).Flags(passwordState).Size(302/dpi), + giu.Custom(func() { + c := giu.GetCanvas() + p := giu.GetCursorScreenPos() + col := color.RGBA{0x4c, 0xc8, 0x4b, 0xff} + + if cPassword != password { + col = color.RGBA{0xc8, 0x4c, 0x4b, 0xff} + } + if password == "" || cPassword == "" || mode == "decrypt" { + col = color.RGBA{0xff, 0xff, 0xff, 0x00} + } + + path := p.Add(image.Pt( + int(math.Round(float64(-20*dpi))), + int(math.Round(float64(12*dpi))), + )) + c.PathArcTo(path, 6*dpi, 0, 2*math.Pi, -1) + c.PathStroke(col, false, 2) + }), + ), + ), + giu.Style().SetDisabled(true).To( + giu.Button(s("W.I.P")).Size(giu.Auto, 0), + ), + ), + ), + + giu.Dummy(0, 3), + giu.Separator(), + giu.Dummy(0, 0), + + giu.Style().SetDisabled(password == "" || (password != cPassword && mode == "encrypt")).To( + giu.Label(s(metadataPrompt)), + giu.Style().SetDisabled(metadataDisabled).To( + giu.InputText(&metadata).Size(giu.Auto), + ), + + giu.Label(s("Advanced:")), + giu.Custom(func() { + if mode != "decrypt" { + giu.Row( + giu.Checkbox(s("Shred temporary files"), &shredTemp), + giu.Dummy(-221, 0), + giu.Checkbox(s("Encode with Reed-Solomon"), &reedsolo), + ).Build() + giu.Row( + giu.Checkbox(s("Use fast mode"), &fast), + giu.Dummy(-221, 0), + giu.Checkbox(s("Delete files when complete"), &deleteWhenDone), + ).Build() + giu.Row( + giu.Checkbox(s("Use paranoid mode"), ¶noid), + giu.Dummy(-221, 0), + giu.Style().SetDisabled(!(len(allFiles) > 1 || len(onlyFolders) > 0)).To( + giu.Checkbox(s("Compress files"), &compress), + ), + ).Build() + giu.Row( + giu.Style().SetDisabled(true).To( + giu.Checkbox(s("Encrypt filename (W.I.P)"), &encryptFilename), + ), + giu.Dummy(-221, 0), + giu.Checkbox(s("Split every"), &split), + giu.InputText(&splitSize).Size(55/dpi).Flags(giu.InputTextFlagsCharsHexadecimal).OnChange(func() { + split = splitSize != "" + }), + giu.Combo("##splitter", splitUnits[splitSelected], splitUnits, &splitSelected).Size(52), + ).Build() + } else { + giu.Checkbox(s("Keep decrypted output even if it's corrupted or modified"), &keep).Build() + giu.Checkbox(s("Delete the encrypted files after a successful decryption"), &deleteWhenDone).Build() + giu.Dummy(0, 52).Build() + } + }), + + giu.Label(s("Save output as:")), + giu.Custom(func() { + w, _ := giu.GetAvailableRegion() + bw, _ := giu.CalcTextSize(s("Change")) + p, _ := giu.GetWindowPadding() + bw += p * 2 + dw := w - bw - p + giu.Style().SetDisabled(true).To( + giu.InputText(&outputFile).Size(dw / dpi / dpi).Flags(giu.InputTextFlagsReadOnly), + ).Build() + giu.SameLine() + giu.Button(s("Change")).Size(bw/dpi, 0).OnClick(func() { + file, _ := dialog.File().Title(s("Save as:")).Save() + if file == "" { + return + } + + if mode == "encrypt" { + if len(allFiles) > 1 || len(onlyFolders) > 0 { + file = strings.TrimSuffix(file, ".zip.pcv") + file = strings.TrimSuffix(file, ".pcv") + if !strings.HasSuffix(file, ".zip.pcv") { + file += ".zip.pcv" + } + } else { + file = strings.TrimSuffix(file, ".pcv") + ind := strings.Index(inputFile, ".") + file += inputFile[ind:] + if !strings.HasSuffix(file, ".pcv") { + file += ".pcv" + } + } + } else { + ind := strings.Index(file, ".") + if ind != -1 { + file = file[:ind] + } + if strings.HasSuffix(inputFile, ".zip.pcv") { + file += ".zip" + } else { + tmp := strings.TrimSuffix(filepath.Base(inputFile), ".pcv") + tmp = tmp[strings.Index(tmp, "."):] + file += tmp + } + } + + outputFile = file + }).Build() + giu.Tooltip(s("Save the output with a custom path and name.")).Build() + }), + + giu.Dummy(0, 2), + giu.Separator(), + giu.Dummy(0, 3), + + giu.Button(s("Start")).Size(giu.Auto, 34).OnClick(func() { + if keyfile && keyfiles == nil { + mainStatus = "Please select your keyfiles." + mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} + return + } + _, err := os.Stat(outputFile) + if err == nil { + showConfirmation = true + giu.Update() + } else { + showProgress = true + giu.Update() + go func() { + work() + working = false + showProgress = false + debug.FreeOSMemory() + giu.Update() + }() + } + }), + giu.Style().SetColor(giu.StyleColorText, mainStatusColor).To( + giu.Label(s(mainStatus)), + ), + ), + ), + giu.TabItem(s("Checksum")).Layout( + giu.Custom(func() { + if giu.IsItemActive() { + tab = 1 + } + }), + giu.Label(s("Toggle the hashes you would like to generate and drop a file here.")), + + // MD5 + giu.Custom(func() { + giu.Checkbox("MD5:", &md5Selected).OnChange(func() { + csMd5 = "" + }).Build() + giu.SameLine() + w, _ := giu.GetAvailableRegion() + bw, _ := giu.CalcTextSize(s("Copy")) + padding, _ := giu.GetWindowPadding() + bw += 2 * padding + size := w - bw - padding + giu.Dummy(size/dpi, 0).Build() + giu.SameLine() + giu.Button(s("Copy")+"##md5").Size(bw/dpi, 0).OnClick(func() { + clipboard.WriteAll(csMd5) + }).Build() + }), + giu.Style().SetColor(giu.StyleColorBorder, md5Color).To( + giu.Style().SetDisabled(true).To( + giu.InputText(&csMd5).Size(giu.Auto).Flags(giu.InputTextFlagsReadOnly), + ), + ), + + // SHA1 + giu.Custom(func() { + giu.Checkbox("SHA1:", &sha1Selected).OnChange(func() { + csSha1 = "" + }).Build() + giu.SameLine() + w, _ := giu.GetAvailableRegion() + bw, _ := giu.CalcTextSize(s("Copy")) + padding, _ := giu.GetWindowPadding() + bw += 2 * padding + size := w - bw - padding + giu.Dummy(size/dpi, 0).Build() + giu.SameLine() + giu.Button(s("Copy")+"##sha1").Size(bw/dpi, 0).OnClick(func() { + clipboard.WriteAll(csSha1) + }).Build() + }), + giu.Style().SetColor(giu.StyleColorBorder, sha1Color).To( + giu.Style().SetDisabled(true).To( + giu.InputText(&csSha1).Size(giu.Auto).Flags(giu.InputTextFlagsReadOnly), + ), + ), + + // SHA256 + giu.Custom(func() { + giu.Checkbox("SHA256:", &sha256Selected).OnChange(func() { + csSha256 = "" + }).Build() + giu.SameLine() + w, _ := giu.GetAvailableRegion() + bw, _ := giu.CalcTextSize(s("Copy")) + padding, _ := giu.GetWindowPadding() + bw += 2 * padding + size := w - bw - padding + giu.Dummy(size/dpi, 0).Build() + giu.SameLine() + giu.Button(s("Copy")+"##sha256").Size(bw/dpi, 0).OnClick(func() { + clipboard.WriteAll(csSha256) + }).Build() + }), + giu.Style().SetColor(giu.StyleColorBorder, sha256Color).To( + giu.Style().SetDisabled(true).To( + giu.InputText(&csSha256).Size(giu.Auto).Flags(giu.InputTextFlagsReadOnly), + ), + ), + + // SHA3-256 + giu.Custom(func() { + giu.Checkbox("SHA3:", &sha3Selected).OnChange(func() { + csSha3 = "" + }).Build() + giu.SameLine() + w, _ := giu.GetAvailableRegion() + bw, _ := giu.CalcTextSize(s("Copy")) + padding, _ := giu.GetWindowPadding() + bw += 2 * padding + size := w - bw - padding + giu.Dummy(size/dpi, 0).Build() + giu.SameLine() + giu.Button(s("Copy")+"##sha3").Size(bw/dpi, 0).OnClick(func() { + clipboard.WriteAll(csSha3) + }).Build() + }), + giu.Style().SetColor(giu.StyleColorBorder, sha3Color).To( + giu.Style().SetDisabled(true).To( + giu.InputText(&csSha3).Size(giu.Auto).Flags(giu.InputTextFlagsReadOnly), + ), + ), + + // BLAKE2b + giu.Custom(func() { + giu.Checkbox("BLAKE2b:", &blake2bSelected).OnChange(func() { + csBlake2b = "" + }).Build() + giu.SameLine() + w, _ := giu.GetAvailableRegion() + bw, _ := giu.CalcTextSize(s("Copy")) + padding, _ := giu.GetWindowPadding() + bw += 2 * padding + size := w - bw - padding + giu.Dummy(size/dpi, 0).Build() + giu.SameLine() + giu.Button(s("Copy")+"##blake2b").Size(bw/dpi, 0).OnClick(func() { + clipboard.WriteAll(csBlake2b) + }).Build() + }), + giu.Style().SetColor(giu.StyleColorBorder, blake2bColor).To( + giu.Style().SetDisabled(true).To( + giu.InputText(&csBlake2b).Size(giu.Auto).Flags(giu.InputTextFlagsReadOnly), + ), + ), + + // BLAKE2s + giu.Custom(func() { + giu.Checkbox("BLAKE2s:", &blake2sSelected).OnChange(func() { + csBlake2s = "" + }).Build() + giu.SameLine() + w, _ := giu.GetAvailableRegion() + bw, _ := giu.CalcTextSize(s("Copy")) + padding, _ := giu.GetWindowPadding() + bw += 2 * padding + size := w - bw - padding + giu.Dummy(size/dpi, 0).Build() + giu.SameLine() + giu.Button(s("Copy")+"##blake2s").Size(bw/dpi, 0).OnClick(func() { + clipboard.WriteAll(csBlake2s) + }).Build() + }), + giu.Style().SetColor(giu.StyleColorBorder, blake2sColor).To( + giu.Style().SetDisabled(true).To( + giu.InputText(&csBlake2s).Size(giu.Auto).Flags(giu.InputTextFlagsReadOnly), + ), + ), + + // Input entry for validating a checksum + giu.Row( + giu.Label(s("Validate a checksum:")), + giu.Custom(func() { + bw, _ := giu.CalcTextSize(s("Paste")) + padding, _ := giu.GetWindowPadding() + bw += 2 * padding + giu.Button(s("Paste")).Size(bw/dpi, 0).OnClick(func() { + tmp, _ := clipboard.ReadAll() + csValidate = tmp + md5Color = color.RGBA{0x00, 0x00, 0x00, 0x00} + sha1Color = color.RGBA{0x00, 0x00, 0x00, 0x00} + sha256Color = color.RGBA{0x00, 0x00, 0x00, 0x00} + sha3Color = color.RGBA{0x00, 0x00, 0x00, 0x00} + blake2bColor = color.RGBA{0x00, 0x00, 0x00, 0x00} + blake2sColor = color.RGBA{0x00, 0x00, 0x00, 0x00} + if csValidate == "" { + return + } + csValidate = strings.ToLower(csValidate) + if csValidate == csMd5 { + md5Color = color.RGBA{0x00, 0xff, 0x00, 0xff} + } else if csValidate == csSha1 { + sha1Color = color.RGBA{0x00, 0xff, 0x00, 0xff} + } else if csValidate == csSha256 { + sha256Color = color.RGBA{0x00, 0xff, 0x00, 0xff} + } else if csValidate == csSha3 { + sha3Color = color.RGBA{0x00, 0xff, 0x00, 0xff} + } else if csValidate == csBlake2b { + blake2bColor = color.RGBA{0x00, 0xff, 0x00, 0xff} + } else if csValidate == csBlake2s { + blake2sColor = color.RGBA{0x00, 0xff, 0x00, 0xff} + } + giu.Update() + }).Build() + }), + giu.Custom(func() { + bw, _ := giu.CalcTextSize(s("Paste")) + padding, _ := giu.GetWindowPadding() + bw += 2 * padding + giu.Button(s("Clear")).Size(bw/dpi, 0).OnClick(func() { + csValidate = "" + md5Color = color.RGBA{0x00, 0x00, 0x00, 0x00} + sha1Color = color.RGBA{0x00, 0x00, 0x00, 0x00} + sha256Color = color.RGBA{0x00, 0x00, 0x00, 0x00} + sha3Color = color.RGBA{0x00, 0x00, 0x00, 0x00} + blake2bColor = color.RGBA{0x00, 0x00, 0x00, 0x00} + blake2sColor = color.RGBA{0x00, 0x00, 0x00, 0x00} + }).Build() + }), + ), + giu.Style().SetDisabled(true).To( + giu.InputText(&csValidate).Size(giu.Auto), + ), + + // Progress bar + giu.Label(s("Progress:")), + giu.ProgressBar(csProgress).Size(giu.Auto, 0), + ), + giu.TabItem(s("Shredder")).Layout( + giu.Custom(func() { + if giu.IsItemActive() { + tab = 2 + } + }), + + giu.Label(s("Drop files and folders here to shred them.")), + giu.Custom(func() { + if runtime.GOOS == "darwin" { + giu.Label(s("Number of passes: Not supported on macOS")).Build() + } else { + giu.Row( + giu.Label(s("Number of passes:")), + giu.SliderInt(&shredPasses, 1, 32).Size(giu.Auto), + ).Build() + } + }), + giu.Style().SetDisabled(true).To( + giu.InputTextMultiline(&shredText).Size(giu.Auto, 300), + ), + giu.Custom(func() { + w, _ := giu.GetAvailableRegion() + bw, _ := giu.CalcTextSize(s("Cancel")) + padding, _ := giu.GetWindowPadding() + bw += 2 * padding + size := w - bw - padding + giu.Row( + giu.ProgressBar(shredProgress).Overlay(shredOverlay).Size(size/dpi, 0), + giu.Button(s("Cancel")).Size(bw/dpi, 0).OnClick(func() { + stopShredding = true + shredProgress = 0 + shredOverlay = "" + }), + ).Build() + }), + giu.Custom(func() { + if len(shredding) > 55 { + shredding = shredding[0:25] + "....." + shredding[len(shredding)-25:] + } + + if shredProgress != 0 { + if shredText == "" { + shredText = "\n" + } + tmp := strings.Split(shredText, "\n") + if shredding != tmp[len(tmp)-2] { + shredText += shredding + "\n" + shredText = strings.TrimPrefix(shredText, "\n") + } + } + + giu.Label((func() string { + if shredProgress == 0 { + shredText = strings.TrimSuffix(shredText, "\n") + return s("Ready.") + } + return s("Shredding...") + })()).Wrapped(true).Build() + }), + ), + giu.TabItem(s("About")).Layout( + giu.Custom(func() { + if giu.IsItemActive() { + tab = 3 + } + }), + giu.Label(fmt.Sprintf(s("Picocrypt %s, created by Evan Su (https://evansu.cc/)."), version)), + giu.Label(s("Released under a GNU GPL v3 License.")), + giu.Label(s("A warm thank you to all the people listed below.")), + giu.Label(s("Patrons:")), + giu.Label(" - Frederick Doe"), + giu.Label(s("Donators:")), + giu.Label(" - jp26"), + giu.Label(" - W.Graham"), + giu.Label(" - N. Chin"), + giu.Label(" - Manjot"), + giu.Label(" - Phil P."), + giu.Label(" - E. Zahard"), + giu.Label(s("Translators:")), + giu.Label("umitseyhan75, digitalblossom, zeeaall, francirc, kurpau"), + giu.Label(s("Other:")), + giu.Label("Fuderal, u/greenreddits, u/Tall_Escape, u/NSABackdoors"), + ), + ).Build() + }), + giu.Custom(func() { + if !windowOptimized || windowOptimized { + windowOptimized = true + var pad int + if tab == 1 { + pad = 6 + } + if tab == 2 { + pad = 1 + } + if tab == 3 { + pad = 2 + } + window.SetSize(int(442*dpi), giu.GetCursorPos().Y+1+pad) + } + }), + ) +} + +func onDrop(names []string) { + if tab == 1 { + go generateChecksums(names[0]) + return + } + if tab == 2 { + shredText = "" + go shred(names, true) + return + } + + if showKeyfile { + keyfiles = append(keyfiles, names...) + var tmp []string + for _, i := range keyfiles { + duplicate := false + for _, j := range tmp { + if i == j { + duplicate = true + } + } + stat, _ := os.Stat(i) + if !duplicate && !stat.IsDir() { + tmp = append(tmp, i) + } + } + keyfiles = tmp + if len(keyfiles) == 1 { + keyfilePrompt = s("Using 1 keyfile.") + } else { + keyfilePrompt = fmt.Sprintf(s("Using %d keyfiles."), len(keyfiles)) + } + return + } + + // Clear variables + recombine = false + onlyFiles = nil + onlyFolders = nil + allFiles = nil + files, folders := 0, 0 + resetUI() + + if len(names) == 1 { + stat, _ := os.Stat(names[0]) + if stat.IsDir() { + // Update variables + mode = "encrypt" + folders++ + inputLabel = s("1 folder selected.") + + // Add the folder + onlyFolders = append(onlyFolders, names[0]) + + // Set the input and output paths + inputFile = filepath.Join(filepath.Dir(names[0]), s("Encrypted")) + ".zip" + outputFile = filepath.Join(filepath.Dir(names[0]), s("Encrypted")) + ".zip.pcv" + } else { + files++ + name := filepath.Base(names[0]) + nums := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} + endsNum := false + for _, i := range nums { + if strings.HasSuffix(names[0], i) { + endsNum = true + } + } + isSplit := strings.Contains(names[0], ".pcv.") && endsNum + + // Decide if encrypting or decrypting + if strings.HasSuffix(names[0], ".pcv") || isSplit { + //var err error + mode = "decrypt" + inputLabel = name + s(" (will decrypt)") + metadataPrompt = s("Metadata (read-only):") + metadataDisabled = true + + if isSplit { + inputLabel = name + s(" (will recombine and decrypt)") + ind := strings.Index(names[0], ".pcv") + names[0] = names[0][:ind+4] + inputFile = names[0] + outputFile = names[0][:ind] + recombine = true + } else { + outputFile = names[0][:len(names[0])-4] + } + + // Open input file in read-only mode + var fin *os.File + if isSplit { + fin, _ = os.Open(names[0] + ".0") + } else { + fin, _ = os.Open(names[0]) + } + + // Use regex to test if input is a valid Picocrypt volume + tmp := make([]byte, 30) + fin.Read(tmp) + if string(tmp[:5]) == "v1.13" { + resetUI() + mainStatus = "Please use Picocrypt v1.13 to decrypt this file." + mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} + fin.Close() + return + } + if valid, _ := regexp.Match(`^v\d\.\d{2}.{10}0?\d+`, tmp); !valid && !isSplit { + resetUI() + mainStatus = "This doesn't seem to be a Picocrypt volume." + mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} + fin.Close() + return + } + fin.Seek(0, 0) + + // Read metadata and insert into box + var err error + tmp = make([]byte, 15) + fin.Read(tmp) + tmp, _ = rsDecode(rs5, tmp) + if string(tmp) == "v1.14" || string(tmp) == "v1.15" || string(tmp) == "v1.16" { + resetUI() + mainStatus = "Please use Picocrypt v1.16 to decrypt this file." + mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} + fin.Close() + return + } + tmp = make([]byte, 15) + fin.Read(tmp) + tmp, err = rsDecode(rs5, tmp) + + if err == nil { + metadataLength, _ := strconv.Atoi(string(tmp)) + tmp = make([]byte, metadataLength*3) + fin.Read(tmp) + metadata = "" + + for i := 0; i < metadataLength*3; i += 3 { + t, err := rsDecode(rs1, tmp[i:i+3]) + if err != nil { + metadata = s("Metadata is corrupted.") + break + } + metadata += string(t) + } + } else { + metadata = s("Metadata is corrupted.") + } + + flags := make([]byte, 18) + fin.Read(flags) + fin.Close() + flags, err = rsDecode(rs6, flags) + if err != nil { + mainStatus = "Input file is corrupt and cannot be decrypted." + mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} + return + } + + if flags[2] == 1 { + keyfile = true + keyfilePrompt = s("Keyfiles required.") + } else { + keyfilePrompt = s("Not applicable.") + } + if flags[5] == 1 { + keyfileOrderMatters = true + } + } else { + mode = "encrypt" + inputLabel = name + s(" (will encrypt)") + inputFile = names[0] + outputFile = names[0] + ".pcv" + } + + // Add the file + onlyFiles = append(onlyFiles, names[0]) + inputFile = names[0] + } + } else { + mode = "encrypt" + + // There are multiple dropped items, check each one + for _, name := range names { + stat, _ := os.Stat(name) + + // Check if item is a file or a directory + if stat.IsDir() { + folders++ + onlyFolders = append(onlyFolders, name) + } else { + files++ + onlyFiles = append(onlyFiles, name) + allFiles = append(allFiles, name) + } + } + + if folders == 0 { + inputLabel = fmt.Sprintf(s("%d files selected."), files) + } else if files == 0 { + inputLabel = fmt.Sprintf(s("%d folders selected."), files) + } else { + if files == 1 && folders > 1 { + inputLabel = fmt.Sprintf(s("1 file and %d folders selected."), folders) + } else if folders == 1 && files > 1 { + inputLabel = fmt.Sprintf(s("%d files and 1 folder selected."), files) + } else if folders == 1 && files == 1 { + inputLabel = s("1 file and 1 folder selected.") + } else { + inputLabel = fmt.Sprintf(s("%d files and %d folders selected."), files, folders) + } + } + + // Set the input and output paths + inputFile = filepath.Join(filepath.Dir(names[0]), s("Encrypted")) + ".zip" + outputFile = filepath.Join(filepath.Dir(names[0]), s("Encrypted")) + ".zip.pcv" + } + // Recursively add all files to 'allFiles' + if folders > 0 { + for _, name := range onlyFolders { + filepath.Walk(name, func(path string, _ os.FileInfo, _ error) error { + stat, _ := os.Stat(path) + if !stat.IsDir() { + allFiles = append(allFiles, path) + } + return nil + }) + } + } +} + +func work() { + popupStatus = s("Starting...") + mainStatus = "Working..." + mainStatusColor = color.RGBA{0xff, 0xff, 0xff, 0xff} + working = true + padded := false + + var salt []byte + var hkdfSalt []byte + var serpentSalt []byte + var nonce []byte + var keyHash []byte + var _keyHash []byte + var keyfileKey []byte + var keyfileHash []byte = make([]byte, 32) + var _keyfileHash []byte + var dataMac []byte + + if mode == "encrypt" { + if compress { + popupStatus = s("Compressing files...") + } else { + popupStatus = s("Combining files...") + } + + // "Tar" files together (a .zip file with no compression) + if len(allFiles) > 1 || len(onlyFolders) > 0 { + var rootDir string + if len(onlyFolders) > 0 { + rootDir = filepath.Dir(onlyFolders[0]) + } else { + rootDir = filepath.Dir(onlyFiles[0]) + } + + file, err := os.Create(inputFile) + if err != nil { + mainStatus = "Access denied by operating system." + mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} + return + } + + w := zip.NewWriter(file) + for i, path := range allFiles { + if !working { + w.Close() + file.Close() + os.Remove(inputFile) + mainStatus = "Operation cancelled by user." + mainStatusColor = color.RGBA{0xff, 0xff, 0xff, 0xff} + return + } + progressInfo = fmt.Sprintf("%d/%d", i, len(allFiles)) + progress = float32(i) / float32(len(allFiles)) + giu.Update() + if path == inputFile { + continue + } + + stat, _ := os.Stat(path) + header, _ := zip.FileInfoHeader(stat) + header.Name = strings.TrimPrefix(path, rootDir) + header.Name = filepath.ToSlash(header.Name) + header.Name = strings.TrimPrefix(header.Name, "/") + + if compress { + header.Method = zip.Deflate + } else { + header.Method = zip.Store + } + writer, _ := w.CreateHeader(header) + file, _ := os.Open(path) + io.Copy(writer, file) + file.Close() + } + w.Flush() + w.Close() + file.Close() + } + } + + if recombine { + popupStatus = s("Recombining file...") + total := 0 + + for { + _, err := os.Stat(fmt.Sprintf("%s.%d", inputFile, total)) + if err != nil { + break + } + total++ + } + + fout, _ := os.Create(inputFile) + for i := 0; i < total; i++ { + fin, _ := os.Open(fmt.Sprintf("%s.%d", inputFile, i)) + for { + data := make([]byte, 1048576) + read, err := fin.Read(data) + if err != nil { + break + } + data = data[:read] + fout.Write(data) + } + fin.Close() + progressInfo = fmt.Sprintf("%d/%d", i, total) + progress = float32(i) / float32(total) + giu.Update() + } + fout.Close() + progressInfo = "" + } + + stat, _ := os.Stat(inputFile) + total := stat.Size() + if mode == "decrypt" { + total -= 789 + } + + // XChaCha20's max message size is 256 GiB + if total > 256*1073741824 { + mainStatus = "Total size is larger than 256 GiB, XChaCha20's limit." + mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} + return + } + + // Open input file in read-only mode + fin, err := os.Open(inputFile) + if err != nil { + mainStatus = "Access denied by operating system." + mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} + return + } + + var fout *os.File + + // If encrypting, generate values; if decrypting, read values from file + if mode == "encrypt" { + popupStatus = s("Generating values...") + giu.Update() + + var err error + fout, err = os.Create(outputFile) + if err != nil { + mainStatus = "Access denied by operating system." + mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} + return + } + + // Generate random cryptography values + salt = make([]byte, 16) + hkdfSalt = make([]byte, 32) + serpentSalt = make([]byte, 16) + nonce = make([]byte, 24) + + // Write version to file + fout.Write(rsEncode(rs5, []byte(version))) + + // Encode the length of the metadata with Reed-Solomon + metadataLength := []byte(fmt.Sprintf("%05d", len(metadata))) + metadataLength = rsEncode(rs5, metadataLength) + + // Write the length of the metadata to file + fout.Write(metadataLength) + + // Reed-Solomon-encode the metadata and write to file + for _, i := range []byte(metadata) { + fout.Write(rsEncode(rs1, []byte{i})) + } + + flags := make([]byte, 6) + if fast { + flags[0] = 1 + } + if paranoid { + flags[1] = 1 + } + if len(keyfiles) > 0 { + flags[2] = 1 + } + if reedsolo { + flags[3] = 1 + } + if total%1048576 >= 1048448 { + flags[4] = 1 + } + if keyfileOrderMatters { + flags[5] = 1 + } + flags = rsEncode(rs6, flags) + fout.Write(flags) + + // Fill salts and nonce with Go's CSPRNG + rand.Read(salt) + rand.Read(hkdfSalt) + rand.Read(serpentSalt) + rand.Read(nonce) + + // Encode salt with Reed-Solomon and write to file + _salt := rsEncode(rs16, salt) + fout.Write(_salt) + + // Encode HKDF salt with Reed-Solomon and write to file + _hkdfSalt := rsEncode(rs32, hkdfSalt) + fout.Write(_hkdfSalt) + + // Encode Serpent salt with Reed-Solomon and write to file + _serpentSalt := rsEncode(rs16, serpentSalt) + fout.Write(_serpentSalt) + + // Encode nonce with Reed-Solomon and write to file + _nonce := rsEncode(rs24, nonce) + fout.Write(_nonce) + + // Write placeholder for hash of key + fout.Write(make([]byte, 192)) + + // Write placeholder for hash of hash of keyfile + fout.Write(make([]byte, 96)) + + // Write placeholder for HMAC-BLAKE2b/HMAC-SHA3 of file + fout.Write(make([]byte, 192)) + } else { + var err1 error + var err2 error + var err3 error + var err4 error + var err5 error + var err6 error + var err7 error + var err8 error + var err9 error + var err10 error + + popupStatus = s("Reading values...") + giu.Update() + + version := make([]byte, 15) + fin.Read(version) + _, err1 = rsDecode(rs5, version) + + tmp := make([]byte, 15) + fin.Read(tmp) + tmp, err2 = rsDecode(rs5, tmp) + metadataLength, _ := strconv.Atoi(string(tmp)) + + fin.Read(make([]byte, metadataLength*3)) + + flags := make([]byte, 18) + fin.Read(flags) + flags, err3 = rsDecode(rs6, flags) + fast = flags[0] == 1 + paranoid = flags[1] == 1 + reedsolo = flags[3] == 1 + padded = flags[4] == 1 + + salt = make([]byte, 48) + fin.Read(salt) + salt, err4 = rsDecode(rs16, salt) + + hkdfSalt = make([]byte, 96) + fin.Read(hkdfSalt) + hkdfSalt, err5 = rsDecode(rs32, hkdfSalt) + + serpentSalt = make([]byte, 48) + fin.Read(serpentSalt) + serpentSalt, err6 = rsDecode(rs16, serpentSalt) + + nonce = make([]byte, 72) + fin.Read(nonce) + nonce, err7 = rsDecode(rs24, nonce) + + _keyHash = make([]byte, 192) + fin.Read(_keyHash) + _keyHash, err8 = rsDecode(rs64, _keyHash) + + _keyfileHash = make([]byte, 96) + fin.Read(_keyfileHash) + _keyfileHash, err9 = rsDecode(rs32, _keyfileHash) + + dataMac = make([]byte, 192) + fin.Read(dataMac) + dataMac, err10 = rsDecode(rs64, dataMac) + + // Is there a better way? + if err1 != nil || err2 != nil || err3 != nil || err4 != nil || err5 != nil || + err6 != nil || err7 != nil || err8 != nil || err9 != nil || err10 != nil { + if keep { + kept = true + } else { + mainStatus = "The header is corrupt and the input file cannot be decrypted." + mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} + fin.Close() + return + } + } + } + + popupStatus = s("Deriving key...") + progress = 0 + progressInfo = "" + giu.Update() + + // Derive encryption/decryption keys and subkeys + var key []byte + if fast { + key = argon2.IDKey( + []byte(password), + salt, + 4, + 131072, + 4, + 32, + ) + } else if paranoid { + key = argon2.IDKey( + []byte(password), + salt, + 8, + 1048576, + 8, + 32, + ) + } else { + key = argon2.IDKey( + []byte(password), + salt, + 4, + 1048576, + 4, + 32, + ) + } + + if !working { + mainStatus = "Operation cancelled by user." + mainStatusColor = color.RGBA{0xff, 0xff, 0xff, 0xff} + if mode == "encrypt" && (len(allFiles) > 1 || len(onlyFolders) > 0) { + os.Remove(outputFile) + } + if recombine { + os.Remove(inputFile) + } + os.Remove(outputFile) + return + } + + if len(keyfiles) > 0 || keyfile { + if keyfileOrderMatters { + var keysum = sha3.New256() + for _, path := range keyfiles { + kin, _ := os.Open(path) + kstat, _ := os.Stat(path) + kbytes := make([]byte, kstat.Size()) + kin.Read(kbytes) + kin.Close() + keysum.Write(kbytes) + } + keyfileKey = keysum.Sum(nil) + keyfileSha3 := sha3.New256() + keyfileSha3.Write(keyfileKey) + keyfileHash = keyfileSha3.Sum(nil) + } else { + var keysum []byte + for _, path := range keyfiles { + kin, _ := os.Open(path) + kstat, _ := os.Stat(path) + kbytes := make([]byte, kstat.Size()) + kin.Read(kbytes) + kin.Close() + ksha3 := sha3.New256() + ksha3.Write(kbytes) + keyfileKey := ksha3.Sum(nil) + if keysum == nil { + keysum = keyfileKey + } else { + for i, j := range keyfileKey { + keysum[i] ^= j + } + } + } + keyfileKey = keysum + keyfileSha3 := sha3.New256() + keyfileSha3.Write(keysum) + keyfileHash = keyfileSha3.Sum(nil) + } + } + + sha3_512 := sha3.New512() + sha3_512.Write(key) + keyHash = sha3_512.Sum(nil) + + // Validate password and/or keyfiles + if mode == "decrypt" { + keyCorrect := true + keyfileCorrect := true + var tmp bool + + keyCorrect = !(subtle.ConstantTimeCompare(keyHash, _keyHash) == 0) + if keyfile { + keyfileCorrect = !(subtle.ConstantTimeCompare(keyfileHash, _keyfileHash) == 0) + tmp = !keyCorrect || !keyfileCorrect + } else { + tmp = !keyCorrect + } + + if tmp || keep { + if keep { + kept = true + } else { + fin.Close() + if !keyCorrect { + mainStatus = "The provided password is incorrect." + } else { + if keyfileOrderMatters { + mainStatus = "Incorrect keyfiles and/or order." + } else { + mainStatus = "Incorrect keyfiles." + } + } + mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} + key = nil + if recombine { + os.Remove(inputFile) + } + return + } + } + + var err error + fout, err = os.Create(outputFile) + if err != nil { + mainStatus = "Access denied by operating system." + mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} + return + } + } + + if len(keyfiles) > 0 || keyfile { + // XOR key and keyfile + tmp := key + key = make([]byte, 32) + for i := range key { + key[i] = tmp[i] ^ keyfileKey[i] + } + } + + done := 0 + counter := 0 + startTime := time.Now() + chacha20, _ := chacha20.NewUnauthenticatedCipher(key, nonce) + + // Use HKDF-SHA3 to generate a subkey + var mac hash.Hash + subkey := make([]byte, 32) + hkdf := hkdf.New(sha3.New256, key, hkdfSalt, nil) + hkdf.Read(subkey) + if fast { + // Keyed BLAKE2b + mac, _ = blake2b.New512(subkey) + } else { + // HMAC-SHA3 + mac = hmac.New(sha3.New512, subkey) + } + + // Generate another subkey and cipher (not used unless paranoid mode is checked) + serpentKey := make([]byte, 32) + hkdf.Read(serpentKey) + _serpent, _ := serpent.NewCipher(serpentKey) + serpentCTR := cipher.NewCTR(_serpent, serpentSalt) + + for { + if !working { + mainStatus = "Operation cancelled by user." + mainStatusColor = color.RGBA{0xff, 0xff, 0xff, 0xff} + fin.Close() + fout.Close() + if mode == "encrypt" && (len(allFiles) > 1 || len(onlyFolders) > 0) { + os.Remove(outputFile) + } + if recombine { + os.Remove(inputFile) + } + os.Remove(outputFile) + return + } + + var data []byte + if mode == "decrypt" && reedsolo { + data = make([]byte, 1114112) + } else { + data = make([]byte, 1048576) + } + + size, err := fin.Read(data) + if err != nil { + break + } + data = data[:size] + _data := make([]byte, len(data)) + + // "Actual" encryption is done in the next couple of lines + if mode == "encrypt" { + if paranoid { + serpentCTR.XORKeyStream(_data, data) + copy(data, _data) + } + + chacha20.XORKeyStream(_data, data) + mac.Write(_data) + + if reedsolo { + copy(data, _data) + _data = nil + if len(data) == 1048576 { + for i := 0; i < 1048576; i += 128 { + tmp := data[i : i+128] + tmp = rsEncode(rs128, tmp) + _data = append(_data, tmp...) + } + } else { + chunks := math.Floor(float64(len(data)) / 128) + for i := 0; float64(i) < chunks; i++ { + tmp := data[i*128 : (i+1)*128] + tmp = rsEncode(rs128, tmp) + _data = append(_data, tmp...) + } + tmp := data[int(chunks*128):] + _data = append(_data, rsEncode(rs128, pad(tmp))...) + } + } + } else { + if reedsolo { + copy(_data, data) + data = nil + if len(_data) == 1114112 { + for i := 0; i < 1114112; i += 136 { + tmp := _data[i : i+136] + tmp, err = rsDecode(rs128, tmp) + if err != nil { + if keep { + kept = true + } else { + mainStatus = "The input file is too corrupted to decrypt." + mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} + fin.Close() + fout.Close() + broken() + return + } + } + if i == 1113976 && done+1114112 >= int(total) && padded { + tmp = unpad(tmp) + } + data = append(data, tmp...) + } + } else { + chunks := len(_data)/136 - 1 + for i := 0; i < chunks; i++ { + tmp := _data[i*136 : (i+1)*136] + tmp, err = rsDecode(rs128, tmp) + if err != nil { + if keep { + kept = true + } else { + mainStatus = "The input file is too corrupted to decrypt." + mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} + fin.Close() + fout.Close() + broken() + return + } + } + data = append(data, tmp...) + } + tmp := _data[int(chunks)*136:] + tmp, err = rsDecode(rs128, tmp) + if err != nil { + if keep { + kept = true + } else { + mainStatus = "The input file is too corrupted to decrypt." + mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} + fin.Close() + fout.Close() + broken() + return + } + } + tmp = unpad(tmp) + data = append(data, tmp...) + } + _data = make([]byte, len(data)) + } + + mac.Write(data) + chacha20.XORKeyStream(_data, data) + + if paranoid { + copy(data, _data) + serpentCTR.XORKeyStream(_data, data) + } + } + fout.Write(_data) + + // Update stats + if mode == "decrypt" && reedsolo { + done += 1114112 + } else { + done += 1048576 + } + counter++ + progress = float32(done) / float32(total) + elapsed := float64(time.Since(startTime)) / math.Pow(10, 9) + speed := float64(done) / elapsed / math.Pow(10, 6) + eta := int(math.Floor(float64(total-int64(done)) / (speed * math.Pow(10, 6)))) + + if progress > 1 { + progress = 1 + } + + progressInfo = fmt.Sprintf("%.2f%%", progress*100) + popupStatus = fmt.Sprintf(s("Working at %.2f MB/s (ETA: %s)"), speed, humanize(eta)) + giu.Update() + } + + if mode == "encrypt" { + // Seek back to header and write important data + fout.Seek(int64(312+len(metadata)*3), 0) + fout.Write(rsEncode(rs64, keyHash)) + fout.Write(rsEncode(rs32, keyfileHash)) + fout.Write(rsEncode(rs64, mac.Sum(nil))) + } else { + // Validate the authenticity of decrypted data + if subtle.ConstantTimeCompare(mac.Sum(nil), dataMac) == 0 { + if keep { + kept = true + } else { + fin.Close() + fout.Close() + broken() + return + } + } + } + + fin.Close() + fout.Close() + + // Split files into chunks + if split { + var splitted []string + popupStatus = s("Splitting file...") + stat, _ := os.Stat(outputFile) + size := stat.Size() + finished := 0 + chunkSize, _ := strconv.Atoi(splitSize) + + // User can choose KiB, MiB, and GiB + if splitSelected == 0 { + chunkSize *= 1024 + } else if splitSelected == 1 { + chunkSize *= 1048576 + } else { + chunkSize *= 1073741824 + } + chunks := int(math.Ceil(float64(size) / float64(chunkSize))) + fin, _ := os.Open(outputFile) + + for i := 0; i < chunks; i++ { + fout, _ := os.Create(fmt.Sprintf("%s.%d", outputFile, i)) + done := 0 + for { + data := make([]byte, 1048576) + read, err := fin.Read(data) + if err != nil { + break + } + if !working { + fin.Close() + fout.Close() + mainStatus = "Operation cancelled by user." + mainStatusColor = color.RGBA{0xff, 0xff, 0xff, 0xff} + + // If user cancels, remove the unfinished files + for _, j := range splitted { + os.Remove(j) + } + os.Remove(fmt.Sprintf("%s.%d", outputFile, i)) + os.Remove(outputFile) + return + } + data = data[:read] + fout.Write(data) + done += read + if done >= chunkSize { + break + } + } + fout.Close() + finished++ + splitted = append(splitted, fmt.Sprintf("%s.%d", outputFile, i)) + progress = float32(finished) / float32(chunks) + progressInfo = fmt.Sprintf("%d/%d", finished, chunks) + giu.Update() + } + fin.Close() + if shredTemp { + progressInfo = "" + popupStatus = s("Shredding temporary files...") + shred([]string{inputFile + ".pcv"}, false) + } else { + os.Remove(inputFile + ".pcv") + } + } + + // Remove the temporary file used to combine a splitted Picocrypt volume + if recombine { + os.Remove(inputFile) + } + + // Delete the temporary zip file if user wishes + if len(allFiles) > 1 || len(onlyFolders) > 0 { + if shredTemp { + progressInfo = "" + popupStatus = s("Shredding temporary files...") + giu.Update() + shred([]string{inputFile}, false) + } else { + os.Remove(inputFile) + } + } + + if deleteWhenDone { + progressInfo = "" + popupStatus = s("Deleted files...") + giu.Update() + if mode == "decrypt" { + if recombine { + total := 0 + for { + _, err := os.Stat(fmt.Sprintf("%s.%d", inputFile, total)) + if err != nil { + break + } + os.Remove(fmt.Sprintf("%s.%d", inputFile, total)) + total++ + } + } else { + os.Remove(inputFile) + } + } else { + for _, i := range onlyFiles { + os.Remove(i) + } + for _, i := range onlyFolders { + os.RemoveAll(i) + } + } + } + + resetUI() + + // If user chose to keep a corrupted/modified file, let them know + if kept { + mainStatus = "The input file is corrupted and/or modified. Please be careful." + mainStatusColor = color.RGBA{0xff, 0xff, 0x00, 0xff} + } else { + mainStatus = "Completed." + mainStatusColor = color.RGBA{0x00, 0xff, 0x00, 0xff} + } + + // Clear UI state + working = false + kept = false + key = nil + popupStatus = s("Ready.") +} + +// This function is run if an issue occurs during decryption +func broken() { + mainStatus = "The input file is either corrupted or intentionally modified." + mainStatusColor = color.RGBA{0xff, 0x00, 0x00, 0xff} + if recombine { + os.Remove(inputFile) + } + os.Remove(outputFile) + giu.Update() +} + +// Generate file checksums +func generateChecksums(file string) { + fin, _ := os.Open(file) + + // Clear UI state + csMd5 = "" + csSha1 = "" + csSha256 = "" + csSha3 = "" + csBlake2b = "" + csBlake2s = "" + md5Color = color.RGBA{0x00, 0x00, 0x00, 0x00} + sha1Color = color.RGBA{0x00, 0x00, 0x00, 0x00} + sha256Color = color.RGBA{0x00, 0x00, 0x00, 0x00} + sha3Color = color.RGBA{0x00, 0x00, 0x00, 0x00} + blake2bColor = color.RGBA{0x00, 0x00, 0x00, 0x00} + blake2sColor = color.RGBA{0x00, 0x00, 0x00, 0x00} + csValidate = "" + + if md5Selected { + csMd5 = s("Calculating...") + } + if sha1Selected { + csSha1 = s("Calculating...") + } + if sha256Selected { + csSha256 = s("Calculating...") + } + if sha3Selected { + csSha3 = s("Calculating...") + } + if blake2bSelected { + csBlake2b = s("Calculating...") + } + if blake2sSelected { + csBlake2s = s("Calculating...") + } + + // Create the checksum objects + crcMd5 := md5.New() + crcSha1 := sha1.New() + crcSha256 := sha256.New() + crcSha3 := sha3.New256() + crcBlake2b, _ := blake2b.New256(nil) + crcBlake2s, _ := blake2s.New256(nil) + + stat, _ := os.Stat(file) + total := stat.Size() + var done int64 = 0 + for { + var data []byte + _data := make([]byte, 1048576) + size, err := fin.Read(_data) + if err != nil { + break + } + data = _data[:size] + + if md5Selected { + crcMd5.Write(data) + } + if sha1Selected { + crcSha1.Write(data) + } + if sha256Selected { + crcSha256.Write(data) + } + if sha3Selected { + crcSha3.Write(data) + } + if blake2bSelected { + crcBlake2b.Write(data) + } + if blake2sSelected { + crcBlake2s.Write(data) + } + + done += int64(size) + csProgress = float32(done) / float32(total) + giu.Update() + } + csProgress = 0 + if md5Selected { + csMd5 = hex.EncodeToString(crcMd5.Sum(nil)) + } + if sha1Selected { + csSha1 = hex.EncodeToString(crcSha1.Sum(nil)) + } + if sha256Selected { + csSha256 = hex.EncodeToString(crcSha256.Sum(nil)) + } + if sha3Selected { + csSha3 = hex.EncodeToString(crcSha3.Sum(nil)) + } + if blake2bSelected { + csBlake2b = hex.EncodeToString(crcBlake2b.Sum(nil)) + } + if blake2sSelected { + csBlake2s = hex.EncodeToString(crcBlake2s.Sum(nil)) + } + + fin.Close() + giu.Update() +} + +// Recursively shred all file(s) and folder(s) passed in as 'names' +func shred(names []string, separate bool) { + stopShredding = false + shredTotal = 0 + shredDone = 0 + + // 'separate' is true if this function is being called from the encryption/decryption tab + if separate { + shredOverlay = s("Shredding...") + } + + // Walk through directories to get the total number of files for statistics + for _, name := range names { + filepath.Walk(name, func(path string, _ os.FileInfo, err error) error { + if err != nil { + return nil + } + stat, _ := os.Stat(path) + if !stat.IsDir() { + shredTotal++ + } + return nil + }) + } + + for _, name := range names { + shredding = name + + // Linux and macOS need a command with similar syntax and usage, so they're combined + if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { + stat, _ := os.Stat(name) + if stat.IsDir() { + var coming []string + + // Walk the folder recursively + filepath.Walk(name, func(path string, _ os.FileInfo, err error) error { + if err != nil { + return nil + } + if stopShredding { + return nil + } + stat, _ := os.Stat(path) + if !stat.IsDir() { + if len(coming) == 128 { + // Use a WaitGroup to parallelize shredding + var wg sync.WaitGroup + for i, j := range coming { + wg.Add(1) + go func(wg *sync.WaitGroup, id int, j string) { + defer wg.Done() + shredding = j + var cmd *exec.Cmd + if runtime.GOOS == "linux" { + cmd = exec.Command("shred", "-ufvz", "-n", strconv.Itoa(int(shredPasses)), j) + } else { + cmd = exec.Command("rm", "-rfP", j) + } + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + cmd.Run() + shredDone++ + shredUpdate(separate) + giu.Update() + }(&wg, i, j) + } + wg.Wait() + coming = nil + } else { + coming = append(coming, path) + } + } + return nil + }) + for _, i := range coming { + if stopShredding { + break + } + go func(i string) { + shredding = i + var cmd *exec.Cmd + if runtime.GOOS == "linux" { + cmd = exec.Command("shred", "-ufvz", "-n", strconv.Itoa(int(shredPasses)), i) + } else { + cmd = exec.Command("rm", "-rfP", i) + } + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + cmd.Run() + shredDone++ + shredUpdate(separate) + giu.Update() + }(i) + } + if !stopShredding { + os.RemoveAll(name) + } + } else { // The path is a file, not a directory, so just shred it + shredding = name + var cmd *exec.Cmd + if runtime.GOOS == "linux" { + cmd = exec.Command("shred", "-ufvz", "-n", strconv.Itoa(int(shredPasses)), name) + } else { + cmd = exec.Command("rm", "-rfP", name) + } + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + cmd.Run() + shredDone++ + shredUpdate(separate) + } + } else if runtime.GOOS == "windows" { + stat, _ := os.Stat(name) + if stat.IsDir() { + // Walk the folder recursively + filepath.Walk(name, func(path string, _ os.FileInfo, err error) error { + if err != nil { + return nil + } + stat, _ := os.Stat(path) + if stat.IsDir() { + if stopShredding { + return nil + } + + t := 0 + files, _ := ioutil.ReadDir(path) + for _, f := range files { + if !f.IsDir() { + t++ + } + } + shredDone += float32(t) + shredUpdate(separate) + shredding = strings.ReplaceAll(path, "\\", "/") + "/*" + cmd := exec.Command(sdelete64path, "*", "-p", strconv.Itoa(int(shredPasses))) + cmd.Dir = path + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + cmd.Run() + giu.Update() + } + return nil + }) + + if !stopShredding { + // sdelete64 doesn't delete the empty folder, so I'll do it manually + os.RemoveAll(name) + } + } else { + shredding = name + cmd := exec.Command(sdelete64path, name, "-p", strconv.Itoa(int(shredPasses))) + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + cmd.Run() + shredDone++ + shredUpdate(separate) + } + } + giu.Update() + if stopShredding { + return + } + } + + // Clear UI state + shredProgress = 0 + shredOverlay = "" +} + +// Update shredding statistics +func shredUpdate(separate bool) { + if separate { + shredOverlay = fmt.Sprintf("%d/%d", int(shredDone), int(shredTotal)) + shredProgress = shredDone / shredTotal + } else { + popupStatus = fmt.Sprintf("%d/%d", int(shredDone), int(shredTotal)) + progress = shredDone / shredTotal + } + giu.Update() +} + +// Reset the UI to a clean state with nothing selected or checked +func resetUI() { + mode = "" + onlyFiles = nil + onlyFolders = nil + allFiles = nil + inputLabel = s("Drop files and folders into this window.") + password = "" + cPassword = "" + keyfiles = nil + keyfile = false + keyfileOrderMatters = false + keyfilePrompt = s("None selected.") + metadata = "" + metadataPrompt = "Metadata:" + metadataDisabled = false + shredTemp = false + keep = false + reedsolo = false + split = false + splitSize = "" + splitSelected = 1 + fast = false + deleteWhenDone = false + paranoid = false + compress = false + encryptFilename = false + inputFile = "" + outputFile = "" + progress = 0 + progressInfo = "" + mainStatus = "Ready." + mainStatusColor = color.RGBA{0xff, 0xff, 0xff, 0xff} + giu.Update() +} + +// Reed-Solomon encoder +func rsEncode(rs *infectious.FEC, data []byte) []byte { + var res []byte + rs.Encode(data, func(s infectious.Share) { + res = append(res, s.DeepCopy().Data[0]) + }) + return res +} + +// Reed-Solomon decoder +func rsDecode(rs *infectious.FEC, data []byte) ([]byte, error) { + tmp := make([]infectious.Share, rs.Total()) + for i := 0; i < rs.Total(); i++ { + tmp[i] = infectious.Share{ + Number: i, + Data: []byte{data[i]}, + } + } + res, err := rs.Decode(nil, tmp) + if err != nil { + if rs.Total() == 136 { + return data[:128], err + } + return data[:rs.Total()/3], err + } + return res, nil +} + +// PKCS7 Pad (for use with Reed-Solomon, not for cryptographic purposes) +func pad(data []byte) []byte { + padLen := 128 - len(data)%128 + padding := bytes.Repeat([]byte{byte(padLen)}, padLen) + return append(data, padding...) +} + +// PKCS7 Unpad +func unpad(data []byte) []byte { + length := len(data) + padLen := int(data[length-1]) + return data[:length-padLen] +} + +func genPassword() string { + chars := "" + if genpassUpper { + chars += "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + } + if genpassLower { + chars += "abcdefghijklmnopqrstuvwxyz" + } + if genpassNums { + chars += "1234567890" + } + if genpassSymbols { + chars += "-=!@#$^&()_+?" + } + if chars == "" { + return chars + } + tmp := make([]byte, genpassLength) + for i := 0; i < int(genpassLength); i++ { + j, _ := rand.Int(rand.Reader, new(big.Int).SetUint64(uint64(len(chars)))) + tmp[i] = chars[j.Int64()] + } + if genpassCopy { + clipboard.WriteAll(string(tmp)) + } + return string(tmp) +} + +// Convert seconds to HH:MM:SS +func humanize(seconds int) string { + hours := int(math.Floor(float64(seconds) / 3600)) + seconds %= 3600 + minutes := int(math.Floor(float64(seconds) / 60)) + seconds %= 60 + hours = int(math.Max(float64(hours), 0)) + minutes = int(math.Max(float64(minutes), 0)) + seconds = int(math.Max(float64(seconds), 0)) + return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds) +} + +func s(term string) string { + for _, i := range locales { + if i.iso == selectedLocale { + for _, j := range locales { + if j.iso == "en" { + for k, l := range j.data { + if l == term { + return i.data[k] + } + } + } + } + + } + } + return term +} + +func main() { + // Parse locales + var obj map[string]json.RawMessage + json.Unmarshal(localeBytes, &obj) + for i := range obj { + var tmp []string + json.Unmarshal(obj[i], &tmp) + locales = append(locales, locale{ + iso: i, + data: tmp, + }) + } + + // Check system locale + for _, i := range locales { + tmp, err := jibber_jabber.DetectIETF() + if err == nil { + if strings.HasPrefix(tmp, i.iso) { + selectedLocale = i.iso + for j, k := range allLocales { + if k == i.iso { + languageSelected = int32(j) + } + } + } + } + } + + // Create a temporary file to store sdelete64.exe + sdelete64, _ := os.CreateTemp("", "sdelete64.*.exe") + sdelete64path = sdelete64.Name() + sdelete64.Write(sdelete64bytes) + sdelete64.Close() + cmd := exec.Command(sdelete64path, "/accepteula") + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + cmd.Run() + + // Set a universal font + giu.SetDefaultFontFromBytes(font, 18) + + // Create the master window + window = giu.NewMasterWindow("Picocrypt", 442, 532, giu.MasterWindowFlagsNotResizable) + dialog.Init() + + // Set window icon + reader := bytes.NewReader(icon) + decoded, _ := png.Decode(reader) + window.SetIcon([]image.Image{decoded}) + + // Set callbacks + window.SetDropCallback(onDrop) + window.SetCloseCallback(func() bool { + return !working + }) + + // Set universal DPI + dpi = giu.Context.GetPlatform().GetContentScale() + + // Start a goroutine to check if a newer version is available + go func() { + v, err := http.Get("https://raw.githubusercontent.com/HACKERALERT/Picocrypt/main/internals/version.txt") + if err == nil { + body, err := io.ReadAll(v.Body) + v.Body.Close() + if err == nil { + if string(body[:5]) != version { + mainStatus = "A newer version is available." + mainStatusColor = color.RGBA{0, 255, 0, 255} + } + } + } + }() + + // Start the UI + window.Run(draw) + + // Window closed, clean up + os.Remove(sdelete64path) +}