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 }