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