From 6f3cbb0a38e2757556961fe467d10553de42d36e Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Thu, 10 Apr 2025 00:27:36 -0400
Subject: [PATCH 01/46] Auto press start/process button on Enter key
---
src/Picocrypt.go | 205 +++++++++++++++++++++++++----------------------
1 file changed, 108 insertions(+), 97 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 10baa94..430b94f 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -173,10 +173,117 @@ func (p *compressorProgress) Read(data []byte) (int, error) {
return read, err
}
+var onClickStartButton = func() {
+ // Start button should be disabled if these conditions are true; don't do anything if so
+ if (len(keyfiles) == 0 && password == "") || (mode == "encrypt" && password != cpassword) {
+ return
+ }
+
+ if keyfile && keyfiles == nil {
+ mainStatus = "Please select your keyfiles"
+ mainStatusColor = RED
+ return
+ }
+ tmp, err := strconv.Atoi(splitSize)
+ if split && (splitSize == "" || tmp <= 0 || err != nil) {
+ mainStatus = "Invalid chunk size"
+ mainStatusColor = RED
+ return
+ }
+
+ // Check if output file already exists
+ _, err = os.Stat(outputFile)
+
+ // 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
+ if err == nil && !recursively {
+ showOverwrite = true
+ modalId++
+ giu.Update()
+ } else { // Nothing to worry about, start working
+ showProgress = true
+ fastDecode = true
+ canCancel = true
+ modalId++
+ giu.Update()
+ 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()
+ }()
+ }
+ }
+}
+
// The main user interface
func draw() {
giu.SingleWindow().Flags(524351).Layout(
giu.Custom(func() {
+ if giu.IsKeyReleased(giu.KeyEnter) {
+ onClickStartButton()
+ return
+ }
if showPassgen {
giu.PopupModal("Generate password:##"+strconv.Itoa(modalId)).Flags(6).Layout(
giu.Row(
@@ -653,103 +760,7 @@ func draw() {
return startLabel
}
return "Process"
- }()).Size(giu.Auto, 34).OnClick(func() {
- if keyfile && keyfiles == nil {
- mainStatus = "Please select your keyfiles"
- mainStatusColor = RED
- return
- }
- tmp, err := strconv.Atoi(splitSize)
- if split && (splitSize == "" || tmp <= 0 || err != nil) {
- mainStatus = "Invalid chunk size"
- mainStatusColor = RED
- return
- }
-
- // Check if output file already exists
- _, err = os.Stat(outputFile)
-
- // 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
- if err == nil && !recursively {
- showOverwrite = true
- modalId++
- giu.Update()
- } else { // Nothing to worry about, start working
- showProgress = true
- fastDecode = true
- canCancel = true
- modalId++
- giu.Update()
- 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()
- }()
- }
- }
- }),
+ }()).Size(giu.Auto, 34).OnClick(onClickStartButton),
giu.Style().SetColor(giu.StyleColorText, mainStatusColor).To(
giu.Label(mainStatus),
),
From d9473f777bf020f79db8b057ca7510413df138d8 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Thu, 10 Apr 2025 00:38:09 -0400
Subject: [PATCH 02/46] Bump to 1.48, update changelog
---
Changelog.md | 5 +++++
VERSION | 2 +-
src/Picocrypt.go | 4 ++--
3 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/Changelog.md b/Changelog.md
index 8a81c2e..4ede0b6 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -3,6 +3,11 @@
Migrate golang.org/x/crypto to standard library imports (https://github.com/golang/go/issues/65269)
+# v1.48 (Released 04/11/2025)
+
+ - ✓ Allow pressing 'Enter' key to press Start/Process button
+
+
# v1.47 (Released 02/19/2025)
- ✓ No code changes, just build on newly released Go 1.24
diff --git a/VERSION b/VERSION
index 99dd716..46284af 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.47
+1.48
\ No newline at end of file
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 430b94f..84ac805 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -2,7 +2,7 @@ package main
/*
-Picocrypt v1.47
+Picocrypt v1.48
Copyright (c) Evan Su
Released under a GNU GPL v3 License
https://github.com/Picocrypt/Picocrypt
@@ -60,7 +60,7 @@ var TRANSPARENT = color.RGBA{0x00, 0x00, 0x00, 0x00}
// Generic variables
var window *giu.MasterWindow
-var version = "v1.47"
+var version = "v1.48"
var dpi float32
var mode string
var working bool
From 333aca2a8053dbda76a0892ac10f73b94396d128 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Thu, 10 Apr 2025 01:20:40 -0400
Subject: [PATCH 03/46] Add warnings for zip and external destinations
When encrypting multiple files, Picocrypt will zip them to a temporary zip file on the target location with a .tmp extension. This comes with two issues: 1. requires double the volume size of free storage; 2. external drive must not be unsafe to host the unencrypted temporary zip file.
Prevent potential footguns by showing warnings where appropriate.
---
src/Picocrypt.go | 46 ++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 42 insertions(+), 4 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 84ac805..2c2cc0a 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -30,6 +30,7 @@ import (
"os"
"path/filepath"
"regexp"
+ "runtime"
"strconv"
"strings"
"time"
@@ -131,6 +132,9 @@ var mainStatus = "Ready"
var mainStatusColor = WHITE
var popupStatus string
+var temporaryZip bool
+var externalDst bool
+
// Progress variables
var progress float32
var progressInfo string
@@ -606,7 +610,7 @@ func draw() {
giu.Checkbox("Paranoid mode", ¶noid),
giu.Tooltip("Provides the highest level of security attainable"),
giu.Dummy(-170, 0),
- giu.Style().SetDisabled(recursively).To(
+ giu.Style().SetDisabled(recursively || !(len(allFiles) > 1 || len(onlyFolders) > 0)).To(
giu.Checkbox("Compress files", &compress).OnChange(func() {
if !(len(allFiles) > 1 || len(onlyFolders) > 0) {
if compress {
@@ -736,6 +740,21 @@ func draw() {
} else {
file += filepath.Ext(inputFile) + ".pcv"
}
+ externalDst = false
+ GOOS := strings.ToLower(runtime.GOOS)
+ if strings.HasPrefix(GOOS, "windows") {
+ if !strings.HasPrefix(file, "C:") {
+ externalDst = true
+ }
+ } else if strings.HasPrefix(GOOS, "linux") {
+ if strings.Contains(file, "/media/") || strings.Contains(file, "/mnt/") {
+ externalDst = true
+ }
+ } else if strings.HasPrefix(GOOS, "darwin") {
+ if strings.Contains(file, "/Volumes/") {
+ externalDst = true
+ }
+ }
} else {
if strings.HasSuffix(inputFile, ".zip.pcv") {
file += ".zip"
@@ -761,9 +780,25 @@ func draw() {
}
return "Process"
}()).Size(giu.Auto, 34).OnClick(onClickStartButton),
- giu.Style().SetColor(giu.StyleColorText, mainStatusColor).To(
- giu.Label(mainStatus),
- ),
+ giu.Custom(func() {
+ if temporaryZip && externalDst {
+ giu.Style().SetColor(giu.StyleColorText, YELLOW).To(
+ giu.Label("Warning: unencrypted temp files will be created"),
+ ).Build()
+ } else if temporaryZip {
+ giu.Style().SetColor(giu.StyleColorText, WHITE).To(
+ giu.Label(mainStatus + " (info: will create temporary files)"),
+ ).Build()
+ } else if externalDst {
+ giu.Style().SetColor(giu.StyleColorText, WHITE).To(
+ giu.Label(mainStatus + " (info: target may be an external drive)"),
+ ).Build()
+ } else {
+ giu.Style().SetColor(giu.StyleColorText, mainStatusColor).To(
+ giu.Label(mainStatus),
+ ).Build()
+ }
+ }),
),
giu.Custom(func() {
@@ -1000,6 +1035,7 @@ func onDrop(names []string) {
// Set the input and output paths
inputFile = filepath.Join(filepath.Dir(names[0]), "Encrypted") + ".zip"
outputFile = inputFile + ".pcv"
+ temporaryZip = true
}
// Recursively add all files in 'onlyFolders' to 'allFiles'
@@ -2262,6 +2298,8 @@ func resetUI() {
mainStatus = "Ready"
mainStatusColor = WHITE
popupStatus = ""
+ temporaryZip = false
+ externalDst = false
progress = 0
progressInfo = ""
From a0e6e30e7b12e5b435b520976acccc48892a8ced Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Thu, 10 Apr 2025 01:37:56 -0400
Subject: [PATCH 04/46] add external drive warnings to changelog and readme
---
Changelog.md | 1 +
README.md | 8 ++++++++
2 files changed, 9 insertions(+)
diff --git a/Changelog.md b/Changelog.md
index 4ede0b6..3a088ad 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -6,6 +6,7 @@
# v1.48 (Released 04/11/2025)
- ✓ Allow pressing 'Enter' key to press Start/Process button
+ - ✓ Warn user when encrypting multiple files to an external drive
# v1.47 (Released 02/19/2025)
diff --git a/README.md b/README.md
index 73f0835..2b15221 100644
--- a/README.md
+++ b/README.md
@@ -92,6 +92,14 @@ While being simple, Picocrypt also strives to be powerful in the hands of knowle
- Recursively: If you want to encrypt and/or decrypt a large set of files individually, this option will tell Picocrypt to go through every recursive file that you drop in and encrypt/decrypt it separately. This is useful, for example, if you are encrypting thousands of large documents and want to be able to decrypt any one of them in particular without having to download and decrypt the entire set of documents. Keep in mind that this is a very complex feature that should only be used if you know what you are doing.
+# Caveats
+When encrypting multiple files, Picocrypt will automatically zip them into one file before encrypting it. However, this requires a two-step process that creates an unencrypted temporary `.zip.tmp` file in the same destination folder. This has two implications:
+
+ - There must be at least double the available free space on the target drive as the combined total size of input files
+ - The target drive must be safe to save confidential data; if not, the unencrypted temporary file may be recoverable even after deletion
+
+To mitigate these caveats, Picocrypt will show info and warning labels accordingly. However, it is best to prevent these issues altogether by always encrypting and decrypting on your main host drive and then copying encrypted volumes to and from external sources, or zipping up input files beforehand and encrypting that single file which doesn't have these caveats.
+
# Security
For more information on how Picocrypt handles cryptography, see Internals for the technical details. If you're worried about the safety of me or this project, let me assure you that this repository won't be hijacked or backdoored. I have 2FA (TOTP) enabled on all accounts with a tie to Picocrypt (GitHub, Reddit, Google, etc.), in addition to full-disk encryption on all of my portable devices. For further hardening, Picocrypt uses my isolated forks of dependencies and I fetch upstream only when I have taken a look at the changes and believe that there aren't any security issues. This means that if a dependency gets hacked or deleted by the author, Picocrypt will be using my fork of it and remain completely unaffected. You can feel confident about using Picocrypt as long as you understand:
From 55ec72864e941fd39e0c43fcc40b285988d73aaa Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Thu, 10 Apr 2025 12:27:49 -0400
Subject: [PATCH 05/46] Only show info/warnings if status is "Ready"
If not, it's probably showing some error message which would happen after starting encryption, so user would've already seen the info/warning status
---
src/Picocrypt.go | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 2c2cc0a..3587e44 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -781,21 +781,27 @@ func draw() {
return "Process"
}()).Size(giu.Auto, 34).OnClick(onClickStartButton),
giu.Custom(func() {
+ if mainStatus != "Ready" {
+ giu.Style().SetColor(giu.StyleColorText, mainStatusColor).To(
+ giu.Label(mainStatus),
+ ).Build()
+ return
+ }
if temporaryZip && externalDst {
giu.Style().SetColor(giu.StyleColorText, YELLOW).To(
giu.Label("Warning: unencrypted temp files will be created"),
).Build()
} else if temporaryZip {
giu.Style().SetColor(giu.StyleColorText, WHITE).To(
- giu.Label(mainStatus + " (info: will create temporary files)"),
+ giu.Label("Ready (info: will create a temporary zip file)"),
).Build()
} else if externalDst {
giu.Style().SetColor(giu.StyleColorText, WHITE).To(
- giu.Label(mainStatus + " (info: target may be an external drive)"),
+ giu.Label("Ready (info: target may be an external drive)"),
).Build()
} else {
giu.Style().SetColor(giu.StyleColorText, mainStatusColor).To(
- giu.Label(mainStatus),
+ giu.Label("Ready"),
).Build()
}
}),
From f429f1b1eeb8f4589bbbaf7c7b298411f7bc2094 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Thu, 10 Apr 2025 12:29:10 -0400
Subject: [PATCH 06/46] README.md: remove installer link
Maybe will add in a future release, but not for 1.48
---
README.md | 2 --
1 file changed, 2 deletions(-)
diff --git a/README.md b/README.md
index 2b15221..ecf5b3e 100644
--- a/README.md
+++ b/README.md
@@ -18,8 +18,6 @@ Picocrypt is a very small (hence Pico), very simple, yet very secure encr
## Windows
Picocrypt for Windows is as simple as it gets. To download the latest, standalone, and portable executable for Windows, click here. If Microsoft Defender or your antivirus flags Picocrypt as a virus, please do your part and submit it as a false positive for the betterment of everyone.
-If you use Picocrypt frequently, you can download an installer here for easier launching. It does not require any admin permissions to install and it also bundles a software OpenGL renderer for compatibility, so if the portable executable isn't working, this installer likely will.
-
## macOS
Picocrypt for macOS is very simple as well. Download Picocrypt here, open the container, and drag Picocrypt to your Applications. You may need to manually trust the app from a terminal and control-click on the app if macOS prevents you from opening it:
```
From 9287fca7b72fad93be223b4968c33726f29c6a74 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Thu, 10 Apr 2025 12:33:21 -0400
Subject: [PATCH 07/46] Remove future section from changelog
The golang.org/x/crypto -> stdlib migration done in Go 1.24 is not a simple find and replace as some types changed. Will stick with golang.org/x/crypto for the foreseeable future.
---
Changelog.md | 5 -----
1 file changed, 5 deletions(-)
diff --git a/Changelog.md b/Changelog.md
index 3a088ad..fffc47f 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,8 +1,3 @@
-# Future
-
- - Migrate golang.org/x/crypto to standard library imports (https://github.com/golang/go/issues/65269)
-
-
# v1.48 (Released 04/11/2025)
- ✓ Allow pressing 'Enter' key to press Start/Process button
From fd95597f02d5662abc0c313ea55ee76623b71109 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Thu, 10 Apr 2025 12:37:35 -0400
Subject: [PATCH 08/46] go get -u -v all && go mod tidy
Nothing really changed, just bumped go version in go.mod of deps to 1.24
---
src/go.mod | 14 +++++++-------
src/go.sum | 28 ++++++++++++++--------------
2 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/src/go.mod b/src/go.mod
index 2f30308..0b8c76d 100644
--- a/src/go.mod
+++ b/src/go.mod
@@ -3,18 +3,18 @@ module Picocrypt
go 1.24
require (
- github.com/Picocrypt/dialog v0.0.0-20240831001746-9ca708a9cd29
- github.com/Picocrypt/giu v0.0.0-20240831005244-5771b35043ac
- github.com/Picocrypt/imgui-go v0.0.0-20240831004007-6f60d7beadf6
- github.com/Picocrypt/infectious v0.0.0-20240830233326-3a050f65f9ec
+ github.com/Picocrypt/dialog v0.0.0-20250410154130-d98dc55ea635
+ github.com/Picocrypt/giu v0.0.0-20250410155113-88f8ef80cbaf
+ github.com/Picocrypt/imgui-go v0.0.0-20250410154824-2e0c0440a8da
+ github.com/Picocrypt/infectious v0.0.0-20250410153626-f2f1c05d0452
github.com/Picocrypt/serpent v0.0.0-20240830233833-9ad6ab254fd7
- github.com/Picocrypt/zxcvbn-go v0.0.0-20240831000415-fccb38ccb913
+ github.com/Picocrypt/zxcvbn-go v0.0.0-20250410153845-b5da60d3e882
golang.org/x/crypto v0.37.0
)
require (
- github.com/Picocrypt/gl v0.0.0-20240831002619-6531d2bba5fc // indirect
- github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20240831003212-7f16c5fb374b // indirect
+ github.com/Picocrypt/gl v0.0.0-20250410154226-55bdd7d785fc // indirect
+ github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20250410154351-5db5bbd64322 // indirect
github.com/Picocrypt/mainthread v0.0.0-20240831004314-496f638392b3 // indirect
github.com/Picocrypt/w32 v0.0.0-20240831001500-1183079d4d57 // indirect
golang.org/x/sys v0.32.0 // indirect
diff --git a/src/go.sum b/src/go.sum
index b6b9df4..1817016 100644
--- a/src/go.sum
+++ b/src/go.sum
@@ -1,23 +1,23 @@
-github.com/Picocrypt/dialog v0.0.0-20240831001746-9ca708a9cd29 h1:WIgRST/mpLiBEG2MF5MRPBDYYevLw7y14cwUEDjG5+Q=
-github.com/Picocrypt/dialog v0.0.0-20240831001746-9ca708a9cd29/go.mod h1:raXVkdcX4495+fW9Ac+kvPMHRNk0rOcNXEWFD71B2As=
-github.com/Picocrypt/giu v0.0.0-20240831005244-5771b35043ac h1:Z21enGbi450NyI7UZSoEuu//axifyGl63BJVjHX3ZXc=
-github.com/Picocrypt/giu v0.0.0-20240831005244-5771b35043ac/go.mod h1:x7jbmZVofU9rn5WJj2+riU85Zo0MFlfp1sMTnKFQhc0=
-github.com/Picocrypt/gl v0.0.0-20240831002619-6531d2bba5fc h1:5ckKMFhiz/Af6+sdkGlw74BU+rKRmoFWqU/rXHGUe3g=
-github.com/Picocrypt/gl v0.0.0-20240831002619-6531d2bba5fc/go.mod h1:VknKAZzEoKP9nqrc/dveCwR5L01B9V8yLqtpvYmQ3DA=
-github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20240831003212-7f16c5fb374b h1:hSaQU4P9KbMg9s2Jp2mTk9G5G+zkf4Yse5YRoxWTDTk=
-github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20240831003212-7f16c5fb374b/go.mod h1:r5awTCSm/ugmTRKmT8Hr0T4xGPI6K35eFK0s3jYCW+s=
-github.com/Picocrypt/imgui-go v0.0.0-20240831004007-6f60d7beadf6 h1:Y6SuxbSkQSU1hdEOpoMvp6Akq3RVX6KP1U4pKjGv3qo=
-github.com/Picocrypt/imgui-go v0.0.0-20240831004007-6f60d7beadf6/go.mod h1:mGfOCkgyafVMIs1tU70va3lFSh6hSb+Vq4paVLX1Fjg=
-github.com/Picocrypt/infectious v0.0.0-20240830233326-3a050f65f9ec h1:/cop0/v0HxIJm1XGDgIlzNJ3e4HhM8nIUPZi5RZ/n1w=
-github.com/Picocrypt/infectious v0.0.0-20240830233326-3a050f65f9ec/go.mod h1:aaFq/WMVxrU2Exl/tXbTFSXajZrqw0mgn/wi42n0fK4=
+github.com/Picocrypt/dialog v0.0.0-20250410154130-d98dc55ea635 h1:mZl+aiuQSQhe/C1Y4NdJ7w6KdjAEyr5Va2JHGdDvF3E=
+github.com/Picocrypt/dialog v0.0.0-20250410154130-d98dc55ea635/go.mod h1:bScGI7SMxXUo4EtwGfDkeA7owLj99QBn8nlqnqxacHU=
+github.com/Picocrypt/giu v0.0.0-20250410155113-88f8ef80cbaf h1:mDXu/vjyoxcXg44ejbpRhiZg1NmzMlez6ikEkZ03G6c=
+github.com/Picocrypt/giu v0.0.0-20250410155113-88f8ef80cbaf/go.mod h1:fDUYbcghlVwlEoWkp+4LwQd1DJft1XZ9uTaHskd2gno=
+github.com/Picocrypt/gl v0.0.0-20250410154226-55bdd7d785fc h1:PhgkrhrXcMVDJUHfTjJ+U1soq+eiaeWheEELXZau62k=
+github.com/Picocrypt/gl v0.0.0-20250410154226-55bdd7d785fc/go.mod h1:aHUxrywhiLVtrH6Yus/4N9VtOFzsEPp1AJj2ioFgswg=
+github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20250410154351-5db5bbd64322 h1:eeD6947JcMIeRhlPkXWedfi8IqdZuX/k0dVM8nEB+bc=
+github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20250410154351-5db5bbd64322/go.mod h1:7StnBznBEzR4cEtV5ptfQfMBgmv05p+b4ImwZ+pXXKQ=
+github.com/Picocrypt/imgui-go v0.0.0-20250410154824-2e0c0440a8da h1:zea0P1s/ykcWU1zbBxVLgCjSwRhT7+u1KoYPQHuStjQ=
+github.com/Picocrypt/imgui-go v0.0.0-20250410154824-2e0c0440a8da/go.mod h1:crmefGfhM5+lQUU/fbS9Hjs8xOlrrJI235vwnktN9PE=
+github.com/Picocrypt/infectious v0.0.0-20250410153626-f2f1c05d0452 h1:9UqCRY4qLDpIfxW+N51JPTn0KDKg88um+BVXApTl0NI=
+github.com/Picocrypt/infectious v0.0.0-20250410153626-f2f1c05d0452/go.mod h1:S99y5mnE1SZcr3n2DNDkdYZ9QJD4OmnGXlQ9TvLLC+M=
github.com/Picocrypt/mainthread v0.0.0-20240831004314-496f638392b3 h1:a62XmbZYhHGDR15C1gxp/IPfJX5SflrJuGpqNoOOK7w=
github.com/Picocrypt/mainthread v0.0.0-20240831004314-496f638392b3/go.mod h1:bsUKeX+/53rCTrItl3YUaeaN5tXl1v6326ZI90xIOsc=
github.com/Picocrypt/serpent v0.0.0-20240830233833-9ad6ab254fd7 h1:G36G2vmQAS7CVoHQrHDGAoCWll/0kPCI8Dk7mgwcJFE=
github.com/Picocrypt/serpent v0.0.0-20240830233833-9ad6ab254fd7/go.mod h1:BxsgRYwUVd92aEwXnXsfXfHw8aHlD/PUyExC/wwk9oI=
github.com/Picocrypt/w32 v0.0.0-20240831001500-1183079d4d57 h1:jusSXTp0h5wz8lxNXStw0jXr/ogZF6rzRF8gu0534hA=
github.com/Picocrypt/w32 v0.0.0-20240831001500-1183079d4d57/go.mod h1:FkeZHdKlITdP34VknO8yLdRY5pCi+iWEhDSA0YsBhZc=
-github.com/Picocrypt/zxcvbn-go v0.0.0-20240831000415-fccb38ccb913 h1:QGv9QiTkNZ2iRmXEd7nNopaUJMBhBdBcsvWPl+v51AY=
-github.com/Picocrypt/zxcvbn-go v0.0.0-20240831000415-fccb38ccb913/go.mod h1:dMyJ/0E4MeBo2wH1ZYmvPTChnYSj2MjLUndvYQt0vGw=
+github.com/Picocrypt/zxcvbn-go v0.0.0-20250410153845-b5da60d3e882 h1:W5f997Hcoi9PiU2j3TSsjryNKQ2+jIiHChpOnf485Xc=
+github.com/Picocrypt/zxcvbn-go v0.0.0-20250410153845-b5da60d3e882/go.mod h1:pIpFJD6Ey6jxU5GXMZ3Kc4wF9B49OJy9wTwwE3bJRPI=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
From a849de0edd9dabd7894b8bf7e651c8680d660357 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Sun, 13 Apr 2025 00:59:56 -0400
Subject: [PATCH 09/46] go get -u -v all && go mod tidy
codeql doesn't like go 1.24 without the .0 after 1.24in go.mod, so I added those in all dependencies
---
src/go.mod | 14 +++++++-------
src/go.sum | 28 ++++++++++++++--------------
2 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/src/go.mod b/src/go.mod
index 0b86bd6..e1efc59 100644
--- a/src/go.mod
+++ b/src/go.mod
@@ -3,18 +3,18 @@ module Picocrypt
go 1.24.2
require (
- github.com/Picocrypt/dialog v0.0.0-20250410154130-d98dc55ea635
- github.com/Picocrypt/giu v0.0.0-20250410155113-88f8ef80cbaf
- github.com/Picocrypt/imgui-go v0.0.0-20250410154824-2e0c0440a8da
- github.com/Picocrypt/infectious v0.0.0-20250410153626-f2f1c05d0452
+ github.com/Picocrypt/dialog v0.0.0-20250412233924-78f7b909315b
+ github.com/Picocrypt/giu v0.0.0-20250412235908-fe90a482e6f2
+ github.com/Picocrypt/imgui-go v0.0.0-20250412235405-d86b230f5fbb
+ github.com/Picocrypt/infectious v0.0.0-20250412183341-9f88c6307b39
github.com/Picocrypt/serpent v0.0.0-20240830233833-9ad6ab254fd7
- github.com/Picocrypt/zxcvbn-go v0.0.0-20250410153845-b5da60d3e882
+ github.com/Picocrypt/zxcvbn-go v0.0.0-20250412183938-d59695960527
golang.org/x/crypto v0.37.0
)
require (
- github.com/Picocrypt/gl v0.0.0-20250410154226-55bdd7d785fc // indirect
- github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20250410154351-5db5bbd64322 // indirect
+ github.com/Picocrypt/gl v0.0.0-20250412234430-767b58dbf936 // indirect
+ github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20250412234750-7b96bfdb8dd8 // indirect
github.com/Picocrypt/mainthread v0.0.0-20240831004314-496f638392b3 // indirect
github.com/Picocrypt/w32 v0.0.0-20240831001500-1183079d4d57 // indirect
golang.org/x/sys v0.32.0 // indirect
diff --git a/src/go.sum b/src/go.sum
index 1817016..7314822 100644
--- a/src/go.sum
+++ b/src/go.sum
@@ -1,23 +1,23 @@
-github.com/Picocrypt/dialog v0.0.0-20250410154130-d98dc55ea635 h1:mZl+aiuQSQhe/C1Y4NdJ7w6KdjAEyr5Va2JHGdDvF3E=
-github.com/Picocrypt/dialog v0.0.0-20250410154130-d98dc55ea635/go.mod h1:bScGI7SMxXUo4EtwGfDkeA7owLj99QBn8nlqnqxacHU=
-github.com/Picocrypt/giu v0.0.0-20250410155113-88f8ef80cbaf h1:mDXu/vjyoxcXg44ejbpRhiZg1NmzMlez6ikEkZ03G6c=
-github.com/Picocrypt/giu v0.0.0-20250410155113-88f8ef80cbaf/go.mod h1:fDUYbcghlVwlEoWkp+4LwQd1DJft1XZ9uTaHskd2gno=
-github.com/Picocrypt/gl v0.0.0-20250410154226-55bdd7d785fc h1:PhgkrhrXcMVDJUHfTjJ+U1soq+eiaeWheEELXZau62k=
-github.com/Picocrypt/gl v0.0.0-20250410154226-55bdd7d785fc/go.mod h1:aHUxrywhiLVtrH6Yus/4N9VtOFzsEPp1AJj2ioFgswg=
-github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20250410154351-5db5bbd64322 h1:eeD6947JcMIeRhlPkXWedfi8IqdZuX/k0dVM8nEB+bc=
-github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20250410154351-5db5bbd64322/go.mod h1:7StnBznBEzR4cEtV5ptfQfMBgmv05p+b4ImwZ+pXXKQ=
-github.com/Picocrypt/imgui-go v0.0.0-20250410154824-2e0c0440a8da h1:zea0P1s/ykcWU1zbBxVLgCjSwRhT7+u1KoYPQHuStjQ=
-github.com/Picocrypt/imgui-go v0.0.0-20250410154824-2e0c0440a8da/go.mod h1:crmefGfhM5+lQUU/fbS9Hjs8xOlrrJI235vwnktN9PE=
-github.com/Picocrypt/infectious v0.0.0-20250410153626-f2f1c05d0452 h1:9UqCRY4qLDpIfxW+N51JPTn0KDKg88um+BVXApTl0NI=
-github.com/Picocrypt/infectious v0.0.0-20250410153626-f2f1c05d0452/go.mod h1:S99y5mnE1SZcr3n2DNDkdYZ9QJD4OmnGXlQ9TvLLC+M=
+github.com/Picocrypt/dialog v0.0.0-20250412233924-78f7b909315b h1:k5YGEx61N6K8l2l6AQ1u5W2aR+47sVZWFyqXS/f5lIA=
+github.com/Picocrypt/dialog v0.0.0-20250412233924-78f7b909315b/go.mod h1:OyaP0Tz19qL3RAGq5Ntues+WVrIbHh5MrfqoA/qhqeg=
+github.com/Picocrypt/giu v0.0.0-20250412235908-fe90a482e6f2 h1:SPR2efZTpZJON/2mNifLi68Gl9Epxh/1nXb3kGGHCcg=
+github.com/Picocrypt/giu v0.0.0-20250412235908-fe90a482e6f2/go.mod h1:jd6AonK0ZI02R7GqLWb4gWJz/A2ClF36Y4fFMR8Lzbk=
+github.com/Picocrypt/gl v0.0.0-20250412234430-767b58dbf936 h1:6MChjQ4AZC2ISBjbgZU/z6tSUxYP50NkRvAu0T2kjlY=
+github.com/Picocrypt/gl v0.0.0-20250412234430-767b58dbf936/go.mod h1:pMdf3io/y3I+zYZ6/xFb3MlI2AgL38enDDIKuR0n2qA=
+github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20250412234750-7b96bfdb8dd8 h1:i8wXJhSYIJTXb6sqBS6JZW7QosI9u8Ysy1BHZCTuZEc=
+github.com/Picocrypt/glfw/v3.3/glfw v0.0.0-20250412234750-7b96bfdb8dd8/go.mod h1:cX5N2TrX03DC5i5eplxopglDue/vHDs+6Ng9G9uItaI=
+github.com/Picocrypt/imgui-go v0.0.0-20250412235405-d86b230f5fbb h1:0XMtv2CXx3QvC9ikeH43fJl6ql8j5EsnaiOqhsToFnY=
+github.com/Picocrypt/imgui-go v0.0.0-20250412235405-d86b230f5fbb/go.mod h1:N+NVTIIMz6icYltvaKHMvmVIllZDYUyscJ8wpcLKDZ4=
+github.com/Picocrypt/infectious v0.0.0-20250412183341-9f88c6307b39 h1:czHyPoiNrILv9xjfQ87UFllJgak8W6gVcYkmfOay/BE=
+github.com/Picocrypt/infectious v0.0.0-20250412183341-9f88c6307b39/go.mod h1:2ZVEanURxuWmxYZ6W6xMMy4ZR6xmQr16Vq/XPTLIspQ=
github.com/Picocrypt/mainthread v0.0.0-20240831004314-496f638392b3 h1:a62XmbZYhHGDR15C1gxp/IPfJX5SflrJuGpqNoOOK7w=
github.com/Picocrypt/mainthread v0.0.0-20240831004314-496f638392b3/go.mod h1:bsUKeX+/53rCTrItl3YUaeaN5tXl1v6326ZI90xIOsc=
github.com/Picocrypt/serpent v0.0.0-20240830233833-9ad6ab254fd7 h1:G36G2vmQAS7CVoHQrHDGAoCWll/0kPCI8Dk7mgwcJFE=
github.com/Picocrypt/serpent v0.0.0-20240830233833-9ad6ab254fd7/go.mod h1:BxsgRYwUVd92aEwXnXsfXfHw8aHlD/PUyExC/wwk9oI=
github.com/Picocrypt/w32 v0.0.0-20240831001500-1183079d4d57 h1:jusSXTp0h5wz8lxNXStw0jXr/ogZF6rzRF8gu0534hA=
github.com/Picocrypt/w32 v0.0.0-20240831001500-1183079d4d57/go.mod h1:FkeZHdKlITdP34VknO8yLdRY5pCi+iWEhDSA0YsBhZc=
-github.com/Picocrypt/zxcvbn-go v0.0.0-20250410153845-b5da60d3e882 h1:W5f997Hcoi9PiU2j3TSsjryNKQ2+jIiHChpOnf485Xc=
-github.com/Picocrypt/zxcvbn-go v0.0.0-20250410153845-b5da60d3e882/go.mod h1:pIpFJD6Ey6jxU5GXMZ3Kc4wF9B49OJy9wTwwE3bJRPI=
+github.com/Picocrypt/zxcvbn-go v0.0.0-20250412183938-d59695960527 h1:IqypAzv5COsByMhiSdwlgafA5SBRG7Z0binnBSo3htM=
+github.com/Picocrypt/zxcvbn-go v0.0.0-20250412183938-d59695960527/go.mod h1:u0rcUNEwy7st1DnPxdOJdTsh0aSRhrdMOxlIGrXR1Ls=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
From 159944a6199b2a6c1af9f00c10a0d0d828a13038 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Sun, 13 Apr 2025 13:23:19 -0400
Subject: [PATCH 10/46] Encrypt temporary zip files
---
src/Picocrypt.go | 59 +++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 56 insertions(+), 3 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 3587e44..57e99dd 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -177,6 +177,33 @@ func (p *compressorProgress) Read(data []byte) (int, error) {
return read, err
}
+type encryptedZipWriter struct {
+ _w io.Writer
+ _cipher *chacha20.Cipher
+}
+
+func (ezw *encryptedZipWriter) Write(data []byte) (n int, err error) {
+ dst := make([]byte, len(data))
+ ezw._cipher.XORKeyStream(dst, data)
+ return ezw._w.Write(dst)
+}
+
+type encryptedZipReader struct {
+ _r io.Reader
+ _cipher *chacha20.Cipher
+}
+
+func (ezr *encryptedZipReader) Read(data []byte) (n int, err error) {
+ src := make([]byte, len(data))
+ n, err = ezr._r.Read(src)
+ if err == nil && n > 0 {
+ dst := make([]byte, n)
+ ezr._cipher.XORKeyStream(dst, src[:n])
+ copy(data, dst)
+ }
+ return n, err
+}
+
var onClickStartButton = func() {
// Start button should be disabled if these conditions are true; don't do anything if so
if (len(keyfiles) == 0 && password == "") || (mode == "encrypt" && password != cpassword) {
@@ -610,7 +637,7 @@ func draw() {
giu.Checkbox("Paranoid mode", ¶noid),
giu.Tooltip("Provides the highest level of security attainable"),
giu.Dummy(-170, 0),
- giu.Style().SetDisabled(recursively || !(len(allFiles) > 1 || len(onlyFolders) > 0)).To(
+ giu.Style().SetDisabled(recursively).To(
giu.Checkbox("Compress files", &compress).OnChange(func() {
if !(len(allFiles) > 1 || len(onlyFolders) > 0) {
if compress {
@@ -1086,6 +1113,17 @@ func work() {
var keyfileHashRef []byte // Same as 'keyfileHash', but used for comparison
var authTag []byte // 64-byte authentication tag (BLAKE2b or HMAC-SHA3)
+ var tempZipCipherW *chacha20.Cipher
+ var tempZipCipherR *chacha20.Cipher
+ var tempZipInUse bool = false
+ func() {
+ key, nonce := make([]byte, 32), make([]byte, 12)
+ rand.Read(key)
+ rand.Read(nonce)
+ tempZipCipherW, _ = chacha20.NewUnauthenticatedCipher(key, nonce)
+ tempZipCipherR, _ = chacha20.NewUnauthenticatedCipher(key, nonce)
+ }()
+
// Combine/compress all files into a .zip file if needed
if len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
// Consider case where compressing only one file
@@ -1111,7 +1149,12 @@ func work() {
}
// Add each file to the .zip
- writer := zip.NewWriter(file)
+ tempZip := encryptedZipWriter{
+ _w: file,
+ _cipher: tempZipCipherW,
+ }
+ tempZipInUse = true
+ writer := zip.NewWriter(&tempZip)
compressStart = time.Now()
for i, path := range files {
progressInfo = fmt.Sprintf("%d/%d", i+1, len(files))
@@ -1744,6 +1787,10 @@ func work() {
// Start the main encryption process
canCancel = true
startTime := time.Now()
+ tempZip := encryptedZipReader{
+ _r: fin,
+ _cipher: tempZipCipherR,
+ }
for {
if !working {
cancel(fin, fout)
@@ -1761,7 +1808,13 @@ func work() {
} else {
src = make([]byte, MiB)
}
- size, err := fin.Read(src)
+
+ var size int
+ if tempZipInUse {
+ size, err = tempZip.Read(src)
+ } else {
+ size, err = fin.Read(src)
+ }
if err != nil {
break
}
From 757c9c23e4f4701a45b65724081aa356c651817f Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Sun, 13 Apr 2025 13:32:56 -0400
Subject: [PATCH 11/46] remove warning for external storage target
since temporary files are now encrypted so no longer matters
---
src/Picocrypt.go | 37 +++++--------------------------------
1 file changed, 5 insertions(+), 32 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 57e99dd..2466fd2 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -30,7 +30,6 @@ import (
"os"
"path/filepath"
"regexp"
- "runtime"
"strconv"
"strings"
"time"
@@ -131,9 +130,7 @@ var startLabel = "Start"
var mainStatus = "Ready"
var mainStatusColor = WHITE
var popupStatus string
-
-var temporaryZip bool
-var externalDst bool
+var usingTempZip bool
// Progress variables
var progress float32
@@ -637,7 +634,7 @@ func draw() {
giu.Checkbox("Paranoid mode", ¶noid),
giu.Tooltip("Provides the highest level of security attainable"),
giu.Dummy(-170, 0),
- giu.Style().SetDisabled(recursively).To(
+ giu.Style().SetDisabled(recursively || !(len(allFiles) > 1 || len(onlyFolders) > 0)).To(
giu.Checkbox("Compress files", &compress).OnChange(func() {
if !(len(allFiles) > 1 || len(onlyFolders) > 0) {
if compress {
@@ -767,21 +764,6 @@ func draw() {
} else {
file += filepath.Ext(inputFile) + ".pcv"
}
- externalDst = false
- GOOS := strings.ToLower(runtime.GOOS)
- if strings.HasPrefix(GOOS, "windows") {
- if !strings.HasPrefix(file, "C:") {
- externalDst = true
- }
- } else if strings.HasPrefix(GOOS, "linux") {
- if strings.Contains(file, "/media/") || strings.Contains(file, "/mnt/") {
- externalDst = true
- }
- } else if strings.HasPrefix(GOOS, "darwin") {
- if strings.Contains(file, "/Volumes/") {
- externalDst = true
- }
- }
} else {
if strings.HasSuffix(inputFile, ".zip.pcv") {
file += ".zip"
@@ -814,18 +796,10 @@ func draw() {
).Build()
return
}
- if temporaryZip && externalDst {
- giu.Style().SetColor(giu.StyleColorText, YELLOW).To(
- giu.Label("Warning: unencrypted temp files will be created"),
- ).Build()
- } else if temporaryZip {
+ if usingTempZip {
giu.Style().SetColor(giu.StyleColorText, WHITE).To(
giu.Label("Ready (info: will create a temporary zip file)"),
).Build()
- } else if externalDst {
- giu.Style().SetColor(giu.StyleColorText, WHITE).To(
- giu.Label("Ready (info: target may be an external drive)"),
- ).Build()
} else {
giu.Style().SetColor(giu.StyleColorText, mainStatusColor).To(
giu.Label("Ready"),
@@ -1068,7 +1042,7 @@ func onDrop(names []string) {
// Set the input and output paths
inputFile = filepath.Join(filepath.Dir(names[0]), "Encrypted") + ".zip"
outputFile = inputFile + ".pcv"
- temporaryZip = true
+ usingTempZip = true
}
// Recursively add all files in 'onlyFolders' to 'allFiles'
@@ -2357,8 +2331,7 @@ func resetUI() {
mainStatus = "Ready"
mainStatusColor = WHITE
popupStatus = ""
- temporaryZip = false
- externalDst = false
+ usingTempZip = false
progress = 0
progressInfo = ""
From 9e7e2e9c4485898ef9fb6f3fb758622239da9f11 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Sun, 13 Apr 2025 13:35:01 -0400
Subject: [PATCH 12/46] Changelog: add to future: remove use of temp files
---
Changelog.md | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/Changelog.md b/Changelog.md
index fffc47f..750f4ef 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,7 +1,12 @@
+# Future
+
+ - Figure out how to remove use of temporary files completely
+
+
# v1.48 (Released 04/11/2025)
- ✓ Allow pressing 'Enter' key to press Start/Process button
- - ✓ Warn user when encrypting multiple files to an external drive
+ - ✓ Encrypt previously unencrypted temporary zip files
# v1.47 (Released 02/19/2025)
From 16bb70dc9733f77e75ffea70af2db3a431435c4a Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Sun, 13 Apr 2025 14:24:02 -0400
Subject: [PATCH 13/46] Add .incomplete to end of wip files
---
Changelog.md | 1 +
src/Picocrypt.go | 29 ++++++++++++++++++-----------
2 files changed, 19 insertions(+), 11 deletions(-)
diff --git a/Changelog.md b/Changelog.md
index 750f4ef..0414cd6 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -7,6 +7,7 @@
- ✓ Allow pressing 'Enter' key to press Start/Process button
- ✓ Encrypt previously unencrypted temporary zip files
+ - ✓ Add `.incomplete` to filenames while work is in progress
# v1.47 (Released 02/19/2025)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 2466fd2..e61c08a 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -1115,7 +1115,7 @@ func work() {
}
// Open a temporary .zip for writing
- inputFile = strings.TrimSuffix(outputFile, ".pcv")
+ inputFile = strings.TrimSuffix(outputFile, ".pcv") + ".tmp"
file, err := os.Create(inputFile)
if err != nil { // Make sure file is writable
accessDenied("Write")
@@ -1388,7 +1388,7 @@ func work() {
}
// Create the output file
- fout, err = os.Create(outputFile)
+ fout, err = os.Create(outputFile + ".incomplete")
if err != nil {
fin.Close()
if len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
@@ -1473,7 +1473,7 @@ func work() {
if len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
os.Remove(inputFile)
}
- os.Remove(outputFile)
+ os.Remove(fout.Name())
return
}
}
@@ -1705,7 +1705,7 @@ func work() {
}
// Create the output file for decryption
- fout, err = os.Create(outputFile)
+ fout, err = os.Create(outputFile + ".incomplete")
if err != nil {
fin.Close()
if recombine {
@@ -1771,7 +1771,7 @@ func work() {
if recombine || len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
os.Remove(inputFile)
}
- os.Remove(outputFile)
+ os.Remove(fout.Name())
return
}
@@ -1908,7 +1908,7 @@ func work() {
if recombine || len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
os.Remove(inputFile)
}
- os.Remove(outputFile)
+ os.Remove(fout.Name())
return
}
@@ -1987,6 +1987,8 @@ func work() {
fin.Close()
fout.Close()
+ os.Rename(outputFile+".incomplete", outputFile)
+
// Add plausible deniability
if mode == "encrypt" && deniability {
popupStatus = "Adding plausible deniability..."
@@ -1994,13 +1996,13 @@ func work() {
giu.Update()
// Get size of volume for showing progress
- stat, _ := os.Stat(fout.Name())
+ stat, _ := os.Stat(outputFile)
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())
+ os.Rename(outputFile, outputFile+".tmp")
+ fin, _ := os.Open(outputFile + ".tmp")
+ fout, _ := os.Create(outputFile + ".incomplete")
// Use a random Argon2 salt and XChaCha20 nonce
salt := make([]byte, 16)
@@ -2050,6 +2052,7 @@ func work() {
fin.Close()
fout.Close()
os.Remove(fin.Name())
+ os.Rename(outputFile+".incomplete", outputFile)
canCancel = true
giu.Update()
}
@@ -2094,7 +2097,7 @@ func work() {
startTime := time.Now()
for i := 0; i < chunks; i++ {
// Make the chunk
- fout, _ := os.Create(fmt.Sprintf("%s.%d", outputFile, i))
+ fout, _ := os.Create(fmt.Sprintf("%s.%d.incomplete", outputFile, i))
done := 0
// Copy data into the chunk
@@ -2160,6 +2163,10 @@ func work() {
fin.Close()
os.Remove(outputFile)
+ names, _ = filepath.Glob(outputFile + ".*.incomplete")
+ for _, i := range names {
+ os.Rename(i, strings.TrimSuffix(i, ".incomplete"))
+ }
}
canCancel = false
From d7a0ee126bfea8f3f024f8b9ba73cda8518335e2 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Sun, 13 Apr 2025 14:33:08 -0400
Subject: [PATCH 14/46] Use encrypted-*.zip.pcv instead of Encrypted.zip.pcv
So you don't always have to rename or delete an existing volume
---
Changelog.md | 3 ++-
src/Picocrypt.go | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/Changelog.md b/Changelog.md
index 0414cd6..d6dba27 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -3,11 +3,12 @@
- Figure out how to remove use of temporary files completely
-# v1.48 (Released 04/11/2025)
+# v1.48 (Released 04/15/2025)
- ✓ Allow pressing 'Enter' key to press Start/Process button
- ✓ Encrypt previously unencrypted temporary zip files
- ✓ Add `.incomplete` to filenames while work is in progress
+ - ✓ Use `encrypted-*.zip.pcv` output name instead of `Encrypted.zip.pcv`
# v1.47 (Released 02/19/2025)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index e61c08a..a9b97c0 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -1040,7 +1040,7 @@ func onDrop(names []string) {
}
// Set the input and output paths
- inputFile = filepath.Join(filepath.Dir(names[0]), "Encrypted") + ".zip"
+ inputFile = filepath.Join(filepath.Dir(names[0]), "encrypted-"+strconv.Itoa(int(time.Now().Unix()))) + ".zip"
outputFile = inputFile + ".pcv"
usingTempZip = true
}
From bad71f95ceb1f26e7913e656059cb0768c9d9258 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Sun, 13 Apr 2025 14:40:52 -0400
Subject: [PATCH 15/46] use 0600 for auto unzip file permissions
prevent executing for safety and only allow user to have access
---
Changelog.md | 1 +
src/Picocrypt.go | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/Changelog.md b/Changelog.md
index d6dba27..1f6d76d 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -9,6 +9,7 @@
✓ Encrypt previously unencrypted temporary zip files
✓ Add `.incomplete` to filenames while work is in progress
✓ Use `encrypted-*.zip.pcv` output name instead of `Encrypted.zip.pcv`
+ ✓ Use 0600 permissions when auto unzipping for optimal security
# v1.47 (Released 02/19/2025)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index a9b97c0..d6a3e8d 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -2502,7 +2502,7 @@ func unpackArchive(zipPath string) error {
outPath := filepath.Join(extractDir, f.Name)
// Otherwise create necessary parent directories
- if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil {
+ if err := os.MkdirAll(filepath.Dir(outPath), 0600); err != nil {
return err
}
From 6a8fdeaa532c607316260c8ff2d27bde28d5e479 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Sun, 13 Apr 2025 14:47:18 -0400
Subject: [PATCH 16/46] Remove caveats from README
No longer needed because temporary zip files are now encrypted.
---
README.md | 8 --------
1 file changed, 8 deletions(-)
diff --git a/README.md b/README.md
index b8f356e..b5491e9 100644
--- a/README.md
+++ b/README.md
@@ -90,14 +90,6 @@ While being simple, Picocrypt also strives to be powerful in the hands of knowle
Recursively: If you want to encrypt and/or decrypt a large set of files individually, this option will tell Picocrypt to go through every recursive file that you drop in and encrypt/decrypt it separately. This is useful, for example, if you are encrypting thousands of large documents and want to be able to decrypt any one of them in particular without having to download and decrypt the entire set of documents. Keep in mind that this is a very complex feature that should only be used if you know what you are doing.
-# Caveats
-When encrypting multiple files, Picocrypt will automatically zip them into one file before encrypting it. However, this requires a two-step process that creates an unencrypted temporary `.zip.tmp` file in the same destination folder. This has two implications:
-
- - There must be at least double the available free space on the target drive as the combined total size of input files
- - The target drive must be safe to save confidential data; if not, the unencrypted temporary file may be recoverable even after deletion
-
-To mitigate these caveats, Picocrypt will show info and warning labels accordingly. However, it is best to prevent these issues altogether by always encrypting and decrypting on your main host drive and then copying encrypted volumes to and from external sources, or zipping up input files beforehand and encrypting that single file which doesn't have these caveats.
-
# Security
For more information on how Picocrypt handles cryptography, see Internals for the technical details. If you're worried about the safety of me or this project, let me assure you that this repository won't be hijacked or backdoored. I have 2FA (TOTP) enabled on all accounts with a tie to Picocrypt (GitHub, etc.), in addition to full-disk encryption on all of my portable devices. For further hardening, Picocrypt uses my isolated forks of dependencies and I fetch upstream only when I have taken a look at the changes and believe that there aren't any security issues. This means that if a dependency gets hacked or deleted by the author, Picocrypt will be using my fork of it and remain completely unaffected. I've also meticulously gone through every single setting in the Picocrypt organization and repos, locking down access behind multiple layers of security such as read-only base-level member permissions, required PRs and mandatory approvals (which no one can do but me), mandatory CODEOWNERS approvals, and I'm the only member of the Picocrypt organization and repos (except for PicoGo). You can feel confident about using Picocrypt as long as you understand:
From c63cf926721b96d3d4d633f52f1f1fd95f334ed8 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Sun, 13 Apr 2025 15:15:43 -0400
Subject: [PATCH 17/46] Start button show "Zip and Encrypt" if temp zip needed
---
Changelog.md | 2 ++
src/Picocrypt.go | 49 ++++++++++++++++++++++++++++++++----------------
2 files changed, 35 insertions(+), 16 deletions(-)
diff --git a/Changelog.md b/Changelog.md
index 1f6d76d..60a5a05 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -6,6 +6,8 @@
# v1.48 (Released 04/15/2025)
- ✓ Allow pressing 'Enter' key to press Start/Process button
+ - ✓ Update "Encrypt" button to "Zip and Encrypt" if multiple files
+ - ✓ Give user estimated required free disk space in status label
- ✓ Encrypt previously unencrypted temporary zip files
- ✓ Add `.incomplete` to filenames while work is in progress
- ✓ Use `encrypted-*.zip.pcv` output name instead of `Encrypted.zip.pcv`
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index d6a3e8d..339be2b 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -131,6 +131,7 @@ var mainStatus = "Ready"
var mainStatusColor = WHITE
var popupStatus string
var usingTempZip bool
+var requiredFreeSpace int64
// Progress variables
var progress float32
@@ -635,15 +636,7 @@ func draw() {
giu.Tooltip("Provides the highest level of security attainable"),
giu.Dummy(-170, 0),
giu.Style().SetDisabled(recursively || !(len(allFiles) > 1 || len(onlyFolders) > 0)).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"
- }
- }
- }),
+ giu.Checkbox("Compress files", &compress),
giu.Tooltip("Compress files with Deflate before encrypting"),
),
).Build()
@@ -747,7 +740,7 @@ func draw() {
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")
+ f.SetInitFilename("encrypted-" + strconv.Itoa(int(time.Now().Unix())))
}
// Get the chosen file path
@@ -796,12 +789,12 @@ func draw() {
).Build()
return
}
- if usingTempZip {
+ if requiredFreeSpace > 0 {
giu.Style().SetColor(giu.StyleColorText, WHITE).To(
- giu.Label("Ready (info: will create a temporary zip file)"),
+ giu.Label("Ready (ensure " + sizeify(requiredFreeSpace) + " of disk space is free)"),
).Build()
} else {
- giu.Style().SetColor(giu.StyleColorText, mainStatusColor).To(
+ giu.Style().SetColor(giu.StyleColorText, WHITE).To(
giu.Label("Ready"),
).Build()
}
@@ -872,12 +865,18 @@ func onDrop(names []string) {
folders++
mode = "encrypt"
inputLabel = "1 folder"
- startLabel = "Encrypt"
+ startLabel = "Zip and Encrypt"
onlyFolders = append(onlyFolders, names[0])
- inputFile = filepath.Join(filepath.Dir(names[0]), "Encrypted") + ".zip"
+ inputFile = filepath.Join(filepath.Dir(names[0]), "encrypted-"+strconv.Itoa(int(time.Now().Unix()))) + ".zip"
outputFile = inputFile + ".pcv"
+ size, err := dirSize(names[0])
+ if err != nil {
+ panic(err)
+ }
+ requiredFreeSpace = 2 * size
} else { // A file was dropped
files++
+ requiredFreeSpace += stat.Size()
// Is the file a part of a split volume?
nums := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
@@ -914,6 +913,7 @@ func onDrop(names []string) {
}
totalFiles++
compressTotal += stat.Size()
+ requiredFreeSpace += stat.Size()
}
} else {
outputFile = names[0][:len(names[0])-4]
@@ -1003,7 +1003,7 @@ func onDrop(names []string) {
}
} else { // There are multiple dropped items
mode = "encrypt"
- startLabel = "Encrypt"
+ startLabel = "Zip and Encrypt"
// Go through each dropped item and add to corresponding slices
for _, name := range names {
@@ -1017,6 +1017,7 @@ func onDrop(names []string) {
allFiles = append(allFiles, name)
compressTotal += stat.Size()
+ requiredFreeSpace += 2 * stat.Size()
inputLabel = fmt.Sprintf("Scanning files... (%s)", sizeify(compressTotal))
giu.Update()
}
@@ -1055,6 +1056,7 @@ func onDrop(names []string) {
if err == nil && !stat.IsDir() {
allFiles = append(allFiles, path)
compressTotal += stat.Size()
+ requiredFreeSpace += 2 * stat.Size()
inputLabel = fmt.Sprintf("Scanning files... (%s)", sizeify(compressTotal))
giu.Update()
}
@@ -2339,6 +2341,7 @@ func resetUI() {
mainStatusColor = WHITE
popupStatus = ""
usingTempZip = false
+ requiredFreeSpace = 0
progress = 0
progressInfo = ""
@@ -2550,6 +2553,20 @@ func unpackArchive(zipPath string) error {
return nil
}
+func dirSize(path string) (int64, error) {
+ var size int64
+ err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if !info.IsDir() {
+ size += info.Size()
+ }
+ return err
+ })
+ return size, err
+}
+
func main() {
// Create the main window
window = giu.NewMasterWindow("Picocrypt "+version[1:], 318, 507, giu.MasterWindowFlagsNotResizable)
From 065a50d90ea8c4294101e6ebcd6760f1de5a9135 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Sun, 13 Apr 2025 18:54:00 -0400
Subject: [PATCH 18/46] fix: 0700 instead of 0600 for mkdirall
---
Changelog.md | 2 +-
src/Picocrypt.go | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Changelog.md b/Changelog.md
index 60a5a05..87ac649 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -11,7 +11,7 @@
- ✓ Encrypt previously unencrypted temporary zip files
- ✓ Add `.incomplete` to filenames while work is in progress
- ✓ Use `encrypted-*.zip.pcv` output name instead of `Encrypted.zip.pcv`
- - ✓ Use 0600 permissions when auto unzipping for optimal security
+ - ✓ Use 0700 permissions when auto unzipping and creating folders
# v1.47 (Released 02/19/2025)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 339be2b..dcd25fe 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -2486,7 +2486,7 @@ func unpackArchive(zipPath string) error {
// Make directory if current entry is a folder
if f.FileInfo().IsDir() {
- if err := os.MkdirAll(outPath, f.Mode()); err != nil {
+ if err := os.MkdirAll(outPath, 0700); err != nil {
return err
}
}
@@ -2505,7 +2505,7 @@ func unpackArchive(zipPath string) error {
outPath := filepath.Join(extractDir, f.Name)
// Otherwise create necessary parent directories
- if err := os.MkdirAll(filepath.Dir(outPath), 0600); err != nil {
+ if err := os.MkdirAll(filepath.Dir(outPath), 0700); err != nil {
return err
}
From b26137d959036f1037542b4bb9b6fd5b5b182300 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Sun, 13 Apr 2025 19:51:27 -0400
Subject: [PATCH 19/46] Update default.yml
---
.github/ISSUE_TEMPLATE/default.yml | 24 +++++++++++++++---------
1 file changed, 15 insertions(+), 9 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/default.yml b/.github/ISSUE_TEMPLATE/default.yml
index 9aa09e5..2dff020 100644
--- a/.github/ISSUE_TEMPLATE/default.yml
+++ b/.github/ISSUE_TEMPLATE/default.yml
@@ -12,11 +12,11 @@ body:
- type: markdown
attributes:
value: |
- Picocrypt is a "finished" piece of software and is in a maintenance-only stage. This does not mean the software is old, outdated, or abandonware, but that the focus is on fixing bugs and ensuring the software continues to work smoothly as opposed to actively developing new features. As well, my time as the developer is limited considering that Picocrypt brings me no monetary benefit and is entirely a gift of my time and skill to the community.
+ Picocrypt is a "finished" piece of software and is in a maintenance-only stage. This does not mean the software is old, outdated, or abandonware, but that the sole focus is on fixing bugs and ensuring the software continues to work smoothly as opposed to actively developing new features. As well, my time as the developer is very limited considering that Picocrypt brings me no monetary benefit and is entirely a gift of my time and skill to the community.
- type: markdown
attributes:
value: |
- *To ensure that issues remain relevant and as time-efficient as possible for me, please follow the guidelines below depending on the type/topic of your issue.*
+ *Therefore, to save me time so that I can focus on the important things, please follow the guidelines below depending on your topic.*
- type: markdown
attributes:
value: |
@@ -24,7 +24,7 @@ body:
- type: markdown
attributes:
value: |
- These are high-priority issues and the main purpose of this repository's issue tracker. Make the issue with a short description, and then once the issue is created, add a comment with as many details as possible. Ping me (@HACKERALERT) in the comment so that I can get to it as soon as possible. Keep in mind that I define "bug" as something wrong with Picocrypt's code itself. If it's not Picocrypt's fault, it's not a bug.
+ These are important; make the issue with a short description, and then once the issue is created, add a comment with as many details as possible. Ping me (@HACKERALERT) in the comment so that I can get to it as soon as possible. Keep in mind that I define "bug" as something wrong with Picocrypt's code itself. If it's not Picocrypt's fault, it's not a bug.
- type: markdown
attributes:
value: |
@@ -32,7 +32,7 @@ body:
- type: markdown
attributes:
value: |
- Usually these issues are not directly caused by Picocrypt's code. Create the issue and in a separate comment, provide details about the environment you're running in (like OS, DE, etc.). **Do not ping me initially.** Let the issue sit for at least *3 days* to allow other users to potentially help you resolve the issue. If after 3 days, you haven't figured things out, then you may ping me (@HACKERALERT).
+ Usually these issues are not directly caused by Picocrypt's code. If you're on Windows, see [here](https://github.com/Picocrypt/Picocrypt/issues/91). If you're on Linux, install some packages and try again (see [here](https://github.com/Picocrypt/Picocrypt/tree/main/src#1-prerequisites)). Picocrypt only targets Windows 11, Ubuntu 24/Debian 12, and macOS 15 or later, so *do not create an issue if your OS is older than those; that is your problem, not mine*. If none of the points above help, create the issue and in a separate comment, provide details about the environment you're running in (like OS, DE, etc.). **Do not ping me initially.** Let the issue sit for at least *5 days* to allow other users to potentially help you resolve the issue. If after 5 days, you haven't figured things out, then you may ping me (@HACKERALERT).
- type: markdown
attributes:
value: |
@@ -40,7 +40,7 @@ body:
- type: markdown
attributes:
value: |
- These are unpreventable; Picocrypt is cryptography, file deletion, and passwords bundled into an executable... which looks similar to ransomware, unfortunately. Please report these false positives to your antivirus software provider and do not create an issue about it.
+ These are unpreventable; report them as false positives to your antivirus software provider and **do not create an issue about it**.
- type: markdown
attributes:
value: |
@@ -48,7 +48,7 @@ body:
- type: markdown
attributes:
value: |
- Create the issue and ask your question or support request in a separate comment. **Do not ping me initially.** Let the issue sit for at least *5 days* to give other users a chance to help you first. If after 5 days, you have not received any assistance, then you may ping me (@HACKERALERT).
+ Create the issue and ask your question or support request in a separate comment. **Do not ping me initially.** Let the issue sit for at least *10 days* to give other users a chance to help you first. If after 10 days, you have not received any assistance, then you may ping me (@HACKERALERT).
- type: markdown
attributes:
value: |
@@ -56,7 +56,7 @@ body:
- type: markdown
attributes:
value: |
- Picocrypt is mature software; I do not intend to add any new features. Generally, do not create any feature requests unless it's very minor and can be implemented with low effort and minimal impact on reliability and security. What is considered "minor" is subjective, but here is an example: "the ability to decrypt a volume entirely in-memory" is pretty significant, while "auto start encryption on pressing the Enter key" is relatively minor. A proof-of-concept link to code or a fork would be appreciated.
+ Picocrypt is mature software; I do not intend to add any new features. **Do not create these types of issues.**
- type: markdown
attributes:
value: |
@@ -64,7 +64,7 @@ body:
- type: markdown
attributes:
value: |
- Picocrypt prioritizes correctness and reliability over performance, so many parts of the code are written sequentially and don't use concurrency. This is intentional and need not be pointed out. Unless performance is absolutely atrocious to the point where it is indicative of a potential bug, do not make issues about performance.
+ Picocrypt prioritizes correctness and reliability over performance, so many parts of the code are written sequentially and don't use concurrency. This is intentional and need not be pointed out. Unless performance is absolutely atrocious to the point where it is indicative of a potential bug, **do not make issues about performance**.
- type: markdown
attributes:
value: |
@@ -72,8 +72,9 @@ body:
- type: markdown
attributes:
value: |
- You will have to use your best judgement here. Read the sections above to get an idea of what I expect to see and do what you think is best. Ideally, ping me only if sufficient time has passed for other users to assist/answer you, or it is best addressed by me directly.
+ You will have to use your best judgement here. Read the sections above to get an idea of what I expect to see and do what you think is best. Ideally, ping me only if sufficient time has passed for other users to assist/answer you, or it is best addressed by me directly. You must first look through existing issues or do a web search (AI can help!) before creating the issue. While I am allowing these generic issues to be made, if they become a hassle, I reserve the right to disallow them in the future.
- type: checkboxes
+ id: confirmation
attributes:
label: "Please confirm:"
options:
@@ -83,7 +84,12 @@ body:
required: true
- label: "I acknowledge my issue may be ignored or closed without explanation"
required: true
+ - label: "I have looked through previous issues and related info already"
+ required: true
+ - label: "I will remember to close my issue when it is resolved"
+ required: true
- type: input
+ id: summary
attributes:
label: "Describe the issue briefly in a few sentences:"
description: "You can add more details in a separate comment after creating the issue."
From f58f5ce249f435466f9e22951fde139a4f019d95 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Sun, 13 Apr 2025 19:59:07 -0400
Subject: [PATCH 20/46] Create close-issues.yml
---
.github/workflows/close-issues.yml | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
create mode 100644 .github/workflows/close-issues.yml
diff --git a/.github/workflows/close-issues.yml b/.github/workflows/close-issues.yml
new file mode 100644
index 0000000..8b962ab
--- /dev/null
+++ b/.github/workflows/close-issues.yml
@@ -0,0 +1,21 @@
+name: Close inactive issues
+on:
+ schedule:
+ - cron: "30 1 * * *"
+jobs:
+ close-issues:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ steps:
+ - uses: actions/stale@v9
+ with:
+ days-before-issue-stale: 30
+ days-before-issue-close: 14
+ stale-issue-label: "stale"
+ stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
+ close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
+ days-before-pr-stale: -1
+ days-before-pr-close: -1
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
From 9b2b69e44220c8a8c19917c3244855941c6b1c46 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Sun, 13 Apr 2025 20:06:21 -0400
Subject: [PATCH 21/46] Update versioninfo.rc
---
dist/windows/versioninfo.rc | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/dist/windows/versioninfo.rc b/dist/windows/versioninfo.rc
index 51e4afc..f2678e3 100644
--- a/dist/windows/versioninfo.rc
+++ b/dist/windows/versioninfo.rc
@@ -1,6 +1,6 @@
1 VERSIONINFO
-FILEVERSION 1,47,0,0
-PRODUCTVERSION 1,47,0,0
+FILEVERSION 1,48,0,0
+PRODUCTVERSION 1,48,0,0
FILEOS 0x40004
FILETYPE 0x1
{
@@ -8,7 +8,7 @@ BLOCK "StringFileInfo"
{
BLOCK "040904B0"
{
- VALUE "FileVersion", "1.47"
+ VALUE "FileVersion", "1.48"
VALUE "LegalCopyright", "\xA9 Evan Su & contributors, GPLv3"
VALUE "ProductName", "Picocrypt"
}
From 75c0a017f96b684d2379ced0b11a557d83f9c3e9 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Thu, 17 Apr 2025 23:37:36 -0400
Subject: [PATCH 22/46] Much more reliable free space estimator
---
src/Picocrypt.go | 34 ++++++++++++++++++++++++++--------
1 file changed, 26 insertions(+), 8 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index dcd25fe..d616ae3 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -790,8 +790,21 @@ func draw() {
return
}
if requiredFreeSpace > 0 {
+ multiplier := 1
+ if len(allFiles) > 1 || len(onlyFolders) > 0 { // need a temporary zip file
+ multiplier++
+ }
+ if deniability {
+ multiplier++
+ }
+ if split {
+ multiplier++
+ }
+ if recombine {
+ multiplier++
+ }
giu.Style().SetColor(giu.StyleColorText, WHITE).To(
- giu.Label("Ready (ensure " + sizeify(requiredFreeSpace) + " of disk space is free)"),
+ giu.Label("Ready (ensure " + sizeify(requiredFreeSpace*int64(multiplier)) + " of disk space is free)"),
).Build()
} else {
giu.Style().SetColor(giu.StyleColorText, WHITE).To(
@@ -858,7 +871,12 @@ func onDrop(names []string) {
// One item dropped
if len(names) == 1 {
- stat, _ := os.Stat(names[0])
+ stat, err := os.Stat(names[0])
+ if err != nil {
+ mainStatus = "Failed to stat dropped item"
+ mainStatusColor = RED
+ return
+ }
// A folder was dropped
if stat.IsDir() {
@@ -873,10 +891,10 @@ func onDrop(names []string) {
if err != nil {
panic(err)
}
- requiredFreeSpace = 2 * size
+ requiredFreeSpace = size
} else { // A file was dropped
files++
- requiredFreeSpace += stat.Size()
+ requiredFreeSpace = stat.Size()
// Is the file a part of a split volume?
nums := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
@@ -913,8 +931,8 @@ func onDrop(names []string) {
}
totalFiles++
compressTotal += stat.Size()
- requiredFreeSpace += stat.Size()
}
+ requiredFreeSpace = compressTotal
} else {
outputFile = names[0][:len(names[0])-4]
}
@@ -1017,7 +1035,7 @@ func onDrop(names []string) {
allFiles = append(allFiles, name)
compressTotal += stat.Size()
- requiredFreeSpace += 2 * stat.Size()
+ requiredFreeSpace += stat.Size()
inputLabel = fmt.Sprintf("Scanning files... (%s)", sizeify(compressTotal))
giu.Update()
}
@@ -1056,7 +1074,7 @@ func onDrop(names []string) {
if err == nil && !stat.IsDir() {
allFiles = append(allFiles, path)
compressTotal += stat.Size()
- requiredFreeSpace += 2 * stat.Size()
+ requiredFreeSpace += stat.Size()
inputLabel = fmt.Sprintf("Scanning files... (%s)", sizeify(compressTotal))
giu.Update()
}
@@ -1101,7 +1119,7 @@ func work() {
}()
// Combine/compress all files into a .zip file if needed
- if len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
+ if len(allFiles) > 1 || len(onlyFolders) > 0 {
// Consider case where compressing only one file
files := allFiles
if len(allFiles) == 0 {
From ee2abd053c15e915f0be9fe19e4e0efc02a661e9 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Thu, 17 Apr 2025 23:46:04 -0400
Subject: [PATCH 23/46] remove duplicate single folder size counting
---
src/Picocrypt.go | 20 +-------------------
1 file changed, 1 insertion(+), 19 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index d616ae3..e27631f 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -803,6 +803,7 @@ func draw() {
if recombine {
multiplier++
}
+ fmt.Println(multiplier, requiredFreeSpace)
giu.Style().SetColor(giu.StyleColorText, WHITE).To(
giu.Label("Ready (ensure " + sizeify(requiredFreeSpace*int64(multiplier)) + " of disk space is free)"),
).Build()
@@ -887,11 +888,6 @@ func onDrop(names []string) {
onlyFolders = append(onlyFolders, names[0])
inputFile = filepath.Join(filepath.Dir(names[0]), "encrypted-"+strconv.Itoa(int(time.Now().Unix()))) + ".zip"
outputFile = inputFile + ".pcv"
- size, err := dirSize(names[0])
- if err != nil {
- panic(err)
- }
- requiredFreeSpace = size
} else { // A file was dropped
files++
requiredFreeSpace = stat.Size()
@@ -2571,20 +2567,6 @@ func unpackArchive(zipPath string) error {
return nil
}
-func dirSize(path string) (int64, error) {
- var size int64
- err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- }
- if !info.IsDir() {
- size += info.Size()
- }
- return err
- })
- return size, err
-}
-
func main() {
// Create the main window
window = giu.NewMasterWindow("Picocrypt "+version[1:], 318, 507, giu.MasterWindowFlagsNotResizable)
From 99a04de2633eceac886414282202b5a59edd5961 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Thu, 17 Apr 2025 23:46:35 -0400
Subject: [PATCH 24/46] oops remove debug print statement
---
src/Picocrypt.go | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index e27631f..6f28c92 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -803,7 +803,6 @@ func draw() {
if recombine {
multiplier++
}
- fmt.Println(multiplier, requiredFreeSpace)
giu.Style().SetColor(giu.StyleColorText, WHITE).To(
giu.Label("Ready (ensure " + sizeify(requiredFreeSpace*int64(multiplier)) + " of disk space is free)"),
).Build()
From 7a1d105a432603866f0d1468f2d175e65ff5b6bc Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Thu, 17 Apr 2025 23:52:22 -0400
Subject: [PATCH 25/46] also increase multiplier if auto unzip
---
src/Picocrypt.go | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 6f28c92..3994195 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -803,6 +803,9 @@ func draw() {
if recombine {
multiplier++
}
+ if autoUnzip {
+ multiplier++
+ }
giu.Style().SetColor(giu.StyleColorText, WHITE).To(
giu.Label("Ready (ensure " + sizeify(requiredFreeSpace*int64(multiplier)) + " of disk space is free)"),
).Build()
From 6140e2beb6bc79b1cfc0fb33253493ff47b54ea6 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 00:06:01 -0400
Subject: [PATCH 26/46] change tooltips for deniability and recursively to a
warning
Average user should never need to use these options. Better warn them against it or at least to read the README about the features to understand what they do. These two options can cause funky/unexpected/unintuitive behaviour unless the user understands what they do.
---
src/Picocrypt.go | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 3994195..49addba 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -651,13 +651,13 @@ func draw() {
giu.Row(
giu.Checkbox("Deniability", &deniability),
- giu.Tooltip("Add plausible deniability to the volume\nIf enabled, comments will not be usable"),
+ giu.Tooltip("Warning: only use this if you know what it does!"),
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"),
+ giu.Tooltip("Warning: only use this if you know what it does!"),
),
).Build()
@@ -690,7 +690,7 @@ func draw() {
sameLevel = false
}
}),
- giu.Tooltip("Extract .zip upon decryption (may overwrite)"),
+ giu.Tooltip("Extract .zip upon decryption (may overwrite files)"),
),
giu.Dummy(-170, 0),
giu.Style().SetDisabled(!autoUnzip).To(
@@ -807,7 +807,7 @@ func draw() {
multiplier++
}
giu.Style().SetColor(giu.StyleColorText, WHITE).To(
- giu.Label("Ready (ensure " + sizeify(requiredFreeSpace*int64(multiplier)) + " of disk space is free)"),
+ giu.Label("Ready (ensure >" + sizeify(requiredFreeSpace*int64(multiplier)) + " of disk space is free)"),
).Build()
} else {
giu.Style().SetColor(giu.StyleColorText, WHITE).To(
From df5ef8e9ee7c57d87732fa16e6f2ca735635d565 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 02:03:10 -0400
Subject: [PATCH 27/46] add TODO to .gitignore
I store some TODOs locally and don't want them in vc
---
.gitignore | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.gitignore b/.gitignore
index 6f6f5e6..e06e4dc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,5 @@
# Go workspace file
go.work
go.work.sum
+
+TODO
From 397ff8a9f982047e12e218a9855da265ae431c15 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 13:42:44 -0400
Subject: [PATCH 28/46] Update Changelog.md
---
Changelog.md | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/Changelog.md b/Changelog.md
index 87ac649..1adf30a 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,9 +1,4 @@
-# Future
-
- - Figure out how to remove use of temporary files completely
-
-
-# v1.48 (Released 04/15/2025)
+# v1.48 (Released 04/18/2025)
- ✓ Allow pressing 'Enter' key to press Start/Process button
- ✓ Update "Encrypt" button to "Zip and Encrypt" if multiple files
@@ -12,6 +7,7 @@
- ✓ Add `.incomplete` to filenames while work is in progress
- ✓ Use `encrypted-*.zip.pcv` output name instead of `Encrypted.zip.pcv`
- ✓ Use 0700 permissions when auto unzipping and creating folders
+ - ✓ Handle many more errors in the code where they were ignored previously
# v1.47 (Released 02/19/2025)
From a8dcc6ffbda5616d14471226bdf17b1eae7cc127 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 13:44:16 -0400
Subject: [PATCH 29/46] Update io.github.picocrypt.Picocrypt.metainfo.xml
---
.../io.github.picocrypt.Picocrypt.metainfo.xml | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml b/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml
index b304d69..a1b3a5d 100644
--- a/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml
+++ b/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml
@@ -44,17 +44,20 @@
-
- https://github.com/Picocrypt/Picocrypt/blob/main/Changelog.md#v146-released-01292025
+
+ https://github.com/Picocrypt/Picocrypt/blob/main/Changelog.md#v148-released-04182025
- - Added Picocrypt version to the window title.
- - Added ability to automatically unpack zip archives during decryption.
+ - Allow pressing 'Enter' key to press Start/Process button
+ - Update "Encrypt" button to "Zip and Encrypt" if multiple files
+ - Give user estimated required free disk space in status label
+ - Encrypt previously unencrypted temporary zip files
+ - Add `.incomplete` to filenames while work is in progress
+ - Use `encrypted-*.zip.pcv` output name instead of `Encrypted.zip.pcv`
+ - Use 0700 permissions when auto unzipping and creating folders
+ - Handle many more errors in the code where they were ignored previously
-
-
-
From 66693564f15bf25797b49c47071036c63bdd759e Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 14:03:37 -0400
Subject: [PATCH 30/46] remove description from flatpak; go to gh repo for more
info
---
...io.github.picocrypt.Picocrypt.metainfo.xml | 20 +------------------
1 file changed, 1 insertion(+), 19 deletions(-)
diff --git a/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml b/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml
index a1b3a5d..fd76ad0 100644
--- a/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml
+++ b/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml
@@ -11,31 +11,13 @@
https://github.com/Picocrypt/Picocrypt
https://github.com/Picocrypt/Picocrypt/issues
-
-
- Picocrypt is a very small (hence Pico), very simple, yet very secure encryption tool that you can use to protect your files. It is designed to be the go-to tool for encryption, with a focus on security, simplicity, and reliability. Picocrypt uses the secure XChaCha20 cipher and the Argon2id key derivation function to provide a high level of security, even from three-letter agencies like the NSA. Your privacy and security is under attack. Take it back with confidence by protecting your files with Picocrypt. For more info, please visit the project's GitHub repository.
-
- A list of features:
-
- - Password generator: Picocrypt provides a secure password generator that you can use to create cryptographically secure passwords. You can customize the password length, as well as the types of characters to include.
- - Comments: Use this to store notes, information, and text along with the file (it won't be encrypted). For example, you can put a description of the file you're encrypting before sending it to someone. When the person you sent it to drops the file into Picocrypt, your description will be shown to that person. Comments are not authenticated, meaning it can be freely modified by an attacker. Thus, it should only be used for informational purposes in trusted environments.
- - Keyfiles: Picocrypt supports the use of keyfiles as an additional form of authentication (or the only form of authentication). It also provides a secure built-in keyfile generator.
- - Paranoid mode: Using this mode will encrypt your data with both XChaCha20 and Serpent in a cascade fashion, and use HMAC-SHA3 to authenticate data instead of BLAKE2b. Argon2 parameters will be increased significantly as well.
- - Reed-Solomon: This feature is very useful if you are planning to archive important data on a cloud provider or external medium for a long time. If checked, Picocrypt will use the Reed-Solomon error correction code to add 8 extra bytes for every 128 bytes of data to prevent file corruption. This means that up to ~3% of your file can corrupt and Picocrypt will still be able to correct the errors and decrypt your files with no corruption.
- - Force decrypt: Picocrypt automatically checks for file integrity upon decryption. If the file has been modified or is corrupted, Picocrypt will automatically delete the output for the user's safety. If you would like to override these safeguards, check this option. Also, if this option is checked and the Reed-Solomon feature was used on the encrypted volume, Picocrypt will attempt to recover as much of the file as possible during decryption.
- - Split into chunks: With Picocrypt, you can choose to split your output file into custom-sized chunks, so large files can become more manageable and easier to upload to cloud providers. Simply choose a unit (KiB, MiB, GiB, or TiB) and enter your desired chunk size for that unit. To decrypt the chunks, simply drag one of them into Picocrypt and the chunks will be automatically recombined during decryption.
- - Compress files: By default, Picocrypt uses a zip file with no compression to quickly merge files together when encrypting multiple files. If you would like to compress these files, however, simply check this box and the standard Deflate compression algorithm will be applied during encryption.
- - Deniability: Picocrypt volumes typically follow an easily recognizable header format. However, if you want to hide the fact that you are encrypting your files, enabling this option will provide you with plausible deniability. The output volume will indistinguishable from a stream of random bytes, and no one can prove it is a volume without the correct password.
- - Recursively: If you want to encrypt and/or decrypt a large set of files individually, this option will tell Picocrypt to go through every recursive file that you drop in and encrypt/decrypt it separately. This is useful, for example, if you are encrypting thousands of large documents and want to be able to decrypt any one of them in particular without having to download and decrypt the entire set of documents.
-
+ Visit the project's GitHub repository to learn more.
-
Utility
Security
-
io.github.picocrypt.Picocrypt.desktop
From 475e89395fff7b970249b8b71deb8a23cac549fe Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 14:08:10 -0400
Subject: [PATCH 31/46] flatpak metainfo.xml: point to latest screenshot.png
---
dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml b/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml
index fd76ad0..05ebab2 100644
--- a/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml
+++ b/dist/flatpak/io.github.picocrypt.Picocrypt.metainfo.xml
@@ -21,7 +21,7 @@
io.github.picocrypt.Picocrypt.desktop
- https://github.com/Picocrypt/Picocrypt/raw/4d4bd47efe88ff25f372db81c4249920d399226b/images/screenshot.png
+ https://raw.githubusercontent.com/Picocrypt/Picocrypt/refs/heads/main/images/screenshot.png
Main window
From 693d0c4754ea2fa5a4315ae7cb8179283cf33fd1 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 14:30:44 -0400
Subject: [PATCH 32/46] handle more errors
---
src/Picocrypt.go | 30 ++++++++++++++++++------------
1 file changed, 18 insertions(+), 12 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 49addba..4e4f1ca 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -48,10 +48,11 @@ import (
)
// Constants
-var KiB = 1 << 10
-var MiB = 1 << 20
-var GiB = 1 << 30
-var TiB = 1 << 40
+const KiB = 1 << 10
+const MiB = 1 << 20
+const GiB = 1 << 30
+const 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}
@@ -141,13 +142,13 @@ var eta string
var canCancel bool
// Reed-Solomon encoders
-var rs1, _ = infectious.NewFEC(1, 3)
-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)
-var rs128, _ = infectious.NewFEC(128, 136)
+var rs1, rsErr1 = infectious.NewFEC(1, 3)
+var rs5, rsErr2 = infectious.NewFEC(5, 15)
+var rs16, rsErr3 = infectious.NewFEC(16, 48)
+var rs24, rsErr4 = infectious.NewFEC(24, 72)
+var rs32, rsErr5 = infectious.NewFEC(32, 96)
+var rs64, rsErr6 = infectious.NewFEC(64, 192)
+var rs128, rsErr7 = infectious.NewFEC(128, 136)
var fastDecode bool
// Compression variables and passthrough
@@ -197,7 +198,9 @@ func (ezr *encryptedZipReader) Read(data []byte) (n int, err error) {
if err == nil && n > 0 {
dst := make([]byte, n)
ezr._cipher.XORKeyStream(dst, src[:n])
- copy(data, dst)
+ if copy(data, dst) != n {
+ panic(errors.New("built-in copy() function failed"))
+ }
}
return n, err
}
@@ -2570,6 +2573,9 @@ func unpackArchive(zipPath string) error {
}
func main() {
+ if rsErr1 != nil || rsErr2 != nil || rsErr3 != nil || rsErr4 != nil || rsErr5 != nil || rsErr6 != nil || rsErr7 != nil {
+ panic(errors.New("rs failed to init"))
+ }
// Create the main window
window = giu.NewMasterWindow("Picocrypt "+version[1:], 318, 507, giu.MasterWindowFlagsNotResizable)
From e72f687ae9a44de791f4078e0015ea1626f4818b Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 14:52:36 -0400
Subject: [PATCH 33/46] handle more errors, hide main modal title bar
---
src/Picocrypt.go | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 4e4f1ca..f62ccab 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -205,7 +205,7 @@ func (ezr *encryptedZipReader) Read(data []byte) (n int, err error) {
return n, err
}
-var onClickStartButton = func() {
+func onClickStartButton() {
// Start button should be disabled if these conditions are true; don't do anything if so
if (len(keyfiles) == 0 && password == "") || (mode == "encrypt" && password != cpassword) {
return
@@ -214,12 +214,14 @@ var onClickStartButton = func() {
if keyfile && keyfiles == nil {
mainStatus = "Please select your keyfiles"
mainStatusColor = RED
+ giu.Update()
return
}
tmp, err := strconv.Atoi(splitSize)
- if split && (splitSize == "" || tmp <= 0 || err != nil) {
+ if split && (splitSize == "" || err != nil || tmp <= 0) {
mainStatus = "Invalid chunk size"
mainStatusColor = RED
+ giu.Update()
return
}
@@ -228,7 +230,10 @@ var onClickStartButton = func() {
// Check if any split chunks already exist
if split {
- names, _ := filepath.Glob(outputFile + ".*")
+ names, err2 := filepath.Glob(outputFile + ".*")
+ if err2 != nil {
+ panic(err2)
+ }
if len(names) > 0 {
err = nil
} else {
@@ -320,7 +325,7 @@ func draw() {
giu.PopupModal("Generate password:##"+strconv.Itoa(modalId)).Flags(6).Layout(
giu.Row(
giu.Label("Length:"),
- giu.SliderInt(&passgenLength, 4, 64).Size(giu.Auto),
+ giu.SliderInt(&passgenLength, 12, 64).Size(giu.Auto),
),
giu.Checkbox("Uppercase", &passgenUpper),
giu.Checkbox("Lowercase", &passgenLower),
@@ -421,7 +426,8 @@ func draw() {
}
if showProgress {
- giu.PopupModal(" ##"+strconv.Itoa(modalId)).Flags(6).Layout(
+ giu.PopupModal("Progress:##"+strconv.Itoa(modalId)).Flags(6|1<<0).Layout(
+ giu.Dummy(0, 0),
giu.Row(
giu.ProgressBar(progress).Size(210, 0).Overlay(progressInfo),
giu.Style().SetDisabled(!canCancel).To(
@@ -438,7 +444,7 @@ func draw() {
),
giu.Label(popupStatus),
).Build()
- giu.OpenPopup(" ##" + strconv.Itoa(modalId))
+ giu.OpenPopup("Progress:##" + strconv.Itoa(modalId))
giu.Update()
}
}),
From 89d461ce9c09d0a0c4122fb04e158474edb6ac1a Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 15:01:37 -0400
Subject: [PATCH 34/46] more error handling around keyfile generator
---
src/Picocrypt.go | 25 +++++++++++++++----------
1 file changed, 15 insertions(+), 10 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index f62ccab..c1ab376 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -587,22 +587,27 @@ func draw() {
}
return filepath.Dir(onlyFolders[0])
}())
- f.SetInitFilename("Keyfile")
+ f.SetInitFilename("keyfile-" + strconv.Itoa(int(time.Now().Unix())) + ".bin")
file, err := f.Save()
if file == "" || err != nil {
return
}
- fout, _ := os.Create(file)
- data := make([]byte, 32)
- if _, err := rand.Read(data); err != nil {
- panic(err)
- }
- _, err = fout.Write(data)
- fout.Close()
+ fout, err := os.Create(file)
if err != nil {
- insufficientSpace(nil, nil)
- os.Remove(file)
+ return
+ }
+ data := make([]byte, 32)
+ if n, err := rand.Read(data); err != nil || n != 32 {
+ panic(errors.New("fatal crypto/rand error"))
+ }
+ n, err := fout.Write(data)
+ if n != 32 {
+ fout.Close()
+ panic(errors.New("failed to write full keyfile"))
+ }
+ if err := fout.Close(); err != nil {
+ panic(err)
} else {
mainStatus = "Ready"
mainStatusColor = WHITE
From 3403cd68a8c6618ef047251363b2da9b685d84ab Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 15:03:26 -0400
Subject: [PATCH 35/46] show keyfile gen errors instead of crashing
---
src/Picocrypt.go | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index c1ab376..a4caeef 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -595,6 +595,9 @@ func draw() {
fout, err := os.Create(file)
if err != nil {
+ mainStatus = "Failed to create keyfile"
+ mainStatusColor = RED
+ giu.Update()
return
}
data := make([]byte, 32)
@@ -611,6 +614,8 @@ func draw() {
} else {
mainStatus = "Ready"
mainStatusColor = WHITE
+ giu.Update()
+ return
}
}),
giu.Tooltip("Generate a cryptographically secure keyfile"),
From a0a7f430e426829213bdba8516fcc4dd39977d4a Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 15:04:28 -0400
Subject: [PATCH 36/46] missing an err != nil check
---
src/Picocrypt.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index a4caeef..5d66c39 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -605,7 +605,7 @@ func draw() {
panic(errors.New("fatal crypto/rand error"))
}
n, err := fout.Write(data)
- if n != 32 {
+ if err != nil || n != 32 {
fout.Close()
panic(errors.New("failed to write full keyfile"))
}
From ca0a74f99d4686b21dac6eb50e16393f702c1599 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 15:33:17 -0400
Subject: [PATCH 37/46] handle more errors
---
src/Picocrypt.go | 114 +++++++++++++++++++++++++++++++++++++----------
1 file changed, 90 insertions(+), 24 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 5d66c39..ceaf848 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -131,7 +131,6 @@ var startLabel = "Start"
var mainStatus = "Ready"
var mainStatusColor = WHITE
var popupStatus string
-var usingTempZip bool
var requiredFreeSpace int64
// Progress variables
@@ -787,6 +786,7 @@ func draw() {
outputFile = file
mainStatus = "Ready"
mainStatusColor = WHITE
+ giu.Update()
}).Build()
giu.Tooltip("Save the output with a custom name and path").Build()
}),
@@ -855,7 +855,7 @@ func onDrop(names []string) {
duplicate = true
}
}
- stat, _ := os.Stat(i)
+ stat, statErr := os.Stat(i)
fin, err := os.Open(i)
if err == nil {
fin.Close()
@@ -866,7 +866,7 @@ func onDrop(names []string) {
giu.Update()
return
}
- if !duplicate && !stat.IsDir() {
+ if !duplicate && statErr == nil && !stat.IsDir() {
tmp = append(tmp, i)
}
}
@@ -897,6 +897,7 @@ func onDrop(names []string) {
if err != nil {
mainStatus = "Failed to stat dropped item"
mainStatusColor = RED
+ giu.Update()
return
}
@@ -965,48 +966,84 @@ func onDrop(names []string) {
if err != nil {
resetUI()
accessDenied("Read")
+ giu.Update()
return
}
// Check if version can be read from header
tmp := make([]byte, 15)
- fin.Read(tmp)
+ if n, err := fin.Read(tmp); err != nil || n != 15 {
+ fin.Close()
+ mainStatus = "Failed to read 15 bytes from file"
+ mainStatusColor = RED
+ giu.Update()
+ return
+ }
tmp, err = rsDecode(rs5, tmp)
- if valid, _ := regexp.Match(`^v\d\.\d{2}`, tmp); !valid || err != nil {
+ if valid, _ := regexp.Match(`^v\d\.\d{2}`, tmp); err != nil || !valid {
// Volume has plausible deniability
deniability = true
mainStatus = "Can't read header, assuming volume is deniable"
fin.Close()
+ giu.Update()
} else {
// Read comments from file and check for corruption
tmp = make([]byte, 15)
- fin.Read(tmp)
+ if n, err := fin.Read(tmp); err != nil || n != 15 {
+ fin.Close()
+ mainStatus = "Failed to read 15 bytes from file"
+ mainStatusColor = RED
+ giu.Update()
+ return
+ }
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
+ commentsLength, err := strconv.Atoi(string(tmp))
+ if err != nil {
+ comments = "Comment length is corrupted"
+ giu.Update()
+ } else {
+ tmp = make([]byte, commentsLength*3)
+ if n, err := fin.Read(tmp); err != nil || n != commentsLength*3 {
+ fin.Close()
+ mainStatus = "Failed to read comments from file"
+ mainStatusColor = RED
+ giu.Update()
+ return
}
- comments += string(t)
+ 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)
+ }
+ giu.Update()
}
} else {
comments = "Comments are corrupted"
+ giu.Update()
}
// Read flags from file and check for corruption
flags := make([]byte, 15)
- fin.Read(flags)
- fin.Close()
+ if n, err := fin.Read(flags); err != nil || n != 15 {
+ fin.Close()
+ mainStatus = "Failed to read 15 bytes from file"
+ mainStatusColor = RED
+ giu.Update()
+ return
+ }
+ if err := fin.Close(); err != nil {
+ panic(err)
+ }
flags, err = rsDecode(rs5, flags)
if err != nil {
mainStatus = "The volume header is damaged"
mainStatusColor = RED
+ giu.Update()
return
}
@@ -1020,6 +1057,7 @@ func onDrop(names []string) {
if flags[2] == 1 {
keyfileOrdered = true
}
+ giu.Update()
}
} else { // One file was dropped for encryption
mode = "encrypt"
@@ -1027,6 +1065,7 @@ func onDrop(names []string) {
startLabel = "Encrypt"
inputFile = names[0]
outputFile = names[0] + ".pcv"
+ giu.Update()
}
// Add the file
@@ -1035,6 +1074,7 @@ func onDrop(names []string) {
if !isSplit {
compressTotal += stat.Size()
}
+ giu.Update()
}
} else { // There are multiple dropped items
mode = "encrypt"
@@ -1042,7 +1082,14 @@ func onDrop(names []string) {
// Go through each dropped item and add to corresponding slices
for _, name := range names {
- stat, _ := os.Stat(name)
+ stat, err := os.Stat(name)
+ if err != nil {
+ resetUI()
+ mainStatus = "Failed to stat dropped items"
+ mainStatusColor = RED
+ giu.Update()
+ return
+ }
if stat.IsDir() {
folders++
onlyFolders = append(onlyFolders, name)
@@ -1078,17 +1125,31 @@ func onDrop(names []string) {
// Set the input and output paths
inputFile = filepath.Join(filepath.Dir(names[0]), "encrypted-"+strconv.Itoa(int(time.Now().Unix()))) + ".zip"
outputFile = inputFile + ".pcv"
- usingTempZip = true
+ giu.Update()
}
// Recursively add all files in 'onlyFolders' to 'allFiles'
go func() {
oldInputLabel := inputLabel
for _, name := range onlyFolders {
- filepath.Walk(name, func(path string, _ os.FileInfo, _ error) error {
+ if filepath.Walk(name, func(path string, _ os.FileInfo, err error) error {
+ if err != nil {
+ resetUI()
+ mainStatus = "Failed to walk through dropped items"
+ mainStatusColor = RED
+ giu.Update()
+ return err
+ }
stat, err := os.Stat(path)
+ if err != nil {
+ resetUI()
+ mainStatus = "Failed to walk through dropped items"
+ mainStatusColor = RED
+ giu.Update()
+ return err
+ }
// If 'path' is a valid file path, add to 'allFiles'
- if err == nil && !stat.IsDir() {
+ if !stat.IsDir() {
allFiles = append(allFiles, path)
compressTotal += stat.Size()
requiredFreeSpace += stat.Size()
@@ -1096,7 +1157,13 @@ func onDrop(names []string) {
giu.Update()
}
return nil
- })
+ }) != nil {
+ resetUI()
+ mainStatus = "Failed to walk through dropped items"
+ mainStatusColor = RED
+ giu.Update()
+ return
+ }
}
inputLabel = fmt.Sprintf("%s (%s)", oldInputLabel, sizeify(compressTotal))
scanning = false
@@ -2375,7 +2442,6 @@ func resetUI() {
mainStatus = "Ready"
mainStatusColor = WHITE
popupStatus = ""
- usingTempZip = false
requiredFreeSpace = 0
progress = 0
From 22a1118f0165584873207cdc91e4d77afb868d3a Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 15:42:42 -0400
Subject: [PATCH 38/46] catch temporary encrypted zip errors
---
src/Picocrypt.go | 22 +++++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index ceaf848..b600eef 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -1194,12 +1194,24 @@ func work() {
var tempZipCipherW *chacha20.Cipher
var tempZipCipherR *chacha20.Cipher
var tempZipInUse bool = false
- func() {
+ func() { // enclose to keep out of parent scope
key, nonce := make([]byte, 32), make([]byte, 12)
- rand.Read(key)
- rand.Read(nonce)
- tempZipCipherW, _ = chacha20.NewUnauthenticatedCipher(key, nonce)
- tempZipCipherR, _ = chacha20.NewUnauthenticatedCipher(key, nonce)
+ if n, err := rand.Read(key); err != nil || n != 32 {
+ panic(errors.New("fatal crypto/rand error"))
+ }
+ if n, err := rand.Read(nonce); err != nil || n != 12 {
+ panic(errors.New("fatal crypto/rand error"))
+ }
+ if bytes.Equal(key, make([]byte, 32)) || bytes.Equal(nonce, make([]byte, 12)) {
+ panic(errors.New("fatal crypto/rand error")) // this should never happen but be safe
+ }
+ var errW error
+ var errR error
+ tempZipCipherW, errW = chacha20.NewUnauthenticatedCipher(key, nonce)
+ tempZipCipherR, errR = chacha20.NewUnauthenticatedCipher(key, nonce)
+ if errW != nil || errR != nil {
+ panic(errors.New("fatal chacha20 init error"))
+ }
}()
// Combine/compress all files into a .zip file if needed
From 7a28e2b2737fcc297dcacc4a3da58cf577ddd0ca Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 15:54:26 -0400
Subject: [PATCH 39/46] handle more errors in temp zip code
---
src/Picocrypt.go | 38 +++++++++++++++++++++++++++++++++-----
1 file changed, 33 insertions(+), 5 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index b600eef..b43020c 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -1253,9 +1253,24 @@ func work() {
// Create file info header (size, last modified, etc.)
stat, err := os.Stat(path)
if err != nil {
- continue // Skip temporary and inaccessible files
+ writer.Close()
+ file.Close()
+ os.Remove(inputFile)
+ resetUI()
+ mainStatus = "Failed to stat input files"
+ mainStatusColor = RED
+ return
+ }
+ header, err := zip.FileInfoHeader(stat)
+ if err != nil {
+ writer.Close()
+ file.Close()
+ os.Remove(inputFile)
+ resetUI()
+ mainStatus = "Failed to create zip.FileInfoHeader"
+ mainStatusColor = RED
+ return
}
- header, _ := zip.FileInfoHeader(stat)
header.Name = strings.TrimPrefix(path, rootDir)
header.Name = filepath.ToSlash(header.Name)
header.Name = strings.TrimPrefix(header.Name, "/")
@@ -1267,7 +1282,16 @@ func work() {
}
// Open the file for reading
- entry, _ := writer.CreateHeader(header)
+ entry, err := writer.CreateHeader(header)
+ if err != nil {
+ writer.Close()
+ file.Close()
+ os.Remove(inputFile)
+ resetUI()
+ mainStatus = "Failed to writer.CreateHeader"
+ mainStatusColor = RED
+ return
+ }
fin, err := os.Open(path)
if err != nil {
writer.Close()
@@ -1298,8 +1322,12 @@ func work() {
return
}
}
- writer.Close()
- file.Close()
+ if err := writer.Close(); err != nil {
+ panic(err)
+ }
+ if err := file.Close(); err != nil {
+ panic(err)
+ }
}
// Recombine a split file if necessary
From d0e4e71b973a137102d87dd2e8a78c15469bb474 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 15:59:04 -0400
Subject: [PATCH 40/46] handle more errors in recombine code
---
src/Picocrypt.go | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index b43020c..a95611c 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -1363,7 +1363,7 @@ func work() {
// Merge all chunks into one file
startTime := time.Now()
- for i := 0; i < totalFiles; i++ {
+ for i := range totalFiles {
fin, err := os.Open(fmt.Sprintf("%s.%d", inputFile, i))
if err != nil {
fout.Close()
@@ -1387,10 +1387,11 @@ func work() {
break
}
data = data[:read]
- _, err = fout.Write(data)
+ var n int
+ n, err = fout.Write(data)
done += read
- if err != nil {
+ if err != nil || n != len(data) {
insufficientSpace(fin, fout)
os.Remove(outputFile + ".pcv")
return
@@ -1402,9 +1403,13 @@ func work() {
popupStatus = fmt.Sprintf("Recombining at %.2f MiB/s (ETA: %s)", speed, eta)
giu.Update()
}
- fin.Close()
+ if err := fin.Close(); err != nil {
+ panic(err)
+ }
+ }
+ if err := fout.Close(); err != nil {
+ panic(err)
}
- fout.Close()
inputFileOld = inputFile
inputFile = outputFile + ".pcv"
}
From 1b55200b848b0366ee31dc12919615e1b2e55add Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 16:13:12 -0400
Subject: [PATCH 41/46] handle more errors in deniability decrypt block
---
src/Picocrypt.go | 70 +++++++++++++++++++++++++++++++++++++-----------
1 file changed, 54 insertions(+), 16 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index a95611c..19bf1a4 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -1423,26 +1423,44 @@ func work() {
giu.Update()
// Get size of volume for showing progress
- stat, _ := os.Stat(inputFile)
+ stat, err := os.Stat(inputFile)
+ if err != nil {
+ // we already read from inputFile successfully in onDrop
+ // so it is very unlikely this err != nil, we can just panic
+ panic(err)
+ }
total := stat.Size()
// Rename input volume to free up the filename
- fin, _ := os.Open(inputFile)
+ fin, err := os.Open(inputFile)
+ if err != nil {
+ panic(err)
+ }
for strings.HasSuffix(inputFile, ".tmp") {
inputFile = strings.TrimSuffix(inputFile, ".tmp")
}
inputFile += ".tmp"
- fout, _ := os.Create(inputFile)
+ fout, err := os.Create(inputFile)
+ if err != nil {
+ panic(err)
+ }
// Get the Argon2 salt and XChaCha20 nonce from input volume
salt := make([]byte, 16)
nonce := make([]byte, 24)
- fin.Read(salt)
- fin.Read(nonce)
+ if n, err := fin.Read(salt); err != nil || n != 16 {
+ panic(errors.New("failed to read 16 bytes from file"))
+ }
+ if n, err := fin.Read(nonce); err != nil || n != 24 {
+ panic(errors.New("failed to read 24 bytes from file"))
+ }
// Generate key and XChaCha20
key := argon2.IDKey([]byte(password), salt, 4, 1<<20, 4, 32)
- chacha, _ := chacha20.NewUnauthenticatedCipher(key, nonce)
+ chacha, err := chacha20.NewUnauthenticatedCipher(key, nonce)
+ if err != nil {
+ panic(err)
+ }
// Decrypt the entire volume
done, counter := 0, 0
@@ -1455,7 +1473,11 @@ func work() {
src = src[:size]
dst := make([]byte, len(src))
chacha.XORKeyStream(dst, src)
- fout.Write(dst)
+ if n, err := fout.Write(dst); err != nil || n != len(dst) {
+ fout.Close()
+ os.Remove(fout.Name())
+ panic(errors.New("failed to write dst"))
+ }
// Update stats
done += size
@@ -1466,23 +1488,39 @@ func work() {
// Change nonce after 60 GiB to prevent overflow
if counter >= 60*GiB {
tmp := sha3.New256()
- tmp.Write(nonce)
+ if n, err := tmp.Write(nonce); err != nil || n != len(nonce) {
+ panic(errors.New("failed to write nonce to tmp during rekeying"))
+ }
nonce = tmp.Sum(nil)[:24]
- chacha, _ = chacha20.NewUnauthenticatedCipher(key, nonce)
+ chacha, err = chacha20.NewUnauthenticatedCipher(key, nonce)
+ if err != nil {
+ panic(err)
+ }
counter = 0
}
}
- fin.Close()
- fout.Close()
+ if err := fin.Close(); err != nil {
+ panic(err)
+ }
+ if err := fout.Close(); err != nil {
+ panic(err)
+ }
// Check if the version can be read from the volume
- fin, _ = os.Open(inputFile)
+ fin, err = os.Open(inputFile)
+ if err != nil {
+ panic(err)
+ }
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 {
+ if n, err := fin.Read(tmp); err != nil || n != 15 {
+ panic(errors.New("failed to read 15 bytes from file"))
+ }
+ if err := fin.Close(); err != nil {
+ panic(err)
+ }
+ tmp, err = rsDecode(rs5, tmp)
+ if valid, _ := regexp.Match(`^v1\.\d{2}`, tmp); err != nil || !valid {
os.Remove(inputFile)
inputFile = strings.TrimSuffix(inputFile, ".tmp")
broken(nil, nil, "Password is incorrect or the file is not a volume", true)
From b0c2943bb038d00a67394437b1f36a2e877f0883 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 16:29:42 -0400
Subject: [PATCH 42/46] handle more errors for keyfile code
---
src/Picocrypt.go | 70 +++++++++++++++++++++++++++++++++++++++---------
1 file changed, 58 insertions(+), 12 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 19bf1a4..53972c5 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -1537,7 +1537,12 @@ func work() {
giu.Update()
// Subtract the header size from the total size if decrypting
- stat, _ := os.Stat(inputFile)
+ stat, err := os.Stat(inputFile)
+ if err != nil {
+ resetUI()
+ accessDenied("Read")
+ return
+ }
total := stat.Size()
if mode == "decrypt" {
total -= 789
@@ -1642,6 +1647,18 @@ func work() {
if _, err := rand.Read(nonce); err != nil {
panic(err)
}
+ if bytes.Equal(salt, make([]byte, 16)) {
+ panic(errors.New("fatal crypto/rand error"))
+ }
+ if bytes.Equal(hkdfSalt, make([]byte, 32)) {
+ panic(errors.New("fatal crypto/rand error"))
+ }
+ if bytes.Equal(serpentIV, make([]byte, 16)) {
+ panic(errors.New("fatal crypto/rand error"))
+ }
+ if bytes.Equal(nonce, make([]byte, 24)) {
+ panic(errors.New("fatal crypto/rand error"))
+ }
// Encode values with Reed-Solomon and write to file
_, errs[4] = fout.Write(rsEncode(rs16, salt))
@@ -1764,6 +1781,9 @@ func work() {
32,
)
}
+ if bytes.Equal(key, make([]byte, 32)) {
+ panic(errors.New("fatal crypto/argon2 error"))
+ }
// If keyfiles are being used
if len(keyfiles) > 0 || keyfile {
@@ -1772,7 +1792,10 @@ func work() {
var keyfileTotal int64
for _, path := range keyfiles {
- stat, _ := os.Stat(path)
+ stat, err := os.Stat(path)
+ if err != nil {
+ panic(err) // we already checked os.Stat in onDrop
+ }
keyfileTotal += stat.Size()
}
@@ -1782,7 +1805,10 @@ func work() {
// For each keyfile...
for _, path := range keyfiles {
- fin, _ := os.Open(path)
+ fin, err := os.Open(path)
+ if err != nil {
+ panic(err)
+ }
for { // Read in chunks of 1 MiB
data := make([]byte, MiB)
size, err := fin.Read(data)
@@ -1790,27 +1816,36 @@ func work() {
break
}
data = data[:size]
- tmp.Write(data) // Hash the data
+ if _, err := tmp.Write(data); err != nil { // Hash the data
+ panic(err)
+ }
// Update progress
keyfileDone += size
progress = float32(keyfileDone) / float32(keyfileTotal)
giu.Update()
}
- fin.Close()
+ if err := fin.Close(); err != nil {
+ panic(err)
+ }
}
keyfileKey = tmp.Sum(nil) // Get the SHA3-256
// Store a hash of 'keyfileKey' for comparison
tmp = sha3.New256()
- tmp.Write(keyfileKey)
+ if _, err := tmp.Write(keyfileKey); err != nil {
+ panic(err)
+ }
keyfileHash = tmp.Sum(nil)
} else { // If order doesn't matter, hash individually and combine
var keyfileDone int
// For each keyfile...
for _, path := range keyfiles {
- fin, _ := os.Open(path)
+ fin, err := os.Open(path)
+ if err != nil {
+ panic(err)
+ }
tmp := sha3.New256()
for { // Read in chunks of 1 MiB
data := make([]byte, MiB)
@@ -1819,14 +1854,18 @@ func work() {
break
}
data = data[:size]
- tmp.Write(data) // Hash the data
+ if _, err := tmp.Write(data); err != nil { // Hash the data
+ panic(err)
+ }
// Update progress
keyfileDone += size
progress = float32(keyfileDone) / float32(keyfileTotal)
giu.Update()
}
- fin.Close()
+ if err := fin.Close(); err != nil {
+ panic(err)
+ }
sum := tmp.Sum(nil) // Get the SHA3-256
@@ -1842,7 +1881,9 @@ func work() {
// Store a hash of 'keyfileKey' for comparison
tmp := sha3.New256()
- tmp.Write(keyfileKey)
+ if _, err := tmp.Write(keyfileKey); err != nil {
+ panic(err)
+ }
keyfileHash = tmp.Sum(nil)
}
}
@@ -1852,7 +1893,9 @@ func work() {
// Hash the encryption key for comparison when decrypting
tmp := sha3.New512()
- tmp.Write(key)
+ if _, err := tmp.Write(key); err != nil {
+ panic(err)
+ }
keyHash = tmp.Sum(nil)
// Validate the password and/or keyfiles
@@ -1926,7 +1969,10 @@ func work() {
}
done, counter := 0, 0
- chacha, _ := chacha20.NewUnauthenticatedCipher(key, nonce)
+ chacha, err := chacha20.NewUnauthenticatedCipher(key, nonce)
+ if err != nil {
+ panic(err)
+ }
// Use HKDF-SHA3 to generate a subkey for the MAC
var mac hash.Hash
From 0cc109bc6b53eb4474cbbb4a5b5ca31f74e94760 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 16:48:29 -0400
Subject: [PATCH 43/46] handle more errors
---
src/Picocrypt.go | 219 ++++++++++++++++++++++++++++++++++++-----------
1 file changed, 167 insertions(+), 52 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 53972c5..4872bd5 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -1978,17 +1978,27 @@ func work() {
var mac hash.Hash
subkey := make([]byte, 32)
hkdf := hkdf.New(sha3.New256, key, hkdfSalt, nil)
- hkdf.Read(subkey)
+ if n, err := hkdf.Read(subkey); err != nil || n != 32 {
+ panic(errors.New("fatal hkdf.Read error"))
+ }
if paranoid {
mac = hmac.New(sha3.New512, subkey) // HMAC-SHA3
} else {
- mac, _ = blake2b.New512(subkey) // Keyed BLAKE2b
+ mac, err = blake2b.New512(subkey) // Keyed BLAKE2b
+ if err != nil {
+ panic(err)
+ }
}
// Generate another subkey for use as Serpent's key
serpentKey := make([]byte, 32)
- hkdf.Read(serpentKey)
- s, _ := serpent.NewCipher(serpentKey)
+ if n, err := hkdf.Read(serpentKey); err != nil || n != 32 {
+ panic(errors.New("fatal hkdf.Read error"))
+ }
+ s, err := serpent.NewCipher(serpentKey)
+ if err != nil {
+ panic(err)
+ }
serpent := cipher.NewCTR(s, serpentIV)
// Start the main encryption process
@@ -2036,7 +2046,9 @@ func work() {
}
chacha.XORKeyStream(dst, src)
- mac.Write(dst)
+ if _, err := mac.Write(dst); err != nil {
+ panic(err)
+ }
if reedsolo {
copy(src, dst)
@@ -2090,7 +2102,7 @@ func work() {
} else {
// Decode the full chunks
chunks := len(dst)/136 - 1
- for i := 0; i < chunks; i++ {
+ for i := range chunks {
tmp, err := rsDecode(rs128, dst[i*136:(i+1)*136])
if err != nil {
if keep {
@@ -2125,7 +2137,9 @@ func work() {
dst = make([]byte, len(src))
}
- mac.Write(src)
+ if _, err := mac.Write(src); err != nil {
+ panic(err)
+ }
chacha.XORKeyStream(dst, src)
if paranoid {
@@ -2167,12 +2181,19 @@ func work() {
if counter >= 60*GiB {
// ChaCha20
nonce = make([]byte, 24)
- hkdf.Read(nonce)
- chacha, _ = chacha20.NewUnauthenticatedCipher(key, nonce)
+ if n, err := hkdf.Read(nonce); err != nil || n != 24 {
+ panic(errors.New("fatal hkdf.Read error"))
+ }
+ chacha, err = chacha20.NewUnauthenticatedCipher(key, nonce)
+ if err != nil {
+ panic(err)
+ }
// Serpent
serpentIV = make([]byte, 16)
- hkdf.Read(serpentIV)
+ if n, err := hkdf.Read(serpentIV); err != nil || n != 16 {
+ panic(errors.New("fatal hkdf.Read error"))
+ }
serpent = cipher.NewCTR(s, serpentIV)
// Reset counter to 0
@@ -2189,10 +2210,18 @@ func work() {
giu.Update()
// Seek back to header and write important values
- fout.Seek(int64(309+len(comments)*3), 0)
- fout.Write(rsEncode(rs64, keyHash))
- fout.Write(rsEncode(rs32, keyfileHash))
- fout.Write(rsEncode(rs64, mac.Sum(nil)))
+ if _, err := fout.Seek(int64(309+len(comments)*3), 0); err != nil {
+ panic(err)
+ }
+ if _, err := fout.Write(rsEncode(rs64, keyHash)); err != nil {
+ panic(err)
+ }
+ if _, err := fout.Write(rsEncode(rs32, keyfileHash)); err != nil {
+ panic(err)
+ }
+ if _, err := fout.Write(rsEncode(rs64, mac.Sum(nil))); err != nil {
+ panic(err)
+ }
} else {
popupStatus = "Comparing values..."
giu.Update()
@@ -2217,10 +2246,16 @@ func work() {
}
}
- fin.Close()
- fout.Close()
+ if err := fin.Close(); err != nil {
+ panic(err)
+ }
+ if err := fout.Close(); err != nil {
+ panic(err)
+ }
- os.Rename(outputFile+".incomplete", outputFile)
+ if err := os.Rename(outputFile+".incomplete", outputFile); err != nil {
+ panic(err)
+ }
// Add plausible deniability
if mode == "encrypt" && deniability {
@@ -2229,29 +2264,51 @@ func work() {
giu.Update()
// Get size of volume for showing progress
- stat, _ := os.Stat(outputFile)
+ stat, err := os.Stat(outputFile)
+ if err != nil {
+ panic(err)
+ }
total := stat.Size()
// Rename the output volume to free up the filename
os.Rename(outputFile, outputFile+".tmp")
- fin, _ := os.Open(outputFile + ".tmp")
- fout, _ := os.Create(outputFile + ".incomplete")
+ fin, err := os.Open(outputFile + ".tmp")
+ if err != nil {
+ panic(err)
+ }
+ fout, err := os.Create(outputFile + ".incomplete")
+ if err != nil {
+ panic(err)
+ }
// Use a random Argon2 salt and XChaCha20 nonce
salt := make([]byte, 16)
nonce := make([]byte, 24)
- if _, err := rand.Read(salt); err != nil {
+ if n, err := rand.Read(salt); err != nil || n != 16 {
+ panic(errors.New("fatal crypto/rand error"))
+ }
+ if n, err := rand.Read(nonce); err != nil || n != 24 {
+ panic(errors.New("fatal crypto/rand error"))
+ }
+ if bytes.Equal(salt, make([]byte, 16)) || bytes.Equal(nonce, make([]byte, 24)) {
+ panic(errors.New("fatal crypto/rand error"))
+ }
+ if _, err := fout.Write(salt); err != nil {
panic(err)
}
- if _, err := rand.Read(nonce); err != nil {
+ if _, err := fout.Write(nonce); err != nil {
panic(err)
}
- 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)
+ if bytes.Equal(key, make([]byte, 32)) {
+ panic(errors.New("fatal crypto/argon2 error"))
+ }
+ chacha, err := chacha20.NewUnauthenticatedCipher(key, nonce)
+ if err != nil {
+ panic(err)
+ }
// Encrypt the entire volume
done, counter := 0, 0
@@ -2264,7 +2321,9 @@ func work() {
src = src[:size]
dst := make([]byte, len(src))
chacha.XORKeyStream(dst, src)
- fout.Write(dst)
+ if _, err := fout.Write(dst); err != nil {
+ panic(err)
+ }
// Update stats
done += size
@@ -2275,17 +2334,30 @@ func work() {
// Change nonce after 60 GiB to prevent overflow
if counter >= 60*GiB {
tmp := sha3.New256()
- tmp.Write(nonce)
+ if _, err := tmp.Write(nonce); err != nil {
+ panic(err)
+ }
nonce = tmp.Sum(nil)[:24]
- chacha, _ = chacha20.NewUnauthenticatedCipher(key, nonce)
+ chacha, err = chacha20.NewUnauthenticatedCipher(key, nonce)
+ if err != nil {
+ panic(err)
+ }
counter = 0
}
}
- fin.Close()
- fout.Close()
- os.Remove(fin.Name())
- os.Rename(outputFile+".incomplete", outputFile)
+ if err := fin.Close(); err != nil {
+ panic(err)
+ }
+ if err := fout.Close(); err != nil {
+ panic(err)
+ }
+ if err := os.Remove(fin.Name()); err != nil {
+ panic(err)
+ }
+ if err := os.Rename(outputFile+".incomplete", outputFile); err != nil {
+ panic(err)
+ }
canCancel = true
giu.Update()
}
@@ -2293,11 +2365,17 @@ func work() {
// Split the file into chunks
if split {
var splitted []string
- stat, _ := os.Stat(outputFile)
+ stat, err := os.Stat(outputFile)
+ if err != nil {
+ panic(err)
+ }
size := stat.Size()
finishedFiles := 0
finishedBytes := 0
- chunkSize, _ := strconv.Atoi(splitSize)
+ chunkSize, err := strconv.Atoi(splitSize)
+ if err != nil {
+ panic(err)
+ }
// Calculate chunk size
if splitSelected == 0 {
@@ -2318,17 +2396,25 @@ func work() {
giu.Update()
// Open the volume for reading
- fin, _ := os.Open(outputFile)
+ fin, err := os.Open(outputFile)
+ if err != nil {
+ panic(err)
+ }
// Delete existing chunks to prevent mixed chunks
- names, _ := filepath.Glob(outputFile + ".*")
+ names, err := filepath.Glob(outputFile + ".*")
+ if err != nil {
+ panic(err)
+ }
for _, i := range names {
- os.Remove(i)
+ if err := os.Remove(i); err != nil {
+ panic(err)
+ }
}
// Start the splitting process
startTime := time.Now()
- for i := 0; i < chunks; i++ {
+ for i := range chunks {
// Make the chunk
fout, _ := os.Create(fmt.Sprintf("%s.%d.incomplete", outputFile, i))
done := 0
@@ -2382,7 +2468,9 @@ func work() {
popupStatus = fmt.Sprintf("Splitting at %.2f MiB/s (ETA: %s)", speed, eta)
giu.Update()
}
- fout.Close()
+ if err := fout.Close(); err != nil {
+ panic(err)
+ }
// Update stats
finishedFiles++
@@ -2394,11 +2482,20 @@ func work() {
giu.Update()
}
- fin.Close()
- os.Remove(outputFile)
- names, _ = filepath.Glob(outputFile + ".*.incomplete")
+ if err := fin.Close(); err != nil {
+ panic(err)
+ }
+ if err := os.Remove(outputFile); err != nil {
+ panic(err)
+ }
+ names, err = filepath.Glob(outputFile + ".*.incomplete")
+ if err != nil {
+ panic(err)
+ }
for _, i := range names {
- os.Rename(i, strings.TrimSuffix(i, ".incomplete"))
+ if err := os.Rename(i, strings.TrimSuffix(i, ".incomplete")); err != nil {
+ panic(err)
+ }
}
}
@@ -2409,9 +2506,13 @@ func work() {
// Delete temporary files used during encryption and decryption
if recombine || len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
- os.Remove(inputFile)
+ if err := os.Remove(inputFile); err != nil {
+ panic(err)
+ }
if deniability {
- os.Remove(strings.TrimSuffix(inputFile, ".tmp"))
+ if err := os.Remove(strings.TrimSuffix(inputFile, ".tmp")); err != nil {
+ panic(err)
+ }
}
}
@@ -2428,26 +2529,38 @@ func work() {
if err != nil {
break
}
- os.Remove(fmt.Sprintf("%s.%d", inputFileOld, i))
+ if err := os.Remove(fmt.Sprintf("%s.%d", inputFileOld, i)); err != nil {
+ panic(err)
+ }
i++
}
} else {
- os.Remove(inputFile)
+ if err := os.Remove(inputFile); err != nil {
+ panic(err)
+ }
if deniability {
- os.Remove(strings.TrimSuffix(inputFile, ".tmp"))
+ if err := os.Remove(strings.TrimSuffix(inputFile, ".tmp")); err != nil {
+ panic(err)
+ }
}
}
} else {
for _, i := range onlyFiles {
- os.Remove(i)
+ if err := os.Remove(i); err != nil {
+ panic(err)
+ }
}
for _, i := range onlyFolders {
- os.RemoveAll(i)
+ if err := os.RemoveAll(i); err != nil {
+ panic(err)
+ }
}
}
}
if mode == "decrypt" && deniability {
- os.Remove(inputFile)
+ if err := os.Remove(inputFile); err != nil {
+ panic(err)
+ }
}
if mode == "decrypt" && !kept && autoUnzip {
@@ -2462,7 +2575,9 @@ func work() {
return
}
- os.Remove(outputFile)
+ if err := os.Remove(outputFile); err != nil {
+ panic(err)
+ }
}
// All done, reset the UI
From 268055804edb9b6257407d001968f212596148d8 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 17:03:09 -0400
Subject: [PATCH 44/46] two os.Removes conflict, allow them to err
deniability and split
---
src/Picocrypt.go | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 4872bd5..79e5049 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -2510,9 +2510,7 @@ func work() {
panic(err)
}
if deniability {
- if err := os.Remove(strings.TrimSuffix(inputFile, ".tmp")); err != nil {
- panic(err)
- }
+ os.Remove(strings.TrimSuffix(inputFile, ".tmp"))
}
}
@@ -2558,9 +2556,7 @@ func work() {
}
}
if mode == "decrypt" && deniability {
- if err := os.Remove(inputFile); err != nil {
- panic(err)
- }
+ os.Remove(inputFile)
}
if mode == "decrypt" && !kept && autoUnzip {
@@ -2710,7 +2706,7 @@ func rsDecode(rs *infectious.FEC, data []byte) ([]byte, error) {
}
tmp := make([]infectious.Share, rs.Total())
- for i := 0; i < rs.Total(); i++ {
+ for i := range rs.Total() {
tmp[i].Number = i
tmp[i].Data = append(tmp[i].Data, data[i])
}
@@ -2757,7 +2753,7 @@ func genPassword() string {
chars += "-=_+!@#$^&()?<>"
}
tmp := make([]byte, passgenLength)
- for i := 0; i < int(passgenLength); i++ {
+ for i := range int(passgenLength) {
j, _ := rand.Int(rand.Reader, big.NewInt(int64(len(chars))))
tmp[i] = chars[j.Int64()]
}
From bbf250be376ed875b3012e2b22df2666c048dca9 Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Fri, 18 Apr 2025 17:15:58 -0400
Subject: [PATCH 45/46] Update default.yml
---
.github/ISSUE_TEMPLATE/default.yml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/.github/ISSUE_TEMPLATE/default.yml b/.github/ISSUE_TEMPLATE/default.yml
index 2dff020..81bad47 100644
--- a/.github/ISSUE_TEMPLATE/default.yml
+++ b/.github/ISSUE_TEMPLATE/default.yml
@@ -33,6 +33,14 @@ body:
attributes:
value: |
Usually these issues are not directly caused by Picocrypt's code. If you're on Windows, see [here](https://github.com/Picocrypt/Picocrypt/issues/91). If you're on Linux, install some packages and try again (see [here](https://github.com/Picocrypt/Picocrypt/tree/main/src#1-prerequisites)). Picocrypt only targets Windows 11, Ubuntu 24/Debian 12, and macOS 15 or later, so *do not create an issue if your OS is older than those; that is your problem, not mine*. If none of the points above help, create the issue and in a separate comment, provide details about the environment you're running in (like OS, DE, etc.). **Do not ping me initially.** Let the issue sit for at least *5 days* to allow other users to potentially help you resolve the issue. If after 5 days, you haven't figured things out, then you may ping me (@HACKERALERT).
+ - type: markdown
+ attributes:
+ value: |
+ ### Picocrypt is crashing
+ - type: markdown
+ attributes:
+ value: |
+ This is almost always caused by input/output files being in locations where you don't have the correct read/write permissions. Try working within your user/home folder only and copy to/from other places to see if that resolves the crash. If not, run Picocrypt from the command line (e.g. `Picocrypt.exe` or `./Picocrypt`) so you can read the crash message. If you still can't fix the crash, create an issue and ping me (@HACKERALERT).
- type: markdown
attributes:
value: |
From e74068c5e60d97de94f5500b11817de0c9ebe11d Mon Sep 17 00:00:00 2001
From: Evan Su <48808396+HACKERALERT@users.noreply.github.com>
Date: Sat, 19 Apr 2025 14:14:56 -0400
Subject: [PATCH 46/46] fix issue #114
---
src/Picocrypt.go | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/Picocrypt.go b/src/Picocrypt.go
index 79e5049..630d03f 100644
--- a/src/Picocrypt.go
+++ b/src/Picocrypt.go
@@ -289,7 +289,9 @@ func onClickStartButton() {
comments = oldComments
paranoid = oldParanoid
reedsolo = oldReedsolo
- deniability = oldDeniability
+ if mode != "decrypt" {
+ deniability = oldDeniability
+ }
split = oldSplit
splitSize = oldSplitSize
splitSelected = oldSplitSelected