Cybersecurity-Projects/PROJECTS/beginner/steganography-multi-tool/internal/payload/envelope.go

203 lines
4.1 KiB
Go

/*
©AngelaMos | 2026
envelope.go
Pack and unpack the crypha payload envelope
Layout: magic(4) ver(1) flags(1) [cipher(1) params(9) salt(16) nonce(12) if encrypted]
len(4) body(N) crc32(4). The header up to nonce is the AEAD additional data.
*/
package payload
import (
"bytes"
"crypto/rand"
"encoding/binary"
"hash/crc32"
)
func Pack(data []byte, opts Options) ([]byte, error) {
if len(data) == 0 {
return nil, ErrEmptyPayload
}
var flags byte
body := data
if opts.Compress {
c, err := compress(body)
if err != nil {
return nil, err
}
body = c
flags |= flagCompressed
}
header := new(bytes.Buffer)
header.Write(magic[:])
header.WriteByte(currentVersion)
if len(opts.Passphrase) > 0 {
flags |= flagEncrypted
header.WriteByte(flags)
salt := make([]byte, saltLen)
if _, err := rand.Read(salt); err != nil {
return nil, err
}
params := paramsForStrength(opts.Strength)
key := deriveKey(opts.Passphrase, salt, params)
aead, cipherID, err := newAEAD(opts.Cipher, key)
if err != nil {
return nil, err
}
nonce := make([]byte, aead.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return nil, err
}
header.WriteByte(cipherID)
var pbuf [paramsLen]byte
binary.BigEndian.PutUint32(pbuf[0:4], params.time)
binary.BigEndian.PutUint32(pbuf[4:8], params.memory)
pbuf[8] = params.threads
header.Write(pbuf[:])
header.Write(salt)
header.Write(nonce)
body = aead.Seal(nil, nonce, body, header.Bytes())
} else {
header.WriteByte(flags)
}
out := new(bytes.Buffer)
out.Write(header.Bytes())
var meta [lenField]byte
binary.BigEndian.PutUint32(meta[:], uint32(len(body)))
out.Write(meta[:])
out.Write(body)
binary.BigEndian.PutUint32(meta[:], crc32.ChecksumIEEE(body))
out.Write(meta[:])
return out.Bytes(), nil
}
type parsed struct {
flags byte
aad []byte
cipherID byte
params kdfParams
salt []byte
nonce []byte
body []byte
}
func parse(env []byte) (*parsed, error) {
off := 0
remaining := func(n int) bool { return off+n <= len(env) }
if !remaining(len(magic)) || !bytes.Equal(env[0:len(magic)], magic[:]) {
return nil, ErrBadMagic
}
off = len(magic)
if !remaining(1) {
return nil, ErrTruncated
}
if env[off] != currentVersion {
return nil, ErrUnsupportedVersion
}
off++
if !remaining(1) {
return nil, ErrTruncated
}
p := &parsed{flags: env[off]}
off++
if p.flags&flagEncrypted != 0 {
if !remaining(1 + paramsLen + saltLen + nonceLen) {
return nil, ErrTruncated
}
p.cipherID = env[off]
off++
p.params = kdfParams{
time: binary.BigEndian.Uint32(env[off : off+4]),
memory: binary.BigEndian.Uint32(env[off+4 : off+8]),
threads: env[off+8],
}
off += paramsLen
if !p.params.valid() {
return nil, ErrBadParams
}
p.salt = env[off : off+saltLen]
off += saltLen
p.nonce = env[off : off+nonceLen]
off += nonceLen
p.aad = env[0:off]
}
if !remaining(lenField) {
return nil, ErrTruncated
}
bodyLen := int(binary.BigEndian.Uint32(env[off : off+lenField]))
off += lenField
if bodyLen < 0 || !remaining(bodyLen) {
return nil, ErrTruncated
}
p.body = env[off : off+bodyLen]
off += bodyLen
if !remaining(crcField) {
return nil, ErrTruncated
}
if crc32.ChecksumIEEE(p.body) != binary.BigEndian.Uint32(env[off:off+crcField]) {
return nil, ErrChecksumMismatch
}
return p, nil
}
func Unpack(env, passphrase []byte) ([]byte, error) {
p, err := parse(env)
if err != nil {
return nil, err
}
body := p.body
if p.flags&flagEncrypted != 0 {
if len(passphrase) == 0 {
return nil, ErrPassphraseRequired
}
key := deriveKey(passphrase, p.salt, p.params)
aead, err := aeadByID(p.cipherID, key)
if err != nil {
return nil, err
}
plain, err := aead.Open(nil, p.nonce, body, p.aad)
if err != nil {
return nil, ErrDecrypt
}
body = plain
}
if p.flags&flagCompressed != 0 {
return decompress(body)
}
return body, nil
}
func Validate(env []byte) error {
_, err := parse(env)
return err
}
func IsEncrypted(env []byte) (bool, error) {
p, err := parse(env)
if err != nil {
return false, err
}
return p.flags&flagEncrypted != 0, nil
}