Compare commits
7 Commits
2f2c9157e9
...
5a5cc0ae6e
Author | SHA1 | Date |
---|---|---|
Evan Su | 5a5cc0ae6e | |
Evan Su | bb8663623f | |
Evan Su | d741adbcfc | |
Evan Su | 198aa075d6 | |
Evan Su | 2de657299a | |
Evan Su | a89c2f9962 | |
Evan Su | 685ea33be9 |
|
@ -5,6 +5,9 @@ on:
|
|||
- "src/*.go"
|
||||
- "src/go.mod"
|
||||
- "src/go.sum"
|
||||
- "cli/picocrypt/*.go"
|
||||
- "cli/picocrypt/go.mod"
|
||||
- "cli/picocrypt/go.sum"
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
jobs:
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
# v1.32 (No ETA)
|
||||
<ul>
|
||||
<li>✓ Added a command-line interface</li>
|
||||
<li>Use Debian 11 as the base for the AppImage instead of Debian 10</li>
|
||||
</ul>
|
||||
|
||||
# v1.31 (Released 11/18/2022)
|
||||
<ul>
|
||||
<li>✓ Force software OpenGL rendering on macOS</li>
|
||||
|
|
13
README.md
13
README.md
|
@ -20,6 +20,9 @@ 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.31/Picocrypt.AppImage">here</a>. While this AppImage should work on most systems, Linux is a mess when it comes to cross-distro and cross-release compatibility, so if the AppImage doesn't work, you can run Picocrypt through Wine or from source using the instructions in the `src/` directory.
|
||||
|
||||
## CLI
|
||||
A command-line interface is available for Picocrypt <a href="/cli">here</a>. Keep in mind that the functionality is extremely limited and is not meant to replace the standard GUI app. Rather, it's best suited for environments where the GUI won't run or you need the ability to automate encryption workflows.
|
||||
|
||||
## Paranoid Pack
|
||||
The Paranoid Pack is a compressed archive that contains executables for Windows, macOS, and Linux, including the source code and dependencies. As long as you have it stored in a place you can access, you'll be able to open it and use Picocrypt on any desktop operating system in case this repository mysteriously vanishes or the entire Internet burns down. Think of it as a seed vault for Picocrypt; as long as one person has the Paranoid Pack within reach, they can share it with the rest of the world and keep Picocrypt functional in case of catastrophic events. The best way to ensure Picocrypt is accessible many decades from now is to keep a Paranoid Pack in a safe place. Get your copy <a href="https://github.com/HACKERALERT/Picocrypt/releases/download/1.31/Paranoid.zip">here</a>.
|
||||
|
||||
|
@ -100,10 +103,6 @@ When I was actively developing Picocrypt, I accepted donations, but now that Pic
|
|||
|
||||
No, Picocrypt is considered feature-complete and won't be getting any new features. Unlike other tools which try to constantly add new features (which introduces new bugs and security holes), Picocrypt focuses on just a few core features but does each of them exceptionally well. Remember Picocrypt's ideology: small, simple, and secure.
|
||||
|
||||
**Is there a command-line interface?**
|
||||
|
||||
No, Picocrypt doesn't have a command-line interface because it's designed to appeal to the general public, most of which isn't very comfortable with a terminal. The UI is what makes Picocrypt powerful and unique, so adding a command-line interface wouldn't be of much benefit for most users.
|
||||
|
||||
**Will Android/iOS be supported?**
|
||||
|
||||
No, I don't plan on supporting Android or iOS because they are very different from traditional desktop operating systems and require different toolchains to develop apps for. Due to the nature of open-source software, however, a community-built version of Picocrypt for Android or iOS may appear in the future.
|
||||
|
@ -144,8 +143,12 @@ A thank you from the bottom of my heart to the significant contributors on Open
|
|||
<li>Cohen ($10)</li>
|
||||
<li>EuA ($10)</li>
|
||||
<li>geevade ($10)</li>
|
||||
<li>Guest ($10)</li>
|
||||
<li>Hilebrinest ($10)</li>
|
||||
<li>gabu.gu ($10)</li>
|
||||
<li>Boat ($10)</li>
|
||||
</ul>
|
||||
<!-- Last updated January 30, 2023 -->
|
||||
<!-- Last updated April 11, 2023 -->
|
||||
|
||||
Also, a huge thanks to the following people who were the first to donate and support Picocrypt:
|
||||
<ul>
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# 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
|
||||
```
|
||||
picocrypt -p password <file>
|
||||
```
|
||||
The CLI is designed to do one thing and one thing only: encrypt and decrypt a single file. Its goal isn't to be full-blown encryption tool, but to provide the basics of file encryption so that you can do the rest. This allows you to write custom scripts to encrypt your weekly backups, secure client files on a server, etc.
|
|
@ -0,0 +1,10 @@
|
|||
module github.com/HACKERALERT/Picocrypt/cli/picocrypt
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/HACKERALERT/crypto v0.0.0-20220905152506-aa0dd62d8f67
|
||||
github.com/HACKERALERT/infectious v0.0.0-20220905152109-2c37b99f37ff
|
||||
)
|
||||
|
||||
require github.com/HACKERALERT/sys v0.0.0-20220905150735-46e319fb60c9 // indirect
|
|
@ -0,0 +1,6 @@
|
|||
github.com/HACKERALERT/crypto v0.0.0-20220905152506-aa0dd62d8f67 h1:4WfPIopYjvBjyDg0IET7mEj32kkihLmvFgwCOmldqK8=
|
||||
github.com/HACKERALERT/crypto v0.0.0-20220905152506-aa0dd62d8f67/go.mod h1:qiHCxMDsCxX4QhXd3kDYWiNOR/DZQZ7nYO/f2OgWst0=
|
||||
github.com/HACKERALERT/infectious v0.0.0-20220905152109-2c37b99f37ff h1:ertDhqhixxQJJPJIpn4wmSVs2fQ3tlSDNuiZ7jHCvEs=
|
||||
github.com/HACKERALERT/infectious v0.0.0-20220905152109-2c37b99f37ff/go.mod h1:GwBVHbiXRUUciGfKWwm4GCL8FvMZBLHrQq23UXW/CU8=
|
||||
github.com/HACKERALERT/sys v0.0.0-20220905150735-46e319fb60c9 h1:raqLJhvqDGk9L4dnJnO0tTV5Lyba2jhQcIHEle+o1dM=
|
||||
github.com/HACKERALERT/sys v0.0.0-20220905150735-46e319fb60c9/go.mod h1:I4esFWbCYc37CXVb+3qJHJiU43NGkLf66kNV/NDnXcU=
|
|
@ -0,0 +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.32")))
|
||||
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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue