Compare commits

...

19 Commits

Author SHA1 Message Date
Evan Su f6a4f0e920
Test CodeQL 2024-04-25 23:42:13 -04:00
Evan Su a8e8a92002
Update codeql-analysis.yml 2024-04-25 23:40:35 -04:00
Evan Su 613ab8232d
Rename -fix to -f 2024-04-25 23:25:53 -04:00
Evan Su c73fba2250 Revert "Rename -fix to -f"
This reverts commit bbe5ee979a.
2024-04-25 23:23:41 -04:00
Evan Su bbe5ee979a
Rename -fix to -f 2024-04-25 23:22:14 -04:00
Evan Su 1b4998d4e2
Handle flags after arguments 2024-04-25 22:13:41 -04:00
Evan Su 97bde62236
Add -fix flag, use fast RS by default 2024-04-25 22:07:05 -04:00
Evan Su 77aea6a8e4
Add progress bars 2024-04-25 18:07:42 -04:00
Evan Su a91428b934
Link to the new v2 CLI 2024-04-25 14:18:21 -04:00
Evan Su 45967b960e
Update Changelog.md 2024-04-25 14:04:52 -04:00
Evan Su e9c2e57b55
Update go.mod 2024-04-25 10:30:22 -04:00
Evan Su 3bec342488
v1: add legacy notice 2024-04-25 10:29:26 -04:00
Evan Su 434410d1d6 Reorganize CLI v1 2024-04-25 10:26:59 -04:00
Evan Su 60a144102c
Add CLI v2 usage instructions 2024-04-25 10:23:12 -04:00
Evan Su 60dd17749c Tidy CLI files 2024-04-25 10:17:52 -04:00
Evan Su 9f7dc39e7c
Create README.md for CLI v2 2024-04-25 10:14:41 -04:00
Evan Su d70ee6effc
Add new and better CLI v2 2024-04-25 10:11:57 -04:00
Evan Su c235f28fd2
Update Changelog.md 2024-04-25 10:09:14 -04:00
Evan Su bb2937e4a1
Begin v1.34! 2024-04-25 10:01:07 -04:00
11 changed files with 952 additions and 256 deletions

View File

@ -5,9 +5,12 @@ on:
- "src/*.go"
- "src/go.mod"
- "src/go.sum"
- "cli/picocrypt/*.go"
- "cli/picocrypt/go.mod"
- "cli/picocrypt/go.sum"
- "cli/v1/picocrypt/*.go"
- "cli/v1/picocrypt/go.mod"
- "cli/v1/picocrypt/go.sum"
- "cli/v2/picocrypt/*.go"
- "cli/v2/picocrypt/go.mod"
- "cli/v2/picocrypt/go.sum"
- "web/*.go"
- "web/go.mod"
- "web/go.sum"
@ -27,12 +30,12 @@ jobs:
language: ['go']
steps:
- name: Checkout Repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3

View File

@ -1,3 +1,17 @@
# Future
<ul>
<li>Migrate golang.org/x/crypto to standard library imports (https://github.com/golang/go/issues/65269)</li>
<li>(Under consideration) Better web app; add advanced features</li>
</ul>
# v1.34 (No ETA)
<ul>
<li>(WIP) New CLI with support for files, folders, globs, paranoid mode, and Reed-Solomon</li>
<li>Unzip after decryption (advanced feature)</li>
<li>Migrate github.com/HACKERALERT/crypto back to golang.org/x/crypto</li>
<li>Distribute raw Linux binary instead of AppImage for better portability</li>
</ul>
# v1.33 (Released 06/27/2023)
<ul>
<li>✓ Add tooltip warning that comments are not encrypted (#164)</li>

View File

@ -24,12 +24,12 @@ Picocrypt for macOS is very simple as well. Download Picocrypt <a href="https://
## Linux
To use Picocrypt on Linux, you can download the AppImage <a href="https://github.com/HACKERALERT/Picocrypt/releases/download/1.33/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 try the <a href="https://snapcraft.io/picocrypt">Snap</a>, run Picocrypt through Wine, or compile from source using the instructions in the `src/` directory.
## CLI
A command-line interface is available for Picocrypt <a href="/cli/v2/picocrypt">here</a>. It can encrypt and decrypt files, folders, and globs, and supports paranoid mode and Reed-Solomon encoding. You can use it on systems that don't have a GUI or can't run the GUI app, or to write automated shell scripts for backups, etc.
## Web
A web interface for Picocrypt is available <a href="https://picocrypt.pages.dev/">here</a>, allowing you to use a lite version of Picocrypt on any device. Keep in mind that its functionality is very limited and you won't be able to use any advanced features or encrypt large files. It is also quite slow compared to the native app.
## 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.33/Paranoid.zip">here</a>.

View File

@ -1,16 +1,18 @@
# CLI
Before you dive in, keep in mind that the CLI is limited in functionality and not meant to replace the GUI in any remote way. It only works with volumes that don't use any keyfiles or advanced features, and you will still need the GUI to do anything more than basic file encryption. You should only use the CLI when you are not able to run the GUI or need an automatable interface for encrypting and decrypting files.
# Installation
If you don't have Go installed, download it from <a href="https://go.dev/dl/">here</a> or install it from your package manager. Then, run the command below:
```bash
go install github.com/HACKERALERT/Picocrypt/cli/picocrypt@latest
```
You should now be able to run `picocrypt` in your terminal. If not, run `export PATH=$PATH:$(go env GOPATH)/bin` and try again.
# Usage
The CLI is designed to do one thing and one thing only: encrypt and decrypt a single file. Thus, it should be very simple to use:
```
picocrypt -p password <file>
```
It's basic by design, allowing you to use it as a secure building block for automating encryption, writing shell scripts, and so on.
**Note: this is a legacy version of the CLI kept for compatibility purposes. You probably want to use <a href="https://github.com/HACKERALERT/Picocrypt/tree/main/cli/v2/picocrypt">v2</a> instead.**
# CLI
Before you dive in, keep in mind that the CLI is limited in functionality and not meant to replace the GUI in any remote way. It only works with volumes that don't use any keyfiles or advanced features, and you will still need the GUI to do anything more than basic file encryption. You should only use the CLI when you are not able to run the GUI or need an automatable interface for encrypting and decrypting files.
# Installation
If you don't have Go installed, download it from <a href="https://go.dev/dl/">here</a> or install it from your package manager. Then, run the command below:
```bash
go install github.com/HACKERALERT/Picocrypt/cli/v1/picocrypt@latest
```
You should now be able to run `picocrypt` in your terminal. If not, run `export PATH=$PATH:$(go env GOPATH)/bin` and try again.
# Usage
The CLI is designed to do one thing and one thing only: encrypt and decrypt a single file. Thus, it should be very simple to use:
```
picocrypt -p password <file>
```
It's basic by design, allowing you to use it as a secure building block for automating encryption, writing shell scripts, and so on.

View File

@ -1,4 +1,4 @@
module github.com/HACKERALERT/Picocrypt/cli/picocrypt
module github.com/HACKERALERT/Picocrypt/cli/v1/picocrypt
go 1.17

View File

@ -1,229 +1,229 @@
package main
import (
"bytes"
"crypto/rand"
"flag"
"fmt"
"os"
"strconv"
"strings"
"github.com/HACKERALERT/crypto/argon2"
"github.com/HACKERALERT/crypto/blake2b"
"github.com/HACKERALERT/crypto/chacha20"
"github.com/HACKERALERT/crypto/hkdf"
"github.com/HACKERALERT/crypto/sha3"
"github.com/HACKERALERT/infectious"
)
var MiB = 1 << 20
var GiB = 1 << 30
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)
func work(filename string, password string) int {
var salt []byte
var hkdfSalt []byte
var nonce []byte
var keyHash []byte
var keyHashRef []byte
var authTag []byte
fin, err := os.Open(filename)
if err != nil {
fmt.Println("Couldn't open input file.")
return 1
}
defer fin.Close()
var fout *os.File
if strings.HasSuffix(filename, ".pcv") {
fout, err = os.Create(strings.TrimSuffix(filename, ".pcv"))
} else {
fout, err = os.Create(filename + ".pcv")
}
if err != nil {
fmt.Println("Couldn't create output file.")
return 1
}
defer fout.Close()
if !strings.HasSuffix(filename, ".pcv") {
salt = make([]byte, 16)
hkdfSalt = make([]byte, 32)
nonce = make([]byte, 24)
rand.Read(salt)
rand.Read(hkdfSalt)
rand.Read(nonce)
fout.Write(rsEncode(rs5, []byte("v1.33")))
fout.Write(rsEncode(rs5, []byte("00000")))
fout.Write(rsEncode(rs5, make([]byte, 5)))
fout.Write(rsEncode(rs16, salt))
fout.Write(rsEncode(rs32, hkdfSalt))
fout.Write(rsEncode(rs16, make([]byte, 16)))
fout.Write(rsEncode(rs24, nonce))
fout.Write(make([]byte, 480))
} else {
errs := make([]error, 7)
comments := make([]byte, 30)
fin.Read(comments)
comments, errs[0] = rsDecode(rs5, comments[15:])
length, _ := strconv.Atoi(string(comments))
fin.Read(make([]byte, length*3))
flags := make([]byte, 15)
fin.Read(flags)
flags, errs[1] = rsDecode(rs5, flags)
salt = make([]byte, 48)
fin.Read(salt)
salt, errs[2] = rsDecode(rs16, salt)
hkdfSalt = make([]byte, 96)
fin.Read(hkdfSalt)
hkdfSalt, errs[3] = rsDecode(rs32, hkdfSalt)
fin.Read(make([]byte, 48))
nonce = make([]byte, 72)
fin.Read(nonce)
nonce, errs[4] = rsDecode(rs24, nonce)
keyHashRef = make([]byte, 192)
fin.Read(keyHashRef)
keyHashRef, errs[5] = rsDecode(rs64, keyHashRef)
fin.Read(make([]byte, 96))
authTag = make([]byte, 192)
fin.Read(authTag)
authTag, errs[6] = rsDecode(rs64, authTag)
for _, err := range errs {
if err != nil {
fmt.Println("The header is corrupted.")
return 1
}
}
if flags[0]+flags[1]+flags[3] > 0 {
fmt.Println("Unsupported volume.")
return 1
}
}
key := argon2.IDKey([]byte(password), salt, 4, 1<<20, 4, 32)
tmp := sha3.New512()
tmp.Write(key)
keyHash = tmp.Sum(nil)
if strings.HasSuffix(filename, ".pcv") && !bytes.Equal(keyHash, keyHashRef) {
fmt.Println("Incorrect password.")
return 1
}
counter := 0
chacha, _ := chacha20.NewUnauthenticatedCipher(key, nonce)
subkey := make([]byte, 32)
hkdf := hkdf.New(sha3.New256, key, hkdfSalt, nil)
hkdf.Read(subkey)
mac, _ := blake2b.New512(subkey)
hkdf.Read(make([]byte, 32))
for {
src := make([]byte, MiB)
size, err := fin.Read(src)
if err != nil {
break
}
src = src[:size]
dst := make([]byte, len(src))
if !strings.HasSuffix(filename, ".pcv") {
chacha.XORKeyStream(dst, src)
mac.Write(dst)
} else {
mac.Write(src)
chacha.XORKeyStream(dst, src)
}
fout.Write(dst)
counter += MiB
if counter >= 60*GiB {
nonce = make([]byte, 24)
hkdf.Read(nonce)
chacha, _ = chacha20.NewUnauthenticatedCipher(key, nonce)
hkdf.Read(make([]byte, 16))
counter = 0
}
}
if !strings.HasSuffix(filename, ".pcv") {
fout.Seek(309, 0)
fout.Write(rsEncode(rs64, keyHash))
fout.Write(rsEncode(rs32, make([]byte, 32)))
fout.Write(rsEncode(rs64, mac.Sum(nil)))
} else {
if !bytes.Equal(mac.Sum(nil), authTag) {
fmt.Println("The file has been modified.")
return 1
}
}
fmt.Println("Operation successful.")
return 0
}
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 = []byte{data[i]}
}
res, err := rs.Decode(nil, tmp)
if err != nil {
return data[:rs.Total()/3], err
}
return res, nil
}
func main() {
flag.Usage = func() { fmt.Println("Usage: picocrypt -p password <file>") }
password := flag.String("p", "", "")
flag.Parse()
filename := flag.Arg(0)
if filename == "" || *password == "" || flag.Arg(1) != "" {
flag.Usage()
os.Exit(1)
}
if _, err := os.Stat(filename); err != nil {
fmt.Println("Input file not found.")
os.Exit(1)
}
if stat, _ := os.Stat(filename); stat.IsDir() {
fmt.Println("Directories are not supported.")
os.Exit(1)
}
if !strings.HasSuffix(filename, ".pcv") {
if _, err := os.Stat(filename + ".pcv"); err == nil {
fmt.Println("Output already exists.")
os.Exit(1)
}
} else {
if _, err := os.Stat(strings.TrimSuffix(filename, ".pcv")); err == nil {
fmt.Println("Output already exists.")
os.Exit(1)
}
}
if work(filename, *password) != 0 {
if !strings.HasSuffix(filename, ".pcv") {
os.Remove(filename + ".pcv")
} else {
os.Remove(strings.TrimSuffix(filename, ".pcv"))
}
os.Exit(1)
}
}
package main
import (
"bytes"
"crypto/rand"
"flag"
"fmt"
"os"
"strconv"
"strings"
"github.com/HACKERALERT/crypto/argon2"
"github.com/HACKERALERT/crypto/blake2b"
"github.com/HACKERALERT/crypto/chacha20"
"github.com/HACKERALERT/crypto/hkdf"
"github.com/HACKERALERT/crypto/sha3"
"github.com/HACKERALERT/infectious"
)
var MiB = 1 << 20
var GiB = 1 << 30
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)
func work(filename string, password string) int {
var salt []byte
var hkdfSalt []byte
var nonce []byte
var keyHash []byte
var keyHashRef []byte
var authTag []byte
fin, err := os.Open(filename)
if err != nil {
fmt.Println("Couldn't open input file.")
return 1
}
defer fin.Close()
var fout *os.File
if strings.HasSuffix(filename, ".pcv") {
fout, err = os.Create(strings.TrimSuffix(filename, ".pcv"))
} else {
fout, err = os.Create(filename + ".pcv")
}
if err != nil {
fmt.Println("Couldn't create output file.")
return 1
}
defer fout.Close()
if !strings.HasSuffix(filename, ".pcv") {
salt = make([]byte, 16)
hkdfSalt = make([]byte, 32)
nonce = make([]byte, 24)
rand.Read(salt)
rand.Read(hkdfSalt)
rand.Read(nonce)
fout.Write(rsEncode(rs5, []byte("v1.33")))
fout.Write(rsEncode(rs5, []byte("00000")))
fout.Write(rsEncode(rs5, make([]byte, 5)))
fout.Write(rsEncode(rs16, salt))
fout.Write(rsEncode(rs32, hkdfSalt))
fout.Write(rsEncode(rs16, make([]byte, 16)))
fout.Write(rsEncode(rs24, nonce))
fout.Write(make([]byte, 480))
} else {
errs := make([]error, 7)
comments := make([]byte, 30)
fin.Read(comments)
comments, errs[0] = rsDecode(rs5, comments[15:])
length, _ := strconv.Atoi(string(comments))
fin.Read(make([]byte, length*3))
flags := make([]byte, 15)
fin.Read(flags)
flags, errs[1] = rsDecode(rs5, flags)
salt = make([]byte, 48)
fin.Read(salt)
salt, errs[2] = rsDecode(rs16, salt)
hkdfSalt = make([]byte, 96)
fin.Read(hkdfSalt)
hkdfSalt, errs[3] = rsDecode(rs32, hkdfSalt)
fin.Read(make([]byte, 48))
nonce = make([]byte, 72)
fin.Read(nonce)
nonce, errs[4] = rsDecode(rs24, nonce)
keyHashRef = make([]byte, 192)
fin.Read(keyHashRef)
keyHashRef, errs[5] = rsDecode(rs64, keyHashRef)
fin.Read(make([]byte, 96))
authTag = make([]byte, 192)
fin.Read(authTag)
authTag, errs[6] = rsDecode(rs64, authTag)
for _, err := range errs {
if err != nil {
fmt.Println("The header is corrupted.")
return 1
}
}
if flags[0]+flags[1]+flags[3] > 0 {
fmt.Println("Unsupported volume.")
return 1
}
}
key := argon2.IDKey([]byte(password), salt, 4, 1<<20, 4, 32)
tmp := sha3.New512()
tmp.Write(key)
keyHash = tmp.Sum(nil)
if strings.HasSuffix(filename, ".pcv") && !bytes.Equal(keyHash, keyHashRef) {
fmt.Println("Incorrect password.")
return 1
}
counter := 0
chacha, _ := chacha20.NewUnauthenticatedCipher(key, nonce)
subkey := make([]byte, 32)
hkdf := hkdf.New(sha3.New256, key, hkdfSalt, nil)
hkdf.Read(subkey)
mac, _ := blake2b.New512(subkey)
hkdf.Read(make([]byte, 32))
for {
src := make([]byte, MiB)
size, err := fin.Read(src)
if err != nil {
break
}
src = src[:size]
dst := make([]byte, len(src))
if !strings.HasSuffix(filename, ".pcv") {
chacha.XORKeyStream(dst, src)
mac.Write(dst)
} else {
mac.Write(src)
chacha.XORKeyStream(dst, src)
}
fout.Write(dst)
counter += MiB
if counter >= 60*GiB {
nonce = make([]byte, 24)
hkdf.Read(nonce)
chacha, _ = chacha20.NewUnauthenticatedCipher(key, nonce)
hkdf.Read(make([]byte, 16))
counter = 0
}
}
if !strings.HasSuffix(filename, ".pcv") {
fout.Seek(309, 0)
fout.Write(rsEncode(rs64, keyHash))
fout.Write(rsEncode(rs32, make([]byte, 32)))
fout.Write(rsEncode(rs64, mac.Sum(nil)))
} else {
if !bytes.Equal(mac.Sum(nil), authTag) {
fmt.Println("The file has been modified.")
return 1
}
}
fmt.Println("Operation successful.")
return 0
}
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 = []byte{data[i]}
}
res, err := rs.Decode(nil, tmp)
if err != nil {
return data[:rs.Total()/3], err
}
return res, nil
}
func main() {
flag.Usage = func() { fmt.Println("Usage: picocrypt -p password <file>") }
password := flag.String("p", "", "")
flag.Parse()
filename := flag.Arg(0)
if filename == "" || *password == "" || flag.Arg(1) != "" {
flag.Usage()
os.Exit(1)
}
if _, err := os.Stat(filename); err != nil {
fmt.Println("Input file not found.")
os.Exit(1)
}
if stat, _ := os.Stat(filename); stat.IsDir() {
fmt.Println("Directories are not supported.")
os.Exit(1)
}
if !strings.HasSuffix(filename, ".pcv") {
if _, err := os.Stat(filename + ".pcv"); err == nil {
fmt.Println("Output already exists.")
os.Exit(1)
}
} else {
if _, err := os.Stat(strings.TrimSuffix(filename, ".pcv")); err == nil {
fmt.Println("Output already exists.")
os.Exit(1)
}
}
if work(filename, *password) != 0 {
if !strings.HasSuffix(filename, ".pcv") {
os.Remove(filename + ".pcv")
} else {
os.Remove(strings.TrimSuffix(filename, ".pcv"))
}
os.Exit(1)
}
}

View File

@ -0,0 +1,29 @@
# Installation
If you don't have Go installed, download it from <a href="https://go.dev/dl/">here</a> or install it from your package manager. Then, run the command below:
```
go install github.com/HACKERALERT/Picocrypt/cli/v2/picocrypt@latest
```
You should now be able to run `picocrypt` in your terminal. If not, run `export PATH=$PATH:$(go env GOPATH)/bin` and try again.
# Usage
```
C:\Users\Evan>picocrypt
Usage: picocrypt [-p]aranoid [-r]eedsolo <item1> [<item2> ...]
Items: can be a file (cat.png), folder (./src), or glob (*.txt)
```
## Examples
To encrypt a single file:
```
picocrypt secret.pdf
```
To encrypt all files in the current working directory:
```
picocrypt *
```
To encrypt all PNGs and JPGs with paranoid mode and Reed-Solomon:
```
picocrypt -p -r *.png *.jpg
```
To decrypt a volume:
```
picocrypt volume.pcv
```

17
cli/v2/picocrypt/go.mod Normal file
View File

@ -0,0 +1,17 @@
module github.com/HACKERALERT/Picocrypt/cli/v2/picocrypt
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 (
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
)

29
cli/v2/picocrypt/go.sum Normal file
View File

@ -0,0 +1,29 @@
github.com/HACKERALERT/infectious v0.0.0-20240424200929-b9ce72346a19 h1:C5t561XXXRJvdiluejbka36n+YaOB4XJuQIo+25hL1k=
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=

602
cli/v2/picocrypt/main.go Normal file
View File

@ -0,0 +1,602 @@
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 MiB = 1 << 20
var GiB = 1 << 30
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, fast bool) ([]byte, error) {
if rs.Total() == 136 && fast {
return data[:128], nil
}
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 <item1> [<item2> ...]")
fmt.Println("Items: can be a file (cat.png), folder (./src), or glob (*.txt)")
}
paranoid := flag.Bool("p", false, "")
reedsolo := flag.Bool("r", false, "")
fix := flag.Bool("f", 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
}
}
}
for _, v := range flag.Args() {
if v == "-p" || v == "-r" || v == "-f" {
fmt.Println("Flags are only accepted before arguments!")
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, !(*fix))
tmp := make([]byte, 15)
fin.Read(tmp)
tmp, errs[1] = rsDecode(rs5, tmp, !(*fix))
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, !(*fix))
*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, !(*fix))
hkdfSalt = make([]byte, 96)
fin.Read(hkdfSalt)
hkdfSalt, errs[4] = rsDecode(rs32, hkdfSalt, !(*fix))
serpentIV = make([]byte, 48)
fin.Read(serpentIV)
serpentIV, errs[5] = rsDecode(rs16, serpentIV, !(*fix))
nonce = make([]byte, 72)
fin.Read(nonce)
nonce, errs[6] = rsDecode(rs24, nonce, !(*fix))
keyHashRef = make([]byte, 192)
fin.Read(keyHashRef)
keyHashRef, errs[7] = rsDecode(rs64, keyHashRef, !(*fix))
keyfileHashRef := make([]byte, 96)
fin.Read(keyfileHashRef)
_, errs[8] = rsDecode(rs32, keyfileHashRef, !(*fix))
authTag = make([]byte, 192)
fin.Read(authTag)
authTag, errs[9] = rsDecode(rs64, authTag, !(*fix))
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], !(*fix))
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], !(*fix))
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:], !(*fix))
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("\nThe input volume is damaged or modified.")
if *reedsolo {
if !(*fix) {
fmt.Println("Fortunately, this volume is encoded with Reed-Solomon.")
fmt.Println("Try again using the '-f' flag to repair the corruption.")
} else {
fmt.Println("The corruption could not be fixed with Reed-Solomon.")
}
}
return 1
}
}
fin.Close()
fout.Close()
fmt.Println("\nCompleted.")
return 0
}
func main() {
os.Exit(work())
}