]> Cypherpunks.ru repositories - gogost.git/blob - mgm/mode.go
Slightly refactored error messages
[gogost.git] / mgm / mode.go
1 // GoGOST -- Pure Go GOST cryptographic functions library
2 // Copyright (C) 2015-2023 Sergey Matveev <stargrave@stargrave.org>
3 //
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.
7 //
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.
12 //
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/>.
15
16 // Multilinear Galois Mode (MGM) block cipher mode.
17 package mgm
18
19 import (
20         "crypto/cipher"
21         "crypto/hmac"
22         "encoding/binary"
23         "errors"
24         "fmt"
25 )
26
27 var InvalidTag = errors.New("gogost/mgm: invalid authentication tag")
28
29 type Mul interface {
30         Mul(x, y []byte) []byte
31 }
32
33 type MGM struct {
34         MaxSize   uint64
35         BlockSize int
36         TagSize   int
37         cipher    cipher.Block
38         icn       []byte
39         bufP      []byte
40         bufC      []byte
41         padded    []byte
42         sum       []byte
43         mul       Mul
44 }
45
46 func NewMGM(cipher cipher.Block, tagSize int) (cipher.AEAD, error) {
47         blockSize := cipher.BlockSize()
48         if !(blockSize == 8 || blockSize == 16) {
49                 return nil, errors.New("gogost/mgm: only {64|128} blocksizes allowed")
50         }
51         if tagSize < 4 || tagSize > blockSize {
52                 return nil, fmt.Errorf("gogost/mgm: invalid tag size (4<=%d<=%d)", tagSize, blockSize)
53         }
54         mgm := MGM{
55                 MaxSize:   uint64(1<<uint(blockSize*8/2) - 1),
56                 BlockSize: blockSize,
57                 TagSize:   tagSize,
58                 cipher:    cipher,
59                 icn:       make([]byte, blockSize),
60                 bufP:      make([]byte, blockSize),
61                 bufC:      make([]byte, blockSize),
62                 padded:    make([]byte, blockSize),
63                 sum:       make([]byte, blockSize),
64         }
65         if blockSize == 8 {
66                 mgm.mul = newMul64()
67         } else {
68                 mgm.mul = newMul128()
69         }
70         return &mgm, nil
71 }
72
73 func (mgm *MGM) NonceSize() int {
74         return mgm.BlockSize
75 }
76
77 func (mgm *MGM) Overhead() int {
78         return mgm.TagSize
79 }
80
81 func incr(data []byte) {
82         for i := len(data) - 1; i >= 0; i-- {
83                 data[i]++
84                 if data[i] != 0 {
85                         return
86                 }
87         }
88 }
89
90 func xor(dst, src1, src2 []byte) {
91         for i := 0; i < len(src1); i++ {
92                 dst[i] = src1[i] ^ src2[i]
93         }
94 }
95
96 func (mgm *MGM) validateNonce(nonce []byte) {
97         if len(nonce) != mgm.BlockSize {
98                 panic("nonce length must be equal to cipher's blocksize")
99         }
100         if nonce[0]&0x80 > 0 {
101                 panic("nonce must not have higher bit set")
102         }
103 }
104
105 func (mgm *MGM) validateSizes(text, additionalData []byte) {
106         if len(text) == 0 && len(additionalData) == 0 {
107                 panic("at least either *text or additionalData must be provided")
108         }
109         if uint64(len(additionalData)) > mgm.MaxSize {
110                 panic("additionalData is too big")
111         }
112         if uint64(len(text)+len(additionalData)) > mgm.MaxSize {
113                 panic("*text with additionalData are too big")
114         }
115 }
116
117 func (mgm *MGM) auth(out, text, ad []byte) {
118         for i := 0; i < mgm.BlockSize; i++ {
119                 mgm.sum[i] = 0
120         }
121         adLen := len(ad) * 8
122         textLen := len(text) * 8
123         mgm.icn[0] |= 0x80
124         mgm.cipher.Encrypt(mgm.bufP, mgm.icn) // Z_1 = E_K(1 || ICN)
125         for len(ad) >= mgm.BlockSize {
126                 mgm.cipher.Encrypt(mgm.bufC, mgm.bufP) // H_i = E_K(Z_i)
127                 xor(                                   // sum (xor)= H_i (x) A_i
128                         mgm.sum,
129                         mgm.sum,
130                         mgm.mul.Mul(mgm.bufC, ad[:mgm.BlockSize]),
131                 )
132                 incr(mgm.bufP[:mgm.BlockSize/2]) // Z_{i+1} = incr_l(Z_i)
133                 ad = ad[mgm.BlockSize:]
134         }
135         if len(ad) > 0 {
136                 copy(mgm.padded, ad)
137                 for i := len(ad); i < mgm.BlockSize; i++ {
138                         mgm.padded[i] = 0
139                 }
140                 mgm.cipher.Encrypt(mgm.bufC, mgm.bufP)
141                 xor(mgm.sum, mgm.sum, mgm.mul.Mul(mgm.bufC, mgm.padded))
142                 incr(mgm.bufP[:mgm.BlockSize/2])
143         }
144
145         for len(text) >= mgm.BlockSize {
146                 mgm.cipher.Encrypt(mgm.bufC, mgm.bufP) // H_{h+j} = E_K(Z_{h+j})
147                 xor(                                   // sum (xor)= H_{h+j} (x) C_j
148                         mgm.sum,
149                         mgm.sum,
150                         mgm.mul.Mul(mgm.bufC, text[:mgm.BlockSize]),
151                 )
152                 incr(mgm.bufP[:mgm.BlockSize/2]) // Z_{h+j+1} = incr_l(Z_{h+j})
153                 text = text[mgm.BlockSize:]
154         }
155         if len(text) > 0 {
156                 copy(mgm.padded, text)
157                 for i := len(text); i < mgm.BlockSize; i++ {
158                         mgm.padded[i] = 0
159                 }
160                 mgm.cipher.Encrypt(mgm.bufC, mgm.bufP)
161                 xor(mgm.sum, mgm.sum, mgm.mul.Mul(mgm.bufC, mgm.padded))
162                 incr(mgm.bufP[:mgm.BlockSize/2])
163         }
164
165         mgm.cipher.Encrypt(mgm.bufP, mgm.bufP) // H_{h+q+1} = E_K(Z_{h+q+1})
166         // len(A) || len(C)
167         if mgm.BlockSize == 8 {
168                 binary.BigEndian.PutUint32(mgm.bufC, uint32(adLen))
169                 binary.BigEndian.PutUint32(mgm.bufC[mgm.BlockSize/2:], uint32(textLen))
170         } else {
171                 binary.BigEndian.PutUint64(mgm.bufC, uint64(adLen))
172                 binary.BigEndian.PutUint64(mgm.bufC[mgm.BlockSize/2:], uint64(textLen))
173         }
174         // sum (xor)= H_{h+q+1} (x) (len(A) || len(C))
175         xor(mgm.sum, mgm.sum, mgm.mul.Mul(mgm.bufC, mgm.bufP))
176         mgm.cipher.Encrypt(mgm.bufP, mgm.sum) // E_K(sum)
177         copy(out, mgm.bufP[:mgm.TagSize])     // MSB_S(E_K(sum))
178 }
179
180 func (mgm *MGM) crypt(out, in []byte) {
181         mgm.icn[0] &= 0x7F
182         mgm.cipher.Encrypt(mgm.bufP, mgm.icn) // Y_1 = E_K(0 || ICN)
183         for len(in) >= mgm.BlockSize {
184                 mgm.cipher.Encrypt(mgm.bufC, mgm.bufP) // E_K(Y_i)
185                 xor(out, mgm.bufC, in)                 // C_i = P_i (xor) E_K(Y_i)
186                 incr(mgm.bufP[mgm.BlockSize/2:])       // Y_i = incr_r(Y_{i-1})
187                 out = out[mgm.BlockSize:]
188                 in = in[mgm.BlockSize:]
189         }
190         if len(in) > 0 {
191                 mgm.cipher.Encrypt(mgm.bufC, mgm.bufP)
192                 xor(out, in, mgm.bufC)
193         }
194 }
195
196 func (mgm *MGM) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
197         mgm.validateNonce(nonce)
198         mgm.validateSizes(plaintext, additionalData)
199         if uint64(len(plaintext)) > mgm.MaxSize {
200                 panic("plaintext is too big")
201         }
202         ret, out := sliceForAppend(dst, len(plaintext)+mgm.TagSize)
203         copy(mgm.icn, nonce)
204         mgm.crypt(out, plaintext)
205         mgm.auth(
206                 out[len(plaintext):len(plaintext)+mgm.TagSize],
207                 out[:len(plaintext)],
208                 additionalData,
209         )
210         return ret
211 }
212
213 // Open the authenticated ciphertext. If authentication tag is invalid,
214 // then InvalidTag error is returned.
215 func (mgm *MGM) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
216         mgm.validateNonce(nonce)
217         mgm.validateSizes(ciphertext, additionalData)
218         if len(ciphertext) < mgm.TagSize {
219                 return nil, fmt.Errorf("ciphertext is too short (%d<%d)", len(ciphertext), mgm.TagSize)
220         }
221         if uint64(len(ciphertext)-mgm.TagSize) > mgm.MaxSize {
222                 panic("ciphertext is too big")
223         }
224         ret, out := sliceForAppend(dst, len(ciphertext)-mgm.TagSize)
225         ct := ciphertext[:len(ciphertext)-mgm.TagSize]
226         copy(mgm.icn, nonce)
227         mgm.auth(mgm.sum, ct, additionalData)
228         if !hmac.Equal(mgm.sum[:mgm.TagSize], ciphertext[len(ciphertext)-mgm.TagSize:]) {
229                 return nil, InvalidTag
230         }
231         mgm.crypt(out, ct)
232         return ret, nil
233 }