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

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