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 }