diff --git a/cli/v2/picocrypt/go.mod b/cli/v2/picocrypt/go.mod index 7e2db9c..43ea7d0 100644 --- a/cli/v2/picocrypt/go.mod +++ b/cli/v2/picocrypt/go.mod @@ -5,8 +5,13 @@ go 1.22.2 require ( github.com/HACKERALERT/infectious v0.0.0-20240424200929-b9ce72346a19 github.com/HACKERALERT/serpent v0.0.0-20210716182301-293b29869c66 + github.com/schollz/progressbar/v3 v3.14.2 golang.org/x/crypto v0.22.0 golang.org/x/term v0.19.0 ) -require golang.org/x/sys v0.19.1-0.20240416221847-9a28524796a5 // indirect +require ( + github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/sys v0.19.1-0.20240416221847-9a28524796a5 // indirect +) diff --git a/cli/v2/picocrypt/go.sum b/cli/v2/picocrypt/go.sum index 829b126..aa9a423 100644 --- a/cli/v2/picocrypt/go.sum +++ b/cli/v2/picocrypt/go.sum @@ -2,9 +2,28 @@ github.com/HACKERALERT/infectious v0.0.0-20240424200929-b9ce72346a19 h1:C5t561XX github.com/HACKERALERT/infectious v0.0.0-20240424200929-b9ce72346a19/go.mod h1:bTnpEk9zNS1sVKg5TRvLkuSEGVqH0+LRfcMurPtcJvY= github.com/HACKERALERT/serpent v0.0.0-20210716182301-293b29869c66 h1:YDpFq+y6mRcu97rn/rhYg8u8FdeO0wzTuLgM2gVkA+c= github.com/HACKERALERT/serpent v0.0.0-20210716182301-293b29869c66/go.mod h1:d/+9q3sIxtIyOgHNgFGr3yGBKKVn5h3vL4hV1qlmoLs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/schollz/progressbar/v3 v3.14.2 h1:EducH6uNLIWsr560zSV1KrTeUb/wZGAHqyMFIEa99ks= +github.com/schollz/progressbar/v3 v3.14.2/go.mod h1:aQAZQnhF4JGFtRJiw/eobaXpsqpVQAftEQ+hLGXaRc4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.1-0.20240416221847-9a28524796a5 h1:0exPaeAtAlmNHCcRJc+hETS3/TcMV+yjoHhlp4+Ff3E= golang.org/x/sys v0.19.1-0.20240416221847-9a28524796a5/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= diff --git a/cli/v2/picocrypt/main.go b/cli/v2/picocrypt/main.go index 61f3a3d..c703867 100644 --- a/cli/v2/picocrypt/main.go +++ b/cli/v2/picocrypt/main.go @@ -1,554 +1,586 @@ -package main - -import ( - "archive/zip" - "bytes" - "crypto/cipher" - "crypto/hmac" - "crypto/rand" - "crypto/subtle" - "flag" - "fmt" - "hash" - "io" - "math" - "os" - "path/filepath" - "strconv" - "strings" - - "github.com/HACKERALERT/infectious" - "github.com/HACKERALERT/serpent" - "golang.org/x/crypto/argon2" - "golang.org/x/crypto/blake2b" - "golang.org/x/crypto/chacha20" - "golang.org/x/crypto/hkdf" - "golang.org/x/crypto/sha3" - "golang.org/x/term" -) - -var KiB = 1 << 10 -var MiB = 1 << 20 -var GiB = 1 << 30 -var TiB = 1 << 40 -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) - -func rsEncode(rs *infectious.FEC, data []byte) []byte { - res := make([]byte, rs.Total()) - rs.Encode(data, func(s infectious.Share) { - res[s.Number] = s.Data[0] - }) - return res -} - -func rsDecode(rs *infectious.FEC, data []byte) ([]byte, error) { - tmp := make([]infectious.Share, rs.Total()) - for i := 0; i < rs.Total(); i++ { - tmp[i].Number = i - tmp[i].Data = append(tmp[i].Data, data[i]) - } - res, err := rs.Decode(nil, tmp) - if err != nil { - if rs.Total() == 136 { - return data[:128], err - } - return data[:rs.Total()/3], err - } - return res, nil -} - -func pad(data []byte) []byte { - padLen := 128 - len(data)%128 - padding := bytes.Repeat([]byte{byte(padLen)}, padLen) - return append(data, padding...) -} - -func unpad(data []byte) []byte { - padLen := int(data[127]) - return data[:128-padLen] -} - -func work() int { - flag.Usage = func() { - fmt.Println("Usage: picocrypt [-p]aranoid [-r]eedsolo [ ...]") - fmt.Println("Items: can be a file (cat.png), folder (./src), or glob (*.txt)") - } - paranoid := flag.Bool("p", false, "") - reedsolo := flag.Bool("r", false, "") - flag.Parse() - - mode := "" - if flag.NArg() == 0 { - flag.Usage() - return 0 - } - if flag.NArg() == 1 { - if strings.HasSuffix(flag.Arg(0), ".pcv") { - mode = "decrypt" - } else { - mode = "encrypt" - } - } else { - mode = "encrypt" - for _, v := range flag.Args() { - if strings.HasSuffix(v, ".pcv") { - fmt.Println("Multiple items cannot contain volumes.") - return 1 - } - } - } - - fin_, fout_ := "", "" - if mode == "decrypt" { - fin_ = flag.Arg(0) - fout_ = strings.TrimSuffix(fin_, ".pcv") - } else { - stat, err := os.Stat(flag.Arg(0)) - if flag.NArg() == 1 && err == nil && !stat.IsDir() { - fin_ = flag.Arg(0) - fout_ = fin_ + ".pcv" - } else { - items := []string{} - for _, v := range flag.Args() { - if strings.Contains(v, "../") || strings.HasPrefix(v, "/") { - fmt.Println("Cannot encrypt outside of current directory.") - return 1 - } - matches, err := filepath.Glob(v) - if err != nil { - fmt.Println("Invalid glob pattern(s).") - return 1 - } - items = append(items, matches...) - } - files := []string{} - for _, v := range items { - stat, err := os.Stat(v) - if err != nil { - fmt.Println("Cannot access input(s).") - return 1 - } - if !stat.IsDir() { - files = append(files, v) - } else { - filepath.Walk(v, func(path string, _ os.FileInfo, _ error) error { - stat, err := os.Stat(path) - if err == nil && !stat.IsDir() { - files = append(files, path) - } - return nil - }) - } - } - if len(files) == 0 { - fmt.Println("Nothing to encrypt.") - return 1 - } - - dir, err := os.Getwd() - if err != nil { - fmt.Println("Cannot get current working directory.") - return 1 - } - file, err := os.CreateTemp("", "") - if err != nil { - fmt.Println("Cannot create temporary file.") - return 1 - } - writer := zip.NewWriter(file) - for _, path := range files { - stat, err := os.Stat(path) - if err != nil { - continue - } - header, err := zip.FileInfoHeader(stat) - if err != nil { - continue - } - abs, err := filepath.Abs(path) - if err != nil { - continue - } - abs = filepath.ToSlash(abs) - header.Name = strings.TrimPrefix(abs, filepath.ToSlash(dir)) - header.Name = strings.TrimPrefix(header.Name, "/") - header.Method = zip.Deflate - entry, err := writer.CreateHeader(header) - if err != nil { - continue - } - fin_, err := os.Open(path) - if err != nil { - writer.Close() - file.Close() - os.Remove(file.Name()) - fmt.Println("Read access to input(s) denied.") - return 1 - } - fmt.Println("Compressing:", abs) - _, err = io.Copy(entry, fin_) - fin_.Close() - if err != nil { - writer.Close() - file.Close() - os.Remove(file.Name()) - fmt.Println("Insufficient disk space.") - return 1 - } - } - writer.Close() - file.Close() - fin_ = file.Name() - fout_ = "encrypted.zip.pcv" - defer os.Remove(file.Name()) - } - } - - var password []byte - var cpassword []byte - var err error - if mode == "encrypt" { - fmt.Print("Password: ") - password, err = term.ReadPassword(int(os.Stdin.Fd())) - if err != nil { - fmt.Println("\nError reading password.") - return 1 - } - fmt.Print("\nConfirm: ") - cpassword, err = term.ReadPassword(int(os.Stdin.Fd())) - if err != nil { - fmt.Println("\nError reading password.") - return 1 - } - if !bytes.Equal(password, cpassword) { - fmt.Println("\nPasswords don't match.") - return 1 - } - fmt.Println() - } else { - fmt.Print("Password: ") - password, err = term.ReadPassword(int(os.Stdin.Fd())) - if err != nil { - fmt.Println("\nError reading password.") - return 1 - } - fmt.Println() - } - - var padded bool - var salt []byte // Argon2 salt, 16 bytes - var hkdfSalt []byte // HKDF-SHA3 salt, 32 bytes - var serpentIV []byte // Serpent IV, 16 bytes - var nonce []byte // 24-byte XChaCha20 nonce - var keyHash []byte // SHA3-512 hash of encryption key - var keyHashRef []byte // Same as 'keyHash', but used for comparison - var authTag []byte // 64-byte authentication tag (BLAKE2b or HMAC-SHA3) - - fin, err := os.Open(fin_) - if err != nil { - fmt.Println("Error accessing input file.") - return 1 - } - _, err = os.Stat(fout_) - if err == nil { - fmt.Println("Output file already exists.") - return 1 - } - fout, err := os.Create(fout_) - if err != nil { - fmt.Println("Error creating output file.") - return 1 - } - - stat, err := os.Stat(fin_) - if err != nil { - fmt.Println("Error accessing input file.") - return 1 - } - total := stat.Size() - if mode == "encrypt" { - fmt.Println("Generating volume header...") - errs := make([]error, 11) - salt = make([]byte, 16) - hkdfSalt = make([]byte, 32) - serpentIV = make([]byte, 16) - nonce = make([]byte, 24) - _, errs[0] = fout.Write(rsEncode(rs5, []byte("v1.34"))) - commentsLength := []byte(fmt.Sprintf("%05d", 0)) - _, errs[1] = fout.Write(rsEncode(rs5, commentsLength)) - flags := make([]byte, 5) - if *paranoid { - flags[0] = 1 - } - if *reedsolo { - flags[3] = 1 - } - if total%int64(MiB) >= int64(MiB)-128 { - flags[4] = 1 - } - _, errs[3] = fout.Write(rsEncode(rs5, flags)) - rand.Read(salt) - rand.Read(hkdfSalt) - rand.Read(serpentIV) - rand.Read(nonce) - _, errs[4] = fout.Write(rsEncode(rs16, salt)) - _, errs[5] = fout.Write(rsEncode(rs32, hkdfSalt)) - _, errs[6] = fout.Write(rsEncode(rs16, serpentIV)) - _, errs[7] = fout.Write(rsEncode(rs24, nonce)) - _, errs[8] = fout.Write(make([]byte, 192)) - _, errs[9] = fout.Write(make([]byte, 96)) - _, errs[10] = fout.Write(make([]byte, 192)) - for _, err := range errs { - if err != nil { - fin.Close() - fout.Close() - os.Remove(fout_) - fmt.Println("Insufficient disk space.") - return 1 - } - } - } else { - fmt.Println("Reading volume header...") - errs := make([]error, 10) - version := make([]byte, 15) - fin.Read(version) - _, errs[0] = rsDecode(rs5, version) - tmp := make([]byte, 15) - fin.Read(tmp) - tmp, errs[1] = rsDecode(rs5, tmp) - commentsLength, _ := strconv.Atoi(string(tmp)) - fin.Read(make([]byte, commentsLength*3)) - total -= int64(commentsLength) * 3 - flags := make([]byte, 15) - fin.Read(flags) - flags, errs[2] = rsDecode(rs5, flags) - *paranoid = flags[0] == 1 - *reedsolo = flags[3] == 1 - padded = flags[4] == 1 - if flags[1] == 1 { - fin.Close() - fout.Close() - os.Remove(fout_) - fmt.Println("Keyfiles are not supported.") - return 1 - } - salt = make([]byte, 48) - fin.Read(salt) - salt, errs[3] = rsDecode(rs16, salt) - hkdfSalt = make([]byte, 96) - fin.Read(hkdfSalt) - hkdfSalt, errs[4] = rsDecode(rs32, hkdfSalt) - serpentIV = make([]byte, 48) - fin.Read(serpentIV) - serpentIV, errs[5] = rsDecode(rs16, serpentIV) - nonce = make([]byte, 72) - fin.Read(nonce) - nonce, errs[6] = rsDecode(rs24, nonce) - keyHashRef = make([]byte, 192) - fin.Read(keyHashRef) - keyHashRef, errs[7] = rsDecode(rs64, keyHashRef) - keyfileHashRef := make([]byte, 96) - fin.Read(keyfileHashRef) - _, errs[8] = rsDecode(rs32, keyfileHashRef) - authTag = make([]byte, 192) - fin.Read(authTag) - authTag, errs[9] = rsDecode(rs64, authTag) - for _, err := range errs { - if err != nil { - fin.Close() - fout.Close() - os.Remove(fout_) - fmt.Println("The volume header is damaged.") - return 1 - } - } - } - - var key []byte - if *paranoid { - key = argon2.IDKey( - password, - salt, - 8, - 1<<20, - 8, - 32, - ) - } else { - key = argon2.IDKey( - password, - salt, - 4, - 1<<20, - 4, - 32, - ) - } - - tmp := sha3.New512() - tmp.Write(key) - keyHash = tmp.Sum(nil) - - if mode == "decrypt" { - if subtle.ConstantTimeCompare(keyHash, keyHashRef) != 1 { - fin.Close() - fout.Close() - os.Remove(fout_) - fmt.Println("Incorrect password.") - return 1 - } - } - - done, counter := 0, 0 - chacha, _ := chacha20.NewUnauthenticatedCipher(key, nonce) - var mac hash.Hash - subkey := make([]byte, 32) - hkdf := hkdf.New(sha3.New256, key, hkdfSalt, nil) - hkdf.Read(subkey) - if *paranoid { - mac = hmac.New(sha3.New512, subkey) - } else { - mac, _ = blake2b.New512(subkey) - } - serpentKey := make([]byte, 32) - hkdf.Read(serpentKey) - s, _ := serpent.NewCipher(serpentKey) - serpent := cipher.NewCTR(s, serpentIV) - - for { - var src []byte - if mode == "decrypt" && *reedsolo { - src = make([]byte, MiB/128*136) - } else { - src = make([]byte, MiB) - } - size, err := fin.Read(src) - if err != nil { - break - } - src = src[:size] - dst := make([]byte, len(src)) - - if mode == "encrypt" { - if *paranoid { - serpent.XORKeyStream(dst, src) - copy(src, dst) - } - chacha.XORKeyStream(dst, src) - mac.Write(dst) - if *reedsolo { - copy(src, dst) - dst = nil - if len(src) == MiB { - for i := 0; i < MiB; i += 128 { - dst = append(dst, rsEncode(rs128, src[i:i+128])...) - } - } else { - chunks := math.Floor(float64(len(src)) / 128) - for i := 0; float64(i) < chunks; i++ { - dst = append(dst, rsEncode(rs128, src[i*128:(i+1)*128])...) - } - dst = append(dst, rsEncode(rs128, pad(src[int(chunks*128):]))...) - } - } - } else { - if *reedsolo { - copy(dst, src) - src = nil - if len(dst) == MiB/128*136 { - for i := 0; i < MiB/128*136; i += 136 { - tmp, err := rsDecode(rs128, dst[i:i+136]) - if err != nil { - fin.Close() - fout.Close() - os.Remove(fout_) - fmt.Println("The input file is irrecoverably damaged.") - return 1 - } - if i == MiB/128*136-136 && done+MiB/128*136 >= int(total) && padded { - tmp = unpad(tmp) - } - src = append(src, tmp...) - } - } else { - chunks := len(dst)/136 - 1 - for i := 0; i < chunks; i++ { - tmp, err := rsDecode(rs128, dst[i*136:(i+1)*136]) - if err != nil { - fin.Close() - fout.Close() - os.Remove(fout_) - fmt.Println("The input file is irrecoverably damaged.") - return 1 - } - src = append(src, tmp...) - } - tmp, err := rsDecode(rs128, dst[int(chunks)*136:]) - if err != nil { - fin.Close() - fout.Close() - os.Remove(fout_) - fmt.Println("The input file is irrecoverably damaged.") - return 1 - } - src = append(src, unpad(tmp)...) - } - dst = make([]byte, len(src)) - } - mac.Write(src) - chacha.XORKeyStream(dst, src) - if *paranoid { - copy(src, dst) - serpent.XORKeyStream(dst, src) - } - } - - _, err = fout.Write(dst) - if err != nil { - fin.Close() - fout.Close() - os.Remove(fout_) - fmt.Println("Insufficient disk space.") - return 1 - } - - if counter >= 60*GiB { - nonce = make([]byte, 24) - hkdf.Read(nonce) - chacha, _ = chacha20.NewUnauthenticatedCipher(key, nonce) - serpentIV = make([]byte, 16) - hkdf.Read(serpentIV) - serpent = cipher.NewCTR(s, serpentIV) - counter = 0 - } - } - - if mode == "encrypt" { - fout.Seek(int64(309+0*3), 0) - fout.Write(rsEncode(rs64, keyHash)) - fout.Write(rsEncode(rs32, make([]byte, 32))) - fout.Write(rsEncode(rs64, mac.Sum(nil))) - } else { - if subtle.ConstantTimeCompare(mac.Sum(nil), authTag) != 1 { - fin.Close() - fout.Close() - os.Remove(fout_) - fmt.Println("The input volume is damaged or modified.") - return 1 - } - } - - fin.Close() - fout.Close() - fmt.Println("Completed.") - return 0 -} - -func main() { - os.Exit(work()) -} +package main + +import ( + "archive/zip" + "bytes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "crypto/subtle" + "flag" + "fmt" + "hash" + "io" + "math" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/HACKERALERT/infectious" + "github.com/HACKERALERT/serpent" + "github.com/schollz/progressbar/v3" + "golang.org/x/crypto/argon2" + "golang.org/x/crypto/blake2b" + "golang.org/x/crypto/chacha20" + "golang.org/x/crypto/hkdf" + "golang.org/x/crypto/sha3" + "golang.org/x/term" +) + +var KiB = 1 << 10 +var MiB = 1 << 20 +var GiB = 1 << 30 +var TiB = 1 << 40 +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) + +func rsEncode(rs *infectious.FEC, data []byte) []byte { + res := make([]byte, rs.Total()) + rs.Encode(data, func(s infectious.Share) { + res[s.Number] = s.Data[0] + }) + return res +} + +func rsDecode(rs *infectious.FEC, data []byte) ([]byte, error) { + tmp := make([]infectious.Share, rs.Total()) + for i := 0; i < rs.Total(); i++ { + tmp[i].Number = i + tmp[i].Data = append(tmp[i].Data, data[i]) + } + res, err := rs.Decode(nil, tmp) + if err != nil { + if rs.Total() == 136 { + return data[:128], err + } + return data[:rs.Total()/3], err + } + return res, nil +} + +func pad(data []byte) []byte { + padLen := 128 - len(data)%128 + padding := bytes.Repeat([]byte{byte(padLen)}, padLen) + return append(data, padding...) +} + +func unpad(data []byte) []byte { + padLen := int(data[127]) + return data[:128-padLen] +} + +func work() int { + flag.Usage = func() { + fmt.Println("Usage: picocrypt [-p]aranoid [-r]eedsolo [ ...]") + fmt.Println("Items: can be a file (cat.png), folder (./src), or glob (*.txt)") + } + paranoid := flag.Bool("p", false, "") + reedsolo := flag.Bool("r", false, "") + flag.Parse() + + mode := "" + if flag.NArg() == 0 { + flag.Usage() + return 0 + } + if flag.NArg() == 1 { + if strings.HasSuffix(flag.Arg(0), ".pcv") { + mode = "decrypt" + } else { + mode = "encrypt" + } + } else { + mode = "encrypt" + for _, v := range flag.Args() { + if strings.HasSuffix(v, ".pcv") { + fmt.Println("Multiple items cannot contain volumes.") + return 1 + } + } + } + + var password, cpassword []byte + var err error + if mode == "encrypt" { + fmt.Print("Password: ") + password, err = term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + fmt.Println("Error reading password.") + return 1 + } + fmt.Print(strings.Repeat("*", len(password)), " | Confirm: ") + cpassword, err = term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + fmt.Println("Error reading password.") + return 1 + } + fmt.Println(strings.Repeat("*", len(cpassword))) + if !bytes.Equal(password, cpassword) { + fmt.Println("Passwords don't match.") + return 1 + } + } else { + fmt.Print("Password: ") + password, err = term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + fmt.Println("Error reading password.") + return 1 + } + fmt.Println(strings.Repeat("*", len(password))) + } + + fin_, fout_ := "", "" + if mode == "decrypt" { + fin_ = flag.Arg(0) + fout_ = strings.TrimSuffix(fin_, ".pcv") + } else { + stat, err := os.Stat(flag.Arg(0)) + if flag.NArg() == 1 && err == nil && !stat.IsDir() { + fin_ = flag.Arg(0) + fout_ = fin_ + ".pcv" + } else { + items := []string{} + for _, v := range flag.Args() { + if strings.Contains(v, "../") || strings.HasPrefix(v, "/") { + fmt.Println("Cannot encrypt outside of current directory.") + return 1 + } + matches, err := filepath.Glob(v) + if err != nil { + fmt.Println("Invalid glob pattern(s).") + return 1 + } + items = append(items, matches...) + } + files := []string{} + for _, v := range items { + stat, err := os.Stat(v) + if err != nil { + fmt.Println("Cannot access input(s).") + return 1 + } + if !stat.IsDir() { + files = append(files, v) + } else { + filepath.Walk(v, func(path string, _ os.FileInfo, _ error) error { + stat, err := os.Stat(path) + if err == nil && !stat.IsDir() { + files = append(files, path) + } + return nil + }) + } + } + if len(files) == 0 { + fmt.Println("Nothing to encrypt.") + return 1 + } + + dir, err := os.Getwd() + if err != nil { + fmt.Println("Cannot get current working directory.") + return 1 + } + file, err := os.CreateTemp("", "") + if err != nil { + fmt.Println("Cannot create temporary file.") + return 1 + } + writer := zip.NewWriter(file) + for i, path := range files { + stat, err := os.Stat(path) + if err != nil { + continue + } + header, err := zip.FileInfoHeader(stat) + if err != nil { + continue + } + abs, err := filepath.Abs(path) + if err != nil { + continue + } + abs = filepath.ToSlash(abs) + header.Name = strings.TrimPrefix(abs, filepath.ToSlash(dir)) + header.Name = strings.TrimPrefix(header.Name, "/") + header.Method = zip.Deflate + entry, err := writer.CreateHeader(header) + if err != nil { + continue + } + fin, err := os.Open(path) + if err != nil { + writer.Close() + file.Close() + os.Remove(file.Name()) + fmt.Println("Read access to input(s) denied.") + return 1 + } + bar := progressbar.NewOptions( + int(stat.Size()), + progressbar.OptionClearOnFinish(), + progressbar.OptionFullWidth(), + progressbar.OptionShowBytes(true), + progressbar.OptionUseIECUnits(true), + progressbar.OptionSetDescription( + fmt.Sprintf("Compressing [%d/%d]:", i+1, len(files)), + ), + ) + _, err = io.Copy(io.MultiWriter(entry, bar), fin) + fin.Close() + if err != nil { + writer.Close() + file.Close() + os.Remove(file.Name()) + fmt.Println("Insufficient disk space.") + return 1 + } + } + writer.Close() + file.Close() + fin_ = file.Name() + fout_ = "encrypted.zip.pcv" + defer os.Remove(file.Name()) + } + } + + var padded bool + var salt []byte // Argon2 salt, 16 bytes + var hkdfSalt []byte // HKDF-SHA3 salt, 32 bytes + var serpentIV []byte // Serpent IV, 16 bytes + var nonce []byte // 24-byte XChaCha20 nonce + var keyHash []byte // SHA3-512 hash of encryption key + var keyHashRef []byte // Same as 'keyHash', but used for comparison + var authTag []byte // 64-byte authentication tag (BLAKE2b or HMAC-SHA3) + + fin, err := os.Open(fin_) + if err != nil { + fmt.Println("Error accessing input file.") + return 1 + } + _, err = os.Stat(fout_) + if err == nil { + fmt.Println("Output file already exists.") + return 1 + } + fout, err := os.Create(fout_) + if err != nil { + fmt.Println("Error creating output file.") + return 1 + } + + stat, err := os.Stat(fin_) + if err != nil { + fmt.Println("Error accessing input file.") + return 1 + } + total := stat.Size() + if mode == "decrypt" { + total -= 789 + } + if mode == "encrypt" { + errs := make([]error, 11) + salt = make([]byte, 16) + hkdfSalt = make([]byte, 32) + serpentIV = make([]byte, 16) + nonce = make([]byte, 24) + _, errs[0] = fout.Write(rsEncode(rs5, []byte("v1.34"))) + commentsLength := []byte(fmt.Sprintf("%05d", 0)) + _, errs[1] = fout.Write(rsEncode(rs5, commentsLength)) + flags := make([]byte, 5) + if *paranoid { + flags[0] = 1 + } + if *reedsolo { + flags[3] = 1 + } + if total%int64(MiB) >= int64(MiB)-128 { + flags[4] = 1 + } + _, errs[3] = fout.Write(rsEncode(rs5, flags)) + rand.Read(salt) + rand.Read(hkdfSalt) + rand.Read(serpentIV) + rand.Read(nonce) + _, errs[4] = fout.Write(rsEncode(rs16, salt)) + _, errs[5] = fout.Write(rsEncode(rs32, hkdfSalt)) + _, errs[6] = fout.Write(rsEncode(rs16, serpentIV)) + _, errs[7] = fout.Write(rsEncode(rs24, nonce)) + _, errs[8] = fout.Write(make([]byte, 192)) + _, errs[9] = fout.Write(make([]byte, 96)) + _, errs[10] = fout.Write(make([]byte, 192)) + for _, err := range errs { + if err != nil { + fin.Close() + fout.Close() + os.Remove(fout_) + fmt.Println("Insufficient disk space.") + return 1 + } + } + } else { + errs := make([]error, 10) + version := make([]byte, 15) + fin.Read(version) + _, errs[0] = rsDecode(rs5, version) + tmp := make([]byte, 15) + fin.Read(tmp) + tmp, errs[1] = rsDecode(rs5, tmp) + commentsLength, _ := strconv.Atoi(string(tmp)) + fin.Read(make([]byte, commentsLength*3)) + total -= int64(commentsLength) * 3 + flags := make([]byte, 15) + fin.Read(flags) + flags, errs[2] = rsDecode(rs5, flags) + *paranoid = flags[0] == 1 + *reedsolo = flags[3] == 1 + padded = flags[4] == 1 + if flags[1] == 1 { + fin.Close() + fout.Close() + os.Remove(fout_) + fmt.Println("Keyfiles are not supported.") + return 1 + } + salt = make([]byte, 48) + fin.Read(salt) + salt, errs[3] = rsDecode(rs16, salt) + hkdfSalt = make([]byte, 96) + fin.Read(hkdfSalt) + hkdfSalt, errs[4] = rsDecode(rs32, hkdfSalt) + serpentIV = make([]byte, 48) + fin.Read(serpentIV) + serpentIV, errs[5] = rsDecode(rs16, serpentIV) + nonce = make([]byte, 72) + fin.Read(nonce) + nonce, errs[6] = rsDecode(rs24, nonce) + keyHashRef = make([]byte, 192) + fin.Read(keyHashRef) + keyHashRef, errs[7] = rsDecode(rs64, keyHashRef) + keyfileHashRef := make([]byte, 96) + fin.Read(keyfileHashRef) + _, errs[8] = rsDecode(rs32, keyfileHashRef) + authTag = make([]byte, 192) + fin.Read(authTag) + authTag, errs[9] = rsDecode(rs64, authTag) + for _, err := range errs { + if err != nil { + fin.Close() + fout.Close() + os.Remove(fout_) + fmt.Println("The volume header is damaged.") + return 1 + } + } + } + + var key []byte + if *paranoid { + key = argon2.IDKey( + password, + salt, + 8, + 1<<20, + 8, + 32, + ) + } else { + key = argon2.IDKey( + password, + salt, + 4, + 1<<20, + 4, + 32, + ) + } + + tmp := sha3.New512() + tmp.Write(key) + keyHash = tmp.Sum(nil) + + if mode == "decrypt" { + if subtle.ConstantTimeCompare(keyHash, keyHashRef) != 1 { + fin.Close() + fout.Close() + os.Remove(fout_) + fmt.Println("Incorrect password.") + return 1 + } + } + + done, counter := 0, 0 + chacha, _ := chacha20.NewUnauthenticatedCipher(key, nonce) + var mac hash.Hash + subkey := make([]byte, 32) + hkdf := hkdf.New(sha3.New256, key, hkdfSalt, nil) + hkdf.Read(subkey) + if *paranoid { + mac = hmac.New(sha3.New512, subkey) + } else { + mac, _ = blake2b.New512(subkey) + } + serpentKey := make([]byte, 32) + hkdf.Read(serpentKey) + s, _ := serpent.NewCipher(serpentKey) + serpent := cipher.NewCTR(s, serpentIV) + + bar := progressbar.NewOptions( + int(total), + progressbar.OptionClearOnFinish(), + progressbar.OptionFullWidth(), + progressbar.OptionShowBytes(true), + progressbar.OptionUseIECUnits(true), + progressbar.OptionSetDescription( + (func() string { + if mode == "encrypt" { + return "Encrypting:" + } + return "Decrypting:" + })(), + ), + ) + for { + var src []byte + if mode == "decrypt" && *reedsolo { + src = make([]byte, MiB/128*136) + } else { + src = make([]byte, MiB) + } + size, err := fin.Read(src) + if err != nil { + break + } + src = src[:size] + dst := make([]byte, len(src)) + + if mode == "encrypt" { + if *paranoid { + serpent.XORKeyStream(dst, src) + copy(src, dst) + } + chacha.XORKeyStream(dst, src) + mac.Write(dst) + if *reedsolo { + copy(src, dst) + dst = nil + if len(src) == MiB { + for i := 0; i < MiB; i += 128 { + dst = append(dst, rsEncode(rs128, src[i:i+128])...) + } + } else { + chunks := math.Floor(float64(len(src)) / 128) + for i := 0; float64(i) < chunks; i++ { + dst = append(dst, rsEncode(rs128, src[i*128:(i+1)*128])...) + } + dst = append(dst, rsEncode(rs128, pad(src[int(chunks*128):]))...) + } + } + } else { + if *reedsolo { + copy(dst, src) + src = nil + if len(dst) == MiB/128*136 { + for i := 0; i < MiB/128*136; i += 136 { + tmp, err := rsDecode(rs128, dst[i:i+136]) + if err != nil { + fin.Close() + fout.Close() + os.Remove(fout_) + fmt.Println("The input file is irrecoverably damaged.") + return 1 + } + if i == MiB/128*136-136 && done+MiB/128*136 >= int(total) && padded { + tmp = unpad(tmp) + } + src = append(src, tmp...) + } + } else { + chunks := len(dst)/136 - 1 + for i := 0; i < chunks; i++ { + tmp, err := rsDecode(rs128, dst[i*136:(i+1)*136]) + if err != nil { + fin.Close() + fout.Close() + os.Remove(fout_) + fmt.Println("The input file is irrecoverably damaged.") + return 1 + } + src = append(src, tmp...) + } + tmp, err := rsDecode(rs128, dst[int(chunks)*136:]) + if err != nil { + fin.Close() + fout.Close() + os.Remove(fout_) + fmt.Println("The input file is irrecoverably damaged.") + return 1 + } + src = append(src, unpad(tmp)...) + } + dst = make([]byte, len(src)) + } + mac.Write(src) + chacha.XORKeyStream(dst, src) + if *paranoid { + copy(src, dst) + serpent.XORKeyStream(dst, src) + } + } + + _, err = fout.Write(dst) + if err != nil { + fin.Close() + fout.Close() + os.Remove(fout_) + fmt.Println("Insufficient disk space.") + return 1 + } + if mode == "decrypt" && *reedsolo { + done += MiB / 128 * 136 + } else { + done += MiB + } + bar.Set(done) + + if counter >= 60*GiB { + nonce = make([]byte, 24) + hkdf.Read(nonce) + chacha, _ = chacha20.NewUnauthenticatedCipher(key, nonce) + serpentIV = make([]byte, 16) + hkdf.Read(serpentIV) + serpent = cipher.NewCTR(s, serpentIV) + counter = 0 + } + } + bar.Set64(total) + + if mode == "encrypt" { + fout.Seek(int64(309+0*3), 0) + fout.Write(rsEncode(rs64, keyHash)) + fout.Write(rsEncode(rs32, make([]byte, 32))) + fout.Write(rsEncode(rs64, mac.Sum(nil))) + } else { + if subtle.ConstantTimeCompare(mac.Sum(nil), authTag) != 1 { + fin.Close() + fout.Close() + os.Remove(fout_) + fmt.Println("The input volume is damaged or modified.") + return 1 + } + } + + fin.Close() + fout.Close() + fmt.Println("\nCompleted.") + return 0 +} + +func main() { + os.Exit(work()) +}