/* 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 }