tUpdate stream data format - cream - Stream encryption utility
 (HTM) git clone git://git.z3bra.org/cream.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
       ---
 (DIR) commit 29772ac13d284197cf99df7462907c38f47e318d
 (DIR) parent dae1337a5f9ea41c1fd743d7469b6b2c936f1c82
 (HTM) Author: Willy Goiffon <contact@z3bra.org>
       Date:   Mon, 17 Oct 2022 18:37:47 +0200
       
       Update stream data format
       
       Diffstat:
         M cream.5                             |      27 +++++++++++++++++++++++++--
         M cream.go                            |     146 +++++++++++++++++++++----------
         M go.mod                              |       2 +-
       
       3 files changed, 125 insertions(+), 50 deletions(-)
       ---
 (DIR) diff --git a/cream.5 b/cream.5
       t@@ -6,12 +6,34 @@
        .Nd Encrypted stream data format
        .Sh DESCRIPTION
        .Nm
       -data is the concatenation of a 16 bytes salt, and a flow of encrypted data.
       +data is the concatenation of a 48 bytes header and a flow of encrypted data.
        .Pp
        Data is encrypted with a key derivated from a
        .Em password
        and a 
        .Em salt .
       +.Sh HEADER FORMAT
       +As a convenience for decrypting the stream, the parameters used for
       +encryption are specified in a 48 bytes header.
       +.Pp
       +Header has the following format (all values in network byte order):
       +.TS
       +|c|        c|        c|
       +|r|        l|        l|.
       +_
       +BYTES        DESCRIPTION        VALUE
       +_
       +6        6*8-bit BE unsigned char        magic string CREAM\e1
       +2        16-bit BE unsigned integer        header version
       +4        32-bit BE unsigned integer        xchacha20 key size
       +4        32-bit BE unsigned integer        xchacha20 iv size
       +4        32-bit BE unsigned integer        xchacha20 block size
       +4        32-bit BE unsigned integer        argon2id time cost
       +4        32-bit BE unsigned integer        argon2id memory
       +4        32-bit BE unsigned integer        argon2id threads
       +16        16*8-bit BE unsigned integer        key derivation salt
       +_
       +.TE
        .Sh CRYPTOGRAPHY INTERNALS
        .Ss Encryption
        XChaCha20-Poly1305 is used to encrypt the data. It is a symetrical cipher,
       t@@ -50,6 +72,7 @@ For use as a symmetric key, you will want to use the exact same parameters
        for both encryption and decryption, otherwise decryption of the stream
        will be impossible.
        .Sh SEE ALSO
       -.Xr cream 1
       +.Xr cream 1 ,
       +.Xr magic 5
        .Sh AUTHORS
        .An Willy Goiffon Aq Mt dev@z3bra.org
 (DIR) diff --git a/cream.go b/cream.go
       t@@ -2,6 +2,7 @@ package main
        
        import (
                "crypto/rand"
       +        "encoding/binary"
                "flag"
                "fmt"
                "io"
       t@@ -14,24 +15,31 @@ import (
        )
        
        const (
       +        cream_magic               = "CREAM\x01"
       +        cream_version             = 1
       +
       +        // default encryption parameters
       +        xchacha20poly1305_key_len = 32
       +        xchacha20poly1305_iv_len  = 16
       +        xchacha20poly1305_buf_len = 4096
       +
                // default key derivation values in libsodium
                argon2id_time_cost        = 2
       -        argon2id_memory_cost      = 67108864 / 1024
       +        argon2id_memory_cost      = 65536 // 64 Kib
                argon2id_threads          = 1
                argon2id_salt_len         = 16
       -        xchacha20poly1305_key_len = 32
       -        xchacha20poly1305_iv_len  = 16
       -        xchacha20poly1305_buf_len = 8192
        )
        
       -var param struct {
       -        argon2id_time_cost uint64
       -        argon2id_memory_cost uint64
       -        argon2id_threads uint64
       -        argon2id_salt_len int64
       -        xchacha20poly1305_key_len uint64
       -        xchacha20poly1305_iv_len uint64
       -        xchacha20poly1305_buf_len uint64
       +type Header struct {
       +        Cream_magic [6]byte
       +        Cream_version uint16
       +        Xchacha20poly1305_key_len uint32
       +        Xchacha20poly1305_iv_len uint32
       +        Xchacha20poly1305_buf_len uint32
       +        Argon2id_time_cost uint32
       +        Argon2id_memory_cost uint32
       +        Argon2id_threads uint32
       +        Argon2id_salt [16]byte
        }
        
        func usage() {
       t@@ -39,31 +47,55 @@ func usage() {
                os.Exit(2)
        }
        
       -func readsalt(f *os.File, salt *[]byte) {
       -        _, err := f.Read(*salt)
       +func dumpheader(h Header) {
       +        fmt.Fprintln(os.Stderr, "CREAM")
       +        fmt.Fprintf(os.Stderr, "magic\t%s (% x)\n", string(h.Cream_magic[:]), h.Cream_magic)
       +        fmt.Fprintf(os.Stderr, "version\t%d\n", h.Cream_version)
       +        fmt.Fprintln(os.Stderr, "\nXCHACHA20POLY1305:")
       +        fmt.Fprintf(os.Stderr, "keylen\t%d\n", h.Xchacha20poly1305_key_len)
       +        fmt.Fprintf(os.Stderr, "IV len\t%d\n", h.Xchacha20poly1305_iv_len)
       +        fmt.Fprintf(os.Stderr, "Block len\t%d\n", h.Xchacha20poly1305_buf_len)
       +        fmt.Fprintln(os.Stderr, "\nARGON2ID")
       +        fmt.Fprintf(os.Stderr, "time\t%d\n", h.Argon2id_time_cost)
       +        fmt.Fprintf(os.Stderr, "memory\t%d\n", h.Argon2id_memory_cost)
       +        fmt.Fprintf(os.Stderr, "thread(s)\t%d\n", h.Argon2id_threads)
       +        fmt.Fprintf(os.Stderr, "salt\t% x\n", h.Argon2id_salt)
       +}
       +
       +func readheader(f *os.File, h *Header) {
       +        err := binary.Read(f, binary.BigEndian, h)
                if err != nil {
                        log.Fatal(err)
                }
       +
       +        if string(h.Cream_magic[:]) != string(cream_magic) {
       +                log.Fatal("Bad magic value: %b", h.Cream_magic)
       +        }
       +
       +        if h.Cream_version > cream_version {
       +                log.Fatal("Version %d not handled", h.Cream_version)
       +        }
       +
       +        dumpheader(*h)
        }
        
       -func deriv(pw []byte, key *[]byte, salt []byte) {
       -        *key = argon2.IDKey(pw, salt,
       -                uint32(param.argon2id_time_cost),
       -                uint32(param.argon2id_memory_cost),
       -                uint8(param.argon2id_threads),
       -                uint32(param.xchacha20poly1305_key_len))
       +func deriv(pw []byte, key *[]byte, time, mem, job uint32, salt []byte) {
       +        klen := len(*key)
       +        *key = argon2.IDKey(pw, salt, time, mem, uint8(job), uint32(klen))
        }
        
       -func encrypt(in *os.File, out *os.File, key []byte, salt []byte) {
       +func encrypt(in *os.File, out *os.File, key []byte, h Header) {
                var tag byte
       -        buf := make([]byte, param.xchacha20poly1305_buf_len)
       +        buf := make([]byte, h.Xchacha20poly1305_buf_len)
        
                enc, nonce, err := secretstream.NewEncryptor(key)
                if err != nil {
                        log.Fatal(err)
                }
        
       -        out.Write(salt)
       +        dumpheader(h)
       +
       +        binary.Write(out, binary.BigEndian, h)
                if err != nil {
                        log.Fatal(err)
                }
       t@@ -95,18 +127,18 @@ func encrypt(in *os.File, out *os.File, key []byte, salt []byte) {
                }
        }
        
       -func decrypt(in *os.File, out *os.File, key []byte) {
       -        buf := make([]byte, param.xchacha20poly1305_buf_len+secretstream.StreamABytes)
       -        header := make([]byte, secretstream.StreamHeaderBytes)
       +func decrypt(in *os.File, out *os.File, skip int, key []byte, blen uint32) {
       +        buf := make([]byte, blen+secretstream.StreamABytes)
       +        nonce := make([]byte, secretstream.StreamHeaderBytes)
        
       -        // Skip beginning of file which (supposedly) contains the salt for the key
       -        in.Seek(param.argon2id_salt_len, os.SEEK_SET)
       -        _, err := in.Read(header)
       +        // Skip beginning of file which (supposedly) contains the cream header
       +        in.Seek(int64(skip), os.SEEK_SET)
       +        _, err := in.Read(nonce)
                if err != nil {
                        log.Fatal(err)
                }
        
       -        dec, err := secretstream.NewDecryptor(key, header)
       +        dec, err := secretstream.NewDecryptor(key, nonce)
                if err != nil {
                        log.Fatal(err)
                }
       t@@ -132,20 +164,27 @@ func decrypt(in *os.File, out *os.File, key []byte) {
        
        func main() {
                var err error
       -        var key, salt, pass []byte
       +        var key, pass []byte
                var filename, saltfile string
                var dflag, eflag bool
        
       +        var time uint64
       +        var memory uint64
       +        var jobnum uint64
       +        var blksiz uint64
       +        var header Header
       +
                in := os.Stdin
                out := os.Stdout
        
       +        copy(header.Cream_magic[:], cream_magic)
       +        header.Cream_version = cream_version
       +
                // Init default cipher values
       -        param.argon2id_salt_len = argon2id_salt_len
       -        param.xchacha20poly1305_key_len = xchacha20poly1305_key_len
       -        param.xchacha20poly1305_iv_len = xchacha20poly1305_iv_len
       +        header.Xchacha20poly1305_key_len = xchacha20poly1305_key_len
       +        header.Xchacha20poly1305_iv_len = xchacha20poly1305_iv_len
        
       -        salt = make([]byte, param.argon2id_salt_len)
       -        key = make([]byte, param.xchacha20poly1305_key_len)
       +        key = make([]byte, header.Xchacha20poly1305_key_len)
        
                flag.StringVar(&filename, "f", "", "Encrypt/decrypt to/from file name")
                flag.StringVar(&saltfile, "s", "", "Read salt from file (encrypt-only)")
       t@@ -153,14 +192,19 @@ func main() {
                flag.BoolVar(&dflag, "d", false, "decrypt input")
        
                // xchacha20/argon2id parameters
       -        flag.Uint64Var(&param.argon2id_time_cost, "t", argon2id_time_cost, "Time cost/Iterations")
       -        flag.Uint64Var(&param.argon2id_memory_cost, "m", argon2id_memory_cost, "Memory cost")
       -        flag.Uint64Var(&param.argon2id_threads, "j", argon2id_threads, "Parallel threads")
       -        flag.Uint64Var(&param.xchacha20poly1305_buf_len, "b", xchacha20poly1305_buf_len, "Buffer size")
       +        flag.Uint64Var(&time, "t", argon2id_time_cost, "Time cost/Iterations")
       +        flag.Uint64Var(&memory, "m", argon2id_memory_cost, "Memory cost")
       +        flag.Uint64Var(&jobnum, "j", argon2id_threads, "Parallel threads")
       +        flag.Uint64Var(&blksiz, "b", xchacha20poly1305_buf_len, "Buffer size")
        
                flag.Usage = usage
                flag.Parse()
        
       +        header.Argon2id_time_cost = uint32(time)
       +        header.Argon2id_memory_cost = uint32(memory)
       +        header.Argon2id_threads = uint32(jobnum)
       +        header.Xchacha20poly1305_buf_len = uint32(blksiz)
       +
                if eflag && dflag {
                        log.Fatal("Cannot use encryption and decryption at the same time")
                }
       t@@ -189,9 +233,13 @@ func main() {
                                defer in.Close()
                        }
        
       -                readsalt(in, &salt)
       -                deriv(pass, &key, salt)
       -                decrypt(in, out, key)
       +                readheader(in, &header)
       +                deriv(pass, &key,
       +                        header.Argon2id_time_cost,
       +                        header.Argon2id_memory_cost,
       +                        header.Argon2id_threads,
       +                        header.Argon2id_salt[:])
       +                decrypt(in, out, binary.Size(header), key, header.Xchacha20poly1305_buf_len)
                } else {
                        if len(saltfile) > 0 {
                                // Read salt from any manually specified file…
       t@@ -199,16 +247,20 @@ func main() {
                                if err != nil {
                                        log.Fatal(err)
                                }
       -                        readsalt(f, &salt)
       +                        _, err = f.Read(header.Argon2id_salt[:])
                                f.Close()
                        } else {
                                // … or generate a random one
       -                        _, err = rand.Read(salt)
       +                        _, err = rand.Read(header.Argon2id_salt[:])
                                if err != nil {
                                        log.Fatal(err)
                                }
                        }
       -                deriv(pass, &key, salt)
       +                deriv(pass, &key,
       +                        header.Argon2id_time_cost,
       +                        header.Argon2id_memory_cost,
       +                        header.Argon2id_threads,
       +                        header.Argon2id_salt[:])
                        if len(filename) > 0 {
                                out, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
                                if err != nil {
       t@@ -216,6 +268,6 @@ func main() {
                                }
                                defer out.Close()
                        }
       -                encrypt(in, out, key, salt)
       +                encrypt(in, out, key, header)
                }
        }
 (DIR) diff --git a/go.mod b/go.mod
       t@@ -1,6 +1,6 @@
        module z3bra.org/safe
        
       -go 1.19
       +go 1.18
        
        require (
                github.com/netfoundry/secretstream v0.1.2