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 """ GOST R 34.13-2015: Modes of operation for block ciphers
18 This module currently includes only padding methods.
21 from pygost.utils import bytes2long
22 from pygost.utils import long2bytes
23 from pygost.utils import strxor
24 from pygost.utils import xrange
30 def pad_size(data_size, blocksize):
31 """Calculate required pad size to full up blocksize
33 if data_size < blocksize:
34 return blocksize - data_size
35 if data_size % blocksize == 0:
37 return blocksize - data_size % blocksize
40 def pad1(data, blocksize):
43 Just fill up with zeros if necessary.
45 return data + b"\x00" * pad_size(len(data), blocksize)
48 def pad2(data, blocksize):
49 """Padding method 2 (also known as ISO/IEC 7816-4)
51 Add one bit and then fill up with zeros.
53 return data + b"\x80" + b"\x00" * pad_size(len(data) + 1, blocksize)
56 def unpad2(data, blocksize):
59 last_block = bytearray(data[-blocksize:])
60 pad_index = last_block.rfind(b"\x80")
62 raise ValueError("Invalid padding")
63 for c in last_block[pad_index + 1:]:
65 raise ValueError("Invalid padding")
66 return data[:-(blocksize - pad_index)]
69 def pad3(data, blocksize):
72 if pad_size(len(data), blocksize) == 0:
74 return pad2(data, blocksize)
77 def ecb_encrypt(encrypter, bs, pt):
78 """ECB encryption mode of operation
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
84 if not pt or len(pt) % bs != 0:
85 raise ValueError("Plaintext is not blocksize aligned")
87 for i in xrange(0, len(pt), bs):
88 ct.append(encrypter(pt[i:i + bs]))
92 def ecb_decrypt(decrypter, bs, ct):
93 """ECB decryption mode of operation
95 :param decrypter: Decrypting function, that takes block as an input
96 :param int bs: cipher's blocksize, bytes
97 :param bytes ct: ciphertext
99 if not ct or len(ct) % bs != 0:
100 raise ValueError("Ciphertext is not blocksize aligned")
102 for i in xrange(0, len(ct), bs):
103 pt.append(decrypter(ct[i:i + bs]))
107 def acpkm(encrypter, bs):
108 """Perform ACPKM key derivation
110 :param encrypter: encrypting function, that takes block as an input
111 :param int bs: cipher's blocksize, bytes
114 encrypter(bytes(bytearray(range(d, d + bs))))
115 for d in range(0x80, 0x80 + bs * (KEYSIZE // bs), bs)
119 def ctr(encrypter, bs, data, iv, _acpkm=None):
120 """Counter mode of operation
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
127 For decryption you use the same function again.
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")
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):
141 _acpkm is not None and
143 ctr_value % acpkm_section_size_in_bs == 0
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)
151 def ctr_acpkm(algo_class, encrypter, section_size, bs, data, iv):
152 """CTR-ACPKM mode of operation
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
161 For decryption you use the same function again.
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))
168 def ofb(encrypter, bs, data, iv):
169 """OFB mode of operation
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
176 For decryption you use the same function again.
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)]
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)
188 def cbc_encrypt(encrypter, bs, pt, iv):
189 """CBC encryption mode of operation
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
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)]
202 for i in xrange(0, len(pt), bs):
203 ct.append(encrypter(strxor(r[0], pt[i:i + bs])))
208 def cbc_decrypt(decrypter, bs, ct, iv):
209 """CBC decryption mode of operation
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
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)]
222 for i in xrange(0, len(ct), bs):
224 pt.append(strxor(r[0], decrypter(blk)))
229 def cfb_encrypt(encrypter, bs, pt, iv):
230 """CFB encryption mode of operation
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
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)]
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]))
247 def cfb_decrypt(encrypter, bs, ct, iv):
248 """CFB decryption mode of operation
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
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)]
259 for i in xrange(0, len(ct) + pad_size(len(ct), bs), bs):
261 pt.append(strxor(encrypter(r[0]), blk))
266 def _mac_shift(bs, data, xor_lsb=0):
267 num = (bytes2long(data) << 1) ^ xor_lsb
268 return long2bytes(num, bs)[-bs:]
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)
283 def mac(encrypter, bs, data):
284 """MAC (known here as CMAC, OMAC1) mode of operation
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
290 Implementation is based on PyCrypto's CMAC one, that is in public domain.
292 k1, k2 = _mac_ks(encrypter, bs)
293 if len(data) % bs == 0:
294 tail_offset = len(data) - bs
296 tail_offset = len(data) - (len(data) % bs)
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,
307 def acpkm_master(algo_class, encrypter, key_section_size, bs, keymat_len):
308 """ACPKM-Master key derivation
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
321 data=b"\x00" * keymat_len,
322 iv=b"\xFF" * (bs // 2),
326 def mac_acpkm_master(algo_class, encrypter, key_section_size, section_size, bs, data):
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
336 if len(data) % bs == 0:
337 tail_offset = len(data) - bs
339 tail_offset = len(data) - (len(data) % bs)
341 sections = len(data) // section_size
342 if len(data) % section_size != 0:
344 keymats = acpkm_master(
349 (KEYSIZE + bs) * sections,
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:]
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,