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