]> Cypherpunks.ru repositories - pygost.git/blob - pygost/mgm.py
Unify docstring's leading space presence
[pygost.git] / pygost / mgm.py
1 # coding: utf-8
2 # PyGOST -- Pure Python GOST cryptographic functions library
3 # Copyright (C) 2015-2020 Sergey Matveev <stargrave@stargrave.org>
4 #
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.
8 #
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.
13 #
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.
17 """
18
19 from hmac import compare_digest
20
21 from pygost.gost3413 import pad1
22 from pygost.utils import bytes2long
23 from pygost.utils import long2bytes
24 from pygost.utils import strxor
25
26
27 def _incr(data, bs):
28     return long2bytes(bytes2long(data) + 1, size=bs // 2)
29
30
31 def incr_r(data, bs):
32     return data[:bs // 2] + _incr(data[bs // 2:], bs)
33
34
35 def incr_l(data, bs):
36     return _incr(data[:bs // 2], bs) + data[bs // 2:]
37
38
39 def nonce_prepare(nonce):
40     """Prepare nonce for MGM usage
41
42     It just clears MSB.
43     """
44     n = bytearray(nonce)
45     n[0] &= 0x7F
46     return bytes(n)
47
48
49 class MGM(object):
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
53
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)
58         """
59         if bs not in (8, 16):
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
65         self.bs = bs
66         self.max_size = (1 << (bs * 8 // 2)) - 1
67         self.r = 0x1B if bs == 8 else 0x87
68
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")
74
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")
80
81     def _mul(self, x, y):
82         x = bytes2long(x)
83         y = bytes2long(y)
84         z = 0
85         max_bit = 1 << (self.bs * 8 - 1)
86         while y > 0:
87             if y & 1 == 1:
88                 z ^= x
89             if x & max_bit > 0:
90                 x = ((x ^ max_bit) << 1) ^ self.r
91             else:
92                 x <<= 1
93             y >>= 1
94         return long2bytes(z, size=self.bs)
95
96     def _crypt(self, icn, data):
97         icn[0] &= 0x7F
98         enc = self.encrypter(bytes(icn))
99         res = []
100         while len(data) > 0:
101             res.append(strxor(self.encrypter(enc), data))
102             enc = incr_r(enc, self.bs)
103             data = data[self.bs:]
104         return b"".join(res)
105
106     def _auth(self, icn, text, ad):
107         icn[0] |= 0x80
108         enc = self.encrypter(bytes(icn))
109         _sum = self.bs * b"\x00"
110         ad_len = len(ad)
111         text_len = len(text)
112         while len(ad) > 0:
113             _sum = strxor(_sum, self._mul(
114                 self.encrypter(enc),
115                 pad1(ad[:self.bs], self.bs),
116             ))
117             enc = incr_l(enc, self.bs)
118             ad = ad[self.bs:]
119         while len(text) > 0:
120             _sum = strxor(_sum, self._mul(
121                 self.encrypter(enc),
122                 pad1(text[:self.bs], self.bs),
123             ))
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)
129         )))
130         return self.encrypter(_sum)[:self.tag_size]
131
132     def seal(self, nonce, plaintext, additional_data):
133         """Seal plaintext
134
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
140         """
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
147
148     def open(self, nonce, ciphertext, additional_data):
149         """Open ciphertext
150
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
157         """
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:],
164         )
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)