2 # PyGOST -- Pure Python GOST cryptographic functions library
3 # Copyright (C) 2015-2017 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, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 """ GOST R 34.13-2015: Modes of operation for block ciphers
19 This module currently includes only padding methods.
22 from pygost.utils import bytes2long
23 from pygost.utils import long2bytes
24 from pygost.utils import strxor
25 from pygost.utils import xrange
28 def pad_size(data_size, blocksize):
29 """Calculate required pad size to full up blocksize
31 if data_size < blocksize:
32 return blocksize - data_size
33 if data_size % blocksize == 0:
35 return blocksize - data_size % blocksize
38 def pad1(data, blocksize):
41 Just fill up with zeros if necessary.
43 return data + b"\x00" * pad_size(len(data), blocksize)
46 def pad2(data, blocksize):
47 """Padding method 2 (also known as ISO/IEC 7816-4)
49 Add one bit and then fill up with zeros.
51 return data + b"\x80" + b"\x00" * pad_size(len(data) + 1, blocksize)
54 def unpad2(data, blocksize):
57 last_block = bytearray(data[-blocksize:])
58 pad_index = last_block.rfind(b"\x80")
60 raise ValueError("Invalid padding")
61 for c in last_block[pad_index + 1:]:
63 raise ValueError("Invalid padding")
64 return data[:-(blocksize - pad_index)]
67 def pad3(data, blocksize):
70 if pad_size(len(data), blocksize) == 0:
72 return pad2(data, blocksize)
75 def ecb_encrypt(encrypter, bs, pt):
76 """ECB encryption mode of operation
78 :param encrypter: Encrypting function, that takes block as an input
79 :param int bs: cipher's blocksize
80 :param bytes pt: already padded plaintext
82 if not pt or len(pt) % bs != 0:
83 raise ValueError("Plaintext is not blocksize aligned")
85 for i in xrange(0, len(pt), bs):
86 ct.append(encrypter(pt[i:i + bs]))
90 def ecb_decrypt(decrypter, bs, ct):
91 """ECB decryption mode of operation
93 :param decrypter: Decrypting function, that takes block as an input
94 :param int bs: cipher's blocksize
95 :param bytes ct: ciphertext
97 if not ct or len(ct) % bs != 0:
98 raise ValueError("Ciphertext is not blocksize aligned")
100 for i in xrange(0, len(ct), bs):
101 pt.append(decrypter(ct[i:i + bs]))
105 def ctr(encrypter, bs, data, iv):
106 """Counter mode of operation
108 :param encrypter: Encrypting function, that takes block as an input
109 :param int bs: cipher's blocksize
110 :param bytes data: plaintext/ciphertext
111 :param bytes iv: half blocksize-sized initialization vector
113 For decryption you use the same function again.
115 if len(iv) != bs // 2:
116 raise ValueError("Invalid IV size")
119 for _ in xrange(0, len(data) + pad_size(len(data), bs), bs):
120 stream.append(encrypter(iv + long2bytes(ctr_value, bs // 2)))
122 return strxor(b"".join(stream), data)
125 def ofb(encrypter, bs, data, iv):
126 """OFB mode of operation
128 :param encrypter: Encrypting function, that takes block as an input
129 :param int bs: cipher's blocksize
130 :param bytes data: plaintext/ciphertext
131 :param bytes iv: double blocksize-sized initialization vector
133 For decryption you use the same function again.
135 if len(iv) < 2 * bs or len(iv) % bs != 0:
136 raise ValueError("Invalid IV size")
137 r = [iv[i:i + bs] for i in range(0, len(iv), bs)]
139 for i in xrange(0, len(data) + pad_size(len(data), bs), bs):
140 r = r[1:] + [encrypter(r[0])]
141 result.append(strxor(r[1], data[i:i + bs]))
142 return b"".join(result)
145 def cbc_encrypt(encrypter, bs, pt, iv):
146 """CBC encryption mode of operation
148 :param encrypter: Encrypting function, that takes block as an input
149 :param int bs: cipher's blocksize
150 :param bytes pt: already padded plaintext
151 :param bytes iv: double blocksize-sized initialization vector
153 if not pt or len(pt) % bs != 0:
154 raise ValueError("Plaintext is not blocksize aligned")
155 if len(iv) < 2 * bs or len(iv) % bs != 0:
156 raise ValueError("Invalid IV size")
157 r = [iv[i:i + bs] for i in range(0, len(iv), bs)]
159 for i in xrange(0, len(pt), bs):
160 ct.append(encrypter(strxor(r[0], pt[i:i + bs])))
165 def cbc_decrypt(decrypter, bs, ct, iv):
166 """CBC decryption mode of operation
168 :param decrypter: Decrypting function, that takes block as an input
169 :param int bs: cipher's blocksize
170 :param bytes ct: ciphertext
171 :param bytes iv: double blocksize-sized initialization vector
173 if not ct or len(ct) % bs != 0:
174 raise ValueError("Ciphertext is not blocksize aligned")
175 if len(iv) < 2 * bs or len(iv) % bs != 0:
176 raise ValueError("Invalid IV size")
177 r = [iv[i:i + bs] for i in range(0, len(iv), bs)]
179 for i in xrange(0, len(ct), bs):
181 pt.append(strxor(r[0], decrypter(blk)))
186 def cfb_encrypt(encrypter, bs, pt, iv):
187 """CFB encryption mode of operation
189 :param encrypter: Encrypting function, that takes block as an input
190 :param int bs: cipher's blocksize
191 :param bytes pt: plaintext
192 :param bytes iv: double blocksize-sized initialization vector
194 if len(iv) < 2 * bs or len(iv) % bs != 0:
195 raise ValueError("Invalid IV size")
196 r = [iv[i:i + bs] for i in range(0, len(iv), bs)]
198 for i in xrange(0, len(pt) + pad_size(len(pt), bs), bs):
199 ct.append(strxor(encrypter(r[0]), pt[i:i + bs]))
204 def cfb_decrypt(encrypter, bs, ct, iv):
205 """CFB decryption mode of operation
207 :param encrypter: Encrypting function, that takes block as an input
208 :param int bs: cipher's blocksize
209 :param bytes ct: ciphertext
210 :param bytes iv: double blocksize-sized initialization vector
212 if len(iv) < 2 * bs or len(iv) % bs != 0:
213 raise ValueError("Invalid IV size")
214 r = [iv[i:i + bs] for i in range(0, len(iv), bs)]
216 for i in xrange(0, len(ct) + pad_size(len(ct), bs), bs):
218 pt.append(strxor(encrypter(r[0]), blk))
223 def _mac_shift(bs, data, xor_lsb=0):
224 num = (bytes2long(data) << 1) ^ xor_lsb
225 return long2bytes(num, bs)[-bs:]
228 def _mac_ks(encrypter, bs):
229 Rb = 0b10000111 if bs == 16 else 0b11011
230 _l = encrypter(bs * b'\x00')
231 k1 = _mac_shift(bs, _l, Rb) if bytearray(_l)[0] & 0x80 > 0 else _mac_shift(bs, _l)
232 k2 = _mac_shift(bs, k1, Rb) if bytearray(k1)[0] & 0x80 > 0 else _mac_shift(bs, k1)
236 def mac(encrypter, bs, data):
237 """MAC (known here as CMAC, OMAC1) mode of operation
239 :param encrypter: Encrypting function, that takes block as an input
240 :param int bs: cipher's blocksize
241 :param bytes data: data to authenticate
243 Implementation is based on PyCrypto's CMAC one, that is in public domain.
245 k1, k2 = _mac_ks(encrypter, bs)
246 if len(data) % bs == 0:
247 tail_offset = len(data) - bs
249 tail_offset = len(data) - (len(data) % bs)
251 for i in xrange(0, tail_offset, bs):
252 prev = encrypter(strxor(data[i:i + bs], prev))
253 tail = data[tail_offset:]
254 return encrypter(strxor(
255 strxor(pad3(tail, bs), prev),
256 k1 if len(tail) == bs else k2,