git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/crypto/xchacha20sha256/xchacha20_sha256.go (about)

     1  package xchacha20sha256
     2  
     3  import (
     4  	"crypto/hmac"
     5  	"crypto/sha256"
     6  	"crypto/subtle"
     7  	"encoding/binary"
     8  	"errors"
     9  	"hash"
    10  
    11  	"git.sr.ht/~pingoo/stdx/crypto/chacha20"
    12  	"github.com/zeebo/blake3"
    13  )
    14  
    15  const (
    16  	KeySize   = 32
    17  	NonceSize = 24
    18  	TagSize   = 32
    19  
    20  	encryptionKeyContext    = "xchacha2-sha256 2023-12-31 23:59:59:999 encryption-key"
    21  	athenticationKeyContext = "xchacha2-sha256 2024-01-01 00:00:00:000 authentication-key"
    22  )
    23  
    24  var (
    25  	ErrOpen = errors.New("xchacha20blake3: error decrypting ciphertext")
    26  )
    27  
    28  type XChaCha20Sha256 struct {
    29  	key [KeySize]byte
    30  }
    31  
    32  func New(key []byte) (*XChaCha20Sha256, error) {
    33  	ret := new(XChaCha20Sha256)
    34  	copy(ret.key[:], key)
    35  	return ret, nil
    36  }
    37  
    38  func (*XChaCha20Sha256) NonceSize() int {
    39  	return NonceSize
    40  }
    41  
    42  func (*XChaCha20Sha256) Overhead() int {
    43  	return TagSize
    44  }
    45  
    46  func (x *XChaCha20Sha256) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
    47  	ret, out := sliceForAppend(dst, len(plaintext)+TagSize)
    48  	ciphertext, tag := out[:len(plaintext)], out[len(plaintext):]
    49  
    50  	var authenticationKey [32]byte
    51  	xchacha20Cipher, _ := chacha20.NewX(x.key[:], nonce)
    52  	xchacha20Cipher.XORKeyStream(authenticationKey[:], authenticationKey[:])
    53  	xchacha20Cipher.SetCounter(1)
    54  	xchacha20Cipher.XORKeyStream(ciphertext, plaintext)
    55  
    56  	macHasher := hmac.New(sha256.New, authenticationKey[:])
    57  	macHasher.Write(additionalData)
    58  	macHasher.Write(ciphertext)
    59  	writeUint64(macHasher, len(additionalData))
    60  	writeUint64(macHasher, len(ciphertext))
    61  	macHasher.Sum(tag[:0])
    62  
    63  	return ret
    64  }
    65  
    66  func (x *XChaCha20Sha256) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
    67  	tag := ciphertext[len(ciphertext)-TagSize:]
    68  	ciphertext = ciphertext[:len(ciphertext)-TagSize]
    69  
    70  	var authenticationKey [32]byte
    71  	xchacha20Cipher, _ := chacha20.NewX(x.key[:], nonce)
    72  	xchacha20Cipher.XORKeyStream(authenticationKey[:], authenticationKey[:])
    73  	xchacha20Cipher.SetCounter(1)
    74  
    75  	var computedTag [TagSize]byte
    76  	macHasher := hmac.New(sha256.New, authenticationKey[:])
    77  	macHasher.Write(additionalData)
    78  	macHasher.Write(ciphertext)
    79  	writeUint64(macHasher, len(additionalData))
    80  	writeUint64(macHasher, len(ciphertext))
    81  	macHasher.Sum(computedTag[:0])
    82  
    83  	ret, plaintext := sliceForAppend(dst, len(ciphertext))
    84  
    85  	if subtle.ConstantTimeCompare(computedTag[:], tag) != 1 {
    86  		for i := range plaintext {
    87  			plaintext[i] = 0
    88  		}
    89  		return nil, ErrOpen
    90  	}
    91  
    92  	xchacha20Cipher.XORKeyStream(plaintext, ciphertext)
    93  
    94  	return ret, nil
    95  }
    96  
    97  func deriveKey(out, parentKey []byte, context string) []byte {
    98  	hasher := blake3.NewDeriveKey(context)
    99  	hasher.Write(parentKey)
   100  	// hasher.Write(binary.LittleEndian.AppendUint64([]byte{}, uint64(len(nonce))))
   101  	// hasher.Write(binary.LittleEndian.AppendUint64([]byte{}, uint64(len(parentKey))))
   102  	return hasher.Sum(out)
   103  }
   104  
   105  // sliceForAppend takes a slice and a requested number of bytes. It returns a
   106  // slice with the contents of the given slice followed by that many bytes and a
   107  // second slice that aliases into it and contains only the extra bytes. If the
   108  // original slice has sufficient capacity then no allocation is performed.
   109  func sliceForAppend(in []byte, n int) (head, tail []byte) {
   110  	if total := len(in) + n; cap(in) >= total {
   111  		head = in[:total]
   112  	} else {
   113  		head = make([]byte, total)
   114  		copy(head, in)
   115  	}
   116  	tail = head[len(in):]
   117  	return
   118  }
   119  
   120  func writeUint64(p hash.Hash, n int) {
   121  	var buf [8]byte
   122  	binary.LittleEndian.PutUint64(buf[:], uint64(n))
   123  	p.Write(buf[:])
   124  }