diff --git a/src/Picocrypt.go b/src/Picocrypt.go index b11ee82..e50eb16 100644 --- a/src/Picocrypt.go +++ b/src/Picocrypt.go @@ -2,7 +2,7 @@ package main /* -Picocrypt v1.13 +Picocrypt v1.14 Copyright (c) Evan Su (https://evansu.cc) Released under a GNU GPL v3 License https://github.com/HACKERALERT/Picocrypt @@ -12,488 +12,589 @@ https://github.com/HACKERALERT/Picocrypt */ import ( - "io" - "os" + _ "embed" + + // Generic "fmt" + "io" + "io/ioutil" + "os" + "os/exec" "math" "time" "sync" - "os/exec" + "hash" + "image" + "bytes" + "regexp" "strings" "strconv" "runtime" "net/http" - "io/ioutil" + "image/png" "image/color" - "crypto/md5" "archive/zip" - "crypto/rand" - "crypto/sha1" "encoding/hex" "path/filepath" - "crypto/sha256" "runtime/debug" - "github.com/pkg/browser" - "github.com/zeebo/blake3" - "golang.org/x/crypto/sha3" + + // Cryptography + "crypto/rand" + "crypto/hmac" + "crypto/subtle" + "crypto/cipher" + "crypto/md5" + "crypto/sha1" + "crypto/sha256" "golang.org/x/crypto/argon2" - g "github.com/AllenDang/giu" + "golang.org/x/crypto/hkdf" + "golang.org/x/crypto/sha3" "golang.org/x/crypto/blake2b" "golang.org/x/crypto/blake2s" - "github.com/atotto/clipboard" - di "github.com/OpenDiablo2/dialog" - "github.com/klauspost/reedsolomon" - ig "github.com/AllenDang/imgui-go" - "golang.org/x/crypto/chacha20poly1305" - "github.com/HACKERALERT/Picocypher/monocypher" + "golang.org/x/crypto/chacha20" + "github.com/HACKERALERT/serpent" // v0.0.0-20210716182301-293b29869c66 + + // GUI + "github.com/AllenDang/giu" + + // Reed-Solomon + "github.com/HACKERALERT/infectious" // v0.0.0-20210730231340-8af02cb9ed0a + + // Helpers + "github.com/HACKERALERT/clipboard" // v0.1.5-0.20210716140604-61d96bf4fc94 + "github.com/HACKERALERT/dialog" // v0.0.0-20210716143851-223edea1d840 + "github.com/HACKERALERT/browser" // v0.0.0-20210730230128-85901a8dd82f + "github.com/HACKERALERT/zxcvbn-go" // v0.0.0-20210730224720-b29e9dba62c2 + ) -var version = "v1.13" -var exe,_ = os.Executable() -var rootDir = filepath.Dir(exe) +var version = "v1.14" + +//go:embed NotoSans-Regular.ttf +var font []byte +//go:embed sdelete64.exe +var sdelete64bytes []byte +//go:embed icon.png +var iconBytes []byte + +// Languages +var languages = []string{ + "English", +} +var languageSelected int32 // Global variables -var dpi float32 -var mode string +var dpi float32 // Used to scale properly in high-resolution displays +var mode string = "" // "encrypt", "decrypt", or "" var working = false -var onlyFiles []string -var onlyFolders []string -var allFiles []string +var onlyFiles []string // Only contains files not in a folder +var onlyFolders []string // Only contains names of folders +var allFiles []string // Contains all files including files in folders var inputFile string var outputFile string +var recombine bool // True if decrypting and the original file was splitted during encryption +var sdelete64path string // The temporary file path where sdelete64.exe will be stored // UI-related global variables -var tab = 0 +var tab = 0 // The index of the currently selected tab var inputLabel = "Drag and drop file(s) and folder(s) into this window." var outputEntry string -var outputWidth float32 = 376 +var outputWidth float32 = 370 var orLabel = "or" -var passwordState = g.InputTextFlags_Password -var showPassword = false -var keyfile = false -var keyfilePrompt = "Keyfile (optional):" -var progress float32 = 0 +var passwordStrength int +var keyfile = false // True if user chooses/chose to use a keyfile +var keyfilePrompt = "Keyfile (optional):" // Changes if decrypting and keyfile was enabled +var showConfirmation = false // True if a confirmation to overwrite is needed +var showProgress = false +var progress float32 = 0 // 0 is 0%, 1 is 100% var progressInfo = "" var status = "Ready." var _status = "Ready." -var _status_color = color.RGBA{0xff,0xff,0xff,255} +var _status_color = color.RGBA{0xff,0xff,0xff,255} // Changes according to status (success, fail, etc.) var splitUnits = []string{ - "KB", - "MB", - "GB", + "KiB", + "MiB", + "GiB", } -var splitSelected int32 -var items = []string{ - "Normal (4 passes)", - "Paranoid (Still in development, don't use)", -} -var itemSelected int32 +var splitSelected int32 // Index of which splitting unit was chosen from above var shredProgress float32 var shredDone float32 -var shredTotal float32 -var shredOverlay string +var shredTotal float32 // Total files to shred (recursive) +var shredOverlay string // Text in shredding progress bar var shredding = "Ready." -var recombine bool - // User input variables var password string -var cPassword string +var cPassword string // Confirm password text entry string variable var keyfilePath string -var keyfileLabel = "Use a keyfile" +var keyfileLabel = "Use a keyfile (Beta)" var metadata string +var shredTemp bool +var paranoid bool var keep bool -var erase bool var reedsolo bool var split bool var splitSize string var fast bool -var _fast bool - -var kept = false +var kept = false // If a file was corrupted/modified, but the output was kept // Reed-Solomon encoders -var rs5_128,_ = reedsolomon.New(5,128) -var rs10_128,_ = reedsolomon.New(10,128) -var rs16_128,_ = reedsolomon.New(16,128) -var rs24_128,_ = reedsolomon.New(24,128) -var rs32_128,_ = reedsolomon.New(32,128) -var rs64_128,_ = reedsolomon.New(64,128) +var rs1,_ = infectious.NewFEC(1,3) // 1 data shards, 3 total -> 2 parity shards +var rs5,_ = infectious.NewFEC(5,15) +var rs10,_ = infectious.NewFEC(10,30) +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) // Used for Reed-Solomon checkbox // File checksum generator variables -var cs_md5 string +var cs_md5 string // A string containing a hex-encoded MD5 hash var cs_sha1 string var cs_sha256 string var cs_sha3_256 string var cs_blake2b string var cs_blake2s string -var cs_blake3 string var cs_validate string -var md5_color = color.RGBA{0x10,0x10,0x10,255} +var md5_color = color.RGBA{0x10,0x10,0x10,255} // Border color that changes upon a match var sha1_color = color.RGBA{0x10,0x10,0x10,255} var sha256_color = color.RGBA{0x10,0x10,0x10,255} var sha3_256_color = color.RGBA{0x10,0x10,0x10,255} var blake2b_color = color.RGBA{0x10,0x10,0x10,255} var blake2s_color = color.RGBA{0x10,0x10,0x10,255} -var blake3_color = color.RGBA{0x10,0x10,0x10,255} var cs_progress float32 = 0 -var md5_selected = false +var md5_selected = false // Whether the checkbox was checked or not var sha1_selected = false var sha256_selected = false var sha3_256_selected = false var blake2b_selected = false var blake2s_selected = false -var blake3_selected = false -// Create the user interface +// Create the UI func startUI(){ - g.SingleWindow("Picocrypt").Layout( - g.Style().SetColor(ig.StyleColorBorder,color.RGBA{0x10,0x10,0x10,255}).To( - // The tab bar, which contains different tabs for different features - g.TabBar("TabBar").Layout( - // File encryption/decryption tab - g.TabItem("Encryption/decryption").Layout( - // Update 'tab' to indicate active tab - g.Custom(func(){ - if g.IsItemActive(){ - tab = 0 - } - }), + giu.SingleWindow().Layout( + giu.Style().SetColor(giu.StyleColorBorder,color.RGBA{0x10,0x10,0x10,255}).To( + giu.Custom(func(){ + // Language dropdown menu + pos := giu.GetCursorPos() + giu.Row( + giu.Dummy(-108,0), + giu.Combo("##language",languages[languageSelected],languages,&languageSelected).Size(100), + ).Build() + giu.SetCursorPos(pos) + + // The tab bar, which contains different tabs for different functions + giu.TabBar().TabItems( + // Main file encryption/decryption tab + giu.TabItem("Main").Layout( + // Update 'tab' to indicate that this is the active tab + giu.Custom(func(){ + if giu.IsItemActive(){ + tab = 0 + } + }), - // Confirm overwrite with a modal - g.PopupModal("Confirmation").Layout( - g.Label("Output already exists. Overwrite?"), - g.Row( - g.Button("No").Size(110,0).OnClick(func(){ - g.CloseCurrentPopup() + // Confirm overwrite with a modal + giu.Custom(func(){ + if showConfirmation{ + giu.PopupModal("Warning:").Layout( + giu.Label("Output already exists. Overwrite?"), + giu.Row( + giu.Button("No").Size(100,0).OnClick(func(){ + giu.CloseCurrentPopup() + showConfirmation = false + }), + giu.Button("Yes").Size(100,0).OnClick(func(){ + giu.CloseCurrentPopup() + showConfirmation = false + giu.Update() + showProgress = true + giu.Update() + go func (){ + work() + working = false + showProgress = false + debug.FreeOSMemory() + giu.Update() + }() + }), + ), + ).Build() + giu.OpenPopup("Warning:") + giu.Update() + } + }), + + // Show encryption/decryption progress with a modal + giu.Custom(func(){ + if showProgress{ + giu.PopupModal(" ").Layout( + // Close modal if not working (encryption/decryption done) + giu.Custom(func(){ + if !working{ + giu.CloseCurrentPopup() + } + }), + // Progress bar + giu.Row( + giu.ProgressBar(progress).Size(280,0).Overlay(progressInfo), + giu.Button("Cancel").Size(58,0).OnClick(func(){ + working = false + }), + ), + giu.Label(status), + ).Build() + giu.OpenPopup(" ") + giu.Update() + } + }), + + // Label listing the input files and a button to clear them + giu.Row( + giu.Label(inputLabel), + giu.Row( + giu.Dummy(-58,0), + giu.Button("Clear").Size(50,0).OnClick(resetUI), + ), + ), + + // Allow user to choose a custom output path and/or name + giu.Label("Save output as:"), + giu.Row( + giu.InputText(&outputEntry).Size(outputWidth/dpi), + giu.Label(orLabel), + giu.Button("Choose").OnClick(func(){ + file,_ := dialog.File().Title("Save output as...").Save() + + // Return if user canceled the file dialog + if file==""{ + return + } + + if len(allFiles)>1||len(onlyFolders)>0{ + // Remove the extra ".zip.pcv" extension if necessary + if strings.HasSuffix(file,".zip.pcv"){ + file = file[:len(file)-8] + } + }else{ + // Remove the extra ".pcv" extension if necessary + if strings.HasSuffix(file,".pcv"){ + file = file[:len(file)-4] + } + } + + outputEntry = file + }).Size(64,0), + ), + + // Prompt for password + giu.Row( + giu.Label("Password:"), + giu.Dummy(-200,0), + giu.Label(keyfilePrompt), + ), + giu.Row( + giu.InputText(&password).Size(240/dpi).Flags(giu.InputTextFlagsPassword).OnChange(func(){ + passwordStrength = zxcvbn.PasswordStrength(password,nil).Score }), - g.Dummy(-118,0), - g.Button("Yes").Size(110,0).OnClick(func(){ - g.CloseCurrentPopup() - go work() + + // Draw a circle with arc length depending on password strength + giu.Custom(func(){ + canvas := giu.GetCanvas() + pos := giu.GetCursorScreenPos() + + var col color.RGBA + switch passwordStrength{ + case 0: + col = color.RGBA{200,76,75,255} + case 1: + col = color.RGBA{169,107,75,255} + case 2: + col = color.RGBA{138,138,75,255} + case 3: + col = color.RGBA{107,169,75,255} + case 4: + col = color.RGBA{76,200,75,255} + } + if password==""||mode=="decrypt"{ + col = color.RGBA{0,0,0,0} + } + + path := pos.Add(image.Pt( + int(math.Round(float64(6*dpi))), + int(math.Round(float64(12*dpi))), + )) + canvas.PathArcTo(path,8*dpi,0,float32(passwordStrength+1)/5*(2*math.Pi),-1) + canvas.PathStroke(col,false,3) }), - + giu.Dummy(-200,0), + + giu.Checkbox(keyfileLabel,&keyfile).OnChange(func(){ + if !keyfile{ + keyfileLabel = "Use a keyfile" + return + } + filename,err := dialog.File().Load() + if err!=nil{ + keyfile = false + return + } + keyfileLabel = filename + keyfilePath = filename + }), + ), + + // Prompt to confirm password + giu.Label("Confirm password:"), + giu.Row( + giu.InputText(&cPassword).Size(240/dpi).Flags(giu.InputTextFlagsPassword), + giu.Custom(func(){ + canvas := giu.GetCanvas() + pos := giu.GetCursorScreenPos() + col := color.RGBA{76,200,75,255} + if cPassword!=password{ + col = color.RGBA{200,76,75,255} + } + if password==""||cPassword==""||mode=="decrypt"{ + col = color.RGBA{0,0,0,0} + } + path := pos.Add(image.Pt( + int(math.Round(float64(6*dpi))), + int(math.Round(float64(12*dpi))), + )) + canvas.PathArcTo(path,8*dpi,0,2*math.Pi,-1) + canvas.PathStroke(col,false,3) + }), + giu.Dummy(-0.0000001,0), + ), + + // Optional metadata + giu.Label("Metadata (optional):"), + giu.InputText(&metadata).Size(-0.0000001), + + giu.Custom(func(){ + if mode!=""{ + giu.Label("Advanced options:").Build() + } + }), + + // Advanced options can be enabled with checkboxes + giu.Custom(func(){ + if mode=="encrypt"{ + giu.Checkbox("Shred temporary files (can be slow for large files)",&shredTemp).Build() + giu.Checkbox("Fast mode (slightly less secure, not as durable)",&fast).Build() + giu.Checkbox("Paranoid mode (extremely secure, but slower)",¶noid).Build() + giu.Row( + giu.Checkbox("Encode with Reed-Solomon to prevent corruption (slow)",&reedsolo), + giu.Button("?").Size(24,25).OnClick(func(){ + browser.OpenURL("https://bit.ly/3A2V7LR") + }), + ).Build() + giu.Row( + giu.Checkbox("Split output into chunks of",&split), + giu.InputText(&splitSize).Size(50).Flags(giu.InputTextFlagsCharsDecimal), + giu.Combo("##splitter",splitUnits[splitSelected],splitUnits,&splitSelected).Size(52), + ).Build() + giu.Dummy(0,1).Build() + }else if mode=="decrypt"{ + giu.Checkbox("Keep decrypted output even if it's corrupted or modified",&keep).Build() + giu.Dummy(0,112).Build() + }else{ + giu.Dummy(0,67).Build() + giu.Label(" No files selected yet.").Build() + giu.Dummy(0,68).Build() + } + }), + + // Start button + giu.Button("Start").Size(-0.0000001,35).OnClick(func(){ + if mode=="encrypt"&&password!=cPassword{ + _status = "Passwords don't match." + _status_color = color.RGBA{0xff,0x00,0x00,255} + return + } + if mode=="encrypt"{ + if len(allFiles)>1||len(onlyFolders)>0{ + outputFile = outputEntry+".zip.pcv" + }else{ + outputFile = outputEntry+".pcv" + } + }else{ + outputFile = outputEntry + } + _,err := os.Stat(outputFile) + if err==nil{ + showConfirmation = true + giu.Update() + }else{ + showProgress = true + giu.Update() + go func (){ + work() + working = false + showProgress = false + debug.FreeOSMemory() + giu.Update() + }() + } + }), + + giu.Style().SetColor(giu.StyleColorText,_status_color).To( + giu.Label(_status), ), ), - // Label listing the input files and a button to clear input files - //g.Dummy(0,10), - g.Row( - g.Label(inputLabel), - g.Dummy(-55,0), - g.Button("Clear").Size(46,0).OnClick(resetUI), - ), - - // Allow user to choose a custom output path and name - //g.Dummy(10,0), - g.Label("Save output as:"), - g.Row( - g.InputText("##output",&outputEntry).Size(outputWidth/dpi), - g.Label(orLabel), - g.Button("Save as").OnClick(func(){ - file,_ := di.File().Title("Save as").Save() - - // Return if user canceled the file dialog - if file==""{ - return + // File checksum generator tab + giu.TabItem("Checksum").Layout( + giu.Custom(func(){ + if giu.IsItemActive(){ + tab = 1 } - - // Remove the extra ".pcv" extension if needed - if strings.HasSuffix(file,".pcv"){ - file = file[:len(file)-4] - } - outputEntry = file }), - ), - // Prompt for password - //g.Dummy(10,0), - g.Row( - g.Label("Password:"), - g.Dummy(-220,0), - g.Label(keyfilePrompt), - ), - g.Row( - g.InputText("##password",&password).Size(200/dpi).Flags(passwordState), - g.Checkbox("##showPassword",&showPassword).OnChange(func(){ - if passwordState==g.InputTextFlags_Password{ - passwordState = g.InputTextFlags_None - }else{ - passwordState = g.InputTextFlags_Password - } - g.Update() - }), - g.Dummy(-220,0), - g.Checkbox(keyfileLabel,&keyfile).OnChange(func(){ - if !keyfile{ - keyfileLabel = "Use a keyfile" - return - } - filename,err := di.File().Load() - if err!=nil{ - keyfile = false - return - } - keyfileLabel = filename - keyfilePath = filename - }), - ), + giu.Label("Toggle the hashes you would like to generate and drop a file here."), - // Prompt to confirm password - //g.Dummy(10,0), - g.Label("Confirm password:"), - g.InputText("##cPassword",&cPassword).Size(200/dpi).Flags(passwordState), + // MD5 + giu.Row( + giu.Checkbox("MD5:",&md5_selected), + giu.Dummy(-58,0), + giu.Button("Copy##md5").Size(50,0).OnClick(func(){ + clipboard.WriteAll(cs_md5) + }), + ), + giu.Style().SetColor(giu.StyleColorBorder,md5_color).To( + giu.InputText(&cs_md5).Size(-0.0000001).Flags(giu.InputTextFlagsReadOnly), + ), - // Optional metadata - //g.Dummy(10,0), - g.Label("Metadata (optional):"), - g.InputTextMultiline("##metadata",&metadata).Size(230,126), + // SHA1 + giu.Row( + giu.Checkbox("SHA1:",&sha1_selected), + giu.Dummy(-58,0), + giu.Button("Copy##sha1").Size(50,0).OnClick(func(){ + clipboard.WriteAll(cs_sha1) + }), + ), + giu.Style().SetColor(giu.StyleColorBorder,sha1_color).To( + giu.InputText(&cs_sha1).Size(-0.0000001).Flags(giu.InputTextFlagsReadOnly), + ), - // Advanced options can be enabled with checkboxes - //g.Dummy(10,0), - g.Checkbox("Keep decrypted output even if it's corrupted or modified",&keep), - g.Checkbox("Securely shred the original file(s) and folder(s)",&erase), - g.Row( - g.Checkbox("(Not ready) Encode with Reed-Solomon to prevent corruption",&reedsolo), - g.Button("?").OnClick(func(){ - browser.OpenURL("https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction") - }), - ), - g.Row( - g.Checkbox("Split output into chunks of",&split), - g.InputText("##splitSize",&splitSize).Size(30).Flags(g.InputTextFlags_CharsDecimal), - g.Combo("##splitter",splitUnits[splitSelected],splitUnits,&splitSelected).Size(40), - //g.Label("MB"), - ), - g.Checkbox("Fast mode (less secure, not as durable)",&fast), + // SHA256 + giu.Row( + giu.Checkbox("SHA256:",&sha256_selected), + giu.Dummy(-58,0), + giu.Button("Copy##sha256").Size(50,0).OnClick(func(){ + clipboard.WriteAll(cs_sha256) + }), + ), + giu.Style().SetColor(giu.StyleColorBorder,sha256_color).To( + giu.InputText(&cs_sha256).Size(-0.0000001).Flags(giu.InputTextFlagsReadOnly), + ), - // Start and cancel buttons - //g.Dummy(10,0), - g.Button("Start").Size(-1,20).OnClick(func(){ - if password!=cPassword{ - _status = "Passwords don't match." - _status_color = color.RGBA{0xff,0x00,0x00,255} - return - } - if mode=="encrypt"{ - outputFile = outputEntry+".pcv" - }else{ - outputFile = outputEntry - } - _,err := os.Stat(outputFile) - if err==nil{ - g.OpenPopup("Confirmation") - }else{ - go work() - } - }), + // SHA3-256 + giu.Row( + giu.Checkbox("SHA3-256:",&sha3_256_selected), + giu.Dummy(-58,0), + giu.Button("Copy##sha3_256").Size(50,0).OnClick(func(){ + clipboard.WriteAll(cs_sha3_256) + }), + ), + giu.Style().SetColor(giu.StyleColorBorder,sha3_256_color).To( + giu.InputText(&cs_sha3_256).Size(-0.0000001).Flags(giu.InputTextFlagsReadOnly), + ), - //g.Dummy(10,0), - g.Style().SetColor(ig.StyleColorText,_status_color).To( - g.Label(_status), - ), - ), + // BLAKE2b + giu.Row( + giu.Checkbox("BLAKE2b:",&blake2b_selected), + giu.Dummy(-58,0), + giu.Button("Copy##blake2b").Size(50,0).OnClick(func(){ + clipboard.WriteAll(cs_blake2b) + }), + ), + giu.Style().SetColor(giu.StyleColorBorder,blake2b_color).To( + giu.InputText(&cs_blake2b).Size(-0.0000001).Flags(giu.InputTextFlagsReadOnly), + ), - // File shredder tab - g.TabItem("Shredder").Layout( - // Update 'tab' to indicate active tab - g.Custom(func(){ - if g.IsItemActive(){ - tab = 1 - } - }), - - g.Label("Select a mode below and drop file(s) and folder(s) here."), - g.Label("Warning: Anything dropped here will be shredded immediately!"), - //g.Dummy(10,0), - g.Combo("##shredder_mode",items[itemSelected],items,&itemSelected).Size(463), - //g.Dummy(10,0), - g.ProgressBar(shredProgress).Overlay(shredOverlay).Size(-1,0), - g.Label(shredding).Wrapped(true), - ), - - // File checksum generator tab - g.TabItem("Checksum").Layout( - // Update 'tab' to indicate active tab - g.Custom(func(){ - if g.IsItemActive(){ - tab = 2 - } - }), - - g.Label("Toggle the hashes you would like to generate and drop a file here."), + // BLAKE2s + giu.Row( + giu.Checkbox("BLAKE2s:",&blake2s_selected), + giu.Dummy(-58,0), + giu.Button("Copy##blake2s").Size(50,0).OnClick(func(){ + clipboard.WriteAll(cs_blake2s) + }), + ), + giu.Style().SetColor(giu.StyleColorBorder,blake2s_color).To( + giu.InputText(&cs_blake2s).Size(-0.0000001).Flags(giu.InputTextFlagsReadOnly), + ), - // MD5 - //g.Dummy(10,0), - g.Row( - g.Checkbox("MD5:",&md5_selected), - g.Dummy(-45,0), - g.Button("Copy##md5").Size(36,0).OnClick(func(){ - clipboard.WriteAll(cs_md5) + // Input entry for validating a checksum + giu.Label("Validate a checksum:"), + giu.InputText(&cs_validate).Size(-0.0000001).OnChange(func(){ + md5_color = color.RGBA{0x10,0x10,0x10,255} + sha1_color = color.RGBA{0x10,0x10,0x10,255} + sha256_color = color.RGBA{0x10,0x10,0x10,255} + sha3_256_color = color.RGBA{0x10,0x10,0x10,255} + blake2b_color = color.RGBA{0x10,0x10,0x10,255} + blake2s_color = color.RGBA{0x10,0x10,0x10,255} + if cs_validate==""{ + return + } + if cs_validate==cs_md5{ + md5_color = color.RGBA{0x00,0xff,0x00,255} + }else if cs_validate==cs_sha1{ + sha1_color = color.RGBA{0x00,0xff,0x00,255} + }else if cs_validate==cs_sha256{ + sha256_color = color.RGBA{0x00,0xff,0x00,255} + }else if cs_validate==cs_sha3_256{ + sha3_256_color = color.RGBA{0x00,0xff,0x00,255} + }else if cs_validate==cs_blake2b{ + blake2b_color = color.RGBA{0x00,0xff,0x00,255} + }else if cs_validate==cs_blake2s{ + blake2s_color = color.RGBA{0x00,0xff,0x00,255} + } + giu.Update() }), - ), - g.Style().SetColor(ig.StyleColorBorder,md5_color).To( - g.InputText("##cs_md5",&cs_md5).Size(-1).Flags(g.InputTextFlags_ReadOnly), + + // Progress bar + giu.Label("Progress:"), + giu.ProgressBar(cs_progress).Size(-0.0000001,0), ), - // SHA1 - //g.Dummy(10,0), - g.Row( - g.Checkbox("SHA1:",&sha1_selected), - g.Dummy(-45,0), - g.Button("Copy##sha1").Size(36,0).OnClick(func(){ - clipboard.WriteAll(cs_sha1) + // File shredder tab + giu.TabItem("Shredder").Layout( + giu.Custom(func(){ + if giu.IsItemActive(){ + tab = 2 + } + }), + + giu.Label("Drop file(s) and folder(s) here to shred them."), + giu.ProgressBar(shredProgress).Overlay(shredOverlay).Size(-0.0000001,0), + giu.Custom(func(){ + if len(shredding)>50{ + shredding = "....."+shredding[len(shredding)-50:] + } + giu.Label(shredding).Wrapped(true).Build() }), ), - g.Style().SetColor(ig.StyleColorBorder,sha1_color).To( - g.InputText("##cs_sha1",&cs_sha1).Size(-1).Flags(g.InputTextFlags_ReadOnly), - ), - // SHA256 - //g.Dummy(10,0), - g.Row( - g.Checkbox("SHA256:",&sha256_selected), - g.Dummy(-45,0), - g.Button("Copy##sha256").Size(36,0).OnClick(func(){ - clipboard.WriteAll(cs_sha256) + // About tab + giu.TabItem("About").Layout( + giu.Custom(func(){ + if giu.IsItemActive(){ + tab = 3 + } }), + giu.Label("Picocrypt "+version+", created by Evan Su (https://evansu.cc)"), ), - g.Style().SetColor(ig.StyleColorBorder,sha256_color).To( - g.InputText("##cs_sha256",&cs_sha256).Size(-1).Flags(g.InputTextFlags_ReadOnly), - ), - - // SHA3-256 - //g.Dummy(10,0), - g.Row( - g.Checkbox("SHA3-256:",&sha3_256_selected), - g.Dummy(-45,0), - g.Button("Copy##sha3_256").Size(36,0).OnClick(func(){ - clipboard.WriteAll(cs_sha3_256) - }), - ), - g.Style().SetColor(ig.StyleColorBorder,sha3_256_color).To( - g.InputText("##cs_sha3_256",&cs_sha3_256).Size(-1).Flags(g.InputTextFlags_ReadOnly), - ), - - // BLAKE2b - //g.Dummy(10,0), - g.Row( - g.Checkbox("BLAKE2b:",&blake2b_selected), - g.Dummy(-45,0), - g.Button("Copy##blake2b").Size(36,0).OnClick(func(){ - clipboard.WriteAll(cs_blake2b) - }), - ), - g.Style().SetColor(ig.StyleColorBorder,blake2b_color).To( - g.InputText("##cs_blake2b",&cs_blake2b).Size(-1).Flags(g.InputTextFlags_ReadOnly), - ), - - // BLAKE2s - //g.Dummy(10,0), - g.Row( - g.Checkbox("BLAKE2s:",&blake2s_selected), - g.Dummy(-45,0), - g.Button("Copy##blake2s").Size(36,0).OnClick(func(){ - clipboard.WriteAll(cs_blake2s) - }), - ), - g.Style().SetColor(ig.StyleColorBorder,blake2s_color).To( - g.InputText("##cs_blake2s",&cs_blake2s).Size(-1).Flags(g.InputTextFlags_ReadOnly), - ), - - // BLAKE3 - //g.Dummy(10,0), - g.Row( - g.Checkbox("BLAKE3:",&blake3_selected), - g.Dummy(-45,0), - g.Button("Copy##blake3").Size(36,0).OnClick(func(){ - clipboard.WriteAll(cs_blake3) - }), - ), - g.Style().SetColor(ig.StyleColorBorder,blake3_color).To( - g.InputText("##cs_blake3",&cs_blake3).Size(-1).Flags(g.InputTextFlags_ReadOnly), - ), - - // Input box for validating checksum - //g.Dummy(10,0), - g.Label("Validate a checksum:"), - g.InputText("##cs_validate",&cs_validate).Size(-1).OnChange(func(){ - md5_color = color.RGBA{0x10,0x10,0x10,255} - sha1_color = color.RGBA{0x10,0x10,0x10,255} - sha256_color = color.RGBA{0x10,0x10,0x10,255} - sha3_256_color = color.RGBA{0x10,0x10,0x10,255} - blake2b_color = color.RGBA{0x10,0x10,0x10,255} - blake2s_color = color.RGBA{0x10,0x10,0x10,255} - blake3_color = color.RGBA{0x10,0x10,0x10,255} - if cs_validate==""{ - return - } - if cs_validate==cs_md5{ - md5_color = color.RGBA{0x00,0xff,0x00,255} - }else if cs_validate==cs_sha1{ - sha1_color = color.RGBA{0x00,0xff,0x00,255} - }else if cs_validate==cs_sha256{ - sha256_color = color.RGBA{0x00,0xff,0x00,255} - }else if cs_validate==cs_sha3_256{ - sha3_256_color = color.RGBA{0x00,0xff,0x00,255} - }else if cs_validate==cs_blake2b{ - blake2b_color = color.RGBA{0x00,0xff,0x00,255} - }else if cs_validate==cs_blake2s{ - blake2s_color = color.RGBA{0x00,0xff,0x00,255} - }else if cs_validate==cs_blake3{ - blake3_color = color.RGBA{0x00,0xff,0x00,255} - } - g.Update() - }), - - // Progress bar - //g.Dummy(10,0), - g.Label("Progress:"), - g.ProgressBar(cs_progress).Size(-1,0), - ), - g.TabItem("About").Layout( - // Update 'tab' to indicate active tab - g.Custom(func(){ - if g.IsItemActive(){ - tab = 3 - } - }), - //g.Dummy(30,0), - g.Label("Picocrypt "+version+", created by Evan Su (https://evansu.cc)"), - ), - ), + ).Build() + }), ), ) - if working{ - g.SingleWindow("Working..").IsOpen(&working).Layout( - //g.Dummy(30,0), - g.Label("Tips:"), - g.Label(" - Choose a strong password with more than 16 characters."), - g.Label(" - Use a unique password that isn't used anywhere else."), - g.Label(" - Trust no one but yourself and never give out your key."), - g.Label(" - For highly sensitive files, encrypt them while offline."), - g.Label(" - An antivirus can be beneficial to prevent keyloggers."), - g.Label(" - Encrypt your root filesystem for maximal security."), - g.Dummy(0,-50), - - - // Progress bar - g.Row( - g.ProgressBar(progress).Size(-54,0).Overlay(progressInfo), - g.Dummy(-59,0), - g.Button("Cancel").Size(50,0).OnClick(func(){ - working = false - }), - ), - //g.Dummy(10,0), - g.Label(status), - ) - } } // Handle files dropped into Picocrypt by user func onDrop(names []string){ - _status = "" + _status = "Ready." recombine = false if tab==0{ // Clear variables @@ -507,7 +608,7 @@ func onDrop(names []string){ // Hide the ".pcv" label orLabel = "or" - outputWidth = 376 + outputWidth = 370 // There's only one dropped item if len(names)==1{ @@ -515,26 +616,26 @@ func onDrop(names []string){ // Check if dropped item is a file or a folder if stat.IsDir(){ + mode = "encrypt" folders++ inputLabel = "1 folder selected." // Add the folder onlyFolders = append(onlyFolders,names[0]) - // Set 'outputEntry' to 'Encrypted.zip' in the same directory - outputEntry = filepath.Join(filepath.Dir(names[0]),"Encrypted.zip") - - mode = "encrypt" - // Show the ".pcv" file extension - orLabel = ".pcv or" - outputWidth = 341 + // Set 'outputEntry' to 'Encrypted' + outputEntry = filepath.Join(filepath.Dir(names[0]),"Encrypted") + + // Show the ".zip.pcv" file extension + orLabel = ".zip.pcv or" + outputWidth = 317 }else{ files++ name := filepath.Base(names[0]) nums := []string{"0","1","2","3","4","5","6","7","8","9"} endsNum := false - for _,i := range(nums){ + for _,i := range nums{ if strings.HasSuffix(names[0],i){ endsNum = true } @@ -547,6 +648,7 @@ func onDrop(names []string){ inputLabel = name+" (will decrypt)" if isSplit{ + inputLabel = name+" (will recombine and decrypt)" ind := strings.Index(names[0],".pcv") names[0] = names[0][:ind] outputEntry = names[0] @@ -554,25 +656,62 @@ func onDrop(names []string){ }else{ outputEntry = names[0][:len(names[0])-4] } - // Open input file in read-only mode fin,_ := os.Open(names[0]) - // Read metadata and insert into box - fin.Read(make([]byte,133)) - tmp := make([]byte,138) + // Use regex to test if input is a valid Picocrypt volume + tmp := make([]byte,30) fin.Read(tmp) - tmp = rsDecode(tmp,rs10_128,10) - metadataLength,_ := strconv.Atoi(string(tmp)) - //fmt.Println(metadataLength) - tmp = make([]byte,metadataLength) - fin.Read(tmp) - metadata = string(tmp) + if string(tmp[:5])=="v1.13"{ + resetUI() + _status = "Please use Picocrypt v1.13 to decrypt this file." + _status_color = color.RGBA{0xff,0x00,0x00,255} + fin.Close() + return + } + if valid,_:=regexp.Match(`^v\d\.\d{2}.{10}0?\d+`,tmp);!valid{ + resetUI() + _status = "This doesn't seem to be a Picocrypt volume." + _status_color = color.RGBA{0xff,0x00,0x00,255} + fin.Close() + return + } + fin.Seek(0,0) - flags := make([]byte,133) + // Read metadata and insert into box + var err error + fin.Read(make([]byte,15)) + tmp = make([]byte,15) + fin.Read(tmp) + tmp,err = rsDecode(rs5,tmp) + + if err==nil{ + metadataLength,_ := strconv.Atoi(string(tmp)) + tmp = make([]byte,metadataLength*3) + fin.Read(tmp) + metadata = "" + + for i:=0;i0{ - for _,name := range(onlyFolders){ + for _,name := range onlyFolders{ filepath.Walk(name,func(path string,_ os.FileInfo,_ error) error{ stat,_ := os.Stat(path) if !stat.IsDir(){ @@ -652,51 +790,76 @@ func onDrop(names []string){ } } }else if tab==1{ - go shred(names,true) - }else if tab==2{ go generateChecksums(names[0]) + }else if tab==2{ + go shred(names,true) } // Update the UI - g.Update() + giu.Update() } // Start encryption/decryption func work(){ - debug.FreeOSMemory() // Set some variables + status = "Starting..." working = true - //headerBroken := false - //reedsoloFixed := 0 - //reedsoloErrors := 0 + padded := false var salt []byte + var hkdfSalt []byte + var serpentSalt []byte var nonce []byte var keyHash []byte var _keyHash []byte var khash []byte - var khash_hash []byte + var khash_hash []byte = make([]byte,32) var _khash_hash []byte - var nonces []byte - _fast = fast + var fileMac []byte // Set the output file based on mode if mode=="encrypt"{ - // Compress files into a zip archive + status = "Combining files..." + + // "Tar" files into a zip archive with a compression level of 0 (store) if len(allFiles)>1||len(onlyFolders)>0{ - rootDir := filepath.Dir(outputEntry) - inputFile = outputEntry - fmt.Println(inputFile) + var rootDir string + if len(onlyFolders)>0{ + rootDir = filepath.Dir(onlyFolders[0]) + }else{ + rootDir = filepath.Dir(onlyFiles[0]) + } + + inputFile = outputEntry+".zip" + outputFile = inputFile+".pcv" file,_ := os.Create(inputFile) w := zip.NewWriter(file) - for _,path := range(allFiles){ + for i,path := range allFiles{ + if !working{ + w.Close() + file.Close() + os.Remove(inputFile) + _status = "Operation cancelled by user." + _status_color = color.RGBA{0xff,0xff,0xff,255} + return + } + progressInfo = fmt.Sprintf("%d/%d",i,len(allFiles)) + progress = float32(i)/float32(len(allFiles)) + giu.Update() if path==inputFile{ continue } stat,_ := os.Stat(path) header,_ := zip.FileInfoHeader(stat) - header.Name = strings.Replace(path,rootDir,"",1) - header.Method = zip.Deflate + header.Name = strings.TrimPrefix(path,rootDir) + + // When Windows contradicts itself :) + if runtime.GOOS=="windows"{ + header.Name = strings.ReplaceAll(header.Name,"\\","/") + header.Name = strings.TrimPrefix(header.Name,"/") + } + + header.Method = zip.Store writer,_ := w.CreateHeader(header) file,_ := os.Open(path) io.Copy(writer,file) @@ -738,7 +901,7 @@ func work(){ fin.Close() progressInfo = fmt.Sprintf("%d/%d",i,total) progress = float32(i)/float32(total) - g.Update() + giu.Update() } fout.Close() outputFile = outputEntry @@ -746,22 +909,27 @@ func work(){ progressInfo = "" } - fmt.Println(inputFile) stat,_ := os.Stat(inputFile) total := stat.Size() - fmt.Println(total) + if mode=="decrypt"{ + total -= 789 + } + + // XChaCha20's max message size is 256 GiB + if total>256*1073741824{ + _status = "Total size is larger than 256 GiB, XChaCha20's limit." + _status_color = color.RGBA{0xff,0x00,0x00,255} + return + } // Open input file in read-only mode fin,_ := os.Open(inputFile) - var fout *os.File - - fmt.Println(mode) // If encrypting, generate values; If decrypting, read values from file if mode=="encrypt"{ status = "Generating values..." - g.Update() + giu.Update() fout,_ = os.OpenFile( outputFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, @@ -770,115 +938,155 @@ func work(){ // Argon2 salt and XChaCha20 nonce salt = make([]byte,16) + hkdfSalt = make([]byte,32) + serpentSalt = make([]byte,16) nonce = make([]byte,24) // Write version to file - fout.Write(rsEncode([]byte(version),rs5_128,133)) + fout.Write(rsEncode(rs5,[]byte(version))) // Encode the length of the metadata with Reed-Solomon - metadataLength := []byte(fmt.Sprintf("%010d",len(metadata))) - //fmt.Println("metadataLength:",metadataLength) - metadataLength = rsEncode(metadataLength,rs10_128,138) + metadataLength := []byte(fmt.Sprintf("%05d",len(metadata))) + metadataLength = rsEncode(rs5,metadataLength) // Write the length of the metadata to file fout.Write(metadataLength) - - // Write the actual metadata - fout.Write([]byte(metadata)) + + // Reed-Solomon-encode the metadata and write to file + for _,i := range []byte(metadata){ + fout.Write(rsEncode(rs1,[]byte{i})) + } flags := make([]byte,5) if fast{ flags[0] = 1 } - if keyfile{ + if paranoid{ flags[1] = 1 } - //fmt.Println("flags:",flags) - flags = rsEncode(flags,rs5_128,133) + if keyfile{ + flags[2] = 1 + } + if reedsolo{ + flags[3] = 1 + } + if total%1048576>=1048448{ + flags[4] = 1 + } + flags = rsEncode(rs5,flags) fout.Write(flags) - // Fill salt and nonce with Go's CSPRNG + // Fill salts and nonce with Go's CSPRNG rand.Read(salt) + rand.Read(hkdfSalt) + rand.Read(serpentSalt) rand.Read(nonce) - - fmt.Println("salt: ",salt) - fmt.Println("nonce: ",nonce) // Encode salt with Reed-Solomon and write to file - _salt := rsEncode(salt,rs16_128,144) + _salt := rsEncode(rs16,salt) fout.Write(_salt) + // Encode HKDF salt with Reed-Solomon and write to file + _hkdfSalt := rsEncode(rs32,hkdfSalt) + fout.Write(_hkdfSalt) + + // Encode Serpent salt with Reed-Solomon and write to file + _serpentSalt := rsEncode(rs16,serpentSalt) + fout.Write(_serpentSalt) + // Encode nonce with Reed-Solomon and write to file - tmp := rsEncode(nonce,rs24_128,152) - fout.Write(tmp) + _nonce := rsEncode(rs24,nonce) + fout.Write(_nonce) // Write placeholder for hash of key fout.Write(make([]byte,192)) // Write placeholder for hash of hash of keyfile - fout.Write(make([]byte,160)) + fout.Write(make([]byte,96)) - - pairs := int(math.Ceil(float64(total)/1048576)) - - offset := 152*pairs+144 - - // Write placeholder for nonce/Poly1305 pairs - fout.Write(make([]byte,offset)) + // Write placeholder for HMAC-BLAKE2b/HMAC-SHA3 of file + fout.Write(make([]byte,192)) }else{ + var err1 error + var err2 error + var err3 error + var err4 error + var err5 error + var err6 error + var err7 error + var err8 error + var err9 error + var err10 error + status = "Reading values..." - g.Update() - version := make([]byte,133) + giu.Update() + version := make([]byte,15) fin.Read(version) - version = rsDecode(version,rs5_128,5) + _,err1 = rsDecode(rs5,version) - tmp := make([]byte,138) + tmp := make([]byte,15) fin.Read(tmp) - tmp = rsDecode(tmp,rs10_128,10) + tmp,err2 = rsDecode(rs5,tmp) metadataLength,_ := strconv.Atoi(string(tmp)) - //fmt.Println("metadataLength",metadataLength) - fin.Read(make([]byte,metadataLength)) + fin.Read(make([]byte,metadataLength*3)) - flags := make([]byte,133) + flags := make([]byte,15) fin.Read(flags) - flags = rsDecode(flags,rs5_128,5) - //fmt.Println("flags",flags) + flags,err3 = rsDecode(rs5,flags) fast = flags[0]==1 - keyfile = flags[1]==1 + paranoid = flags[1]==1 + keyfile = flags[2]==1 + reedsolo = flags[3]==1 + padded = flags[4]==1 - salt = make([]byte,144) + salt = make([]byte,48) fin.Read(salt) - salt = rsDecode(salt,rs16_128,16) + salt,err4 = rsDecode(rs16,salt) + + hkdfSalt = make([]byte,96) + fin.Read(hkdfSalt) + hkdfSalt,err5 = rsDecode(rs32,hkdfSalt) + + serpentSalt = make([]byte,48) + fin.Read(serpentSalt) + serpentSalt,err6 = rsDecode(rs16,serpentSalt) - nonce = make([]byte,152) + nonce = make([]byte,72) fin.Read(nonce) - nonce = rsDecode(nonce,rs24_128,24) - - fmt.Println("salt: ",salt) - fmt.Println("nonce: ",nonce) + nonce,err7 = rsDecode(rs24,nonce) _keyHash = make([]byte,192) fin.Read(_keyHash) - _keyHash = rsDecode(_keyHash,rs64_128,64) - //fmt.Println("keyHash",keyHash) + _keyHash,err8 = rsDecode(rs64,_keyHash) - _khash_hash = make([]byte,160) + _khash_hash = make([]byte,96) fin.Read(_khash_hash) - _khash_hash = rsDecode(_khash_hash,rs32_128,32) - //fmt.Println("crcHash",crcHash) - - _tmp := math.Ceil(float64(total-int64(metadataLength+1196))/float64(1048744)) - nonces = make([]byte,int(_tmp*152)+144) - fin.Read(nonces) - //fmt.Println("Nonces: ",nonces) + _khash_hash,err9 = rsDecode(rs32,_khash_hash) + + fileMac = make([]byte,192) + fin.Read(fileMac) + fileMac,err10 = rsDecode(rs64,fileMac) + + // Is there a better way? + if err1!=nil||err2!=nil||err3!=nil||err4!=nil||err5!=nil||err6!=nil||err7!=nil||err8!=nil||err9!=nil||err10!=nil{ + if keep{ + kept = true + }else{ + _status = "The header is corrupt and the input file cannot be decrypted." + _status_color = color.RGBA{0xff,0x00,0x00,255} + fin.Close() + return + } + } } - - g.Update() + status = "Deriving key..." progress = 0 + progressInfo = "" + giu.Update() - // Derive encryption/decryption key + // Derive encryption/decryption key and subkeys var key []byte if fast{ key = argon2.IDKey( @@ -899,20 +1107,25 @@ func work(){ 32, )[:] } - - fmt.Println("key",key) + if !working{ _status = "Operation cancelled by user." _status_color = color.RGBA{0xff,0xff,0xff,255} - fast = _fast - debug.FreeOSMemory() + fin.Close() + fout.Close() + if mode=="encrypt"&&(len(allFiles)>1||len(onlyFolders)>0){ + os.Remove(outputEntry+".zip") + } + if recombine{ + os.Remove(inputFile) + } + os.Remove(outputFile) return } if keyfile{ kin,_ := os.Open(keyfilePath) kstat,_ := os.Stat(keyfilePath) - fmt.Println(kstat.Size()) kbytes := make([]byte,kstat.Size()) kin.Read(kbytes) kin.Close() @@ -923,45 +1136,33 @@ func work(){ khash_sha3 := sha3.New256() khash_sha3.Write(khash) khash_hash = khash_sha3.Sum(nil) - fmt.Println("khash",khash) - fmt.Println("khash_hash",khash_hash) } - //key = make([]byte,32) - fmt.Println("output",outputFile) sha3_512 := sha3.New512() sha3_512.Write(key) keyHash = sha3_512.Sum(nil) - //fmt.Println("keyHash: ",keyHash) - // Check is password is correct + // Validate password and/or keyfile if mode=="decrypt"{ keyCorrect := true keyfileCorrect := true var tmp bool - for i,j := range(_keyHash){ - if keyHash[i]!=j{ - keyCorrect = false - break - } + if subtle.ConstantTimeCompare(keyHash,_keyHash)==0{ + keyCorrect = false } if keyfile{ - for i,j := range(_khash_hash){ - if khash_hash[i]!=j{ - keyfileCorrect = false - break - } + if subtle.ConstantTimeCompare(khash_hash,_khash_hash)==0{ + keyfileCorrect = false } tmp = !keyCorrect||!keyfileCorrect }else{ tmp = !keyCorrect } - if tmp{ + if tmp||keep{ if keep{ kept = true }else{ fin.Close() - working = false if !keyCorrect{ _status = "The provided password is incorrect." }else{ @@ -969,235 +1170,238 @@ func work(){ } _status_color = color.RGBA{0xff,0x00,0x00,255} key = nil - fast = _fast if recombine{ os.Remove(inputFile) } - debug.FreeOSMemory() + os.Remove(outputFile) return } } - fout,_ = os.OpenFile( - outputFile, - os.O_RDWR|os.O_CREATE|os.O_TRUNC, - 0755, - ) + fout,_ = os.Create(outputFile) } if keyfile{ // XOR key and keyfile tmp := key key = make([]byte,32) - for i,_ := range(key){ + for i,_ := range key{ key[i] = tmp[i]^khash[i] } - fmt.Println("key",key) } - done := 0 counter := 0 startTime := time.Now() + chacha20,_ := chacha20.NewUnauthenticatedCipher(key,nonce) - cipher,_ := chacha20poly1305.NewX(key) - - if mode=="decrypt"{ - _mac := nonces[len(nonces)-144:] - _mac = rsDecode(_mac,rs16_128,16) - //fmt.Println("_mac ",_mac) - nonces = nonces[:len(nonces)-144] - var tmp []byte - var chunk []byte - for i,j := range(nonces){ - chunk = append(chunk,j) - if (i+1)%152==0{ - chunk = rsDecode(chunk,rs24_128,24) - for _,k := range(chunk){ - tmp = append(tmp,k) + // Use HKDF-SHA3 to generate a subkey + var mac hash.Hash + subkey := make([]byte,32) + hkdf := hkdf.New(sha3.New256,key,hkdfSalt,nil) + hkdf.Read(subkey) + if fast{ + // Keyed BLAKE2b + mac,_ = blake2b.New512(subkey) + }else{ + // HMAC-SHA3 + mac = hmac.New(sha3.New512,subkey) + } + + // Generate another subkey and cipher (not used unless paranoid mode is checked) + serpentKey := make([]byte,32) + hkdf.Read(serpentKey) + _serpent,_ := serpent.NewCipher(serpentKey) + serpentCTR := cipher.NewCTR(_serpent,serpentSalt) + + for{ + if !working{ + _status = "Operation cancelled by user." + _status_color = color.RGBA{0xff,0xff,0xff,255} + fin.Close() + fout.Close() + if mode=="encrypt"&&(len(allFiles)>1||len(onlyFolders)>0){ + os.Remove(outputEntry+".zip") + } + if recombine{ + os.Remove(inputFile) + } + os.Remove(outputFile) + return + } + + var data []byte + if mode=="decrypt"&&reedsolo{ + data = make([]byte,1114112) + }else{ + data = make([]byte,1048576) + } + + size,err := fin.Read(data) + if err!=nil{ + break + } + data = data[:size] + _data := make([]byte,len(data)) + + // "Actual" encryption is done in the next couple of lines + if mode=="encrypt"{ + if paranoid{ + serpentCTR.XORKeyStream(_data,data) + copy(data,_data) + } + + chacha20.XORKeyStream(_data,data) + mac.Write(_data) + + if reedsolo{ + copy(data,_data) + _data = nil + if len(data)==1048576{ + for i:=0;i<1048576;i+=128{ + tmp := data[i:i+128] + tmp = rsEncode(rs128,tmp) + _data = append(_data,tmp...) + } + }else{ + chunks := math.Floor(float64(len(data))/128) + for i:=0;float64(i)=int(total)&&padded{ + tmp = unpad(tmp) + } + data = append(data,tmp...) + } + }else{ + chunks := len(_data)/136-1 + for i:=0;i1{ + progress = 1 + } + + progressInfo = fmt.Sprintf("%.2f%%",progress*100) + status = fmt.Sprintf("Working at %.2f MB/s (ETA: %s)",speed,humanize(eta)) + giu.Update() + } + + if mode=="encrypt"{ + // Seek back to header and write important data + fout.Seek(int64(309+len(metadata)*3),0) + fout.Write(rsEncode(rs64,keyHash)) + fout.Write(rsEncode(rs32,khash_hash)) + fout.Write(rsEncode(rs64,mac.Sum(nil))) + }else{ + // Validate the authenticity of decrypted data + if subtle.ConstantTimeCompare(mac.Sum(nil),fileMac)==0{ if keep{ kept = true }else{ fin.Close() fout.Close() - working = false - _status = "The file is either corrupted or intentionally modified." - _status_color = color.RGBA{0xff,0x00,0x00,255} - fast = _fast - debug.FreeOSMemory() + broken() return } } - //fmt.Println("UNENCRYPTED NONCES: ",nonces) - } - for{ - if !working{ - fin.Close() - fout.Close() - os.Remove(outputFile) - _status = "Operation cancelled by user." - _status_color = color.RGBA{0xff,0xff,0xff,255} - fast = _fast - debug.FreeOSMemory() - return - } - //fmt.Println("Encrypt/decrypt loop") - var _data []byte - var data []byte - var _nonce []byte - if mode=="encrypt"{ - _data = make([]byte,1048576) - }else{ - _data = make([]byte,1048592) - } - - size,err := fin.Read(_data) - if err!=nil{ - break - } - data = _data[:size] - - - if mode=="encrypt"{ - _nonce = make([]byte,24) - rand.Read(_nonce) - for _,i := range(_nonce){ - nonces = append(nonces,i) - } - }else{ - _nonce = nonces[counter*24:counter*24+24] - } - - //fmt.Println("Data nonce: ",_nonce) - //fmt.Println("Data: ",data) - if mode=="encrypt"{ - if fast{ - data = cipher.Seal(nil,_nonce,data,nil) - fout.Write(data) - //crc.Write(data) - }else{ - mac,data := monocypher.Lock(data,_nonce,key) - fout.Write(data) - fout.Write(mac) - //crc.Write(data) - //crc.Write(mac) - } - - //fout.Write(data) - }else{ - //fmt.Println("DECODE LOOP") - //crc.Write(data) - if fast{ - data,err = cipher.Open(nil,_nonce,data,nil) - if err!=nil{ - if keep{ - kept = true - mac := data[len(data)-16:] - data = data[:len(data)-16] - data,_ = monocypher.Unlock(data,_nonce,key,mac) - }else{ - fin.Close() - fout.Close() - broken() - fast = _fast - debug.FreeOSMemory() - return - } - } - }else{ - //crc.Write(data) - mac := data[len(data)-16:] - data = data[:len(data)-16] - var authentic bool - data,authentic = monocypher.Unlock(data,_nonce,key,mac) - if !authentic{ - if keep{ - kept = true - }else{ - fin.Close() - fout.Close() - broken() - fast = _fast - debug.FreeOSMemory() - return - } - } - } - fout.Write(data) - //fmt.Println(authentic) - //fmt.Println("DECRYPTED DATA: ",data) - } - - done += 1048576 - counter++ - - progress = float32(done)/float32(total) - - elapsed:= float64(int64(time.Now().Sub(startTime)))/float64(1000000000) - - speed := (float64(done)/elapsed)/1000000 - eta := math.Abs(float64(total-int64(done))/(speed*1000000)) - - progressInfo = fmt.Sprintf("%.2f%%",progress*100) - - status = fmt.Sprintf("Working at %.2f MB/s (ETA: %.1fs)",speed,eta) - - g.Update() } - if mode=="encrypt"{ - //fmt.Println("'nonces' before RS: ",nonces) - fout.Seek(int64(700+len(metadata)),0) - fout.Write(rsEncode(keyHash,rs64_128,192)) - fout.Write(rsEncode(khash_hash,rs32_128,160)) - - _mac,tmp := monocypher.Lock(nonces,nonce,key) - fmt.Println(_mac) - //tmp := cipher.Seal(nil,nonce,nonces,nil) - //fmt.Println("ENCRYPTED NONCES: ",tmp) - //_mac := tmp[len(tmp)-16:] - - //tmp = tmp[:len(tmp)-16] - var chunk []byte - - for i,j := range(tmp){ - chunk = append(chunk,j) - if (i+1)%24==0{ - fout.Write(rsEncode(chunk,rs24_128,152)) - //fmt.Println(rsEncode(chunk,rs24_128,152)) - chunk = nil - } - } - fout.Write(rsEncode(_mac,rs16_128,144)) - - }else{ - //fmt.Println("crcHash: ",crcHash) - //fmt.Println("crc.Sum: ",crc.Sum(nil)) - } - fin.Close() fout.Close() + // Split files into chunks if split{ + var splitted []string status = "Splitting file..." stat,_ := os.Stat(outputFile) size := stat.Size() finished := 0 - var splitted []string - fmt.Println(size) chunkSize,_ := strconv.Atoi(splitSize) - fmt.Println(splitSelected) + + // User can choose KiB, MiB, and GiB if splitSelected==0{ chunkSize *= 1024 }else if splitSelected==1{ @@ -1207,13 +1411,9 @@ func work(){ } chunks := int(math.Ceil(float64(size)/float64(chunkSize))) fin,_ := os.Open(outputFile) + for i:=0;i1||len(onlyFolders)>0{ + os.Remove(outputEntry+".zip") + } os.Remove(outputFile) - fast = _fast - debug.FreeOSMemory() return } data = data[:read] @@ -1248,224 +1450,82 @@ func work(){ splitted = append(splitted,fmt.Sprintf("%s.%d",outputFile,i)) progress = float32(finished)/float32(chunks) progressInfo = fmt.Sprintf("%d/%d",finished,chunks) - g.Update() + giu.Update() } fin.Close() - if erase{ - status = "Shredding the unsplitted file..." + if shredTemp{ progressInfo = "" + status = "Shredding temporary files..." shred([]string{outputFile}[:],false) }else{ os.Remove(outputFile) } } + // Remove the temporary file used to combine a splitted Picocrypt volume if recombine{ os.Remove(inputFile) } - // Delete the temporary zip file - if len(allFiles)>1{ - if erase{ + // Delete the temporary zip file if user wishes + if len(allFiles)>1||len(onlyFolders)>0{ + if shredTemp{ progressInfo = "" status = "Shredding temporary files..." - shred([]string{outputEntry}[:],false) + giu.Update() + shred([]string{outputEntry+".zip"}[:],false) }else{ - os.Remove(outputEntry) + os.Remove(outputEntry+".zip") } } - fmt.Println("==============================") - if erase{ - progressInfo = "" - shred(onlyFiles,false) - shred(onlyFolders,false) - } - - - - resetUI() + + // If user chose to keep a corrupted/modified file, let them know if kept{ - _status = "The input is corrupted and/or modified. Please be careful." + _status = "The input file is corrupted and/or modified. Please be careful." _status_color = color.RGBA{0xff,0xff,0x00,255} }else{ _status = "Completed." _status_color = color.RGBA{0x00,0xff,0x00,255} } + + // Clear UI state working = false kept = false key = nil status = "Ready." - debug.FreeOSMemory() - fmt.Println("Exit goroutine") } -func shred(names []string,separate bool){ - shredTotal = 0 - shredDone = 0 - if separate{ - shredOverlay = "Shredding..." +// This function is run if an issue occurs during decryption +func broken(){ + _status = "The input file is either corrupted or intentionally modified." + _status_color = color.RGBA{0xff,0x00,0x00,255} + if recombine{ + os.Remove(inputFile) } - for _,name := range(names){ - filepath.Walk(name,func(path string,_ os.FileInfo,err error) error{ - if err!=nil{ - return nil - } - stat,_ := os.Stat(path) - if !stat.IsDir(){ - shredTotal++ - } - return nil - }) - } - for _,name := range(names){ - shredding = name - if runtime.GOOS=="linux"||runtime.GOOS=="darwin"{ - stat,_ := os.Stat(name) - if stat.IsDir(){ - var coming []string - filepath.Walk(name,func(path string,_ os.FileInfo,err error) error{ - if err!=nil{ - return nil - } - fmt.Println(path) - stat,_ := os.Stat(path) - if !stat.IsDir(){ - if len(coming)==128{ - var wg sync.WaitGroup - for i,j := range(coming){ - wg.Add(1) - go func(wg *sync.WaitGroup,id int,j string){ - defer wg.Done() - cmd := exec.Command("") - if runtime.GOOS=="linux"{ - cmd = exec.Command("shred","-ufvz","-n","3",j) - }else{ - cmd = exec.Command("rm","-rfP",j) - } - output,err := cmd.Output() - fmt.Println(err) - fmt.Println(output) - shredding = j - shredDone++ - shredUpdate(separate) - g.Update() - }(&wg,i,j) - - } - wg.Wait() - coming = nil - }else{ - coming = append(coming,path) - } - } - return nil - }) - fmt.Println(coming) - for _,i := range(coming){ - go func(){ - cmd := exec.Command("") - if runtime.GOOS=="linux"{ - cmd = exec.Command("shred","-ufvz","-n","3",i) - }else{ - cmd = exec.Command("rm","-rfP",i) - } - output,err := cmd.Output() - fmt.Println(err) - fmt.Println(output) - shredding = i - shredDone++ - shredUpdate(separate) - g.Update() - }() - } - os.RemoveAll(name) - }else{ - cmd := exec.Command("") - if runtime.GOOS=="linux"{ - cmd = exec.Command("shred","-ufvz","-n","3",name) - }else{ - cmd = exec.Command("rm","-rfP",name) - } - cmd.Run() - shredding = name+"/*" - shredDone++ - shredUpdate(separate) - } - }else if runtime.GOOS=="windows"{ - stat,_ := os.Stat(name) - if stat.IsDir(){ - filepath.Walk(name,func(path string,_ os.FileInfo,err error) error{ - if err!=nil{ - return nil - } - fmt.Println(path) - stat,_ := os.Stat(path) - if stat.IsDir(){ - t := 0 - files,_ := ioutil.ReadDir(path) - for _,f := range(files){ - if !f.IsDir(){ - t++ - } - } - shredDone += float32(t) - shredUpdate(separate) - tmp := filepath.Join(rootDir,"sdelete64.exe") - cmd := exec.Command(tmp,"*","-p","4") - cmd.Dir = path - cmd.Run() - shredding = strings.ReplaceAll(path,"\\","/")+"/*" - } - return nil - }) - os.RemoveAll(name) - }else{ - o,e := exec.Command(filepath.Join(rootDir,"sdelete64.exe"),name,"-p","4").Output() - fmt.Println(string(o),e) - shredDone++ - shredUpdate(separate) - } - } - fmt.Println(name) - g.Update() - } - /*if itemSelected==1&&runtime.GOOS=="windows"{ - cmd := exec.Command("cipher","/w:\""+name+"\"") - stdout,err := cmd.Output() - if err!=nil{ - fmt.Println(err) - } - fmt.Println(string(stdout)) - }*/ - shredding = "Ready." - shredProgress = 0 - shredOverlay = "" + os.Remove(outputFile) + giu.Update() } -func shredUpdate(separate bool){ - if separate{ - shredOverlay = fmt.Sprintf("%d/%d",int(shredDone),int(shredTotal)) - shredProgress = shredDone/shredTotal - }else{ - status = fmt.Sprintf("%d/%d",int(shredDone),int(shredTotal)) - progress = shredDone/shredTotal - } - g.Update() -} - -// Generate file checksums +// Generate file checksums (pretty straightforward) func generateChecksums(file string){ fin,_ := os.Open(file) + // Clear UI state cs_md5 = "" cs_sha1 = "" cs_sha256 = "" cs_sha3_256 = "" cs_blake2b = "" cs_blake2s = "" - cs_blake3 = "" + md5_color = color.RGBA{0x10,0x10,0x10,255} + sha1_color = color.RGBA{0x10,0x10,0x10,255} + sha256_color = color.RGBA{0x10,0x10,0x10,255} + sha3_256_color = color.RGBA{0x10,0x10,0x10,255} + blake2b_color = color.RGBA{0x10,0x10,0x10,255} + blake2s_color = color.RGBA{0x10,0x10,0x10,255} + cs_validate = "" if md5_selected{ cs_md5 = "Calculating..." @@ -1485,17 +1545,15 @@ func generateChecksums(file string){ if blake2s_selected{ cs_blake2s = "Calculating..." } - if blake3_selected{ - cs_blake3 = "Calculating..." - } + // Create the checksum objects crc_md5 := md5.New() crc_sha1 := sha1.New() crc_sha256 := sha256.New() crc_sha3_256 := sha3.New256() crc_blake2b,_ := blake2b.New256(nil) crc_blake2s,_ := blake2s.New256(nil) - crc_blake3 := blake3.New() + stat,_ := os.Stat(file) total := stat.Size() var done int64 = 0 @@ -1526,13 +1584,10 @@ func generateChecksums(file string){ if blake2s_selected{ crc_blake2s.Write(data) } - if blake3_selected{ - crc_blake3.Write(data) - } done += int64(size) cs_progress = float32(done)/float32(total) - g.Update() + giu.Update() } cs_progress = 0 if md5_selected{ @@ -1553,94 +1608,285 @@ func generateChecksums(file string){ if blake2s_selected{ cs_blake2s = hex.EncodeToString(crc_blake2s.Sum(nil)) } - if blake3_selected{ - cs_blake3 = hex.EncodeToString(crc_blake3.Sum(nil)) - } - g.Update() + + fin.Close() + giu.Update() } +// Recursively shred all file(s) and folder(s) passed in as 'names' +func shred(names []string,separate bool){ + shredTotal = 0 + shredDone = 0 -// Reset the UI to a clean state with no nothing selected + // 'separate' is true if this function is being called from the encryption/decryption tab + if separate{ + shredOverlay = "Shredding..." + } + + // Walk through directories to get the total number of files for statistics + for _,name := range names{ + filepath.Walk(name,func(path string,_ os.FileInfo,err error) error{ + if err!=nil{ + return nil + } + stat,_ := os.Stat(path) + if !stat.IsDir(){ + shredTotal++ + } + return nil + }) + } + + for _,name := range names{ + shredding = name + + // Linux and macOS need a command with similar syntax and usage, so they're combined + if runtime.GOOS=="linux"||runtime.GOOS=="darwin"{ + stat,_ := os.Stat(name) + if stat.IsDir(){ + var coming []string + + // Walk the folder recursively + filepath.Walk(name,func(path string,_ os.FileInfo,err error) error{ + if err!=nil{ + return nil + } + stat,_ := os.Stat(path) + if !stat.IsDir(){ + if len(coming)==128{ + // Use a WaitGroup to parallelize shredding + var wg sync.WaitGroup + for i,j := range coming{ + wg.Add(1) + go func(wg *sync.WaitGroup,id int,j string){ + defer wg.Done() + cmd := exec.Command("") + if runtime.GOOS=="linux"{ + cmd = exec.Command("shred","-ufvz","-n","3",j) + }else{ + cmd = exec.Command("rm","-rfP",j) + } + cmd.Run() + shredding = j + shredDone++ + shredUpdate(separate) + giu.Update() + }(&wg,i,j) + } + wg.Wait() + coming = nil + }else{ + coming = append(coming,path) + } + } + return nil + }) + for _,i := range coming{ + go func(){ + cmd := exec.Command("") + if runtime.GOOS=="linux"{ + cmd = exec.Command("shred","-ufvz","-n","3",i) + }else{ + cmd = exec.Command("rm","-rfP",i) + } + cmd.Run() + shredding = i + shredDone++ + shredUpdate(separate) + giu.Update() + }() + } + os.RemoveAll(name) + }else{ // The path is a file, not a directory, so just shred it + cmd := exec.Command("") + if runtime.GOOS=="linux"{ + cmd = exec.Command("shred","-ufvz","-n","3",name) + }else{ + cmd = exec.Command("rm","-rfP",name) + } + cmd.Run() + shredding = name+"/*" + shredDone++ + shredUpdate(separate) + } + }else if runtime.GOOS=="windows"{ + stat,_ := os.Stat(name) + if stat.IsDir(){ + // Walk the folder recursively + filepath.Walk(name,func(path string,_ os.FileInfo,err error) error{ + if err!=nil{ + return nil + } + stat,_ := os.Stat(path) + if stat.IsDir(){ + t := 0 + files,_ := ioutil.ReadDir(path) + for _,f := range files{ + if !f.IsDir(){ + t++ + } + } + shredDone += float32(t) + shredUpdate(separate) + cmd := exec.Command(sdelete64path,"*","-p","4") + cmd.Dir = path + cmd.Run() + shredding = strings.ReplaceAll(path,"\\","/")+"/*" + } + return nil + }) + // sdelete64 doesn't delete the empty folder, so I'll do it manually + os.RemoveAll(name) + }else{ + exec.Command(sdelete64path,name,"-p","4").Run() + shredDone++ + shredUpdate(separate) + } + } + giu.Update() + } + + // Clear UI state + shredding = "Ready." + shredProgress = 0 + shredOverlay = "" +} + +// Update shredding statistics +func shredUpdate(separate bool){ + if separate{ + shredOverlay = fmt.Sprintf("%d/%d",int(shredDone),int(shredTotal)) + shredProgress = shredDone/shredTotal + }else{ + status = fmt.Sprintf("%d/%d",int(shredDone),int(shredTotal)) + progress = shredDone/shredTotal + } + giu.Update() +} + +// Reset the UI to a clean state with nothing selected or checked func resetUI(){ + mode = "" inputLabel = "Drag and drop file(s) and folder(s) into this window." outputEntry = "" orLabel = "or" - outputWidth = 376 + outputWidth = 370 password = "" cPassword = "" keyfilePrompt = "Keyfile (optional):" keyfileLabel = "Use a keyfile" keyfile = false metadata = "" + shredTemp = false keep = false - erase = false reedsolo = false split = false splitSize = "" fast = false + paranoid = false progress = 0 progressInfo = "" - _status = "" + _status = "Ready." _status_color = color.RGBA{0xff,0xff,0xff,255} - g.Update() + giu.Update() } -func broken(){ - working = false - _status = "The file is either corrupted or intentionally modified." - _status_color = color.RGBA{0xff,0x00,0x00,255} - os.Remove(outputFile) - debug.FreeOSMemory() +// Reed-Solomon encoder +func rsEncode(rs *infectious.FEC,data []byte) []byte{ + var res []byte + rs.Encode(data,func(s infectious.Share){ + res = append(res,s.DeepCopy().Data[0]) + }) + return res } -func rsEncode(data []byte,encoder reedsolomon.Encoder,size int) []byte{ - shards,_ := encoder.Split(data) - encoder.Encode(shards) - tmp := make([]byte,size) - for i,shard := range(shards){ - tmp[i] = shard[0] +// Reed-Solomon decoder +func rsDecode(rs *infectious.FEC,data []byte) ([]byte,error){ + tmp := make([]infectious.Share,rs.Total()) + for i:=0;i