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(¶m.argon2id_time_cost, "t", argon2id_time_cost, "Time cost/Iterations")
- flag.Uint64Var(¶m.argon2id_memory_cost, "m", argon2id_memory_cost, "Memory cost")
- flag.Uint64Var(¶m.argon2id_threads, "j", argon2id_threads, "Parallel threads")
- flag.Uint64Var(¶m.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