2 # PyGOST -- Pure Python GOST cryptographic functions library
3 # Copyright (C) 2015-2022 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 """GOST R 34.13-2015: Modes of operation for block ciphers
18 This module currently includes only padding methods.
21 from os import urandom
23 from pygost.utils import bytes2long
24 from pygost.utils import long2bytes
25 from pygost.utils import strxor
26 from pygost.utils import xrange
32 def pad_size(data_size, blocksize):
33 """Calculate required pad size to full up blocksize
35 if data_size < blocksize:
36 return blocksize - data_size
37 if data_size % blocksize == 0:
39 return blocksize - data_size % blocksize
42 def pad1(data, blocksize):
45 Just fill up with zeros if necessary.
47 return data + b"\x00" * pad_size(len(data), blocksize)
50 def pad2(data, blocksize):
51 """Padding method 2 (also known as ISO/IEC 7816-4)
53 Add one bit and then fill up with zeros.
55 return data + b"\x80" + b"\x00" * pad_size(len(data) + 1, blocksize)
58 def unpad2(data, blocksize):
61 last_block = bytearray(data[-blocksize:])
62 pad_index = last_block.rfind(b"\x80")
64 raise ValueError("Invalid padding")
65 for c in last_block[pad_index + 1:]:
67 raise ValueError("Invalid padding")
68 return data[:-(blocksize - pad_index)]
71 def pad3(data, blocksize):
74 if pad_size(len(data), blocksize) == 0:
76 return pad2(data, blocksize)
79 def ecb_encrypt(encrypter, bs, pt):
80 """ECB encryption mode of operation
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
86 if not pt or len(pt) % bs != 0:
87 raise ValueError("Plaintext is not blocksize aligned")
89 for i in xrange(0, len(pt), bs):
90 ct.append(encrypter(pt[i:i + bs]))
94 def ecb_decrypt(decrypter, bs, ct):
95 """ECB decryption mode of operation
97 :param decrypter: Decrypting function, that takes block as an input
98 :param int bs: cipher's blocksize, bytes
99 :param bytes ct: ciphertext
101 if not ct or len(ct) % bs != 0:
102 raise ValueError("Ciphertext is not blocksize aligned")
104 for i in xrange(0, len(ct), bs):
105 pt.append(decrypter(ct[i:i + bs]))
109 def acpkm(encrypter, bs):
110 """Perform ACPKM key derivation
112 :param encrypter: encrypting function, that takes block as an input
113 :param int bs: cipher's blocksize, bytes
116 encrypter(bytes(bytearray(range(d, d + bs))))
117 for d in range(0x80, 0x80 + bs * (KEYSIZE // bs), bs)
121 def ctr(encrypter, bs, data, iv, _acpkm=None):
122 """Counter mode of operation
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
129 For decryption you use the same function again.
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")
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):
143 _acpkm is not None and
145 ctr_value % acpkm_section_size_in_bs == 0
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)
153 def ctr_acpkm(algo_class, encrypter, section_size, bs, data, iv):
154 """CTR-ACPKM mode of operation
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
163 For decryption you use the same function again.
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))
170 def ofb(encrypter, bs, data, iv):
171 """OFB mode of operation
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
178 For decryption you use the same function again.
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)]
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)
190 def cbc_encrypt(encrypter, bs, pt, iv):
191 """CBC encryption mode of operation
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
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)]
204 for i in xrange(0, len(pt), bs):
205 ct.append(encrypter(strxor(r[0], pt[i:i + bs])))
210 def cbc_decrypt(decrypter, bs, ct, iv):
211 """CBC decryption mode of operation
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
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)]
224 for i in xrange(0, len(ct), bs):
226 pt.append(strxor(r[0], decrypter(blk)))
231 def cfb_encrypt(encrypter, bs, pt, iv):
232 """CFB encryption mode of operation
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
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)]
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]))
249 def cfb_decrypt(encrypter, bs, ct, iv):
250 """CFB decryption mode of operation
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
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)]
261 for i in xrange(0, len(ct) + pad_size(len(ct), bs), bs):
263 pt.append(strxor(encrypter(r[0]), blk))
268 def _mac_shift(bs, data, xor_lsb=0):
269 num = (bytes2long(data) << 1) ^ xor_lsb
270 return long2bytes(num, bs)[-bs:]
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)
285 def mac(encrypter, bs, data):
286 """MAC (known here as CMAC, OMAC1) mode of operation
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
292 Implementation is based on PyCrypto's CMAC one, that is in public domain.
294 k1, k2 = _mac_ks(encrypter, bs)
295 if len(data) % bs == 0:
296 tail_offset = len(data) - bs
298 tail_offset = len(data) - (len(data) % bs)
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,
309 def acpkm_master(algo_class, encrypter, key_section_size, bs, keymat_len):
310 """ACPKM-Master key derivation
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
323 data=b"\x00" * keymat_len,
324 iv=b"\xFF" * (bs // 2),
328 def mac_acpkm_master(algo_class, encrypter, key_section_size, section_size, bs, data):
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
338 if len(data) % bs == 0:
339 tail_offset = len(data) - bs
341 tail_offset = len(data) - (len(data) % bs)
343 sections = len(data) // section_size
344 if len(data) % section_size != 0:
346 keymats = acpkm_master(
351 (KEYSIZE + bs) * sections,
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:]
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,
372 def pad_iso10126(data, blocksize):
375 Does not exist in 34.13, but added for convenience.
376 It uses urandom call for getting the randomness.
378 pad_len = blocksize - len(data) % blocksize
381 return b"".join((data, urandom(pad_len - 1), bytes((pad_len,))))
384 def unpad_iso10126(data, blocksize):
385 """Unpad :py:func:`pygost.gost3413.pad_iso10126`
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]