/*
Copyright © 2022-2025 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package main
import (
"crypto/cipher"
cryptorand "crypto/rand"
"errors"
"fmt"
"io"
"log"
"math/big"
mathrand "math/rand"
"os"
"time"
"unsafe"
"golang.org/x/crypto/argon2"
chapo "golang.org/x/crypto/chacha20poly1305"
)
const (
SaltSize = 32 // in bytes
NonceSize = 24 // in bytes. taken from aead.NonceSize()
KeySize = uint32(32) // KeySize is 32 bytes (256 bits).
KeyTime = uint32(5)
KeyMemory = uint32(1024 * 64) // KeyMemory in KiB. here, 64 MiB.
KeyThreads = uint8(4)
chunkSize = 1024 * 32 // chunkSize in bytes. here, 32 KiB.
letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-"
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letters) {
b[i] = letters[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return *(*string)(unsafe.Pointer(&b))
}
func GetRandomKey() ([]byte, error) {
password, err := GenerateSecureRandomBytes(int(chapo.KeySize))
if err != nil {
return nil, err
}
salt, err := GenerateSecureRandomBytes(chapo.NonceSizeX)
if err != nil {
return nil, err
}
key := argon2.IDKey(password, salt, KeyTime, KeyMemory, KeyThreads, chapo.KeySize)
return key, nil
}
func Encrypt(c *Conf, filename string) error {
info, err := os.Stat(filename)
if err != nil {
return err
}
size := info.Size()
outfile, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return err
}
defer func() {
if err := outfile.Close(); err != nil {
log.Fatal(err)
}
}()
key, err := GetRandomKey()
if err != nil {
return err
}
aead, err := chapo.NewX(key)
if err != nil {
return err
}
for i := 0; i < c.count; i++ {
for {
if size < chunkSize {
if err := EncryptChunk(aead, outfile, size); err != nil {
return err
}
break
}
if err := EncryptChunk(aead, outfile, chunkSize); err != nil {
return err
}
size = size - chunkSize
if size <= 0 {
break
}
}
}
return nil
}
func EncryptChunk(aead cipher.AEAD, file *os.File, size int64) error {
chunk := make([]byte, size)
nonce, err := GenerateSecureRandomBytes(int(chapo.NonceSizeX))
if err != nil {
return err
}
cipher := aead.Seal(nil, nonce, chunk, nil)
n, err := file.Write(cipher[:size])
if err != nil {
return err
}
if int64(n) != size {
return errors.New("invalid number of bytes written")
}
return nil
}