2 # PyGOST -- Pure Python GOST cryptographic functions library
3 # Copyright (C) 2015-2020 Sergey Matveev <stargrave@stargrave.org>
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, version 3 of the License.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 """Multilinear Galois Mode (MGM) block cipher mode.
19 from hmac import compare_digest
21 from pygost.gost3413 import pad1
22 from pygost.utils import bytes2long
23 from pygost.utils import long2bytes
24 from pygost.utils import strxor
28 return long2bytes(bytes2long(data) + 1, size=bs // 2)
32 return data[:bs // 2] + _incr(data[bs // 2:], bs)
36 return _incr(data[:bs // 2], bs) + data[bs // 2:]
39 def nonce_prepare(nonce):
40 """Prepare nonce for MGM usage
50 # Implementation is fully based on go.cypherpunks.ru/gogost/mgm
51 def __init__(self, encrypter, bs, tag_size=None):
52 """Multilinear Galois Mode (MGM) block cipher mode
54 :param encrypter: encrypting function, that takes block as an input
55 :param int bs: cipher's blocksize
56 :param int tag_size: authentication tag size
57 (defaults to blocksize if not specified)
60 raise ValueError("only 64/128-bit blocksizes allowed")
61 self.tag_size = bs if tag_size is None else bs
62 if self.tag_size < 4 or self.tag_size > bs:
63 raise ValueError("invalid tag_size")
64 self.encrypter = encrypter
66 self.max_size = (1 << (bs * 8 // 2)) - 1
67 self.r = 0x1B if bs == 8 else 0x87
69 def _validate_nonce(self, nonce):
70 if len(nonce) != self.bs:
71 raise ValueError("nonce length must be equal to cipher's blocksize")
72 if bytearray(nonce)[0] & 0x80 > 0:
73 raise ValueError("nonce must not have higher bit set")
75 def _validate_sizes(self, plaintext, additional_data):
76 if len(plaintext) == 0 and len(additional_data) == 0:
77 raise ValueError("at least one of plaintext or additional_data required")
78 if len(plaintext) + len(additional_data) > self.max_size:
79 raise ValueError("plaintext+additional_data are too big")
85 max_bit = 1 << (self.bs * 8 - 1)
90 x = ((x ^ max_bit) << 1) ^ self.r
94 return long2bytes(z, size=self.bs)
96 def _crypt(self, icn, data):
98 enc = self.encrypter(bytes(icn))
101 res.append(strxor(self.encrypter(enc), data))
102 enc = incr_r(enc, self.bs)
103 data = data[self.bs:]
106 def _auth(self, icn, text, ad):
108 enc = self.encrypter(bytes(icn))
109 _sum = self.bs * b"\x00"
113 _sum = strxor(_sum, self._mul(
115 pad1(ad[:self.bs], self.bs),
117 enc = incr_l(enc, self.bs)
120 _sum = strxor(_sum, self._mul(
122 pad1(text[:self.bs], self.bs),
124 enc = incr_l(enc, self.bs)
125 text = text[self.bs:]
126 _sum = strxor(_sum, self._mul(self.encrypter(enc), (
127 long2bytes(ad_len * 8, size=self.bs // 2) +
128 long2bytes(text_len * 8, size=self.bs // 2)
130 return self.encrypter(_sum)[:self.tag_size]
132 def seal(self, nonce, plaintext, additional_data):
135 :param bytes nonce: blocksize-sized nonce.
136 Assure that it does not have MSB bit set
137 (:py:func:`pygost.mgm.nonce_prepare` helps)
138 :param bytes plaintext: plaintext to be encrypted and authenticated
139 :param bytes additional_data: additional data to be authenticated
141 self._validate_nonce(nonce)
142 self._validate_sizes(plaintext, additional_data)
143 icn = bytearray(nonce)
144 ciphertext = self._crypt(icn, plaintext)
145 tag = self._auth(icn, ciphertext, additional_data)
146 return ciphertext + tag
148 def open(self, nonce, ciphertext, additional_data):
151 :param bytes nonce: blocksize-sized nonce.
152 Assure that it does not have MSB bit set
153 (:py:func:`pygost.mgm.nonce_prepare` helps)
154 :param bytes ciphertext: ciphertext to be decrypted and authenticated
155 :param bytes additional_data: additional data to be authenticated
156 :raises ValueError: if ciphertext authentication fails
158 self._validate_nonce(nonce)
159 self._validate_sizes(ciphertext, additional_data)
160 icn = bytearray(nonce)
161 ciphertext, tag_expected = (
162 ciphertext[:-self.tag_size],
163 ciphertext[-self.tag_size:],
165 tag = self._auth(icn, ciphertext, additional_data)
166 if not compare_digest(tag_expected, tag):
167 raise ValueError("invalid authentication tag")
168 return self._crypt(icn, ciphertext)