1 // GoGOST -- Pure Go GOST cryptographic functions library
2 // Copyright (C) 2015-2023 Sergey Matveev <stargrave@stargrave.org>
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, version 3 of the License.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 // Multilinear Galois Mode (MGM) block cipher mode.
26 var InvalidTag = errors.New("gogost/mgm: invalid authentication tag")
29 Mul(x, y []byte) []byte
45 func NewMGM(cipher cipher.Block, tagSize int) (cipher.AEAD, error) {
46 blockSize := cipher.BlockSize()
47 if !(blockSize == 8 || blockSize == 16) {
48 return nil, errors.New("gogost/mgm: only 64/128 blocksizes allowed")
50 if tagSize < 4 || tagSize > blockSize {
51 return nil, errors.New("gogost/mgm: invalid tag size")
54 MaxSize: uint64(1<<uint(blockSize*8/2) - 1),
58 icn: make([]byte, blockSize),
59 bufP: make([]byte, blockSize),
60 bufC: make([]byte, blockSize),
61 padded: make([]byte, blockSize),
62 sum: make([]byte, blockSize),
72 func (mgm *MGM) NonceSize() int {
76 func (mgm *MGM) Overhead() int {
80 func incr(data []byte) {
81 for i := len(data) - 1; i >= 0; i-- {
89 func xor(dst, src1, src2 []byte) {
90 for i := 0; i < len(src1); i++ {
91 dst[i] = src1[i] ^ src2[i]
95 func (mgm *MGM) validateNonce(nonce []byte) {
96 if len(nonce) != mgm.BlockSize {
97 panic("nonce length must be equal to cipher's blocksize")
99 if nonce[0]&0x80 > 0 {
100 panic("nonce must not have higher bit set")
104 func (mgm *MGM) validateSizes(text, additionalData []byte) {
105 if len(text) == 0 && len(additionalData) == 0 {
106 panic("at least either *text or additionalData must be provided")
108 if uint64(len(additionalData)) > mgm.MaxSize {
109 panic("additionalData is too big")
111 if uint64(len(text)+len(additionalData)) > mgm.MaxSize {
112 panic("*text with additionalData are too big")
116 func (mgm *MGM) auth(out, text, ad []byte) {
117 for i := 0; i < mgm.BlockSize; i++ {
121 textLen := len(text) * 8
123 mgm.cipher.Encrypt(mgm.bufP, mgm.icn) // Z_1 = E_K(1 || ICN)
124 for len(ad) >= mgm.BlockSize {
125 mgm.cipher.Encrypt(mgm.bufC, mgm.bufP) // H_i = E_K(Z_i)
126 xor( // sum (xor)= H_i (x) A_i
129 mgm.mul.Mul(mgm.bufC, ad[:mgm.BlockSize]),
131 incr(mgm.bufP[:mgm.BlockSize/2]) // Z_{i+1} = incr_l(Z_i)
132 ad = ad[mgm.BlockSize:]
136 for i := len(ad); i < mgm.BlockSize; i++ {
139 mgm.cipher.Encrypt(mgm.bufC, mgm.bufP)
140 xor(mgm.sum, mgm.sum, mgm.mul.Mul(mgm.bufC, mgm.padded))
141 incr(mgm.bufP[:mgm.BlockSize/2])
144 for len(text) >= mgm.BlockSize {
145 mgm.cipher.Encrypt(mgm.bufC, mgm.bufP) // H_{h+j} = E_K(Z_{h+j})
146 xor( // sum (xor)= H_{h+j} (x) C_j
149 mgm.mul.Mul(mgm.bufC, text[:mgm.BlockSize]),
151 incr(mgm.bufP[:mgm.BlockSize/2]) // Z_{h+j+1} = incr_l(Z_{h+j})
152 text = text[mgm.BlockSize:]
155 copy(mgm.padded, text)
156 for i := len(text); i < mgm.BlockSize; i++ {
159 mgm.cipher.Encrypt(mgm.bufC, mgm.bufP)
160 xor(mgm.sum, mgm.sum, mgm.mul.Mul(mgm.bufC, mgm.padded))
161 incr(mgm.bufP[:mgm.BlockSize/2])
164 mgm.cipher.Encrypt(mgm.bufP, mgm.bufP) // H_{h+q+1} = E_K(Z_{h+q+1})
166 if mgm.BlockSize == 8 {
167 binary.BigEndian.PutUint32(mgm.bufC, uint32(adLen))
168 binary.BigEndian.PutUint32(mgm.bufC[mgm.BlockSize/2:], uint32(textLen))
170 binary.BigEndian.PutUint64(mgm.bufC, uint64(adLen))
171 binary.BigEndian.PutUint64(mgm.bufC[mgm.BlockSize/2:], uint64(textLen))
173 // sum (xor)= H_{h+q+1} (x) (len(A) || len(C))
174 xor(mgm.sum, mgm.sum, mgm.mul.Mul(mgm.bufC, mgm.bufP))
175 mgm.cipher.Encrypt(mgm.bufP, mgm.sum) // E_K(sum)
176 copy(out, mgm.bufP[:mgm.TagSize]) // MSB_S(E_K(sum))
179 func (mgm *MGM) crypt(out, in []byte) {
181 mgm.cipher.Encrypt(mgm.bufP, mgm.icn) // Y_1 = E_K(0 || ICN)
182 for len(in) >= mgm.BlockSize {
183 mgm.cipher.Encrypt(mgm.bufC, mgm.bufP) // E_K(Y_i)
184 xor(out, mgm.bufC, in) // C_i = P_i (xor) E_K(Y_i)
185 incr(mgm.bufP[mgm.BlockSize/2:]) // Y_i = incr_r(Y_{i-1})
186 out = out[mgm.BlockSize:]
187 in = in[mgm.BlockSize:]
190 mgm.cipher.Encrypt(mgm.bufC, mgm.bufP)
191 xor(out, in, mgm.bufC)
195 func (mgm *MGM) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
196 mgm.validateNonce(nonce)
197 mgm.validateSizes(plaintext, additionalData)
198 if uint64(len(plaintext)) > mgm.MaxSize {
199 panic("plaintext is too big")
201 ret, out := sliceForAppend(dst, len(plaintext)+mgm.TagSize)
203 mgm.crypt(out, plaintext)
205 out[len(plaintext):len(plaintext)+mgm.TagSize],
206 out[:len(plaintext)],
212 // Open the authenticated ciphertext. If authentication tag is invalid,
213 // then InvalidTag error is returned.
214 func (mgm *MGM) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
215 mgm.validateNonce(nonce)
216 mgm.validateSizes(ciphertext, additionalData)
217 if len(ciphertext) < mgm.TagSize {
218 return nil, errors.New("ciphertext is too short")
220 if uint64(len(ciphertext)-mgm.TagSize) > mgm.MaxSize {
221 panic("ciphertext is too big")
223 ret, out := sliceForAppend(dst, len(ciphertext)-mgm.TagSize)
224 ct := ciphertext[:len(ciphertext)-mgm.TagSize]
226 mgm.auth(mgm.sum, ct, additionalData)
227 if !hmac.Equal(mgm.sum[:mgm.TagSize], ciphertext[len(ciphertext)-mgm.TagSize:]) {
228 return nil, InvalidTag