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
27 def pad_size(data_size, blocksize):
28 """Calculate required pad size to full up blocksize
30 if data_size < blocksize:
31 return blocksize - data_size
32 if data_size % blocksize == 0:
34 return blocksize - data_size % blocksize
37 def pad1(data, blocksize):
40 Just fill up with zeros if necessary.
42 return data + b"\x00" * pad_size(len(data), blocksize)
45 def pad2(data, blocksize):
46 """Padding method 2 (also known as ISO/IEC 7816-4)
48 Add one bit and then fill up with zeros.
50 return data + b"\x80" + b"\x00" * pad_size(len(data) + 1, blocksize)
53 def unpad2(data, blocksize):
56 last_block = bytearray(data[-blocksize:])
57 pad_index = last_block.rfind(b"\x80")
59 raise ValueError("Invalid padding")
60 for c in last_block[pad_index + 1:]:
62 raise ValueError("Invalid padding")
63 return data[:-(blocksize - pad_index)]
66 def pad3(data, blocksize):
69 if pad_size(len(data), blocksize) == 0:
71 return pad2(data, blocksize)
74 def ecb_encrypt(encrypter, bs, pt):
75 """ECB encryption mode of operation
77 :param encrypter: encrypting function, that takes block as an input
78 :param int bs: cipher's blocksize
79 :param bytes pt: already padded plaintext
81 if not pt or len(pt) % bs != 0:
82 raise ValueError("Plaintext is not blocksize aligned")
84 for i in xrange(0, len(pt), bs):
85 ct.append(encrypter(pt[i:i + bs]))
89 def ecb_decrypt(decrypter, bs, ct):
90 """ECB decryption mode of operation
92 :param decrypter: Decrypting function, that takes block as an input
93 :param int bs: cipher's blocksize
94 :param bytes ct: ciphertext
96 if not ct or len(ct) % bs != 0:
97 raise ValueError("Ciphertext is not blocksize aligned")
99 for i in xrange(0, len(ct), bs):
100 pt.append(decrypter(ct[i:i + bs]))
104 def ctr(encrypter, bs, data, iv):
105 """Counter mode of operation
107 :param encrypter: encrypting function, that takes block as an input
108 :param int bs: cipher's blocksize
109 :param bytes data: plaintext/ciphertext
110 :param bytes iv: half blocksize-sized initialization vector
112 For decryption you use the same function again.
114 if len(iv) != bs // 2:
115 raise ValueError("Invalid IV size")
118 for _ in xrange(0, len(data) + pad_size(len(data), bs), bs):
119 stream.append(encrypter(iv + long2bytes(ctr_value, bs // 2)))
121 return strxor(b"".join(stream), data)
124 def ofb(encrypter, bs, data, iv):
125 """OFB mode of operation
127 :param encrypter: encrypting function, that takes block as an input
128 :param int bs: cipher's blocksize
129 :param bytes data: plaintext/ciphertext
130 :param bytes iv: blocksize-sized initialization vector
132 For decryption you use the same function again.
134 if len(iv) < bs or len(iv) % bs != 0:
135 raise ValueError("Invalid IV size")
136 r = [iv[i:i + bs] for i in range(0, len(iv), bs)]
138 for i in xrange(0, len(data) + pad_size(len(data), bs), bs):
139 r = r[1:] + [encrypter(r[0])]
140 result.append(strxor(r[-1], data[i:i + bs]))
141 return b"".join(result)
144 def cbc_encrypt(encrypter, bs, pt, iv):
145 """CBC encryption mode of operation
147 :param encrypter: encrypting function, that takes block as an input
148 :param int bs: cipher's blocksize
149 :param bytes pt: already padded plaintext
150 :param bytes iv: blocksize-sized initialization vector
152 if not pt or len(pt) % bs != 0:
153 raise ValueError("Plaintext is not blocksize aligned")
154 if len(iv) < bs or len(iv) % bs != 0:
155 raise ValueError("Invalid IV size")
156 r = [iv[i:i + bs] for i in range(0, len(iv), bs)]
158 for i in xrange(0, len(pt), bs):
159 ct.append(encrypter(strxor(r[0], pt[i:i + bs])))
164 def cbc_decrypt(decrypter, bs, ct, iv):
165 """CBC decryption mode of operation
167 :param decrypter: Decrypting function, that takes block as an input
168 :param int bs: cipher's blocksize
169 :param bytes ct: ciphertext
170 :param bytes iv: blocksize-sized initialization vector
172 if not ct or len(ct) % bs != 0:
173 raise ValueError("Ciphertext is not blocksize aligned")
174 if len(iv) < bs or len(iv) % bs != 0:
175 raise ValueError("Invalid IV size")
176 r = [iv[i:i + bs] for i in range(0, len(iv), bs)]
178 for i in xrange(0, len(ct), bs):
180 pt.append(strxor(r[0], decrypter(blk)))
185 def cfb_encrypt(encrypter, bs, pt, iv):
186 """CFB encryption mode of operation
188 :param encrypter: encrypting function, that takes block as an input
189 :param int bs: cipher's blocksize
190 :param bytes pt: plaintext
191 :param bytes iv: blocksize-sized initialization vector
193 if len(iv) < bs or len(iv) % bs != 0:
194 raise ValueError("Invalid IV size")
195 r = [iv[i:i + bs] for i in range(0, len(iv), bs)]
197 for i in xrange(0, len(pt) + pad_size(len(pt), bs), bs):
198 ct.append(strxor(encrypter(r[0]), pt[i:i + bs]))
203 def cfb_decrypt(encrypter, bs, ct, iv):
204 """CFB decryption mode of operation
206 :param encrypter: encrypting function, that takes block as an input
207 :param int bs: cipher's blocksize
208 :param bytes ct: ciphertext
209 :param bytes iv: blocksize-sized initialization vector
211 if len(iv) < bs or len(iv) % bs != 0:
212 raise ValueError("Invalid IV size")
213 r = [iv[i:i + bs] for i in range(0, len(iv), bs)]
215 for i in xrange(0, len(ct) + pad_size(len(ct), bs), bs):
217 pt.append(strxor(encrypter(r[0]), blk))
222 def _mac_shift(bs, data, xor_lsb=0):
223 num = (bytes2long(data) << 1) ^ xor_lsb
224 return long2bytes(num, bs)[-bs:]
227 def _mac_ks(encrypter, bs):
228 Rb = 0b10000111 if bs == 16 else 0b11011
229 _l = encrypter(bs * b'\x00')
230 k1 = _mac_shift(bs, _l, Rb) if bytearray(_l)[0] & 0x80 > 0 else _mac_shift(bs, _l)
231 k2 = _mac_shift(bs, k1, Rb) if bytearray(k1)[0] & 0x80 > 0 else _mac_shift(bs, k1)
235 def mac(encrypter, bs, data):
236 """MAC (known here as CMAC, OMAC1) mode of operation
238 :param encrypter: encrypting function, that takes block as an input
239 :param int bs: cipher's blocksize
240 :param bytes data: data to authenticate
242 Implementation is based on PyCrypto's CMAC one, that is in public domain.
244 k1, k2 = _mac_ks(encrypter, bs)
245 if len(data) % bs == 0:
246 tail_offset = len(data) - bs
248 tail_offset = len(data) - (len(data) % bs)
250 for i in xrange(0, tail_offset, bs):
251 prev = encrypter(strxor(data[i:i + bs], prev))
252 tail = data[tail_offset:]
253 return encrypter(strxor(
254 strxor(pad3(tail, bs), prev),
255 k1 if len(tail) == bs else k2,