Compare commits
19 Commits
86bc3d3c69
...
f6a4f0e920
Author | SHA1 | Date |
---|---|---|
Evan Su | f6a4f0e920 | |
Evan Su | a8e8a92002 | |
Evan Su | 613ab8232d | |
Evan Su | c73fba2250 | |
Evan Su | bbe5ee979a | |
Evan Su | 1b4998d4e2 | |
Evan Su | 97bde62236 | |
Evan Su | 77aea6a8e4 | |
Evan Su | a91428b934 | |
Evan Su | 45967b960e | |
Evan Su | e9c2e57b55 | |
Evan Su | 3bec342488 | |
Evan Su | 434410d1d6 | |
Evan Su | 60a144102c | |
Evan Su | 60dd17749c | |
Evan Su | 9f7dc39e7c | |
Evan Su | d70ee6effc | |
Evan Su | c235f28fd2 | |
Evan Su | bb2937e4a1 |
|
@ -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
|
||||
|
|
14
Changelog.md
14
Changelog.md
|
@ -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>
|
||||
|
|
|
@ -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>.
|
||||
|
||||
|
|
|
@ -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.
|
|
@ -1,4 +1,4 @@
|
|||
module github.com/HACKERALERT/Picocrypt/cli/picocrypt
|
||||
module github.com/HACKERALERT/Picocrypt/cli/v1/picocrypt
|
||||
|
||||
go 1.17
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
```
|
|
@ -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
|
||||
)
|
|
@ -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=
|
|
@ -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())
|
||||
}
|
Loading…
Reference in New Issue