Compare commits

...

7 Commits

Author SHA1 Message Date
Evan Su 0be9be45d1
Update download links 2023-04-28 20:09:13 -04:00
Evan Su df0c4924a9
Release v1.32 2023-04-28 18:43:37 -04:00
Evan Su cc8636e171
Change wording 2023-04-28 15:13:16 -04:00
Evan Su 3a98fcc441
Update Picocrypt.go 2023-04-27 23:03:55 -04:00
Evan Su 8ab0c4ce04
Test CodeQL 2023-04-27 23:02:04 -04:00
Evan Su c0f5c300ee
Update actions/checkout 2023-04-27 23:00:59 -04:00
Evan Su 71bd746f9a
Add features 2023-04-27 22:52:01 -04:00
4 changed files with 378 additions and 133 deletions

View File

@ -24,7 +24,7 @@ jobs:
language: ['go']
steps:
- name: Checkout Repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:

View File

@ -1,8 +1,9 @@
# v1.32 (No ETA)
# v1.32 (Released 04/28/2023)
<ul>
<li>✓ Added a command-line interface</li>
<li>Use Debian 11 as the base for the AppImage instead of Debian 10</li>
<li>Include software rendering DLLs in the Paranoid Pack for future proofing</li>
<li>✓ Use Debian 11 as the base for the AppImage instead of Debian 10</li>
<li>✓ Include software rendering DLLs in the Paranoid Pack for future proofing</li>
<li>✓ Add plausible deniability and recursive encryption</li>
</ul>
# v1.31 (Released 11/18/2022)

View File

@ -12,19 +12,19 @@ Picocrypt is a very small (hence <i>Pico</i>), very simple, yet very secure encr
**Important**: There are multiple entities under the name "Picocrypt". For example, there's an old encryption tool called PicoCrypt that uses a broken cipher. There's also an ERC-funded research project called PICOCRYPT. There are even domains related to Picocrypt that I've never registered. Please don't confuse any of these unrelated projects with Picocrypt (this project). Make sure to only download Picocrypt from this repository to ensure that you get the authentic and backdoor-free Picocrypt. When sharing Picocrypt with others, be sure to link to this repository to prevent any confusion.
## Windows
Picocrypt for Windows is as simple as it gets. To download the latest, standalone, and portable executable for Windows, click <a href="https://github.com/HACKERALERT/Picocrypt/releases/download/1.31/Picocrypt.exe">here</a>. 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.
Picocrypt for Windows is as simple as it gets. To download the latest, standalone, and portable executable for Windows, click <a href="https://github.com/HACKERALERT/Picocrypt/releases/download/1.32/Picocrypt.exe">here</a>. 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.
## macOS
Picocrypt for macOS is very simple as well. Download Picocrypt <a href="https://github.com/HACKERALERT/Picocrypt/releases/download/1.31/Picocrypt.app.zip">here</a>, extract the zip file, and run Picocrypt which is inside. If you can't open Picocrypt because it's not from a verified developer, control-click on Picocrypt and hit Open to bypass the warning. Keep in mind that Picocrypt runs through Rosetta 2 and requires OpenGL, and may not work in the future should Apple remove either.
Picocrypt for macOS is very simple as well. Download Picocrypt <a href="https://github.com/HACKERALERT/Picocrypt/releases/download/1.32/Picocrypt.app.zip">here</a>, extract the zip file, and run Picocrypt which is inside. If you can't open Picocrypt because it's not from a verified developer, control-click on Picocrypt and hit Open to bypass the warning. Keep in mind that Picocrypt runs through Rosetta 2 and requires OpenGL, and may not work in the future should Apple remove either.
## Linux
To use Picocrypt on Linux, you can download the AppImage <a href="https://github.com/HACKERALERT/Picocrypt/releases/download/1.31/Picocrypt.AppImage">here</a>. While this AppImage should work on most systems, Linux is a mess when it comes to cross-distro and cross-release compatibility, so if the AppImage doesn't work, you can run Picocrypt through Wine or from source using the instructions in the `src/` directory.
To use Picocrypt on Linux, you can download the AppImage <a href="https://github.com/HACKERALERT/Picocrypt/releases/download/1.32/Picocrypt.AppImage">here</a>. While this AppImage should work on most systems, Linux is a mess when it comes to cross-distro and cross-release compatibility, so if the AppImage doesn't work, you can run Picocrypt through Wine or from source using the instructions in the `src/` directory.
## CLI
A command-line interface is available for Picocrypt <a href="/cli">here</a>. Keep in mind that the functionality is extremely limited and is not meant to replace the standard GUI app. Rather, it's best suited for environments where the GUI won't run or you need the ability to automate encryption workflows.
## Paranoid Pack
The Paranoid Pack is a compressed archive that contains executables for Windows, macOS, and Linux, including the source code and dependencies. As long as you have it stored in a place you can access, you'll be able to open it and use Picocrypt on any desktop operating system in case this repository mysteriously vanishes or the entire Internet burns down. Think of it as a seed vault for Picocrypt; as long as one person has the Paranoid Pack within reach, they can share it with the rest of the world and keep Picocrypt functional in case of catastrophic events. The best way to ensure Picocrypt is accessible many decades from now is to keep a Paranoid Pack in a safe place. Get your copy <a href="https://github.com/HACKERALERT/Picocrypt/releases/download/1.31/Paranoid.zip">here</a>.
The Paranoid Pack is a compressed archive that contains executables for Windows, macOS, and Linux, including the source code and dependencies. As long as you have it stored in a place you can access, you'll be able to open it and use Picocrypt on any desktop operating system in case this repository mysteriously vanishes or the entire Internet burns down. Think of it as a seed vault for Picocrypt; as long as one person has the Paranoid Pack within reach, they can share it with the rest of the world and keep Picocrypt functional in case of catastrophic events. The best way to ensure Picocrypt is accessible many decades from now is to keep a Paranoid Pack in a safe place. Get your copy <a href="https://github.com/HACKERALERT/Picocrypt/releases/download/1.32/Paranoid.zip">here</a>.
# Why Picocrypt?
Why should you use Picocrypt instead of VeraCrypt, 7-Zip, BitLocker, or Cryptomator? Here are a few reasons why you should choose Picocrypt:

View File

@ -109,6 +109,8 @@ var commentsDisabled bool
// Advanced options
var paranoid bool
var reedsolo bool
var deniability bool
var recursively bool
var split bool
var splitSize string
var splitUnits = []string{"KiB", "MiB", "GiB", "TiB", "Total"}
@ -412,7 +414,7 @@ func draw() {
),
giu.Dummy(0, 0),
giu.Style().SetDisabled(mode == "decrypt" && !keyfile).To(
giu.Style().SetDisabled(mode == "decrypt" && !keyfile && !deniability).To(
giu.Row(
giu.Label("Keyfiles:"),
giu.Button("Edit").Size(54, 0).OnClick(func() {
@ -484,16 +486,18 @@ func draw() {
giu.Checkbox("Paranoid mode", &paranoid),
giu.Tooltip("Provides the highest level of security attainable."),
giu.Dummy(-170, 0),
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.Style().SetDisabled(recursively).To(
giu.Checkbox("Compress files", &compress).OnChange(func() {
if !(len(allFiles) > 1 || len(onlyFolders) > 0) {
if compress {
outputFile = filepath.Join(filepath.Dir(outputFile), "Encrypted") + ".zip.pcv"
} else {
outputFile = filepath.Join(filepath.Dir(outputFile), filepath.Base(inputFile)) + ".pcv"
}
}
}
}),
giu.Tooltip("Compress files with Deflate before encrypting."),
}),
giu.Tooltip("Compress files with Deflate before encrypting."),
),
).Build()
giu.Row(
@ -504,6 +508,18 @@ func draw() {
giu.Tooltip("Delete the input files after encryption."),
).Build()
giu.Row(
giu.Checkbox("Deniability", &deniability),
giu.Tooltip("Add plausible deniability to the volume."),
giu.Dummy(-170, 0),
giu.Style().SetDisabled(!(len(allFiles) > 1 || len(onlyFolders) > 0)).To(
giu.Checkbox("Recursively", &recursively).OnChange(func() {
compress = false
}),
giu.Tooltip("Encrypt and decrypt recursive files individually."),
),
).Build()
giu.Row(
giu.Checkbox("Split into chunks:", &split),
giu.Tooltip("Split the output file into smaller chunks."),
@ -517,8 +533,10 @@ func draw() {
).Build()
} else {
giu.Row(
giu.Checkbox("Force decrypt", &keep),
giu.Tooltip("Override security measures when decrypting."),
giu.Style().SetDisabled(deniability).To(
giu.Checkbox("Force decrypt", &keep),
giu.Tooltip("Override security measures when decrypting."),
),
giu.Dummy(-170, 0),
giu.Checkbox("Delete volume", &delete),
giu.Tooltip("Delete the volume after a successful decryption."),
@ -526,77 +544,87 @@ func draw() {
}
}),
giu.Label("Save output as:"),
giu.Custom(func() {
w, _ := giu.GetAvailableRegion()
bw, _ := giu.CalcTextSize("Change")
p, _ := giu.GetWindowPadding()
bw += p * 2
dw := w - bw - p
giu.Style().SetDisabled(true).To(
giu.InputText(func() *string {
tmp := ""
if outputFile == "" {
giu.Style().SetDisabled(recursively).To(
giu.Label("Save output as:"),
giu.Custom(func() {
w, _ := giu.GetAvailableRegion()
bw, _ := giu.CalcTextSize("Change")
p, _ := giu.GetWindowPadding()
bw += p * 2
dw := w - bw - p
giu.Style().SetDisabled(true).To(
giu.InputText(func() *string {
tmp := ""
if outputFile == "" {
return &tmp
}
tmp = filepath.Base(outputFile)
if split {
tmp += ".*"
}
if recursively {
tmp = "(multiple values)"
}
return &tmp
}()).Size(dw / dpi / dpi).Flags(16384),
).Build()
giu.SameLine()
giu.Button("Change").Size(bw/dpi, 0).OnClick(func() {
f := dialog.File().Title("Choose where to save the output. Don't include extensions.")
f.SetStartDir(func() string {
if len(onlyFiles) > 0 {
return filepath.Dir(onlyFiles[0])
}
return filepath.Dir(onlyFolders[0])
}())
// Prefill the filename
tmp := strings.TrimSuffix(filepath.Base(outputFile), ".pcv")
f.SetInitFilename(strings.TrimSuffix(tmp, filepath.Ext(tmp)))
if mode == "encrypt" && (len(allFiles) > 1 || len(onlyFolders) > 0 || compress) {
f.SetInitFilename("Encrypted")
}
tmp = filepath.Base(outputFile)
if split {
tmp += ".*"
// Get the chosen file path
file, err := f.Save()
if file == "" || err != nil {
return
}
return &tmp
}()).Size(dw / dpi / dpi).Flags(16384),
).Build()
file = filepath.Join(filepath.Dir(file), strings.Split(filepath.Base(file), ".")[0])
giu.SameLine()
giu.Button("Change").Size(bw/dpi, 0).OnClick(func() {
f := dialog.File().Title("Choose where to save the output. Don't include extensions.")
f.SetStartDir(func() string {
if len(onlyFiles) > 0 {
return filepath.Dir(onlyFiles[0])
}
return filepath.Dir(onlyFolders[0])
}())
// Prefill the filename
tmp := strings.TrimSuffix(filepath.Base(outputFile), ".pcv")
f.SetInitFilename(strings.TrimSuffix(tmp, filepath.Ext(tmp)))
if mode == "encrypt" && (len(allFiles) > 1 || len(onlyFolders) > 0 || compress) {
f.SetInitFilename("Encrypted")
}
// Get the chosen file path
file, err := f.Save()
if file == "" || err != nil {
return
}
file = filepath.Join(filepath.Dir(file), strings.Split(filepath.Base(file), ".")[0])
// Add the correct extensions
if mode == "encrypt" {
if len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
file += ".zip.pcv"
// Add the correct extensions
if mode == "encrypt" {
if len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
file += ".zip.pcv"
} else {
file += filepath.Ext(inputFile) + ".pcv"
}
} else {
file += filepath.Ext(inputFile) + ".pcv"
if strings.HasSuffix(inputFile, ".zip.pcv") {
file += ".zip"
} else {
tmp := strings.TrimSuffix(filepath.Base(inputFile), ".pcv")
file += filepath.Ext(tmp)
}
}
} else {
if strings.HasSuffix(inputFile, ".zip.pcv") {
file += ".zip"
} else {
tmp := strings.TrimSuffix(filepath.Base(inputFile), ".pcv")
file += filepath.Ext(tmp)
}
}
outputFile = file
mainStatus = "Ready."
mainStatusColor = WHITE
}).Build()
giu.Tooltip("Save the output with a custom name and path.").Build()
}),
outputFile = file
mainStatus = "Ready."
mainStatusColor = WHITE
}).Build()
giu.Tooltip("Save the output with a custom name and path.").Build()
}),
),
giu.Dummy(0, 0),
giu.Separator(),
giu.Dummy(0, 0),
giu.Button(startLabel).Size(giu.Auto, 34).OnClick(func() {
giu.Button(func() string {
if !recursively {
return startLabel
}
return "Process"
}()).Size(giu.Auto, 34).OnClick(func() {
if keyfile && keyfiles == nil {
mainStatus = "Please select your keyfiles."
mainStatusColor = RED
@ -623,7 +651,7 @@ func draw() {
}
// If files already exist, show the overwrite modal
if err == nil {
if err == nil && !recursively {
showOverwrite = true
modalId++
giu.Update()
@ -633,12 +661,64 @@ func draw() {
canCancel = true
modalId++
giu.Update()
go func() {
work()
working = false
showProgress = false
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()
}()
}
}
}),
giu.Style().SetColor(giu.StyleColorText, mainStatusColor).To(
@ -771,59 +851,58 @@ func onDrop(names []string) {
return
}
// Use regex to test if the input is a valid Picocrypt volume
// Check if version can be read from header
tmp := make([]byte, 15)
fin.Read(tmp)
tmp, err = rsDecode(rs5, tmp)
if valid, _ := regexp.Match(`^v1\.\d{2}`, tmp); !valid || err != nil {
resetUI()
mainStatus = "This doesn't seem like a Picocrypt volume."
mainStatusColor = RED
// Volume has plausible deniability
deniability = true
mainStatus = "Can't read header, assuming volume is deniable."
fin.Close()
return
}
// Read comments from file and check for corruption
tmp = make([]byte, 15)
fin.Read(tmp)
tmp, err = rsDecode(rs5, tmp)
if err == nil {
commentsLength, _ := strconv.Atoi(string(tmp))
tmp = make([]byte, commentsLength*3)
} else {
// Read comments from file and check for corruption
tmp = make([]byte, 15)
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
tmp, err = rsDecode(rs5, tmp)
if err == nil {
commentsLength, _ := strconv.Atoi(string(tmp))
tmp = make([]byte, commentsLength*3)
fin.Read(tmp)
comments = ""
for i := 0; i < commentsLength*3; i += 3 {
t, err := rsDecode(rs1, tmp[i:i+3])
if err != nil {
comments = "Comments are corrupted."
break
}
comments += string(t)
}
comments += string(t)
} else {
comments = "Comments are corrupted."
}
} else {
comments = "Comments are corrupted."
}
// Read flags from file and check for corruption
flags := make([]byte, 15)
fin.Read(flags)
fin.Close()
flags, err = rsDecode(rs5, flags)
if err != nil {
mainStatus = "The volume header is damaged."
mainStatusColor = RED
return
}
// Read flags from file and check for corruption
flags := make([]byte, 15)
fin.Read(flags)
fin.Close()
flags, err = rsDecode(rs5, flags)
if err != nil {
mainStatus = "The volume header is damaged."
mainStatusColor = RED
return
}
// Update UI and variables according to flags
if flags[1] == 1 {
keyfile = true
keyfileLabel = "Keyfiles required."
} else {
keyfileLabel = "Not applicable."
}
if flags[2] == 1 {
keyfileOrdered = true
// Update UI and variables according to flags
if flags[1] == 1 {
keyfile = true
keyfileLabel = "Keyfiles required."
} else {
keyfileLabel = "Not applicable."
}
if flags[2] == 1 {
keyfileOrdered = true
}
}
} else { // One file was dropped for encryption
mode = "encrypt"
@ -1087,6 +1166,85 @@ func work() {
inputFile = outputFile + ".pcv"
}
// Input volume has plausible deniability
if mode == "decrypt" && deniability {
popupStatus = "Removing deniability protection..."
progressInfo = ""
progress = 0
canCancel = false
giu.Update()
// Get size of volume for showing progress
stat, _ := os.Stat(inputFile)
total := stat.Size()
// Rename input volume to free up the filename
fin, _ := os.Open(inputFile)
for strings.HasSuffix(inputFile, ".tmp") {
inputFile = strings.TrimSuffix(inputFile, ".tmp")
}
inputFile += ".tmp"
fout, _ := os.Create(inputFile)
// Get the Argon2 salt and XChaCha20 nonce from input volume
salt := make([]byte, 16)
nonce := make([]byte, 24)
fin.Read(salt)
fin.Read(nonce)
// Generate key and XChaCha20
key := argon2.IDKey([]byte(password), salt, 4, 1<<20, 4, 32)
chacha, _ := chacha20.NewUnauthenticatedCipher(key, nonce)
// Decrypt the entire volume
done, counter := 0, 0
for {
src := make([]byte, MiB)
size, err := fin.Read(src)
if err != nil {
break
}
src = src[:size]
dst := make([]byte, len(src))
chacha.XORKeyStream(dst, src)
fout.Write(dst)
// Update stats
done += size
counter += MiB
progress = float32(float64(done) / float64(total))
giu.Update()
// Change nonce after 60 GiB to prevent overflow
if counter >= 60*GiB {
tmp := sha3.New256()
tmp.Write(nonce)
nonce = tmp.Sum(nil)[:24]
chacha, _ = chacha20.NewUnauthenticatedCipher(key, nonce)
counter = 0
}
}
fin.Close()
fout.Close()
// Check if the version can be read from the volume
fin, _ = os.Open(inputFile)
tmp := make([]byte, 15)
fin.Read(tmp)
fin.Close()
tmp, err := rsDecode(rs5, tmp)
if valid, _ := regexp.Match(`^v1\.\d{2}`, tmp); !valid || err != nil {
os.Remove(inputFile)
inputFile = strings.TrimSuffix(inputFile, ".tmp")
broken(nil, nil, "Password is incorrect or the file is not a volume.", true)
if recombine {
inputFile = inputFileOld
}
return
}
}
canCancel = false
progress = 0
progressInfo = ""
@ -1232,6 +1390,10 @@ func work() {
paranoid = flags[0] == 1
reedsolo = flags[3] == 1
padded = flags[4] == 1
if deniability {
keyfile = flags[1] == 1
keyfileOrdered = flags[2] == 1
}
salt = make([]byte, 48)
fin.Read(salt)
@ -1365,7 +1527,7 @@ func work() {
keyCorrect := subtle.ConstantTimeCompare(keyHash, keyHashRef) == 1
keyfileCorrect := subtle.ConstantTimeCompare(keyfileHash, keyfileHashRef) == 1
incorrect := !keyCorrect
if keyfile {
if keyfile || len(keyfiles) > 0 {
incorrect = !keyCorrect || !keyfileCorrect
}
@ -1382,8 +1544,16 @@ func work() {
} else {
mainStatus = "Incorrect keyfiles."
}
if deniability {
fin.Close()
os.Remove(inputFile)
inputFile = strings.TrimSuffix(inputFile, ".tmp")
}
}
broken(fin, nil, mainStatus, true)
if recombine {
inputFile = inputFileOld
}
return
}
}
@ -1648,6 +1818,69 @@ func work() {
fin.Close()
fout.Close()
// Add plausible deniability
if mode == "encrypt" && deniability {
popupStatus = "Adding plausible deniability..."
canCancel = false
giu.Update()
// Get size of volume for showing progress
stat, _ := os.Stat(fout.Name())
total := stat.Size()
// Rename the output volume to free up the filename
os.Rename(fout.Name(), fout.Name()+".tmp")
fin, _ := os.Open(fout.Name() + ".tmp")
fout, _ := os.Create(fout.Name())
// Use a random Argon2 salt and XChaCha20 nonce
salt := make([]byte, 16)
nonce := make([]byte, 24)
rand.Read(salt)
rand.Read(nonce)
fout.Write(salt)
fout.Write(nonce)
// Generate key and XChaCha20
key := argon2.IDKey([]byte(password), salt, 4, 1<<20, 4, 32)
chacha, _ := chacha20.NewUnauthenticatedCipher(key, nonce)
// Encrypt the entire volume
done, counter := 0, 0
for {
src := make([]byte, MiB)
size, err := fin.Read(src)
if err != nil {
break
}
src = src[:size]
dst := make([]byte, len(src))
chacha.XORKeyStream(dst, src)
fout.Write(dst)
// Update stats
done += size
counter += MiB
progress = float32(float64(done) / float64(total))
giu.Update()
// Change nonce after 60 GiB to prevent overflow
if counter >= 60*GiB {
tmp := sha3.New256()
tmp.Write(nonce)
nonce = tmp.Sum(nil)[:24]
chacha, _ = chacha20.NewUnauthenticatedCipher(key, nonce)
counter = 0
}
}
fin.Close()
fout.Close()
os.Remove(fin.Name())
canCancel = true
giu.Update()
}
// Split the file into chunks
if split {
var splitted []string
@ -1764,6 +1997,9 @@ func work() {
// Delete temporary files used during encryption and decryption
if recombine || len(allFiles) > 1 || len(onlyFolders) > 0 || compress {
os.Remove(inputFile)
if deniability {
os.Remove(strings.TrimSuffix(inputFile, ".tmp"))
}
}
// Delete the input files if the user chooses
@ -1784,6 +2020,9 @@ func work() {
}
} else {
os.Remove(inputFile)
if deniability {
os.Remove(strings.TrimSuffix(inputFile, ".tmp"))
}
}
} else {
for _, i := range onlyFiles {
@ -1794,6 +2033,9 @@ func work() {
}
}
}
if mode == "decrypt" && deniability {
os.Remove(inputFile)
}
// All done, reset the UI
oldKept := kept
@ -1884,6 +2126,8 @@ func resetUI() {
paranoid = false
reedsolo = false
deniability = false
recursively = false
split = false
splitSize = ""
splitSelected = 1
@ -2013,7 +2257,7 @@ func sizeify(size int64) string {
func main() {
// Create the main window
window = giu.NewMasterWindow("Picocrypt", 318, 479, giu.MasterWindowFlagsNotResizable)
window = giu.NewMasterWindow("Picocrypt", 318, 507, giu.MasterWindowFlagsNotResizable)
// Start the dialog module
dialog.Init()