]> Cypherpunks.ru repositories - pygost.git/blob - pygost/gost3413.py
Unify docstring's leading space presence
[pygost.git] / pygost / gost3413.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 """GOST R 34.13-2015: Modes of operation for block ciphers
17
18 This module currently includes only padding methods.
19 """
20
21 from pygost.utils import bytes2long
22 from pygost.utils import long2bytes
23 from pygost.utils import strxor
24 from pygost.utils import xrange
25
26
27 KEYSIZE = 32
28
29
30 def pad_size(data_size, blocksize):
31     """Calculate required pad size to full up blocksize
32     """
33     if data_size < blocksize:
34         return blocksize - data_size
35     if data_size % blocksize == 0:
36         return 0
37     return blocksize - data_size % blocksize
38
39
40 def pad1(data, blocksize):
41     """Padding method 1
42
43     Just fill up with zeros if necessary.
44     """
45     return data + b"\x00" * pad_size(len(data), blocksize)
46
47
48 def pad2(data, blocksize):
49     """Padding method 2 (also known as ISO/IEC 7816-4)
50
51     Add one bit and then fill up with zeros.
52     """
53     return data + b"\x80" + b"\x00" * pad_size(len(data) + 1, blocksize)
54
55
56 def unpad2(data, blocksize):
57     """Unpad method 2
58     """
59     last_block = bytearray(data[-blocksize:])
60     pad_index = last_block.rfind(b"\x80")
61     if pad_index == -1:
62         raise ValueError("Invalid padding")
63     for c in last_block[pad_index + 1:]:
64         if c != 0:
65             raise ValueError("Invalid padding")
66     return data[:-(blocksize - pad_index)]
67
68
69 def pad3(data, blocksize):
70     """Padding method 3
71     """
72     if pad_size(len(data), blocksize) == 0:
73         return data
74     return pad2(data, blocksize)
75
76
77 def ecb_encrypt(encrypter, bs, pt):
78     """ECB encryption mode of operation
79
80     :param encrypter: encrypting function, that takes block as an input
81     :param int bs: cipher's blocksize, bytes
82     :param bytes pt: already padded plaintext
83     """
84     if not pt or len(pt) % bs != 0:
85         raise ValueError("Plaintext is not blocksize aligned")
86     ct = []
87     for i in xrange(0, len(pt), bs):
88         ct.append(encrypter(pt[i:i + bs]))
89     return b"".join(ct)
90
91
92 def ecb_decrypt(decrypter, bs, ct):
93     """ECB decryption mode of operation
94
95     :param decrypter: Decrypting function, that takes block as an input
96     :param int bs: cipher's blocksize, bytes
97     :param bytes ct: ciphertext
98     """
99     if not ct or len(ct) % bs != 0:
100         raise ValueError("Ciphertext is not blocksize aligned")
101     pt = []
102     for i in xrange(0, len(ct), bs):
103         pt.append(decrypter(ct[i:i + bs]))
104     return b"".join(pt)
105
106
107 def acpkm(encrypter, bs):
108     """Perform ACPKM key derivation
109
110     :param encrypter: encrypting function, that takes block as an input
111     :param int bs: cipher's blocksize, bytes
112     """
113     return b"".join([
114         encrypter(bytes(bytearray(range(d, d + bs))))
115         for d in range(0x80, 0x80 + bs * (KEYSIZE // bs), bs)
116     ])
117
118
119 def ctr(encrypter, bs, data, iv, _acpkm=None):
120     """Counter mode of operation
121
122     :param encrypter: encrypting function, that takes block as an input
123     :param int bs: cipher's blocksize, bytes
124     :param bytes data: plaintext/ciphertext
125     :param bytes iv: half blocksize-sized initialization vector
126
127     For decryption you use the same function again.
128     """
129     if len(iv) != bs // 2:
130         raise ValueError("Invalid IV size")
131     if len(data) > bs * (1 << (8 * (bs // 2 - 1))):
132         raise ValueError("Too big data")
133     stream = []
134     ctr_value = 0
135     ctr_max_value = 1 << (8 * (bs // 2))
136     if _acpkm is not None:
137         acpkm_algo_class, acpkm_section_size_in_bs = _acpkm
138         acpkm_section_size_in_bs //= bs
139     for _ in xrange(0, len(data) + pad_size(len(data), bs), bs):
140         if (
141                 _acpkm is not None and
142                 ctr_value != 0 and
143                 ctr_value % acpkm_section_size_in_bs == 0
144         ):
145             encrypter = acpkm_algo_class(acpkm(encrypter, bs)).encrypt
146         stream.append(encrypter(iv + long2bytes(ctr_value, bs // 2)))
147         ctr_value = (ctr_value + 1) % ctr_max_value
148     return strxor(b"".join(stream), data)
149
150
151 def ctr_acpkm(algo_class, encrypter, section_size, bs, data, iv):
152     """CTR-ACPKM mode of operation
153
154     :param algo_class: pygost.gost3412's algorithm class
155     :param encrypter: encrypting function, that takes block as an input
156     :param int section_size: ACPKM'es section size (N), in bytes
157     :param int bs: cipher's blocksize, bytes
158     :param bytes data: plaintext/ciphertext
159     :param bytes iv: half blocksize-sized initialization vector
160
161     For decryption you use the same function again.
162     """
163     if section_size % bs != 0:
164         raise ValueError("section_size must be multiple of bs")
165     return ctr(encrypter, bs, data, iv, _acpkm=(algo_class, section_size))
166
167
168 def ofb(encrypter, bs, data, iv):
169     """OFB mode of operation
170
171     :param encrypter: encrypting function, that takes block as an input
172     :param int bs: cipher's blocksize, bytes
173     :param bytes data: plaintext/ciphertext
174     :param bytes iv: blocksize-sized initialization vector
175
176     For decryption you use the same function again.
177     """
178     if len(iv) < bs or len(iv) % bs != 0:
179         raise ValueError("Invalid IV size")
180     r = [iv[i:i + bs] for i in range(0, len(iv), bs)]
181     result = []
182     for i in xrange(0, len(data) + pad_size(len(data), bs), bs):
183         r = r[1:] + [encrypter(r[0])]
184         result.append(strxor(r[-1], data[i:i + bs]))
185     return b"".join(result)
186
187
188 def cbc_encrypt(encrypter, bs, pt, iv):
189     """CBC encryption mode of operation
190
191     :param encrypter: encrypting function, that takes block as an input
192     :param int bs: cipher's blocksize, bytes
193     :param bytes pt: already padded plaintext
194     :param bytes iv: blocksize-sized initialization vector
195     """
196     if not pt or len(pt) % bs != 0:
197         raise ValueError("Plaintext is not blocksize aligned")
198     if len(iv) < bs or len(iv) % bs != 0:
199         raise ValueError("Invalid IV size")
200     r = [iv[i:i + bs] for i in range(0, len(iv), bs)]
201     ct = []
202     for i in xrange(0, len(pt), bs):
203         ct.append(encrypter(strxor(r[0], pt[i:i + bs])))
204         r = r[1:] + [ct[-1]]
205     return b"".join(ct)
206
207
208 def cbc_decrypt(decrypter, bs, ct, iv):
209     """CBC decryption mode of operation
210
211     :param decrypter: Decrypting function, that takes block as an input
212     :param int bs: cipher's blocksize, bytes
213     :param bytes ct: ciphertext
214     :param bytes iv: blocksize-sized initialization vector
215     """
216     if not ct or len(ct) % bs != 0:
217         raise ValueError("Ciphertext is not blocksize aligned")
218     if len(iv) < bs or len(iv) % bs != 0:
219         raise ValueError("Invalid IV size")
220     r = [iv[i:i + bs] for i in range(0, len(iv), bs)]
221     pt = []
222     for i in xrange(0, len(ct), bs):
223         blk = ct[i:i + bs]
224         pt.append(strxor(r[0], decrypter(blk)))
225         r = r[1:] + [blk]
226     return b"".join(pt)
227
228
229 def cfb_encrypt(encrypter, bs, pt, iv):
230     """CFB encryption mode of operation
231
232     :param encrypter: encrypting function, that takes block as an input
233     :param int bs: cipher's blocksize, bytes
234     :param bytes pt: plaintext
235     :param bytes iv: blocksize-sized initialization vector
236     """
237     if len(iv) < bs or len(iv) % bs != 0:
238         raise ValueError("Invalid IV size")
239     r = [iv[i:i + bs] for i in range(0, len(iv), bs)]
240     ct = []
241     for i in xrange(0, len(pt) + pad_size(len(pt), bs), bs):
242         ct.append(strxor(encrypter(r[0]), pt[i:i + bs]))
243         r = r[1:] + [ct[-1]]
244     return b"".join(ct)
245
246
247 def cfb_decrypt(encrypter, bs, ct, iv):
248     """CFB decryption mode of operation
249
250     :param encrypter: encrypting function, that takes block as an input
251     :param int bs: cipher's blocksize, bytes
252     :param bytes ct: ciphertext
253     :param bytes iv: blocksize-sized initialization vector
254     """
255     if len(iv) < bs or len(iv) % bs != 0:
256         raise ValueError("Invalid IV size")
257     r = [iv[i:i + bs] for i in range(0, len(iv), bs)]
258     pt = []
259     for i in xrange(0, len(ct) + pad_size(len(ct), bs), bs):
260         blk = ct[i:i + bs]
261         pt.append(strxor(encrypter(r[0]), blk))
262         r = r[1:] + [blk]
263     return b"".join(pt)
264
265
266 def _mac_shift(bs, data, xor_lsb=0):
267     num = (bytes2long(data) << 1) ^ xor_lsb
268     return long2bytes(num, bs)[-bs:]
269
270
271 Rb64 = 0b11011
272 Rb128 = 0b10000111
273
274
275 def _mac_ks(encrypter, bs):
276     Rb = Rb128 if bs == 16 else Rb64
277     _l = encrypter(bs * b"\x00")
278     k1 = _mac_shift(bs, _l, Rb) if bytearray(_l)[0] & 0x80 > 0 else _mac_shift(bs, _l)
279     k2 = _mac_shift(bs, k1, Rb) if bytearray(k1)[0] & 0x80 > 0 else _mac_shift(bs, k1)
280     return k1, k2
281
282
283 def mac(encrypter, bs, data):
284     """MAC (known here as CMAC, OMAC1) mode of operation
285
286     :param encrypter: encrypting function, that takes block as an input
287     :param int bs: cipher's blocksize, bytes
288     :param bytes data: data to authenticate
289
290     Implementation is based on PyCrypto's CMAC one, that is in public domain.
291     """
292     k1, k2 = _mac_ks(encrypter, bs)
293     if len(data) % bs == 0:
294         tail_offset = len(data) - bs
295     else:
296         tail_offset = len(data) - (len(data) % bs)
297     prev = bs * b"\x00"
298     for i in xrange(0, tail_offset, bs):
299         prev = encrypter(strxor(data[i:i + bs], prev))
300     tail = data[tail_offset:]
301     return encrypter(strxor(
302         strxor(pad3(tail, bs), prev),
303         k1 if len(tail) == bs else k2,
304     ))
305
306
307 def acpkm_master(algo_class, encrypter, key_section_size, bs, keymat_len):
308     """ACPKM-Master key derivation
309
310     :param algo_class: pygost.gost3412's algorithm class
311     :param encrypter: encrypting function, that takes block as an input
312     :param int key_section_size: ACPKM'es key section size (T*), in bytes
313     :param int bs: cipher's blocksize, bytes
314     :param int keymat_len: length of key material to produce
315     """
316     return ctr_acpkm(
317         algo_class,
318         encrypter,
319         key_section_size,
320         bs,
321         data=b"\x00" * keymat_len,
322         iv=b"\xFF" * (bs // 2),
323     )
324
325
326 def mac_acpkm_master(algo_class, encrypter, key_section_size, section_size, bs, data):
327     """OMAC-ACPKM-Master
328
329     :param algo_class: pygost.gost3412's algorithm class
330     :param encrypter: encrypting function, that takes block as an input
331     :param int key_section_size: ACPKM'es key section size (T*), in bytes
332     :param int section_size: ACPKM'es section size (N), in bytes
333     :param int bs: cipher's blocksize, bytes
334     :param bytes data: data to authenticate
335     """
336     if len(data) % bs == 0:
337         tail_offset = len(data) - bs
338     else:
339         tail_offset = len(data) - (len(data) % bs)
340     prev = bs * b"\x00"
341     sections = len(data) // section_size
342     if len(data) % section_size != 0:
343         sections += 1
344     keymats = acpkm_master(
345         algo_class,
346         encrypter,
347         key_section_size,
348         bs,
349         (KEYSIZE + bs) * sections,
350     )
351     for i in xrange(0, tail_offset, bs):
352         if i % section_size == 0:
353             keymat, keymats = keymats[:KEYSIZE + bs], keymats[KEYSIZE + bs:]
354             key, k1 = keymat[:KEYSIZE], keymat[KEYSIZE:]
355             encrypter = algo_class(key).encrypt
356         prev = encrypter(strxor(data[i:i + bs], prev))
357     tail = data[tail_offset:]
358     if len(tail) == bs:
359         key, k1 = keymats[:KEYSIZE], keymats[KEYSIZE:]
360         encrypter = algo_class(key).encrypt
361     k2 = long2bytes(bytes2long(k1) << 1, size=bs)
362     if bytearray(k1)[0] & 0x80 != 0:
363         k2 = strxor(k2, long2bytes(Rb128 if bs == 16 else Rb64, size=bs))
364     return encrypter(strxor(
365         strxor(pad3(tail, bs), prev),
366         k1 if len(tail) == bs else k2,
367     ))