"bytes"
"crypto/cipher"
"crypto/hkdf"
+ "crypto/subtle"
"errors"
"hash"
"io"
DEMAlgo = "chapoly-krkc"
)
+type keymat struct {
+ key []byte
+ iv []byte
+}
+
type job struct {
- bufReady chan struct{}
- processed chan struct{}
- keyAndCommitment []byte
- buf []byte
- tail bool
+ bufReady chan struct{}
+ processed chan error
+ keymat keymat
+ buf []byte
}
func blake2bHash() hash.Hash {
}
ready := make(chan *job, procs)
dones := make(chan *job, procs)
- keyAndCommitments := make(chan []byte, procs)
+ keymats := make(chan keymat, procs)
go func() {
ck := cek
- var keyAndCommitment []byte
+ var key []byte
+ var iv []byte
var errHKDF error
for {
- keyAndCommitment, errHKDF = hkdf.Expand(
- blake2bHash, ck, "cm/encrypted/chapoly-krkc",
- chacha20poly1305.KeySize+CommitmentLen)
+ key, errHKDF = hkdf.Expand(
+ blake2bHash, ck, "cm/encrypted/chapoly-krkc/key",
+ chacha20poly1305.KeySize)
+ if errHKDF != nil {
+ panic(errHKDF)
+ }
+ iv, errHKDF = hkdf.Expand(
+ blake2bHash, ck, "cm/encrypted/chapoly-krkc/iv",
+ chacha20poly1305.NonceSizeX)
if errHKDF != nil {
panic(errHKDF)
}
- keyAndCommitments <- keyAndCommitment
+ keymats <- keymat{key: key, iv: iv}
ck, errHKDF = hkdf.Extract(blake2bHash, nil, ck)
if errHKDF != nil {
panic(errHKDF)
if err != nil {
return
}
- var errUnauth error
for range procs {
go func() {
- var ciph cipher.AEAD
j := job{
buf: make([]byte, blobChunkLen),
bufReady: make(chan struct{}),
- processed: make(chan struct{}),
+ processed: make(chan error),
}
- nonce := make([]byte, chacha20poly1305.NonceSize)
ready <- &j
- var errOpen error
+ var errJob error
+ var ciph cipher.AEAD
+ var com hash.Hash
+ var tag []byte
+ ourCom := make([]byte, CommitmentLen)
for {
<-j.bufReady
- ciph, err = chacha20poly1305.New(
- j.keyAndCommitment[:chacha20poly1305.KeySize],
- )
- if err != nil {
- panic(err)
+ ciph, errJob = chacha20poly1305.NewX(j.keymat.key)
+ if errJob != nil {
+ panic(errJob)
}
- if j.tail {
- nonce[len(nonce)-1] = 0x01
+ com, errJob = blake2b.New(CommitmentLen, nil)
+ if errJob != nil {
+ panic(errJob)
}
if seal {
ciph.Seal(
j.buf[:0],
- nonce,
+ j.keymat.iv,
j.buf[:len(j.buf)-chacha20poly1305.Overhead-CommitmentLen],
nil,
)
- copy(
- j.buf[len(j.buf)-CommitmentLen:],
- j.keyAndCommitment[chacha20poly1305.KeySize:],
- )
+ tag = j.buf[len(j.buf)-chacha20poly1305.Overhead-CommitmentLen:]
+ tag = tag[:chacha20poly1305.Overhead]
+ com.Write(j.keymat.key)
+ com.Write(j.keymat.iv)
+ com.Write(tag)
+ com.Sum(j.buf[:len(j.buf)-CommitmentLen])
} else {
- if bytes.Equal(
+ tag = j.buf[len(j.buf)-chacha20poly1305.Overhead-CommitmentLen:]
+ tag = tag[:chacha20poly1305.Overhead]
+ com.Write(j.keymat.key)
+ com.Write(j.keymat.iv)
+ com.Write(tag)
+ ourCom = com.Sum(ourCom[:0])
+ if subtle.ConstantTimeCompare(
+ ourCom,
j.buf[len(j.buf)-CommitmentLen:],
- j.keyAndCommitment[chacha20poly1305.KeySize:],
- ) {
- j.buf, errOpen = ciph.Open(
- j.buf[:0], nonce, j.buf[:len(j.buf)-CommitmentLen], nil,
+ ) == 1 {
+ j.buf, errJob = ciph.Open(
+ j.buf[:0], j.keymat.iv, j.buf[:len(j.buf)-CommitmentLen], nil,
)
- if errOpen == nil {
- errUnauth = errOpen
- }
} else {
- errUnauth = errors.New("commitment differs")
+ errJob = errors.New("commitment differs")
}
}
- j.processed <- struct{}{}
+ j.processed <- errJob
}
}()
}
go func() {
var n int64
for j := range dones {
- <-j.processed
+ errW = <-j.processed
+ if errW != nil {
+ j.buf = j.buf[:blobChunkLen]
+ ready <- j
+ break
+ }
if seal {
if blob {
n, errW = keks.BinEncode(w, j.buf)
var eof bool
var n int
var j *job
+ var tailMet bool
for !eof {
- if errUnauth != nil {
- err = errUnauth
- close(dones)
- <-finished
+ select {
+ case <-finished:
+ err = errW
return
+ default:
}
j = <-ready
if seal {
if !seal && n == 0 {
break
}
+ j.keymat = <-keymats
if (seal && n < ChunkLen) || (!seal && n < blobChunkLen) {
- j.tail = true
+ j.keymat.iv[chacha20poly1305.NonceSizeX-1] |= 0x01
+ tailMet = true
+ } else {
+ j.keymat.iv[chacha20poly1305.NonceSizeX-1] &= 0xFE
}
- j.keyAndCommitment = <-keyAndCommitments
j.bufReady <- struct{}{}
dones <- j
}
close(dones)
<-finished
- if errUnauth == nil {
- err = errW
- } else {
- err = errUnauth
+ err = errW
+ if err != nil {
+ return
+ }
+ if !tailMet {
+ err = errors.New("no tail met")
}
return
}
@verbatim
CK0 = CEK
CKi = HKDF-Extract(BLAKE2b, salt="", ikm=CK{i-1})
-KEY || COMMITMENT = HKDF-Expand(BLAKE2b, prk=CKi, info="cm/encrypted/chapoly-krkc")
-ChaCha20-Poly1305(key=KEY, ad="", nonce=11*0x00 || tail-flag, data=chunk) || COMMITMENT
+KEY = HKDF-Expand(BLAKE2b, prk=CKi, info="cm/encrypted/chapoly-krkc/key")
+IV = HKDF-Expand(BLAKE2b, prk=CKi, info="cm/encrypted/chapoly-krkc/iv", len=24)
+if last chunk { IV[23] |= 0x01 } else { IV[23] &= 0xFE }
+CIPHERTEXT || TAG = XChaCha20-Poly1305(key=KEY, ad="", nonce=IV, data=chunk)
+COMMITMENT = BLAKE2b-256(KEY || IV || TAG)
+CIPHERTEXT || TAG || COMMITMENT
@end verbatim
-Chaining key (CK) advances with every chunk. 256-bits encryption key and
-key commitment are derived from the chaining key.
+Chaining key (CK) advances with every chunk. 256-bit encryption key and
+randomised 192-bit nonce (initialisation vector) are derived from it.
-@code{tail-flag} is a byte indicating if that is the last chunk in the
-payload. It equals to 0x01 for the last chunk and to 0x00 for other
-ones. Last chunk should be smaller than previous ones, maybe (payload)
-even empty.
+Nonce's lowest bit is set only if this is the last chunk we encrypting.
@code{/payload}'s chunk length equals to 128KiB+16+32 bytes.