Picocrypt/src/Picocrypt.go

2324 lines
60 KiB
Go
Raw Permalink Normal View History

2021-11-13 13:03:21 +13:00
package main
2023-04-28 15:03:55 +12:00
2021-11-13 13:03:21 +13:00
/*
2021-12-23 05:56:33 +13:00
Picocrypt v1.34
2022-09-26 17:02:31 +13:00
Copyright (c) Evan Su
2021-11-13 13:03:21 +13:00
Released under a GNU GPL v3 License
https://github.com/HACKERALERT/Picocrypt
~ In cryptography we trust ~
*/
import (
"archive/zip"
"bytes"
2022-03-20 05:43:32 +13:00
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/subtle"
2021-11-13 13:03:21 +13:00
"fmt"
"hash"
"image"
"image/color"
"io"
"math"
"math/big"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
2022-03-20 05:43:32 +13:00
"github.com/HACKERALERT/dialog"
"github.com/HACKERALERT/giu"
2022-05-08 14:35:33 +12:00
"github.com/HACKERALERT/imgui-go"
2022-03-20 05:43:32 +13:00
"github.com/HACKERALERT/infectious"
"github.com/HACKERALERT/serpent"
"github.com/HACKERALERT/zxcvbn-go"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/hkdf"
"golang.org/x/crypto/sha3"
2021-11-13 13:03:21 +13:00
)
2022-05-16 14:45:09 +12:00
// Constants
var KiB = 1 << 10
var MiB = 1 << 20
var GiB = 1 << 30
var TiB = 1 << 40
var WHITE = color.RGBA{0xff, 0xff, 0xff, 0xff}
var RED = color.RGBA{0xff, 0x00, 0x00, 0xff}
var GREEN = color.RGBA{0x00, 0xff, 0x00, 0xff}
var YELLOW = color.RGBA{0xff, 0xff, 0x00, 0xff}
var TRANSPARENT = color.RGBA{0x00, 0x00, 0x00, 0x00}
2021-11-13 13:03:21 +13:00
// Generic variables
var window *giu.MasterWindow
2023-06-28 10:15:56 +12:00
var version = "v1.33"
2021-11-13 13:03:21 +13:00
var dpi float32
var mode string
var working bool
2022-05-16 14:45:09 +12:00
var scanning bool
2021-11-13 13:03:21 +13:00
2022-04-02 16:16:04 +13:00
// Popup modals
2022-05-16 14:45:09 +12:00
var modalId int
var showPassgen bool
var showKeyfile bool
var showOverwrite bool
var showProgress bool
2022-04-02 16:16:04 +13:00
// Input and output files
2022-05-16 14:45:09 +12:00
var inputFile string
2022-05-17 08:10:58 +12:00
var inputFileOld string
2022-05-16 14:45:09 +12:00
var outputFile string
2021-11-13 13:03:21 +13:00
var onlyFiles []string
var onlyFolders []string
var allFiles []string
var inputLabel = "Drop files and folders into this window."
2022-05-16 14:45:09 +12:00
// Password and confirm password
2021-11-13 13:03:21 +13:00
var password string
2022-04-02 16:16:04 +13:00
var cpassword string
2021-11-13 13:03:21 +13:00
var passwordStrength int
var passwordState = giu.InputTextFlagsPassword
var passwordStateLabel = "Show"
2022-05-16 14:45:09 +12:00
// Password generator
2022-04-02 16:16:04 +13:00
var passgenLength int32 = 32
2022-05-16 14:45:09 +12:00
var passgenUpper bool
var passgenLower bool
var passgenNums bool
var passgenSymbols bool
var passgenCopy bool
2021-11-13 13:03:21 +13:00
// Keyfile variables
var keyfile bool
var keyfiles []string
2022-05-16 14:45:09 +12:00
var keyfileOrdered bool
var keyfileLabel = "None selected."
2021-11-13 13:03:21 +13:00
2022-04-02 16:16:04 +13:00
// Comments variables
var comments string
2022-05-16 14:45:09 +12:00
var commentsLabel = "Comments:"
2022-04-02 16:16:04 +13:00
var commentsDisabled bool
2021-11-13 13:03:21 +13:00
// Advanced options
var paranoid bool
var reedsolo bool
2023-04-28 14:52:01 +12:00
var deniability bool
var recursively bool
2021-11-13 13:03:21 +13:00
var split bool
var splitSize string
2022-05-02 09:13:28 +12:00
var splitUnits = []string{"KiB", "MiB", "GiB", "TiB", "Total"}
2021-11-13 13:03:21 +13:00
var splitSelected int32 = 1
2022-05-16 14:45:09 +12:00
var recombine bool
2021-11-13 13:03:21 +13:00
var compress bool
2022-05-16 14:45:09 +12:00
var delete bool
2021-11-13 13:03:21 +13:00
var keep bool
var kept bool
// Status variables
2022-04-14 13:32:11 +12:00
var startLabel = "Start"
2021-11-13 13:03:21 +13:00
var mainStatus = "Ready."
2022-05-16 14:45:09 +12:00
var mainStatusColor = WHITE
2021-11-13 13:03:21 +13:00
var popupStatus string
// Progress variables
var progress float32
var progressInfo string
2022-05-16 14:45:09 +12:00
var speed float64
var eta string
var canCancel bool
2021-11-13 13:03:21 +13:00
2022-05-16 14:45:09 +12:00
// Reed-Solomon encoders
var rs1, _ = infectious.NewFEC(1, 3)
2021-11-13 13:03:21 +13:00
var rs5, _ = infectious.NewFEC(5, 15)
var rs16, _ = infectious.NewFEC(16, 48)
var rs24, _ = infectious.NewFEC(24, 72)
var rs32, _ = infectious.NewFEC(32, 96)
var rs64, _ = infectious.NewFEC(64, 192)
2022-05-16 14:45:09 +12:00
var rs128, _ = infectious.NewFEC(128, 136)
var fastDecode bool
2022-04-02 16:16:04 +13:00
2022-05-16 14:45:09 +12:00
// Compression variables and passthrough
2022-04-02 16:16:04 +13:00
var compressDone int64
var compressTotal int64
2022-05-16 14:45:09 +12:00
var compressStart time.Time
2021-11-13 13:03:21 +13:00
2022-04-02 16:16:04 +13:00
type compressorProgress struct {
io.Reader
}
func (p *compressorProgress) Read(data []byte) (int, error) {
2022-05-16 14:45:09 +12:00
if !working {
return 0, io.EOF
}
2022-04-02 16:16:04 +13:00
read, err := p.Reader.Read(data)
compressDone += int64(read)
2022-05-16 14:45:09 +12:00
progress, speed, eta = statify(compressDone, compressTotal, compressStart)
if compress {
popupStatus = fmt.Sprintf("Compressing at %.2f MiB/s (ETA: %s)", speed, eta)
} else {
popupStatus = fmt.Sprintf("Combining at %.2f MiB/s (ETA: %s)", speed, eta)
}
2022-04-02 16:16:04 +13:00
giu.Update()
return read, err
}
2022-05-16 14:45:09 +12:00
// The main user interface
2021-11-13 13:03:21 +13:00
func draw() {
2022-03-20 16:23:11 +13:00
giu.SingleWindow().Flags(524351).Layout(
2021-11-13 13:03:21 +13:00
giu.Custom(func() {
2022-04-02 16:16:04 +13:00
if showPassgen {
giu.PopupModal("Generate password:##"+strconv.Itoa(modalId)).Flags(6).Layout(
2022-03-20 05:43:32 +13:00
giu.Row(
giu.Label("Length:"),
2022-04-02 16:16:04 +13:00
giu.SliderInt(&passgenLength, 4, 64).Size(giu.Auto),
2022-03-20 05:43:32 +13:00
),
2022-04-02 16:16:04 +13:00
giu.Checkbox("Uppercase", &passgenUpper),
giu.Checkbox("Lowercase", &passgenLower),
giu.Checkbox("Numbers", &passgenNums),
giu.Checkbox("Symbols", &passgenSymbols),
2022-11-18 13:44:54 +13:00
giu.Checkbox("Copy to clipboard", &passgenCopy),
2022-03-20 05:43:32 +13:00
giu.Row(
giu.Button("Cancel").Size(100, 0).OnClick(func() {
giu.CloseCurrentPopup()
2022-04-02 16:16:04 +13:00
showPassgen = false
2022-03-20 05:43:32 +13:00
}),
2022-05-07 15:33:44 +12:00
giu.Style().SetDisabled(!(passgenUpper || passgenLower || passgenNums || passgenSymbols)).To(
giu.Button("Generate").Size(100, 0).OnClick(func() {
password = genPassword()
cpassword = password
passwordStrength = zxcvbn.PasswordStrength(password, nil).Score
2022-05-16 14:45:09 +12:00
2022-05-07 15:33:44 +12:00
giu.CloseCurrentPopup()
showPassgen = false
}),
),
2022-03-20 05:43:32 +13:00
),
).Build()
2022-04-02 16:16:04 +13:00
giu.OpenPopup("Generate password:##" + strconv.Itoa(modalId))
2022-03-20 05:43:32 +13:00
giu.Update()
}
2021-11-13 13:03:21 +13:00
2022-03-20 05:43:32 +13:00
if showKeyfile {
2022-04-02 16:16:04 +13:00
giu.PopupModal("Manage keyfiles:##"+strconv.Itoa(modalId)).Flags(70).Layout(
2022-03-20 05:43:32 +13:00
giu.Label("Drag and drop your keyfiles here."),
2021-11-13 13:03:21 +13:00
giu.Custom(func() {
2022-03-20 05:43:32 +13:00
if mode != "decrypt" {
2022-05-16 14:45:09 +12:00
giu.Checkbox("Require correct order", &keyfileOrdered).Build()
2022-08-20 15:35:00 +12:00
giu.Tooltip("Ordering of keyfiles will matter.").Build()
2022-05-16 14:45:09 +12:00
} else if keyfileOrdered {
2022-08-20 15:35:00 +12:00
giu.Label("Correct ordering is required.").Build()
2021-11-13 13:03:21 +13:00
}
}),
giu.Custom(func() {
2022-08-20 15:35:00 +12:00
if len(keyfiles) > 0 {
giu.Separator().Build()
}
2022-03-20 05:43:32 +13:00
for _, i := range keyfiles {
2022-04-02 16:16:04 +13:00
giu.Label(filepath.Base(i)).Build()
2021-11-13 13:03:21 +13:00
}
}),
2022-03-20 05:43:32 +13:00
giu.Row(
2022-04-02 16:16:04 +13:00
giu.Button("Clear").Size(100, 0).OnClick(func() {
2022-03-20 05:43:32 +13:00
keyfiles = nil
2022-04-14 13:32:11 +12:00
if keyfile {
2022-05-16 14:45:09 +12:00
keyfileLabel = "Keyfiles required."
2022-04-14 13:32:11 +12:00
} else {
2022-05-16 14:45:09 +12:00
keyfileLabel = "None selected."
2022-04-14 13:32:11 +12:00
}
2022-04-02 16:16:04 +13:00
modalId++
2022-05-16 14:45:09 +12:00
giu.Update()
2022-03-20 05:43:32 +13:00
}),
giu.Tooltip("Remove all keyfiles."),
2022-04-02 16:16:04 +13:00
giu.Button("Done").Size(100, 0).OnClick(func() {
2022-03-20 05:43:32 +13:00
giu.CloseCurrentPopup()
showKeyfile = false
}),
),
).Build()
2022-04-02 16:16:04 +13:00
giu.OpenPopup("Manage keyfiles:##" + strconv.Itoa(modalId))
2022-03-20 05:43:32 +13:00
giu.Update()
}
2021-11-13 13:03:21 +13:00
2022-05-16 14:45:09 +12:00
if showOverwrite {
2022-04-02 16:16:04 +13:00
giu.PopupModal("Warning:##"+strconv.Itoa(modalId)).Flags(6).Layout(
2022-03-20 05:43:32 +13:00
giu.Label("Output already exists. Overwrite?"),
giu.Row(
giu.Button("No").Size(100, 0).OnClick(func() {
giu.CloseCurrentPopup()
2022-05-16 14:45:09 +12:00
showOverwrite = false
2022-03-20 05:43:32 +13:00
}),
giu.Button("Yes").Size(100, 0).OnClick(func() {
giu.CloseCurrentPopup()
2022-05-16 14:45:09 +12:00
showOverwrite = false
2022-03-20 05:43:32 +13:00
showProgress = true
2022-05-16 14:45:09 +12:00
fastDecode = true
canCancel = true
modalId++
2021-11-13 13:03:21 +13:00
giu.Update()
2022-03-20 05:43:32 +13:00
go func() {
work()
working = false
showProgress = false
giu.Update()
}()
}),
),
).Build()
2022-04-02 16:16:04 +13:00
giu.OpenPopup("Warning:##" + strconv.Itoa(modalId))
2022-03-20 05:43:32 +13:00
giu.Update()
}
2021-11-13 13:03:21 +13:00
2022-03-20 05:43:32 +13:00
if showProgress {
2022-04-02 16:16:04 +13:00
giu.PopupModal(" ##"+strconv.Itoa(modalId)).Flags(6).Layout(
2021-11-13 13:03:21 +13:00
giu.Row(
2022-05-16 14:45:09 +12:00
giu.ProgressBar(progress).Size(210, 0).Overlay(progressInfo),
giu.Style().SetDisabled(!canCancel).To(
giu.Button(func() string {
if working {
return "Cancel"
}
return "..."
}()).Size(58, 0).OnClick(func() {
working = false
canCancel = false
}),
),
2021-11-13 13:03:21 +13:00
),
2022-03-20 05:43:32 +13:00
giu.Label(popupStatus),
).Build()
2022-04-02 16:16:04 +13:00
giu.OpenPopup(" ##" + strconv.Itoa(modalId))
2022-03-20 05:43:32 +13:00
giu.Update()
}
}),
2021-11-13 13:03:21 +13:00
2022-03-20 05:43:32 +13:00
giu.Row(
giu.Label(inputLabel),
giu.Custom(func() {
bw, _ := giu.CalcTextSize("Clear")
p, _ := giu.GetWindowPadding()
bw += p * 2
2022-04-02 16:16:04 +13:00
giu.Dummy((bw+p)/-dpi, 0).Build()
2022-03-20 05:43:32 +13:00
giu.SameLine()
2022-05-16 14:45:09 +12:00
giu.Style().SetDisabled((len(allFiles) == 0 && len(onlyFiles) == 0) || scanning).To(
2022-03-20 05:43:32 +13:00
giu.Button("Clear").Size(bw/dpi, 0).OnClick(resetUI),
giu.Tooltip("Clear all input files and reset UI state."),
).Build()
}),
),
giu.Separator(),
2022-05-16 14:45:09 +12:00
giu.Style().SetDisabled((len(allFiles) == 0 && len(onlyFiles) == 0) || scanning).To(
2022-04-02 16:16:04 +13:00
giu.Label("Password:"),
2022-03-20 05:43:32 +13:00
giu.Row(
giu.Button(passwordStateLabel).Size(54, 0).OnClick(func() {
if passwordState == giu.InputTextFlagsPassword {
passwordState = giu.InputTextFlagsNone
passwordStateLabel = "Hide"
} else {
passwordState = giu.InputTextFlagsPassword
passwordStateLabel = "Show"
}
2022-05-16 14:45:09 +12:00
giu.Update()
2022-03-20 05:43:32 +13:00
}),
2022-04-02 16:16:04 +13:00
giu.Tooltip("Toggle the visibility of password entries."),
2022-03-20 05:43:32 +13:00
giu.Button("Clear").Size(54, 0).OnClick(func() {
password = ""
2022-04-02 16:16:04 +13:00
cpassword = ""
2022-05-16 14:45:09 +12:00
giu.Update()
2022-03-20 05:43:32 +13:00
}),
2022-04-02 16:16:04 +13:00
giu.Tooltip("Clear the password entries."),
2022-03-20 05:43:32 +13:00
2022-11-18 13:44:54 +13:00
giu.Button("Copy").Size(54, 0).OnClick(func() {
giu.Context.GetPlatform().SetClipboard(password)
giu.Update()
}),
giu.Tooltip("Copy the password into your clipboard."),
2022-03-20 05:43:32 +13:00
2022-11-18 13:44:54 +13:00
giu.Button("Paste").Size(54, 0).OnClick(func() {
tmp := giu.Context.GetPlatform().GetClipboard()
password = tmp
if mode != "decrypt" {
cpassword = tmp
}
passwordStrength = zxcvbn.PasswordStrength(password, nil).Score
giu.Update()
}),
giu.Tooltip("Paste a password from your clipboard."),
2021-11-13 13:03:21 +13:00
2022-03-20 05:43:32 +13:00
giu.Style().SetDisabled(mode == "decrypt").To(
giu.Button("Create").Size(54, 0).OnClick(func() {
2022-04-02 16:16:04 +13:00
showPassgen = true
2022-05-16 14:45:09 +12:00
modalId++
giu.Update()
2022-03-20 05:43:32 +13:00
}),
),
2022-04-02 16:16:04 +13:00
giu.Tooltip("Generate a cryptographically secure password."),
2022-03-20 05:43:32 +13:00
),
giu.Row(
giu.InputText(&password).Flags(passwordState).Size(302/dpi).OnChange(func() {
passwordStrength = zxcvbn.PasswordStrength(password, nil).Score
2022-05-16 14:45:09 +12:00
giu.Update()
2022-03-20 05:43:32 +13:00
}),
giu.Custom(func() {
c := giu.GetCanvas()
p := giu.GetCursorScreenPos()
2022-04-02 16:16:04 +13:00
col := color.RGBA{
uint8(0xc8 - 31*passwordStrength),
uint8(0x4c + 31*passwordStrength), 0x4b, 0xff,
2022-03-20 05:43:32 +13:00
}
if password == "" || mode == "decrypt" {
2022-05-16 14:45:09 +12:00
col = TRANSPARENT
2022-03-20 05:43:32 +13:00
}
path := p.Add(image.Pt(
2022-04-02 16:16:04 +13:00
int(math.Round(-20*float64(dpi))),
int(math.Round(12*float64(dpi))),
2022-03-20 05:43:32 +13:00
))
2022-04-02 16:16:04 +13:00
c.PathArcTo(path, 6*dpi, -math.Pi/2, math.Pi*(.4*float32(passwordStrength)-.1), -1)
2022-03-20 05:43:32 +13:00
c.PathStroke(col, false, 2)
}),
),
2021-11-13 13:03:21 +13:00
2022-04-02 16:16:04 +13:00
giu.Dummy(0, 0),
giu.Style().SetDisabled(password == "" || mode == "decrypt").To(
giu.Label("Confirm password:"),
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" {
2022-05-16 14:45:09 +12:00
col = TRANSPARENT
2022-04-02 16:16:04 +13:00
}
path := p.Add(image.Pt(
int(math.Round(-20*float64(dpi))),
int(math.Round(12*float64(dpi))),
))
c.PathArcTo(path, 6*dpi, 0, 2*math.Pi, -1)
c.PathStroke(col, false, 2)
}),
2022-03-20 05:43:32 +13:00
),
),
2022-04-02 16:16:04 +13:00
giu.Dummy(0, 0),
2023-04-28 14:52:01 +12:00
giu.Style().SetDisabled(mode == "decrypt" && !keyfile && !deniability).To(
2022-04-02 16:16:04 +13:00
giu.Row(
giu.Label("Keyfiles:"),
giu.Button("Edit").Size(54, 0).OnClick(func() {
showKeyfile = true
2022-05-16 14:45:09 +12:00
modalId++
giu.Update()
2022-04-02 16:16:04 +13:00
}),
2022-07-14 12:40:47 +12:00
giu.Tooltip("Manage keyfiles to use for "+(func() string {
if mode != "decrypt" {
return "encryption."
}
return "decryption."
}())),
2022-04-02 16:16:04 +13:00
giu.Style().SetDisabled(mode == "decrypt").To(
giu.Button("Create").Size(54, 0).OnClick(func() {
2022-04-14 13:32:11 +12:00
f := dialog.File().Title("Choose where to save the keyfile.")
2022-04-02 16:16:04 +13:00
f.SetStartDir(func() string {
if len(onlyFiles) > 0 {
return filepath.Dir(onlyFiles[0])
}
return filepath.Dir(onlyFolders[0])
}())
2022-04-14 13:32:11 +12:00
f.SetInitFilename("Keyfile")
file, err := f.Save()
if file == "" || err != nil {
2022-04-02 16:16:04 +13:00
return
2021-11-13 13:03:21 +13:00
}
2022-03-20 05:43:32 +13:00
2022-04-02 16:16:04 +13:00
fout, _ := os.Create(file)
2022-05-24 12:40:01 +12:00
data := make([]byte, KiB)
2022-04-02 16:16:04 +13:00
rand.Read(data)
2022-05-24 12:40:01 +12:00
_, err = fout.Write(data)
2022-04-02 16:16:04 +13:00
fout.Close()
2022-05-24 12:40:01 +12:00
if err != nil {
insufficientSpace(nil, nil)
os.Remove(file)
} else {
mainStatus = "Ready."
mainStatusColor = WHITE
}
2021-11-13 13:03:21 +13:00
}),
2022-04-02 16:16:04 +13:00
giu.Tooltip("Generate a cryptographically secure keyfile."),
),
giu.Style().SetDisabled(true).To(
2022-05-16 14:45:09 +12:00
giu.InputText(&keyfileLabel).Size(giu.Auto),
2021-11-13 13:03:21 +13:00
),
2022-03-20 05:43:32 +13:00
),
),
),
giu.Separator(),
2022-05-16 14:45:09 +12:00
giu.Style().SetDisabled(mode != "decrypt" && ((len(keyfiles) == 0 && password == "") || (password != cpassword))).To(
giu.Style().SetDisabled(mode == "decrypt" && (comments == "" || comments == "Comments are corrupted.")).To(
giu.Label(commentsLabel),
2022-05-02 09:13:28 +12:00
giu.InputText(&comments).Size(giu.Auto).Flags(func() giu.InputTextFlags {
if commentsDisabled {
return giu.InputTextFlagsReadOnly
}
return giu.InputTextFlagsNone
}()),
2023-06-28 10:15:56 +12:00
giu.Custom(func() {
if !commentsDisabled {
giu.Tooltip("Note: comments are not encrypted!").Build()
}
}),
2022-05-02 09:13:28 +12:00
),
),
2022-05-16 14:45:09 +12:00
giu.Style().SetDisabled((len(keyfiles) == 0 && password == "") || (mode == "encrypt" && password != cpassword)).To(
2022-03-20 05:43:32 +13:00
giu.Label("Advanced:"),
giu.Custom(func() {
if mode != "decrypt" {
giu.Row(
2022-04-02 16:16:04 +13:00
giu.Checkbox("Paranoid mode", &paranoid),
giu.Tooltip("Provides the highest level of security attainable."),
giu.Dummy(-170, 0),
2023-04-28 14:52:01 +12:00
giu.Style().SetDisabled(recursively).To(
giu.Checkbox("Compress files", &compress).OnChange(func() {
if !(len(allFiles) > 1 || len(onlyFolders) > 0) {
if compress {
outputFile = filepath.Join(filepath.Dir(outputFile), "Encrypted") + ".zip.pcv"
} else {
outputFile = filepath.Join(filepath.Dir(outputFile), filepath.Base(inputFile)) + ".pcv"
}
2022-05-20 15:56:45 +12:00
}
2023-04-28 14:52:01 +12:00
}),
giu.Tooltip("Compress files with Deflate before encrypting."),
),
2022-03-20 05:43:32 +13:00
).Build()
2022-04-02 16:16:04 +13:00
giu.Row(
giu.Checkbox("Reed-Solomon", &reedsolo),
2022-07-01 15:54:58 +12:00
giu.Tooltip("Prevent file corruption with erasure coding."),
2022-04-02 16:16:04 +13:00
giu.Dummy(-170, 0),
2022-05-16 14:45:09 +12:00
giu.Checkbox("Delete files", &delete),
2022-04-02 16:16:04 +13:00
giu.Tooltip("Delete the input files after encryption."),
).Build()
2023-04-28 14:52:01 +12:00
giu.Row(
giu.Checkbox("Deniability", &deniability),
giu.Tooltip("Add plausible deniability to the volume."),
giu.Dummy(-170, 0),
giu.Style().SetDisabled(!(len(allFiles) > 1 || len(onlyFolders) > 0)).To(
giu.Checkbox("Recursively", &recursively).OnChange(func() {
compress = false
}),
giu.Tooltip("Encrypt and decrypt recursive files individually."),
),
).Build()
2022-03-20 05:43:32 +13:00
giu.Row(
2022-04-02 16:16:04 +13:00
giu.Checkbox("Split into chunks:", &split),
giu.Tooltip("Split the output file into smaller chunks."),
giu.Dummy(-170, 0),
2022-05-16 14:45:09 +12:00
giu.InputText(&splitSize).Size(86/dpi).Flags(2).OnChange(func() {
2022-03-20 05:43:32 +13:00
split = splitSize != ""
}),
2022-04-02 16:16:04 +13:00
giu.Tooltip("Choose the chunk size."),
giu.Combo("##splitter", splitUnits[splitSelected], splitUnits, &splitSelected).Size(68),
2022-05-16 14:45:09 +12:00
giu.Tooltip("Choose the chunk units."),
2022-03-20 05:43:32 +13:00
).Build()
} else {
2022-04-02 16:16:04 +13:00
giu.Row(
2023-04-28 14:52:01 +12:00
giu.Style().SetDisabled(deniability).To(
giu.Checkbox("Force decrypt", &keep),
giu.Tooltip("Override security measures when decrypting."),
),
2022-04-02 16:16:04 +13:00
giu.Dummy(-170, 0),
2022-05-16 14:45:09 +12:00
giu.Checkbox("Delete volume", &delete),
2022-04-02 16:16:04 +13:00
giu.Tooltip("Delete the volume after a successful decryption."),
).Build()
2022-03-20 05:43:32 +13:00
}
}),
2023-04-28 14:52:01 +12:00
giu.Style().SetDisabled(recursively).To(
giu.Label("Save output as:"),
giu.Custom(func() {
w, _ := giu.GetAvailableRegion()
bw, _ := giu.CalcTextSize("Change")
p, _ := giu.GetWindowPadding()
bw += p * 2
dw := w - bw - p
giu.Style().SetDisabled(true).To(
giu.InputText(func() *string {
tmp := ""
if outputFile == "" {
return &tmp
}
tmp = filepath.Base(outputFile)
if split {
tmp += ".*"
}
if recursively {
tmp = "(multiple values)"
}
2022-04-19 09:46:13 +12:00
return &tmp
2023-04-28 14:52:01 +12:00
}()).Size(dw / dpi / dpi).Flags(16384),
).Build()
2022-04-14 13:32:11 +12:00
2023-04-28 14:52:01 +12:00
giu.SameLine()
giu.Button("Change").Size(bw/dpi, 0).OnClick(func() {
f := dialog.File().Title("Choose where to save the output. Don't include extensions.")
f.SetStartDir(func() string {
if len(onlyFiles) > 0 {
return filepath.Dir(onlyFiles[0])
}
return filepath.Dir(onlyFolders[0])
}())
// Prefill the filename
tmp := strings.TrimSuffix(filepath.Base(outputFile), ".pcv")
f.SetInitFilename(strings.TrimSuffix(tmp, filepath.Ext(tmp)))
if mode == "encrypt" && (len(allFiles) > 1 || len(onlyFolders) > 0 || compress) {
f.SetInitFilename("Encrypted")
2022-04-02 16:16:04 +13:00
}
2021-11-13 13:03:21 +13:00
2023-04-28 14:52:01 +12:00
// Get the chosen file path
file, err := f.Save()
if file == "" || err != nil {
return
2022-03-20 05:43:32 +13:00
}
2023-04-28 14:52:01 +12:00
file = filepath.Join(filepath.Dir(file), strings.Split(filepath.Base(file), ".")[0])
// Add the correct extensions
if mode == "encrypt" {
if len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
file += ".zip.pcv"
} else {
file += filepath.Ext(inputFile) + ".pcv"
}
2022-03-20 05:43:32 +13:00
} else {
2023-04-28 14:52:01 +12:00
if strings.HasSuffix(inputFile, ".zip.pcv") {
file += ".zip"
} else {
tmp := strings.TrimSuffix(filepath.Base(inputFile), ".pcv")
file += filepath.Ext(tmp)
}
2022-03-20 05:43:32 +13:00
}
2023-04-28 14:52:01 +12:00
outputFile = file
mainStatus = "Ready."
mainStatusColor = WHITE
}).Build()
giu.Tooltip("Save the output with a custom name and path.").Build()
}),
),
2021-11-13 13:03:21 +13:00
2022-04-02 16:16:04 +13:00
giu.Dummy(0, 0),
2022-03-20 05:43:32 +13:00
giu.Separator(),
2022-04-02 16:16:04 +13:00
giu.Dummy(0, 0),
2023-04-28 14:52:01 +12:00
giu.Button(func() string {
if !recursively {
return startLabel
}
2023-04-29 07:13:16 +12:00
return "Process"
2023-04-28 14:52:01 +12:00
}()).Size(giu.Auto, 34).OnClick(func() {
2022-03-20 05:43:32 +13:00
if keyfile && keyfiles == nil {
mainStatus = "Please select your keyfiles."
2022-05-16 14:45:09 +12:00
mainStatusColor = RED
2022-03-20 05:43:32 +13:00
return
}
2022-05-02 09:13:28 +12:00
tmp, err := strconv.Atoi(splitSize)
if split && (splitSize == "" || tmp <= 0 || err != nil) {
2022-08-20 15:35:00 +12:00
mainStatus = "Invalid chunk size."
2022-05-16 14:45:09 +12:00
mainStatusColor = RED
2022-05-02 09:13:28 +12:00
return
}
2022-08-20 15:35:00 +12:00
// Check if output file already exists
2022-05-02 09:13:28 +12:00
_, err = os.Stat(outputFile)
2022-08-20 15:35:00 +12:00
// Check if any split chunks already exist
if split {
names, _ := filepath.Glob(outputFile + ".*")
if len(names) > 0 {
err = nil
} else {
err = os.ErrNotExist
}
}
// If files already exist, show the overwrite modal
2023-04-28 14:52:01 +12:00
if err == nil && !recursively {
2022-05-16 14:45:09 +12:00
showOverwrite = true
2022-04-02 16:16:04 +13:00
modalId++
2022-03-20 05:43:32 +13:00
giu.Update()
2022-08-20 15:35:00 +12:00
} else { // Nothing to worry about, start working
2022-03-20 05:43:32 +13:00
showProgress = true
2022-05-16 14:45:09 +12:00
fastDecode = true
canCancel = true
modalId++
2022-03-20 05:43:32 +13:00
giu.Update()
2023-04-28 14:52:01 +12:00
if !recursively {
go func() {
work()
working = false
showProgress = false
giu.Update()
}()
} else {
// Store variables as they will be cleared
oldPassword := password
oldKeyfile := keyfile
oldKeyfiles := keyfiles
oldKeyfileOrdered := keyfileOrdered
oldKeyfileLabel := keyfileLabel
oldComments := comments
oldParanoid := paranoid
oldReedsolo := reedsolo
oldDeniability := deniability
oldSplit := split
oldSplitSize := splitSize
oldSplitSelected := splitSelected
oldDelete := delete
files := allFiles
go func() {
for _, file := range files {
// Simulate dropping the file
onDrop([]string{file})
// Restore variables and options
password = oldPassword
cpassword = oldPassword
keyfile = oldKeyfile
keyfiles = oldKeyfiles
keyfileOrdered = oldKeyfileOrdered
keyfileLabel = oldKeyfileLabel
comments = oldComments
paranoid = oldParanoid
reedsolo = oldReedsolo
deniability = oldDeniability
split = oldSplit
splitSize = oldSplitSize
splitSelected = oldSplitSelected
delete = oldDelete
work()
if !working {
resetUI()
cancel(nil, nil)
showProgress = false
giu.Update()
return
}
}
working = false
showProgress = false
giu.Update()
}()
}
2022-03-20 05:43:32 +13:00
}
}),
giu.Style().SetColor(giu.StyleColorText, mainStatusColor).To(
giu.Label(mainStatus),
),
),
2021-11-13 13:03:21 +13:00
giu.Custom(func() {
2022-04-02 16:16:04 +13:00
window.SetSize(int(318*dpi), giu.GetCursorPos().Y+1)
2021-11-13 13:03:21 +13:00
}),
)
}
func onDrop(names []string) {
if showKeyfile {
keyfiles = append(keyfiles, names...)
2022-04-02 16:16:04 +13:00
2022-05-24 12:40:01 +12:00
// Make sure keyfiles are accessible, remove duplicates
2021-11-13 13:03:21 +13:00
var tmp []string
for _, i := range keyfiles {
duplicate := false
for _, j := range tmp {
if i == j {
duplicate = true
}
}
stat, _ := os.Stat(i)
2022-05-16 14:45:09 +12:00
fin, err := os.Open(i)
if err == nil {
fin.Close()
2022-05-24 12:40:01 +12:00
} else {
showKeyfile = false
resetUI()
accessDenied("Keyfile read")
giu.Update()
return
2022-05-16 14:45:09 +12:00
}
if !duplicate && !stat.IsDir() && err == nil {
2021-11-13 13:03:21 +13:00
tmp = append(tmp, i)
}
}
keyfiles = tmp
2022-04-02 16:16:04 +13:00
// Update the keyfile status
2022-05-16 14:45:09 +12:00
if len(keyfiles) == 0 {
keyfileLabel = "None selected."
} else if len(keyfiles) == 1 {
keyfileLabel = "Using 1 keyfile."
2021-11-13 13:03:21 +13:00
} else {
2022-05-16 14:45:09 +12:00
keyfileLabel = fmt.Sprintf("Using %d keyfiles.", len(keyfiles))
2021-11-13 13:03:21 +13:00
}
2022-04-02 16:16:04 +13:00
modalId++
2022-05-16 14:45:09 +12:00
giu.Update()
2021-11-13 13:03:21 +13:00
return
}
2022-05-16 14:45:09 +12:00
scanning = true
2022-08-20 15:35:00 +12:00
files, folders := 0, 0
compressDone, compressTotal = 0, 0
2021-11-13 13:03:21 +13:00
resetUI()
2022-04-02 16:16:04 +13:00
// One item dropped
2021-11-13 13:03:21 +13:00
if len(names) == 1 {
stat, _ := os.Stat(names[0])
2022-04-02 16:16:04 +13:00
// A folder was dropped
2021-11-13 13:03:21 +13:00
if stat.IsDir() {
folders++
2022-04-02 16:16:04 +13:00
mode = "encrypt"
2022-05-18 12:44:03 +12:00
inputLabel = "1 folder."
2022-04-14 13:32:11 +12:00
startLabel = "Encrypt"
2021-11-13 13:03:21 +13:00
onlyFolders = append(onlyFolders, names[0])
2022-03-20 05:43:32 +13:00
inputFile = filepath.Join(filepath.Dir(names[0]), "Encrypted") + ".zip"
2022-04-02 16:16:04 +13:00
outputFile = inputFile + ".pcv"
} else { // A file was dropped
2021-11-13 13:03:21 +13:00
files++
2022-04-02 16:16:04 +13:00
// Is the file a part of a split volume?
2021-11-13 13:03:21 +13:00
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 {
mode = "decrypt"
2022-05-18 12:44:03 +12:00
inputLabel = "Volume for decryption."
2022-04-14 13:32:11 +12:00
startLabel = "Decrypt"
2022-05-16 14:45:09 +12:00
commentsLabel = "Comments (read-only):"
2022-04-02 16:16:04 +13:00
commentsDisabled = true
2021-11-13 13:03:21 +13:00
2022-05-16 14:45:09 +12:00
// Get the correct input and output filenames
2021-11-13 13:03:21 +13:00
if isSplit {
ind := strings.Index(names[0], ".pcv")
names[0] = names[0][:ind+4]
inputFile = names[0]
outputFile = names[0][:ind]
recombine = true
2022-05-18 12:44:03 +12:00
// Find out the number of splitted chunks
2022-05-24 12:40:01 +12:00
totalFiles := 0
2022-05-18 12:44:03 +12:00
for {
stat, err := os.Stat(fmt.Sprintf("%s.%d", inputFile, totalFiles))
if err != nil {
break
}
totalFiles++
2022-08-20 15:35:00 +12:00
compressTotal += stat.Size()
2022-05-18 12:44:03 +12:00
}
2021-11-13 13:03:21 +13:00
} else {
outputFile = names[0][:len(names[0])-4]
}
2022-04-02 16:16:04 +13:00
// Open the input file in read-only mode
2021-11-13 13:03:21 +13:00
var fin *os.File
2022-05-16 14:45:09 +12:00
var err error
2021-11-13 13:03:21 +13:00
if isSplit {
2022-05-16 14:45:09 +12:00
fin, err = os.Open(names[0] + ".0")
2021-11-13 13:03:21 +13:00
} else {
2022-05-16 14:45:09 +12:00
fin, err = os.Open(names[0])
2021-11-13 13:03:21 +13:00
}
2022-05-16 14:45:09 +12:00
if err != nil {
2021-11-13 13:03:21 +13:00
resetUI()
2022-05-16 14:45:09 +12:00
accessDenied("Read")
2021-11-13 13:03:21 +13:00
return
}
2023-04-28 14:52:01 +12:00
// Check if version can be read from header
2022-05-16 14:45:09 +12:00
tmp := make([]byte, 15)
2021-11-13 13:03:21 +13:00
fin.Read(tmp)
2022-05-16 14:45:09 +12:00
tmp, err = rsDecode(rs5, tmp)
2022-11-18 13:44:54 +13:00
if valid, _ := regexp.Match(`^v1\.\d{2}`, tmp); !valid || err != nil {
2023-04-28 14:52:01 +12:00
// Volume has plausible deniability
deniability = true
mainStatus = "Can't read header, assuming volume is deniable."
2021-12-23 06:03:38 +13:00
fin.Close()
2023-04-28 14:52:01 +12:00
} else {
// Read comments from file and check for corruption
tmp = make([]byte, 15)
2021-11-13 13:03:21 +13:00
fin.Read(tmp)
2023-04-28 14:52:01 +12:00
tmp, err = rsDecode(rs5, tmp)
if err == nil {
commentsLength, _ := strconv.Atoi(string(tmp))
tmp = make([]byte, commentsLength*3)
fin.Read(tmp)
comments = ""
for i := 0; i < commentsLength*3; i += 3 {
t, err := rsDecode(rs1, tmp[i:i+3])
if err != nil {
comments = "Comments are corrupted."
break
}
comments += string(t)
2021-11-13 13:03:21 +13:00
}
2023-04-28 14:52:01 +12:00
} else {
comments = "Comments are corrupted."
2021-11-13 13:03:21 +13:00
}
2023-04-28 14:52:01 +12:00
// Read flags from file and check for corruption
flags := make([]byte, 15)
fin.Read(flags)
fin.Close()
flags, err = rsDecode(rs5, flags)
if err != nil {
mainStatus = "The volume header is damaged."
mainStatusColor = RED
return
}
2021-11-13 13:03:21 +13:00
2023-04-28 14:52:01 +12:00
// Update UI and variables according to flags
if flags[1] == 1 {
keyfile = true
keyfileLabel = "Keyfiles required."
} else {
keyfileLabel = "Not applicable."
}
if flags[2] == 1 {
keyfileOrdered = true
}
2021-11-13 13:03:21 +13:00
}
2022-05-16 14:45:09 +12:00
} else { // One file was dropped for encryption
2021-11-13 13:03:21 +13:00
mode = "encrypt"
2022-05-18 12:44:03 +12:00
inputLabel = "1 file."
2022-04-14 13:32:11 +12:00
startLabel = "Encrypt"
2021-11-13 13:03:21 +13:00
inputFile = names[0]
outputFile = names[0] + ".pcv"
}
// Add the file
onlyFiles = append(onlyFiles, names[0])
inputFile = names[0]
2022-05-18 12:44:03 +12:00
if !isSplit {
2022-08-20 15:35:00 +12:00
compressTotal += stat.Size()
2022-05-18 12:44:03 +12:00
}
2021-11-13 13:03:21 +13:00
}
2022-04-02 16:16:04 +13:00
} else { // There are multiple dropped items
2021-11-13 13:03:21 +13:00
mode = "encrypt"
2022-05-16 14:45:09 +12:00
startLabel = "Encrypt"
2021-11-13 13:03:21 +13:00
2022-04-02 16:16:04 +13:00
// Go through each dropped item and add to corresponding slices
2021-11-13 13:03:21 +13:00
for _, name := range names {
stat, _ := os.Stat(name)
if stat.IsDir() {
folders++
onlyFolders = append(onlyFolders, name)
} else {
files++
onlyFiles = append(onlyFiles, name)
allFiles = append(allFiles, name)
2022-05-16 14:45:09 +12:00
2022-08-20 15:35:00 +12:00
compressTotal += stat.Size()
inputLabel = fmt.Sprintf("Scanning files... (%s)", sizeify(compressTotal))
2022-05-16 14:45:09 +12:00
giu.Update()
2021-11-13 13:03:21 +13:00
}
}
2022-04-02 16:16:04 +13:00
// Update UI with the number of files and folders selected
2021-11-13 13:03:21 +13:00
if folders == 0 {
2022-05-18 12:44:03 +12:00
inputLabel = fmt.Sprintf("%d files.", files)
2021-11-13 13:03:21 +13:00
} else if files == 0 {
2022-05-18 12:44:03 +12:00
inputLabel = fmt.Sprintf("%d folders.", folders)
2021-11-13 13:03:21 +13:00
} else {
if files == 1 && folders > 1 {
2022-05-18 12:44:03 +12:00
inputLabel = fmt.Sprintf("1 file and %d folders.", folders)
2021-11-13 13:03:21 +13:00
} else if folders == 1 && files > 1 {
2022-05-18 12:44:03 +12:00
inputLabel = fmt.Sprintf("%d files and 1 folder.", files)
2021-11-13 13:03:21 +13:00
} else if folders == 1 && files == 1 {
2022-05-18 12:44:03 +12:00
inputLabel = "1 file and 1 folder."
2021-11-13 13:03:21 +13:00
} else {
2022-05-18 12:44:03 +12:00
inputLabel = fmt.Sprintf("%d files and %d folders.", files, folders)
2021-11-13 13:03:21 +13:00
}
}
// Set the input and output paths
2022-03-20 05:43:32 +13:00
inputFile = filepath.Join(filepath.Dir(names[0]), "Encrypted") + ".zip"
2022-04-02 16:16:04 +13:00
outputFile = inputFile + ".pcv"
2021-11-13 13:03:21 +13:00
}
2022-04-02 16:16:04 +13:00
// Recursively add all files in 'onlyFolders' to 'allFiles'
2022-05-16 14:45:09 +12:00
go func() {
oldInputLabel := inputLabel
for _, name := range onlyFolders {
filepath.Walk(name, func(path string, _ os.FileInfo, _ error) error {
2022-08-20 15:35:00 +12:00
stat, err := os.Stat(path)
// If 'path' is a valid file path, add to 'allFiles'
if err == nil && !stat.IsDir() {
2022-05-16 14:45:09 +12:00
allFiles = append(allFiles, path)
2022-08-20 15:35:00 +12:00
compressTotal += stat.Size()
inputLabel = fmt.Sprintf("Scanning files... (%s)", sizeify(compressTotal))
2022-05-16 14:45:09 +12:00
giu.Update()
}
return nil
})
}
2022-08-20 15:35:00 +12:00
inputLabel = fmt.Sprintf("%s (%s)", oldInputLabel, sizeify(compressTotal))
2022-05-16 14:45:09 +12:00
scanning = false
giu.Update()
}()
2021-11-13 13:03:21 +13:00
}
func work() {
2022-03-20 05:43:32 +13:00
popupStatus = "Starting..."
2021-11-13 13:03:21 +13:00
mainStatus = "Working..."
2022-05-16 14:45:09 +12:00
mainStatusColor = WHITE
2021-11-13 13:03:21 +13:00
working = true
padded := false
2022-04-02 16:16:04 +13:00
giu.Update()
2021-11-13 13:03:21 +13:00
2022-05-16 14:45:09 +12:00
// Cryptography values
var salt []byte // Argon2 salt, 16 bytes
var hkdfSalt []byte // HKDF-SHA3 salt, 32 bytes
2022-08-12 13:41:36 +12:00
var serpentIV []byte // Serpent IV, 16 bytes
2022-05-16 14:45:09 +12:00
var nonce []byte // 24-byte XChaCha20 nonce
var keyHash []byte // SHA3-512 hash of encryption key
var keyHashRef []byte // Same as 'keyHash', but used for comparison
var keyfileKey []byte // The SHA3-256 hashes of keyfiles
var keyfileHash = make([]byte, 32) // The SHA3-256 of 'keyfileKey'
var keyfileHashRef []byte // Same as 'keyfileHash', but used for comparison
var authTag []byte // 64-byte authentication tag (BLAKE2b or HMAC-SHA3)
// Combine/compress all files into a .zip file if needed
2022-05-20 15:56:45 +12:00
if len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
// Consider case where compressing only one file
files := allFiles
if len(allFiles) == 0 {
files = onlyFiles
}
2022-05-16 14:45:09 +12:00
// Get the root directory of the selected files
var rootDir string
if len(onlyFolders) > 0 {
rootDir = filepath.Dir(onlyFolders[0])
2021-11-13 13:03:21 +13:00
} else {
2022-05-16 14:45:09 +12:00
rootDir = filepath.Dir(onlyFiles[0])
}
2022-08-20 15:35:00 +12:00
// Open a temporary .zip for writing
2022-11-18 17:18:08 +13:00
inputFile = strings.TrimSuffix(outputFile, ".pcv")
file, err := os.Create(inputFile)
2022-08-20 15:35:00 +12:00
if err != nil { // Make sure file is writable
2022-05-16 14:45:09 +12:00
accessDenied("Write")
return
}
2022-05-24 12:40:01 +12:00
// Add each file to the .zip
2022-05-16 14:45:09 +12:00
writer := zip.NewWriter(file)
compressStart = time.Now()
2022-05-20 15:56:45 +12:00
for i, path := range files {
progressInfo = fmt.Sprintf("%d/%d", i+1, len(files))
2022-05-16 14:45:09 +12:00
giu.Update()
2021-11-13 13:03:21 +13:00
2022-05-16 14:45:09 +12:00
// Create file info header (size, last modified, etc.)
2022-08-20 15:35:00 +12:00
stat, err := os.Stat(path)
if err != nil {
continue // Skip temporary and inaccessible files
}
2022-05-16 14:45:09 +12:00
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
2021-11-13 13:03:21 +13:00
} else {
2022-05-16 14:45:09 +12:00
header.Method = zip.Store
2021-11-13 13:03:21 +13:00
}
2022-05-16 14:45:09 +12:00
// Open the file for reading
entry, _ := writer.CreateHeader(header)
fin, err := os.Open(path)
2021-11-13 13:03:21 +13:00
if err != nil {
2022-05-16 14:45:09 +12:00
writer.Close()
file.Close()
os.Remove(inputFile)
resetUI()
accessDenied("Read")
2021-11-13 13:03:21 +13:00
return
}
2022-05-16 14:45:09 +12:00
// Use a passthrough to catch compression progress
passthrough := &compressorProgress{Reader: fin}
buf := make([]byte, MiB)
2022-05-18 12:44:03 +12:00
_, err = io.CopyBuffer(entry, passthrough, buf)
2022-05-16 14:45:09 +12:00
fin.Close()
2022-04-02 16:16:04 +13:00
2022-05-18 12:44:03 +12:00
if err != nil {
writer.Close()
2022-08-22 15:04:26 +12:00
insufficientSpace(nil, file)
2022-05-18 12:44:03 +12:00
os.Remove(inputFile)
return
}
2022-05-16 14:45:09 +12:00
if !working {
writer.Close()
2022-08-22 15:04:26 +12:00
cancel(nil, file)
2022-05-16 14:45:09 +12:00
os.Remove(inputFile)
return
2021-11-13 13:03:21 +13:00
}
}
2022-05-16 14:45:09 +12:00
writer.Close()
file.Close()
2021-11-13 13:03:21 +13:00
}
2022-04-02 16:16:04 +13:00
// Recombine a split file if necessary
2021-11-13 13:03:21 +13:00
if recombine {
2022-05-16 14:45:09 +12:00
totalFiles := 0
2022-04-02 16:16:04 +13:00
totalBytes := int64(0)
done := 0
2021-11-13 13:03:21 +13:00
2022-04-02 16:16:04 +13:00
// Find out the number of splitted chunks
2021-11-13 13:03:21 +13:00
for {
2022-05-16 14:45:09 +12:00
stat, err := os.Stat(fmt.Sprintf("%s.%d", inputFile, totalFiles))
2021-11-13 13:03:21 +13:00
if err != nil {
break
}
2022-05-16 14:45:09 +12:00
totalFiles++
2022-04-02 16:16:04 +13:00
totalBytes += stat.Size()
2021-11-13 13:03:21 +13:00
}
2022-08-20 15:35:00 +12:00
// Make sure not to overwrite anything
_, err := os.Stat(outputFile + ".pcv")
if err == nil { // File already exists
mainStatus = "Please remove " + filepath.Base(outputFile+".pcv") + "."
mainStatusColor = RED
return
}
2022-05-16 14:45:09 +12:00
// Create a .pcv to combine chunks into
fout, err := os.Create(outputFile + ".pcv")
2022-08-20 15:35:00 +12:00
if err != nil { // Make sure file is writable
2022-05-16 14:45:09 +12:00
accessDenied("Write")
return
}
2022-04-02 16:16:04 +13:00
// Merge all chunks into one file
2022-05-16 14:45:09 +12:00
startTime := time.Now()
for i := 0; i < totalFiles; i++ {
2022-05-24 12:40:01 +12:00
fin, err := os.Open(fmt.Sprintf("%s.%d", inputFile, i))
if err != nil {
fout.Close()
os.Remove(outputFile + ".pcv")
resetUI()
accessDenied("Read")
return
}
2021-11-13 13:03:21 +13:00
for {
2022-05-16 14:45:09 +12:00
if !working {
2022-05-24 12:40:01 +12:00
cancel(fin, fout)
2022-05-16 14:45:09 +12:00
os.Remove(outputFile + ".pcv")
return
}
// Copy from the chunk into the .pcv
data := make([]byte, MiB)
2021-11-13 13:03:21 +13:00
read, err := fin.Read(data)
if err != nil {
break
}
data = data[:read]
2022-05-18 12:44:03 +12:00
_, err = fout.Write(data)
2022-04-02 16:16:04 +13:00
done += read
2022-05-16 14:45:09 +12:00
2022-05-18 12:44:03 +12:00
if err != nil {
2022-05-24 12:40:01 +12:00
insufficientSpace(fin, fout)
2022-05-18 12:44:03 +12:00
os.Remove(outputFile + ".pcv")
return
}
2022-05-16 14:45:09 +12:00
// Update the stats
progress, speed, eta = statify(int64(done), totalBytes, startTime)
progressInfo = fmt.Sprintf("%d/%d", i+1, totalFiles)
popupStatus = fmt.Sprintf("Recombining at %.2f MiB/s (ETA: %s)", speed, eta)
2022-04-02 16:16:04 +13:00
giu.Update()
2021-11-13 13:03:21 +13:00
}
fin.Close()
}
fout.Close()
2022-05-17 08:10:58 +12:00
inputFileOld = inputFile
2022-05-16 14:45:09 +12:00
inputFile = outputFile + ".pcv"
2021-11-13 13:03:21 +13:00
}
2023-04-28 14:52:01 +12:00
// Input volume has plausible deniability
if mode == "decrypt" && deniability {
popupStatus = "Removing deniability protection..."
progressInfo = ""
progress = 0
canCancel = false
giu.Update()
// Get size of volume for showing progress
stat, _ := os.Stat(inputFile)
total := stat.Size()
// Rename input volume to free up the filename
fin, _ := os.Open(inputFile)
for strings.HasSuffix(inputFile, ".tmp") {
inputFile = strings.TrimSuffix(inputFile, ".tmp")
}
inputFile += ".tmp"
fout, _ := os.Create(inputFile)
// Get the Argon2 salt and XChaCha20 nonce from input volume
salt := make([]byte, 16)
nonce := make([]byte, 24)
fin.Read(salt)
fin.Read(nonce)
// Generate key and XChaCha20
key := argon2.IDKey([]byte(password), salt, 4, 1<<20, 4, 32)
chacha, _ := chacha20.NewUnauthenticatedCipher(key, nonce)
// Decrypt the entire volume
done, counter := 0, 0
for {
src := make([]byte, MiB)
size, err := fin.Read(src)
if err != nil {
break
}
src = src[:size]
dst := make([]byte, len(src))
chacha.XORKeyStream(dst, src)
fout.Write(dst)
// Update stats
done += size
counter += MiB
progress = float32(float64(done) / float64(total))
giu.Update()
// Change nonce after 60 GiB to prevent overflow
if counter >= 60*GiB {
tmp := sha3.New256()
tmp.Write(nonce)
nonce = tmp.Sum(nil)[:24]
chacha, _ = chacha20.NewUnauthenticatedCipher(key, nonce)
counter = 0
}
}
fin.Close()
fout.Close()
// Check if the version can be read from the volume
fin, _ = os.Open(inputFile)
tmp := make([]byte, 15)
fin.Read(tmp)
fin.Close()
tmp, err := rsDecode(rs5, tmp)
if valid, _ := regexp.Match(`^v1\.\d{2}`, tmp); !valid || err != nil {
os.Remove(inputFile)
inputFile = strings.TrimSuffix(inputFile, ".tmp")
broken(nil, nil, "Password is incorrect or the file is not a volume.", true)
if recombine {
inputFile = inputFileOld
}
return
}
}
2022-05-16 14:45:09 +12:00
canCancel = false
progress = 0
progressInfo = ""
giu.Update()
2022-04-02 16:16:04 +13:00
// Subtract the header size from the total size if decrypting
2021-11-13 13:03:21 +13:00
stat, _ := os.Stat(inputFile)
total := stat.Size()
if mode == "decrypt" {
2022-04-07 08:18:07 +12:00
total -= 789
2021-11-13 13:03:21 +13:00
}
// Open input file in read-only mode
fin, err := os.Open(inputFile)
if err != nil {
2022-05-16 14:45:09 +12:00
resetUI()
accessDenied("Read")
2021-11-13 13:03:21 +13:00
return
}
2022-05-16 14:45:09 +12:00
2022-05-24 12:40:01 +12:00
// Setup output file
2021-11-13 13:03:21 +13:00
var fout *os.File
2022-04-02 16:16:04 +13:00
// If encrypting, generate values and write to file
2021-11-13 13:03:21 +13:00
if mode == "encrypt" {
2022-03-20 05:43:32 +13:00
popupStatus = "Generating values..."
2021-11-13 13:03:21 +13:00
giu.Update()
2022-05-24 12:40:01 +12:00
// Stores any errors when writing to file
errs := make([]error, 11)
2022-08-20 15:35:00 +12:00
// Make sure not to overwrite anything
_, err = os.Stat(outputFile)
if split && err == nil { // File already exists
fin.Close()
2022-08-22 15:04:26 +12:00
if len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
os.Remove(inputFile)
}
2022-08-20 15:35:00 +12:00
mainStatus = "Please remove " + filepath.Base(outputFile) + "."
mainStatusColor = RED
return
}
2022-04-02 16:16:04 +13:00
// Create the output file
2021-11-13 13:03:21 +13:00
fout, err = os.Create(outputFile)
if err != nil {
2022-04-02 16:16:04 +13:00
fin.Close()
2022-08-22 15:04:26 +12:00
if len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
os.Remove(inputFile)
}
2022-05-16 14:45:09 +12:00
accessDenied("Write")
2021-11-13 13:03:21 +13:00
return
}
2022-04-02 16:16:04 +13:00
// Set up cryptographic values
2021-11-13 13:03:21 +13:00
salt = make([]byte, 16)
hkdfSalt = make([]byte, 32)
2022-08-12 13:41:36 +12:00
serpentIV = make([]byte, 16)
2021-11-13 13:03:21 +13:00
nonce = make([]byte, 24)
2022-04-02 16:16:04 +13:00
// Write the program version to file
2022-05-24 12:40:01 +12:00
_, errs[0] = fout.Write(rsEncode(rs5, []byte(version)))
2021-11-13 13:03:21 +13:00
2022-04-02 16:16:04 +13:00
// Encode and write the comment length to file
commentsLength := []byte(fmt.Sprintf("%05d", len(comments)))
2022-05-24 12:40:01 +12:00
_, errs[1] = fout.Write(rsEncode(rs5, commentsLength))
2021-11-13 13:03:21 +13:00
2022-04-02 16:16:04 +13:00
// Encode the comment and write to file
for _, i := range []byte(comments) {
2022-05-24 12:40:01 +12:00
_, err := fout.Write(rsEncode(rs1, []byte{i}))
if err != nil {
errs[2] = err
}
2021-11-13 13:03:21 +13:00
}
2022-04-02 16:16:04 +13:00
// Configure flags and write to file
2021-12-23 05:56:33 +13:00
flags := make([]byte, 5)
2022-04-02 16:16:04 +13:00
if paranoid { // Paranoid mode selected
2021-11-13 13:03:21 +13:00
flags[0] = 1
}
2022-04-02 16:16:04 +13:00
if len(keyfiles) > 0 { // Keyfiles are being used
2021-11-13 13:03:21 +13:00
flags[1] = 1
}
2022-05-16 14:45:09 +12:00
if keyfileOrdered { // Order of keyfiles matter
2021-11-13 13:03:21 +13:00
flags[2] = 1
}
2022-04-02 16:16:04 +13:00
if reedsolo { // Full Reed-Solomon encoding is selected
2021-11-13 13:03:21 +13:00
flags[3] = 1
}
2022-05-16 14:45:09 +12:00
if total%int64(MiB) >= int64(MiB)-128 { // Reed-Solomon internals
2021-11-13 13:03:21 +13:00
flags[4] = 1
}
2022-05-24 12:40:01 +12:00
_, errs[3] = fout.Write(rsEncode(rs5, flags))
2021-11-13 13:03:21 +13:00
2022-04-02 16:16:04 +13:00
// Fill values with Go's CSPRNG
2021-11-13 13:03:21 +13:00
rand.Read(salt)
rand.Read(hkdfSalt)
2022-08-12 13:41:36 +12:00
rand.Read(serpentIV)
2021-11-13 13:03:21 +13:00
rand.Read(nonce)
2022-04-02 16:16:04 +13:00
// Encode values with Reed-Solomon and write to file
2022-05-24 12:40:01 +12:00
_, errs[4] = fout.Write(rsEncode(rs16, salt))
_, errs[5] = fout.Write(rsEncode(rs32, hkdfSalt))
2022-08-12 13:41:36 +12:00
_, errs[6] = fout.Write(rsEncode(rs16, serpentIV))
2022-05-24 12:40:01 +12:00
_, errs[7] = fout.Write(rsEncode(rs24, nonce))
2022-04-02 16:16:04 +13:00
// Write placeholders for future use
2022-05-24 12:40:01 +12:00
_, errs[8] = fout.Write(make([]byte, 192)) // Hash of encryption key
_, errs[9] = fout.Write(make([]byte, 96)) // Hash of keyfile key
_, errs[10] = fout.Write(make([]byte, 192)) // BLAKE2b/HMAC-SHA3 tag
for _, err := range errs {
if err != nil {
insufficientSpace(fin, fout)
if len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
os.Remove(inputFile)
}
2022-08-22 15:04:26 +12:00
os.Remove(outputFile)
2022-05-24 12:40:01 +12:00
return
}
}
2022-04-02 16:16:04 +13:00
} else { // Decrypting, read values from file and decode
2022-03-20 05:43:32 +13:00
popupStatus = "Reading values..."
2021-11-13 13:03:21 +13:00
giu.Update()
2022-05-16 14:45:09 +12:00
// Stores any Reed-Solomon decoding errors
2022-04-02 16:16:04 +13:00
errs := make([]error, 10)
2021-11-13 13:03:21 +13:00
version := make([]byte, 15)
fin.Read(version)
2022-04-02 16:16:04 +13:00
_, errs[0] = rsDecode(rs5, version)
2021-11-13 13:03:21 +13:00
tmp := make([]byte, 15)
fin.Read(tmp)
2022-04-02 16:16:04 +13:00
tmp, errs[1] = rsDecode(rs5, tmp)
commentsLength, _ := strconv.Atoi(string(tmp))
fin.Read(make([]byte, commentsLength*3))
2022-04-07 08:18:07 +12:00
total -= int64(commentsLength) * 3
2021-11-13 13:03:21 +13:00
2021-12-23 05:56:33 +13:00
flags := make([]byte, 15)
2021-11-13 13:03:21 +13:00
fin.Read(flags)
2022-04-02 16:16:04 +13:00
flags, errs[2] = rsDecode(rs5, flags)
2021-12-23 05:56:33 +13:00
paranoid = flags[0] == 1
2021-11-13 13:03:21 +13:00
reedsolo = flags[3] == 1
padded = flags[4] == 1
2023-04-28 14:52:01 +12:00
if deniability {
keyfile = flags[1] == 1
keyfileOrdered = flags[2] == 1
}
2021-11-13 13:03:21 +13:00
salt = make([]byte, 48)
fin.Read(salt)
2022-04-02 16:16:04 +13:00
salt, errs[3] = rsDecode(rs16, salt)
2021-11-13 13:03:21 +13:00
hkdfSalt = make([]byte, 96)
fin.Read(hkdfSalt)
2022-04-02 16:16:04 +13:00
hkdfSalt, errs[4] = rsDecode(rs32, hkdfSalt)
2021-11-13 13:03:21 +13:00
2022-08-12 13:41:36 +12:00
serpentIV = make([]byte, 48)
fin.Read(serpentIV)
serpentIV, errs[5] = rsDecode(rs16, serpentIV)
2021-11-13 13:03:21 +13:00
nonce = make([]byte, 72)
fin.Read(nonce)
2022-04-02 16:16:04 +13:00
nonce, errs[6] = rsDecode(rs24, nonce)
2021-11-13 13:03:21 +13:00
2022-05-16 14:45:09 +12:00
keyHashRef = make([]byte, 192)
fin.Read(keyHashRef)
keyHashRef, errs[7] = rsDecode(rs64, keyHashRef)
2021-11-13 13:03:21 +13:00
2022-05-16 14:45:09 +12:00
keyfileHashRef = make([]byte, 96)
fin.Read(keyfileHashRef)
keyfileHashRef, errs[8] = rsDecode(rs32, keyfileHashRef)
2021-11-13 13:03:21 +13:00
2022-05-16 14:45:09 +12:00
authTag = make([]byte, 192)
fin.Read(authTag)
authTag, errs[9] = rsDecode(rs64, authTag)
2021-11-13 13:03:21 +13:00
2022-04-02 16:16:04 +13:00
// If there was an issue during decoding, the header is corrupted
for _, err := range errs {
if err != nil {
if keep { // If the user chooses to force decrypt
kept = true
} else {
2022-08-22 15:04:26 +12:00
broken(fin, nil, "The volume header is damaged.", true)
2022-04-02 16:16:04 +13:00
return
}
2021-11-13 13:03:21 +13:00
}
}
}
2022-03-20 05:43:32 +13:00
popupStatus = "Deriving key..."
2021-11-13 13:03:21 +13:00
giu.Update()
2022-04-02 16:16:04 +13:00
// Derive encryption keys and subkeys
2021-11-13 13:03:21 +13:00
var key []byte
2022-05-16 14:45:09 +12:00
if paranoid {
2021-11-13 13:03:21 +13:00
key = argon2.IDKey(
[]byte(password),
salt,
2022-04-19 09:46:13 +12:00
8, // 8 passes
1<<20, // 1 GiB memory
8, // 8 threads
32, // 32-byte output key
2021-11-13 13:03:21 +13:00
)
2022-05-16 14:45:09 +12:00
} else {
2021-11-13 13:03:21 +13:00
key = argon2.IDKey(
[]byte(password),
salt,
4,
2022-04-19 09:46:13 +12:00
1<<20,
2021-11-13 13:03:21 +13:00
4,
32,
)
}
2022-04-02 16:16:04 +13:00
// If keyfiles are being used
2021-11-13 13:03:21 +13:00
if len(keyfiles) > 0 || keyfile {
2022-05-16 14:45:09 +12:00
popupStatus = "Reading keyfiles..."
giu.Update()
2023-06-28 10:15:56 +12:00
var keyfileTotal int64
for _, path := range keyfiles {
stat, _ := os.Stat(path)
keyfileTotal += stat.Size()
}
2022-05-16 14:45:09 +12:00
if keyfileOrdered { // If order matters, hash progressively
var tmp = sha3.New256()
2023-06-28 10:15:56 +12:00
var keyfileDone int
2022-05-24 12:40:01 +12:00
// For each keyfile...
2021-11-13 13:03:21 +13:00
for _, path := range keyfiles {
2022-05-16 14:45:09 +12:00
fin, _ := os.Open(path)
2023-06-28 10:15:56 +12:00
for { // Read in chunks of 1 MiB
data := make([]byte, MiB)
size, err := fin.Read(data)
if err != nil {
break
}
data = data[:size]
tmp.Write(data) // Hash the data
// Update progress
keyfileDone += size
progress = float32(keyfileDone) / float32(keyfileTotal)
giu.Update()
}
2022-05-16 14:45:09 +12:00
fin.Close()
2021-11-13 13:03:21 +13:00
}
2022-05-24 12:40:01 +12:00
keyfileKey = tmp.Sum(nil) // Get the SHA3-256
2022-05-16 14:45:09 +12:00
2022-05-24 12:40:01 +12:00
// Store a hash of 'keyfileKey' for comparison
2022-05-16 14:45:09 +12:00
tmp = sha3.New256()
tmp.Write(keyfileKey)
keyfileHash = tmp.Sum(nil)
2022-04-02 16:16:04 +13:00
} else { // If order doesn't matter, hash individually and combine
2023-06-28 10:15:56 +12:00
var keyfileDone int
// For each keyfile...
2021-11-13 13:03:21 +13:00
for _, path := range keyfiles {
2022-05-16 14:45:09 +12:00
fin, _ := os.Open(path)
2023-06-28 10:15:56 +12:00
tmp := sha3.New256()
for { // Read in chunks of 1 MiB
data := make([]byte, MiB)
size, err := fin.Read(data)
if err != nil {
break
}
data = data[:size]
tmp.Write(data) // Hash the data
// Update progress
keyfileDone += size
progress = float32(keyfileDone) / float32(keyfileTotal)
giu.Update()
}
2022-05-16 14:45:09 +12:00
fin.Close()
2022-05-24 12:40:01 +12:00
2023-06-28 10:15:56 +12:00
sum := tmp.Sum(nil) // Get the SHA3-256
2022-05-24 12:40:01 +12:00
// XOR keyfile hash with 'keyfileKey'
2022-05-16 14:45:09 +12:00
if keyfileKey == nil {
keyfileKey = sum
2021-11-13 13:03:21 +13:00
} else {
2022-05-16 14:45:09 +12:00
for i, j := range sum {
keyfileKey[i] ^= j
2021-11-13 13:03:21 +13:00
}
}
}
2022-05-16 14:45:09 +12:00
2022-05-24 12:40:01 +12:00
// Store a hash of 'keyfileKey' for comparison
2022-05-16 14:45:09 +12:00
tmp := sha3.New256()
tmp.Write(keyfileKey)
keyfileHash = tmp.Sum(nil)
2021-11-13 13:03:21 +13:00
}
}
2022-05-16 14:45:09 +12:00
popupStatus = "Calculating values..."
giu.Update()
// Hash the encryption key for comparison when decrypting
tmp := sha3.New512()
tmp.Write(key)
keyHash = tmp.Sum(nil)
2021-11-13 13:03:21 +13:00
2022-04-02 16:16:04 +13:00
// Validate the password and/or keyfiles
2021-11-13 13:03:21 +13:00
if mode == "decrypt" {
2022-05-16 14:45:09 +12:00
keyCorrect := subtle.ConstantTimeCompare(keyHash, keyHashRef) == 1
keyfileCorrect := subtle.ConstantTimeCompare(keyfileHash, keyfileHashRef) == 1
incorrect := !keyCorrect
2023-04-28 14:52:01 +12:00
if keyfile || len(keyfiles) > 0 {
2022-04-02 16:16:04 +13:00
incorrect = !keyCorrect || !keyfileCorrect
2021-11-13 13:03:21 +13:00
}
2022-05-16 14:45:09 +12:00
// If something is incorrect
2022-04-02 16:16:04 +13:00
if incorrect {
2021-11-13 13:03:21 +13:00
if keep {
kept = true
} else {
if !keyCorrect {
mainStatus = "The provided password is incorrect."
} else {
2022-05-16 14:45:09 +12:00
if keyfileOrdered {
2022-08-20 15:35:00 +12:00
mainStatus = "Incorrect keyfiles or ordering."
2021-11-13 13:03:21 +13:00
} else {
mainStatus = "Incorrect keyfiles."
}
2023-04-28 14:52:01 +12:00
if deniability {
fin.Close()
os.Remove(inputFile)
inputFile = strings.TrimSuffix(inputFile, ".tmp")
}
2021-11-13 13:03:21 +13:00
}
2022-08-22 15:04:26 +12:00
broken(fin, nil, mainStatus, true)
2023-04-28 14:52:01 +12:00
if recombine {
inputFile = inputFileOld
}
2021-11-13 13:03:21 +13:00
return
}
}
2022-04-02 16:16:04 +13:00
// Create the output file for decryption
2021-11-13 13:03:21 +13:00
fout, err = os.Create(outputFile)
if err != nil {
2022-04-02 16:16:04 +13:00
fin.Close()
2022-08-22 15:04:26 +12:00
if recombine {
os.Remove(inputFile)
}
2022-05-16 14:45:09 +12:00
accessDenied("Write")
2021-11-13 13:03:21 +13:00
return
}
}
if len(keyfiles) > 0 || keyfile {
2023-06-28 10:15:56 +12:00
// Prevent an even number of duplicate keyfiles
if bytes.Equal(keyfileKey, make([]byte, 32)) {
mainStatus = "Duplicate keyfiles detected."
mainStatusColor = RED
fin.Close()
if len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
os.Remove(inputFile)
}
fout.Close()
os.Remove(fout.Name())
return
}
2022-05-16 14:45:09 +12:00
// XOR the encryption key with the keyfile key
2021-11-13 13:03:21 +13:00
tmp := key
key = make([]byte, 32)
for i := range key {
key[i] = tmp[i] ^ keyfileKey[i]
}
}
2022-05-16 14:45:09 +12:00
done, counter := 0, 0
2022-04-19 09:46:13 +12:00
chacha, _ := chacha20.NewUnauthenticatedCipher(key, nonce)
2021-11-13 13:03:21 +13:00
2022-05-24 12:40:01 +12:00
// Use HKDF-SHA3 to generate a subkey for the MAC
2021-11-13 13:03:21 +13:00
var mac hash.Hash
subkey := make([]byte, 32)
hkdf := hkdf.New(sha3.New256, key, hkdfSalt, nil)
hkdf.Read(subkey)
2021-12-23 05:56:33 +13:00
if paranoid {
2022-04-02 16:16:04 +13:00
mac = hmac.New(sha3.New512, subkey) // HMAC-SHA3
2021-12-23 05:56:33 +13:00
} else {
2022-04-02 16:16:04 +13:00
mac, _ = blake2b.New512(subkey) // Keyed BLAKE2b
2021-11-13 13:03:21 +13:00
}
2022-08-12 13:41:36 +12:00
// Generate another subkey for use as Serpent's key
2021-11-13 13:03:21 +13:00
serpentKey := make([]byte, 32)
hkdf.Read(serpentKey)
2022-04-19 09:46:13 +12:00
s, _ := serpent.NewCipher(serpentKey)
2022-08-12 13:41:36 +12:00
serpent := cipher.NewCTR(s, serpentIV)
2021-11-13 13:03:21 +13:00
2022-05-24 12:40:01 +12:00
// Start the main encryption process
2022-05-16 14:45:09 +12:00
canCancel = true
startTime := time.Now()
2021-11-13 13:03:21 +13:00
for {
if !working {
2022-05-24 12:40:01 +12:00
cancel(fin, fout)
2022-05-20 15:56:45 +12:00
if recombine || len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
2022-04-02 16:16:04 +13:00
os.Remove(inputFile)
}
2021-11-13 13:03:21 +13:00
os.Remove(outputFile)
return
}
2022-04-02 16:16:04 +13:00
// Read in data from the file
var src []byte
2021-11-13 13:03:21 +13:00
if mode == "decrypt" && reedsolo {
2022-05-16 14:45:09 +12:00
src = make([]byte, MiB/128*136)
2021-11-13 13:03:21 +13:00
} else {
2022-05-16 14:45:09 +12:00
src = make([]byte, MiB)
2021-11-13 13:03:21 +13:00
}
2022-04-02 16:16:04 +13:00
size, err := fin.Read(src)
2021-11-13 13:03:21 +13:00
if err != nil {
break
}
2022-04-02 16:16:04 +13:00
src = src[:size]
dst := make([]byte, len(src))
2021-11-13 13:03:21 +13:00
2022-04-02 16:16:04 +13:00
// Do the actual encryption
2021-11-13 13:03:21 +13:00
if mode == "encrypt" {
if paranoid {
2022-04-02 16:16:04 +13:00
serpent.XORKeyStream(dst, src)
copy(src, dst)
2021-11-13 13:03:21 +13:00
}
2022-04-19 09:46:13 +12:00
chacha.XORKeyStream(dst, src)
2022-04-02 16:16:04 +13:00
mac.Write(dst)
2021-11-13 13:03:21 +13:00
if reedsolo {
2022-04-02 16:16:04 +13:00
copy(src, dst)
dst = nil
// If a full MiB is available
2022-05-16 14:45:09 +12:00
if len(src) == MiB {
2022-04-02 16:16:04 +13:00
// Encode every chunk
2022-05-16 14:45:09 +12:00
for i := 0; i < MiB; i += 128 {
2022-04-02 16:16:04 +13:00
dst = append(dst, rsEncode(rs128, src[i:i+128])...)
2021-11-13 13:03:21 +13:00
}
} else {
2022-04-02 16:16:04 +13:00
// Encode the full chunks
chunks := math.Floor(float64(len(src)) / 128)
2021-11-13 13:03:21 +13:00
for i := 0; float64(i) < chunks; i++ {
2022-04-02 16:16:04 +13:00
dst = append(dst, rsEncode(rs128, src[i*128:(i+1)*128])...)
2021-11-13 13:03:21 +13:00
}
2022-04-02 16:16:04 +13:00
// Pad and encode the final partial chunk
dst = append(dst, rsEncode(rs128, pad(src[int(chunks*128):]))...)
2021-11-13 13:03:21 +13:00
}
}
2022-04-02 16:16:04 +13:00
} else { // Decryption
2021-11-13 13:03:21 +13:00
if reedsolo {
2022-04-02 16:16:04 +13:00
copy(dst, src)
src = nil
// If a complete 1 MiB block is available
2022-05-16 14:45:09 +12:00
if len(dst) == MiB/128*136 {
2022-04-02 16:16:04 +13:00
// Decode every chunk
2022-05-16 14:45:09 +12:00
for i := 0; i < MiB/128*136; i += 136 {
2022-04-02 16:16:04 +13:00
tmp, err := rsDecode(rs128, dst[i:i+136])
2021-11-13 13:03:21 +13:00
if err != nil {
if keep {
kept = true
} else {
2022-08-22 15:04:26 +12:00
broken(fin, fout, "The input file is irrecoverably damaged.", false)
2021-11-13 13:03:21 +13:00
return
}
}
2022-05-16 14:45:09 +12:00
if i == MiB/128*136-136 && done+MiB/128*136 >= int(total) && padded {
2021-11-13 13:03:21 +13:00
tmp = unpad(tmp)
}
2022-04-02 16:16:04 +13:00
src = append(src, tmp...)
2022-05-16 14:45:09 +12:00
if !fastDecode && i%17408 == 0 {
progress, speed, eta = statify(int64(done+i), total, startTime)
progressInfo = fmt.Sprintf("%.2f%%", progress*100)
popupStatus = fmt.Sprintf("Repairing at %.2f MiB/s (ETA: %s)", speed, eta)
giu.Update()
}
2021-11-13 13:03:21 +13:00
}
} else {
2022-04-02 16:16:04 +13:00
// Decode the full chunks
chunks := len(dst)/136 - 1
2021-11-13 13:03:21 +13:00
for i := 0; i < chunks; i++ {
2022-04-02 16:16:04 +13:00
tmp, err := rsDecode(rs128, dst[i*136:(i+1)*136])
2021-11-13 13:03:21 +13:00
if err != nil {
if keep {
kept = true
} else {
2022-08-22 15:04:26 +12:00
broken(fin, fout, "The input file is irrecoverably damaged.", false)
2021-11-13 13:03:21 +13:00
return
}
}
2022-04-02 16:16:04 +13:00
src = append(src, tmp...)
2022-05-16 14:45:09 +12:00
if !fastDecode && i%128 == 0 {
progress, speed, eta = statify(int64(done+i*136), total, startTime)
progressInfo = fmt.Sprintf("%.2f%%", progress*100)
popupStatus = fmt.Sprintf("Repairing at %.2f MiB/s (ETA: %s)", speed, eta)
giu.Update()
}
2021-11-13 13:03:21 +13:00
}
2022-04-02 16:16:04 +13:00
// Unpad and decode the final partial chunk
tmp, err := rsDecode(rs128, dst[int(chunks)*136:])
2021-11-13 13:03:21 +13:00
if err != nil {
if keep {
kept = true
} else {
2022-08-22 15:04:26 +12:00
broken(fin, fout, "The input file is irrecoverably damaged.", false)
2021-11-13 13:03:21 +13:00
return
}
}
2022-04-02 16:16:04 +13:00
src = append(src, unpad(tmp)...)
2021-11-13 13:03:21 +13:00
}
2022-04-02 16:16:04 +13:00
dst = make([]byte, len(src))
2021-11-13 13:03:21 +13:00
}
2022-04-02 16:16:04 +13:00
mac.Write(src)
2022-04-19 09:46:13 +12:00
chacha.XORKeyStream(dst, src)
2021-11-13 13:03:21 +13:00
if paranoid {
2022-04-02 16:16:04 +13:00
copy(src, dst)
serpent.XORKeyStream(dst, src)
2021-11-13 13:03:21 +13:00
}
}
2022-05-20 15:56:45 +12:00
2022-05-24 12:40:01 +12:00
// Write the data to output file
2022-05-18 12:44:03 +12:00
_, err = fout.Write(dst)
if err != nil {
2022-05-24 12:40:01 +12:00
insufficientSpace(fin, fout)
2022-05-20 15:56:45 +12:00
if recombine || len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
2022-05-18 12:44:03 +12:00
os.Remove(inputFile)
}
os.Remove(outputFile)
return
}
2021-11-13 13:03:21 +13:00
// Update stats
if mode == "decrypt" && reedsolo {
2022-05-16 14:45:09 +12:00
done += MiB / 128 * 136
2021-11-13 13:03:21 +13:00
} else {
2022-05-16 14:45:09 +12:00
done += MiB
2021-11-13 13:03:21 +13:00
}
2022-05-16 14:45:09 +12:00
counter += MiB
progress, speed, eta = statify(int64(done), total, startTime)
2021-11-13 13:03:21 +13:00
progressInfo = fmt.Sprintf("%.2f%%", progress*100)
2022-05-16 14:45:09 +12:00
if mode == "encrypt" {
popupStatus = fmt.Sprintf("Encrypting at %.2f MiB/s (ETA: %s)", speed, eta)
} else {
if fastDecode {
popupStatus = fmt.Sprintf("Decrypting at %.2f MiB/s (ETA: %s)", speed, eta)
}
}
2021-11-13 13:03:21 +13:00
giu.Update()
2022-04-19 09:46:13 +12:00
2022-08-12 13:41:36 +12:00
// Change nonce/IV after 60 GiB to prevent overflow
2022-05-16 14:45:09 +12:00
if counter >= 60*GiB {
// ChaCha20
2022-04-19 09:46:13 +12:00
nonce = make([]byte, 24)
hkdf.Read(nonce)
chacha, _ = chacha20.NewUnauthenticatedCipher(key, nonce)
2022-05-16 14:45:09 +12:00
// Serpent
2022-08-12 13:41:36 +12:00
serpentIV = make([]byte, 16)
hkdf.Read(serpentIV)
serpent = cipher.NewCTR(s, serpentIV)
2022-05-16 14:45:09 +12:00
// Reset counter to 0
counter = 0
2022-04-19 09:46:13 +12:00
}
2021-11-13 13:03:21 +13:00
}
2022-05-16 14:45:09 +12:00
progress = 0
progressInfo = ""
giu.Update()
2021-11-13 13:03:21 +13:00
if mode == "encrypt" {
2022-05-16 14:45:09 +12:00
popupStatus = "Writing values..."
giu.Update()
// Seek back to header and write important values
2022-04-02 16:16:04 +13:00
fout.Seek(int64(309+len(comments)*3), 0)
2021-11-13 13:03:21 +13:00
fout.Write(rsEncode(rs64, keyHash))
fout.Write(rsEncode(rs32, keyfileHash))
fout.Write(rsEncode(rs64, mac.Sum(nil)))
} else {
2022-05-16 14:45:09 +12:00
popupStatus = "Comparing values..."
giu.Update()
2021-11-13 13:03:21 +13:00
// Validate the authenticity of decrypted data
2022-05-16 14:45:09 +12:00
if subtle.ConstantTimeCompare(mac.Sum(nil), authTag) == 0 {
2022-05-24 12:40:01 +12:00
// Decrypt again but this time rebuilding the input data
2022-05-16 14:45:09 +12:00
if reedsolo && fastDecode {
fastDecode = false
fin.Close()
fout.Close()
work()
return
}
2022-05-24 12:40:01 +12:00
2021-11-13 13:03:21 +13:00
if keep {
kept = true
} else {
2022-08-22 15:04:26 +12:00
broken(fin, fout, "The input file is damaged or modified.", false)
2021-11-13 13:03:21 +13:00
return
}
}
}
fin.Close()
fout.Close()
2023-04-28 14:52:01 +12:00
// Add plausible deniability
if mode == "encrypt" && deniability {
popupStatus = "Adding plausible deniability..."
canCancel = false
giu.Update()
// Get size of volume for showing progress
stat, _ := os.Stat(fout.Name())
total := stat.Size()
// Rename the output volume to free up the filename
os.Rename(fout.Name(), fout.Name()+".tmp")
fin, _ := os.Open(fout.Name() + ".tmp")
fout, _ := os.Create(fout.Name())
// Use a random Argon2 salt and XChaCha20 nonce
salt := make([]byte, 16)
nonce := make([]byte, 24)
rand.Read(salt)
rand.Read(nonce)
fout.Write(salt)
fout.Write(nonce)
// Generate key and XChaCha20
key := argon2.IDKey([]byte(password), salt, 4, 1<<20, 4, 32)
chacha, _ := chacha20.NewUnauthenticatedCipher(key, nonce)
// Encrypt the entire volume
done, counter := 0, 0
for {
src := make([]byte, MiB)
size, err := fin.Read(src)
if err != nil {
break
}
src = src[:size]
dst := make([]byte, len(src))
chacha.XORKeyStream(dst, src)
fout.Write(dst)
// Update stats
done += size
counter += MiB
progress = float32(float64(done) / float64(total))
giu.Update()
// Change nonce after 60 GiB to prevent overflow
if counter >= 60*GiB {
tmp := sha3.New256()
tmp.Write(nonce)
nonce = tmp.Sum(nil)[:24]
chacha, _ = chacha20.NewUnauthenticatedCipher(key, nonce)
counter = 0
}
}
fin.Close()
fout.Close()
os.Remove(fin.Name())
canCancel = true
giu.Update()
}
2022-05-16 14:45:09 +12:00
// Split the file into chunks
2021-11-13 13:03:21 +13:00
if split {
var splitted []string
stat, _ := os.Stat(outputFile)
size := stat.Size()
2022-05-16 14:45:09 +12:00
finishedFiles := 0
finishedBytes := 0
2021-11-13 13:03:21 +13:00
chunkSize, _ := strconv.Atoi(splitSize)
2022-05-16 14:45:09 +12:00
// Calculate chunk size
2021-11-13 13:03:21 +13:00
if splitSelected == 0 {
2022-05-16 14:45:09 +12:00
chunkSize *= KiB
2021-11-13 13:03:21 +13:00
} else if splitSelected == 1 {
2022-05-16 14:45:09 +12:00
chunkSize *= MiB
2022-04-19 09:46:13 +12:00
} else if splitSelected == 2 {
2022-05-16 14:45:09 +12:00
chunkSize *= GiB
2022-05-02 09:13:28 +12:00
} else if splitSelected == 3 {
2022-05-16 14:45:09 +12:00
chunkSize *= TiB
2022-05-02 09:13:28 +12:00
} else {
chunkSize = int(math.Ceil(float64(size) / float64(chunkSize)))
2021-11-13 13:03:21 +13:00
}
2022-04-02 16:16:04 +13:00
// Get the number of required chunks
2021-11-13 13:03:21 +13:00
chunks := int(math.Ceil(float64(size) / float64(chunkSize)))
2022-05-16 14:45:09 +12:00
progressInfo = fmt.Sprintf("%d/%d", finishedFiles+1, chunks)
2022-04-02 16:16:04 +13:00
giu.Update()
2022-08-20 15:35:00 +12:00
// Open the volume for reading
2021-11-13 13:03:21 +13:00
fin, _ := os.Open(outputFile)
2022-08-22 15:04:26 +12:00
// Delete existing chunks to prevent mixed chunks
names, _ := filepath.Glob(outputFile + ".*")
for _, i := range names {
os.Remove(i)
}
2022-05-24 12:40:01 +12:00
// Start the splitting process
2022-05-16 14:45:09 +12:00
startTime := time.Now()
for i := 0; i < chunks; i++ {
// Make the chunk
2021-11-13 13:03:21 +13:00
fout, _ := os.Create(fmt.Sprintf("%s.%d", outputFile, i))
done := 0
2022-04-02 16:16:04 +13:00
// Copy data into the chunk
2021-11-13 13:03:21 +13:00
for {
2022-05-16 14:45:09 +12:00
data := make([]byte, MiB)
2022-05-02 09:13:28 +12:00
for done+len(data) > chunkSize {
data = make([]byte, int(math.Ceil(float64(len(data))/2)))
}
2021-11-13 13:03:21 +13:00
read, err := fin.Read(data)
if err != nil {
break
}
if !working {
2022-05-24 12:40:01 +12:00
cancel(fin, fout)
2022-05-20 15:56:45 +12:00
if len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
2022-04-02 16:16:04 +13:00
os.Remove(inputFile)
}
2022-05-16 14:45:09 +12:00
os.Remove(outputFile)
2022-08-22 15:04:26 +12:00
for _, j := range splitted { // Remove existing chunks
2021-11-13 13:03:21 +13:00
os.Remove(j)
}
os.Remove(fmt.Sprintf("%s.%d", outputFile, i))
return
}
2022-05-16 14:45:09 +12:00
2021-11-13 13:03:21 +13:00
data = data[:read]
2022-05-18 12:44:03 +12:00
_, err = fout.Write(data)
if err != nil {
2022-05-24 12:40:01 +12:00
insufficientSpace(fin, fout)
2022-05-20 15:56:45 +12:00
if len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
2022-05-18 12:44:03 +12:00
os.Remove(inputFile)
}
os.Remove(outputFile)
2022-08-22 15:04:26 +12:00
for _, j := range splitted { // Remove existing chunks
2022-05-18 12:44:03 +12:00
os.Remove(j)
}
os.Remove(fmt.Sprintf("%s.%d", outputFile, i))
return
}
2021-11-13 13:03:21 +13:00
done += read
if done >= chunkSize {
break
}
2022-04-02 16:16:04 +13:00
2022-05-16 14:45:09 +12:00
// Update stats
finishedBytes += read
progress, speed, eta = statify(int64(finishedBytes), int64(size), startTime)
popupStatus = fmt.Sprintf("Splitting at %.2f MiB/s (ETA: %s)", speed, eta)
2022-04-02 16:16:04 +13:00
giu.Update()
2021-11-13 13:03:21 +13:00
}
fout.Close()
2022-04-02 16:16:04 +13:00
// Update stats
2022-05-16 14:45:09 +12:00
finishedFiles++
if finishedFiles == chunks {
finishedFiles--
2022-04-02 16:16:04 +13:00
}
2021-11-13 13:03:21 +13:00
splitted = append(splitted, fmt.Sprintf("%s.%d", outputFile, i))
2022-05-16 14:45:09 +12:00
progressInfo = fmt.Sprintf("%d/%d", finishedFiles+1, chunks)
2021-11-13 13:03:21 +13:00
giu.Update()
}
2022-04-02 16:16:04 +13:00
2021-11-13 13:03:21 +13:00
fin.Close()
2021-11-20 13:05:13 +13:00
os.Remove(outputFile)
2021-11-13 13:03:21 +13:00
}
2022-05-16 14:45:09 +12:00
canCancel = false
progress = 0
progressInfo = ""
giu.Update()
2022-08-22 15:04:26 +12:00
// Delete temporary files used during encryption and decryption
if recombine || len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
2021-11-20 13:05:13 +13:00
os.Remove(inputFile)
2023-04-28 14:52:01 +12:00
if deniability {
os.Remove(strings.TrimSuffix(inputFile, ".tmp"))
}
2021-11-13 13:03:21 +13:00
}
2022-05-16 14:45:09 +12:00
// Delete the input files if the user chooses
if delete {
2022-04-02 16:16:04 +13:00
popupStatus = "Deleting files..."
2021-11-13 13:03:21 +13:00
giu.Update()
2022-05-16 14:45:09 +12:00
2021-11-13 13:03:21 +13:00
if mode == "decrypt" {
2022-05-24 12:40:01 +12:00
if recombine { // Remove each chunk of volume
2022-05-16 14:45:09 +12:00
i := 0
2021-11-13 13:03:21 +13:00
for {
2022-05-17 08:10:58 +12:00
_, err := os.Stat(fmt.Sprintf("%s.%d", inputFileOld, i))
2021-11-13 13:03:21 +13:00
if err != nil {
break
}
2022-05-17 08:10:58 +12:00
os.Remove(fmt.Sprintf("%s.%d", inputFileOld, i))
2022-05-16 14:45:09 +12:00
i++
2021-11-13 13:03:21 +13:00
}
} else {
os.Remove(inputFile)
2023-04-28 14:52:01 +12:00
if deniability {
os.Remove(strings.TrimSuffix(inputFile, ".tmp"))
}
2021-11-13 13:03:21 +13:00
}
} else {
for _, i := range onlyFiles {
os.Remove(i)
}
for _, i := range onlyFolders {
os.RemoveAll(i)
}
}
}
2023-04-28 14:52:01 +12:00
if mode == "decrypt" && deniability {
os.Remove(inputFile)
}
2021-11-13 13:03:21 +13:00
2022-04-02 16:16:04 +13:00
// All done, reset the UI
2022-05-16 14:45:09 +12:00
oldKept := kept
2021-11-13 13:03:21 +13:00
resetUI()
2022-05-16 14:45:09 +12:00
kept = oldKept
2021-11-13 13:03:21 +13:00
2022-04-02 16:16:04 +13:00
// If the user chose to keep a corrupted/modified file, let them know
2021-11-13 13:03:21 +13:00
if kept {
2022-04-02 16:16:04 +13:00
mainStatus = "The input file was modified. Please be careful."
2022-05-16 14:45:09 +12:00
mainStatusColor = YELLOW
2021-11-13 13:03:21 +13:00
} else {
mainStatus = "Completed."
2022-05-16 14:45:09 +12:00
mainStatusColor = GREEN
2021-11-13 13:03:21 +13:00
}
2022-05-16 14:45:09 +12:00
}
2021-11-13 13:03:21 +13:00
2022-05-16 14:45:09 +12:00
// If the OS denies reading or writing to a file
func accessDenied(s string) {
mainStatus = s + " access denied by operating system."
mainStatusColor = RED
2021-11-13 13:03:21 +13:00
}
2022-05-18 12:44:03 +12:00
// If there isn't enough disk space
2022-05-24 12:40:01 +12:00
func insufficientSpace(fin *os.File, fout *os.File) {
fin.Close()
fout.Close()
2022-05-18 12:44:03 +12:00
mainStatus = "Insufficient disk space."
mainStatusColor = RED
}
2022-05-16 14:45:09 +12:00
// If corruption is detected during decryption
2022-08-22 15:04:26 +12:00
func broken(fin *os.File, fout *os.File, message string, keepOutput bool) {
2022-05-16 14:45:09 +12:00
fin.Close()
fout.Close()
mainStatus = message
mainStatusColor = RED
2022-04-02 16:16:04 +13:00
// Clean up files since decryption failed
2021-11-13 13:03:21 +13:00
if recombine {
os.Remove(inputFile)
}
2022-08-22 15:04:26 +12:00
if !keepOutput {
os.Remove(outputFile)
}
2021-11-13 13:03:21 +13:00
}
2022-05-24 12:40:01 +12:00
// Stop working if user hits "Cancel"
func cancel(fin *os.File, fout *os.File) {
fin.Close()
fout.Close()
2022-05-16 14:45:09 +12:00
mainStatus = "Operation cancelled by user."
mainStatusColor = WHITE
}
2021-11-13 13:03:21 +13:00
// Reset the UI to a clean state with nothing selected or checked
func resetUI() {
2022-05-08 14:35:33 +12:00
imgui.ClearActiveID()
2021-11-13 13:03:21 +13:00
mode = ""
2022-05-16 14:45:09 +12:00
inputFile = ""
2022-05-17 08:10:58 +12:00
inputFileOld = ""
2022-05-16 14:45:09 +12:00
outputFile = ""
2021-11-13 13:03:21 +13:00
onlyFiles = nil
onlyFolders = nil
allFiles = nil
2022-03-20 05:43:32 +13:00
inputLabel = "Drop files and folders into this window."
2022-05-16 14:45:09 +12:00
password = ""
cpassword = ""
passwordState = giu.InputTextFlagsPassword
passwordStateLabel = "Show"
2022-05-07 15:33:44 +12:00
passgenLength = 32
passgenUpper = true
passgenLower = true
passgenNums = true
passgenSymbols = true
2022-11-18 13:44:54 +13:00
passgenCopy = true
2022-05-16 14:45:09 +12:00
2021-11-13 13:03:21 +13:00
keyfile = false
2022-05-16 14:45:09 +12:00
keyfiles = nil
keyfileOrdered = false
keyfileLabel = "None selected."
2022-04-02 16:16:04 +13:00
comments = ""
2022-05-16 14:45:09 +12:00
commentsLabel = "Comments:"
2022-04-02 16:16:04 +13:00
commentsDisabled = false
2022-05-16 14:45:09 +12:00
paranoid = false
2021-11-13 13:03:21 +13:00
reedsolo = false
2023-04-28 14:52:01 +12:00
deniability = false
recursively = false
2021-11-13 13:03:21 +13:00
split = false
splitSize = ""
splitSelected = 1
2022-05-16 14:45:09 +12:00
recombine = false
2021-11-13 13:03:21 +13:00
compress = false
2022-05-16 14:45:09 +12:00
delete = false
keep = false
kept = false
startLabel = "Start"
mainStatus = "Ready."
mainStatusColor = WHITE
popupStatus = ""
2021-11-13 13:03:21 +13:00
progress = 0
progressInfo = ""
giu.Update()
}
// Reed-Solomon encoder
func rsEncode(rs *infectious.FEC, data []byte) []byte {
2022-04-02 16:16:04 +13:00
res := make([]byte, rs.Total())
2021-11-13 13:03:21 +13:00
rs.Encode(data, func(s infectious.Share) {
2022-04-02 16:16:04 +13:00
res[s.Number] = s.Data[0]
2021-11-13 13:03:21 +13:00
})
return res
}
// Reed-Solomon decoder
func rsDecode(rs *infectious.FEC, data []byte) ([]byte, error) {
2022-05-16 14:45:09 +12:00
// If fast decode, just return the first 128 bytes
if rs.Total() == 136 && fastDecode {
return data[:128], nil
}
2021-11-13 13:03:21 +13:00
tmp := make([]infectious.Share, rs.Total())
for i := 0; i < rs.Total(); i++ {
2022-04-02 16:16:04 +13:00
tmp[i].Number = i
tmp[i].Data = append(tmp[i].Data, data[i])
2021-11-13 13:03:21 +13:00
}
res, err := rs.Decode(nil, tmp)
2022-04-02 16:16:04 +13:00
2022-05-16 14:45:09 +12:00
// Force decode the data but return the error as well
2021-11-13 13:03:21 +13:00
if err != nil {
if rs.Total() == 136 {
return data[:128], err
}
return data[:rs.Total()/3], err
}
2022-05-24 12:40:01 +12:00
// No issues, return the decoded data
2021-11-13 13:03:21 +13:00
return res, nil
}
2022-04-02 16:16:04 +13:00
// PKCS#7 pad (for use with Reed-Solomon)
2021-11-13 13:03:21 +13:00
func pad(data []byte) []byte {
padLen := 128 - len(data)%128
padding := bytes.Repeat([]byte{byte(padLen)}, padLen)
return append(data, padding...)
}
2022-04-02 16:16:04 +13:00
// PKCS#7 unpad
2021-11-13 13:03:21 +13:00
func unpad(data []byte) []byte {
2022-05-16 14:45:09 +12:00
padLen := int(data[127])
return data[:128-padLen]
2021-11-13 13:03:21 +13:00
}
2022-04-02 16:16:04 +13:00
// Generate a cryptographically secure password
2021-11-13 13:03:21 +13:00
func genPassword() string {
chars := ""
2022-04-02 16:16:04 +13:00
if passgenUpper {
2021-11-13 13:03:21 +13:00
chars += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
}
2022-04-02 16:16:04 +13:00
if passgenLower {
2021-11-13 13:03:21 +13:00
chars += "abcdefghijklmnopqrstuvwxyz"
}
2022-04-02 16:16:04 +13:00
if passgenNums {
2021-11-13 13:03:21 +13:00
chars += "1234567890"
}
2022-04-02 16:16:04 +13:00
if passgenSymbols {
2022-05-16 14:45:09 +12:00
chars += "-=_+!@#$^&()?<>"
2021-11-13 13:03:21 +13:00
}
2022-04-02 16:16:04 +13:00
tmp := make([]byte, passgenLength)
for i := 0; i < int(passgenLength); i++ {
2022-05-16 14:45:09 +12:00
j, _ := rand.Int(rand.Reader, big.NewInt(int64(len(chars))))
2021-11-13 13:03:21 +13:00
tmp[i] = chars[j.Int64()]
}
2022-04-02 16:16:04 +13:00
if passgenCopy {
2022-11-18 13:44:54 +13:00
giu.Context.GetPlatform().SetClipboard(string(tmp))
2021-11-13 13:03:21 +13:00
}
return string(tmp)
}
2022-05-16 14:45:09 +12:00
// Convert done, total, and starting time to progress, speed, and ETA
func statify(done int64, total int64, start time.Time) (float32, float64, string) {
progress := float32(done) / float32(total)
elapsed := float64(time.Since(start)) / float64(MiB) / 1000
speed := float64(done) / elapsed / float64(MiB)
eta := int(math.Floor(float64(total-done) / (speed * float64(MiB))))
return float32(math.Min(float64(progress), 1)), speed, timeify(eta)
}
2021-11-13 13:03:21 +13:00
// Convert seconds to HH:MM:SS
2022-05-16 14:45:09 +12:00
func timeify(seconds int) string {
2021-11-13 13:03:21 +13:00
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)
}
2022-04-19 09:46:13 +12:00
// Convert bytes to KiB, MiB, etc.
func sizeify(size int64) string {
2022-05-16 14:45:09 +12:00
if size >= int64(TiB) {
2022-05-18 12:44:03 +12:00
return fmt.Sprintf("%.2f TiB", float64(size)/float64(TiB))
2022-05-16 14:45:09 +12:00
} else if size >= int64(GiB) {
2022-05-18 12:44:03 +12:00
return fmt.Sprintf("%.2f GiB", float64(size)/float64(GiB))
2022-05-16 14:45:09 +12:00
} else if size >= int64(MiB) {
2022-05-18 12:44:03 +12:00
return fmt.Sprintf("%.2f MiB", float64(size)/float64(MiB))
2022-04-19 09:46:13 +12:00
} else {
2022-05-18 12:44:03 +12:00
return fmt.Sprintf("%.2f KiB", float64(size)/float64(KiB))
2022-04-19 09:46:13 +12:00
}
}
2021-11-13 13:03:21 +13:00
func main() {
2022-04-02 16:16:04 +13:00
// Create the main window
2023-04-28 14:52:01 +12:00
window = giu.NewMasterWindow("Picocrypt", 318, 507, giu.MasterWindowFlagsNotResizable)
2022-04-02 16:16:04 +13:00
// Start the dialog module
2021-11-13 13:03:21 +13:00
dialog.Init()
// Set callbacks
window.SetDropCallback(onDrop)
window.SetCloseCallback(func() bool {
2022-05-16 14:45:09 +12:00
return !working && !showProgress
2021-11-13 13:03:21 +13:00
})
// Set universal DPI
dpi = giu.Context.GetPlatform().GetContentScale()
// Start the UI
window.Run(draw)
}