]> Cypherpunks.ru repositories - pygost.git/blob - pygost/gost3413.py
34.13-2015 does not require double blocksized IVs
[pygost.git] / pygost / gost3413.py
1 # coding: utf-8
2 # PyGOST -- Pure Python GOST cryptographic functions library
3 # Copyright (C) 2015-2018 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, either version 3 of the License, or
8 # (at your option) any later version.
9 #
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.
14 #
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
18
19 This module currently includes only padding methods.
20 """
21
22 from pygost.utils import bytes2long
23 from pygost.utils import long2bytes
24 from pygost.utils import strxor
25 from pygost.utils import xrange
26
27
28 def pad_size(data_size, blocksize):
29     """Calculate required pad size to full up blocksize
30     """
31     if data_size < blocksize:
32         return blocksize - data_size
33     if data_size % blocksize == 0:
34         return 0
35     return blocksize - data_size % blocksize
36
37
38 def pad1(data, blocksize):
39     """Padding method 1
40
41     Just fill up with zeros if necessary.
42     """
43     return data + b"\x00" * pad_size(len(data), blocksize)
44
45
46 def pad2(data, blocksize):
47     """Padding method 2 (also known as ISO/IEC 7816-4)
48
49     Add one bit and then fill up with zeros.
50     """
51     return data + b"\x80" + b"\x00" * pad_size(len(data) + 1, blocksize)
52
53
54 def unpad2(data, blocksize):
55     """Unpad method 2
56     """
57     last_block = bytearray(data[-blocksize:])
58     pad_index = last_block.rfind(b"\x80")
59     if pad_index == -1:
60         raise ValueError("Invalid padding")
61     for c in last_block[pad_index + 1:]:
62         if c != 0:
63             raise ValueError("Invalid padding")
64     return data[:-(blocksize - pad_index)]
65
66
67 def pad3(data, blocksize):
68     """Padding method 3
69     """
70     if pad_size(len(data), blocksize) == 0:
71         return data
72     return pad2(data, blocksize)
73
74
75 def ecb_encrypt(encrypter, bs, pt):
76     """ECB encryption mode of operation
77
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
81     """
82     if not pt or len(pt) % bs != 0:
83         raise ValueError("Plaintext is not blocksize aligned")
84     ct = []
85     for i in xrange(0, len(pt), bs):
86         ct.append(encrypter(pt[i:i + bs]))
87     return b"".join(ct)
88
89
90 def ecb_decrypt(decrypter, bs, ct):
91     """ECB decryption mode of operation
92
93     :param decrypter: Decrypting function, that takes block as an input
94     :param int bs: cipher's blocksize
95     :param bytes ct: ciphertext
96     """
97     if not ct or len(ct) % bs != 0:
98         raise ValueError("Ciphertext is not blocksize aligned")
99     pt = []
100     for i in xrange(0, len(ct), bs):
101         pt.append(decrypter(ct[i:i + bs]))
102     return b"".join(pt)
103
104
105 def ctr(encrypter, bs, data, iv):
106     """Counter mode of operation
107
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
112
113     For decryption you use the same function again.
114     """
115     if len(iv) != bs // 2:
116         raise ValueError("Invalid IV size")
117     stream = []
118     ctr_value = 0
119     for _ in xrange(0, len(data) + pad_size(len(data), bs), bs):
120         stream.append(encrypter(iv + long2bytes(ctr_value, bs // 2)))
121         ctr_value += 1
122     return strxor(b"".join(stream), data)
123
124
125 def ofb(encrypter, bs, data, iv):
126     """OFB mode of operation
127
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: blocksize-sized initialization vector
132
133     For decryption you use the same function again.
134     """
135     if len(iv) < 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)]
138     result = []
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)
143
144
145 def cbc_encrypt(encrypter, bs, pt, iv):
146     """CBC encryption mode of operation
147
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: blocksize-sized initialization vector
152     """
153     if not pt or len(pt) % bs != 0:
154         raise ValueError("Plaintext is not blocksize aligned")
155     if len(iv) < 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)]
158     ct = []
159     for i in xrange(0, len(pt), bs):
160         ct.append(encrypter(strxor(r[0], pt[i:i + bs])))
161         r = r[1:] + [ct[-1]]
162     return b"".join(ct)
163
164
165 def cbc_decrypt(decrypter, bs, ct, iv):
166     """CBC decryption mode of operation
167
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: blocksize-sized initialization vector
172     """
173     if not ct or len(ct) % bs != 0:
174         raise ValueError("Ciphertext is not blocksize aligned")
175     if len(iv) < 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)]
178     pt = []
179     for i in xrange(0, len(ct), bs):
180         blk = ct[i:i + bs]
181         pt.append(strxor(r[0], decrypter(blk)))
182         r = r[1:] + [blk]
183     return b"".join(pt)
184
185
186 def cfb_encrypt(encrypter, bs, pt, iv):
187     """CFB encryption mode of operation
188
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: blocksize-sized initialization vector
193     """
194     if len(iv) < 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)]
197     ct = []
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]))
200         r = r[1:] + [ct[-1]]
201     return b"".join(ct)
202
203
204 def cfb_decrypt(encrypter, bs, ct, iv):
205     """CFB decryption mode of operation
206
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: blocksize-sized initialization vector
211     """
212     if len(iv) < 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)]
215     pt = []
216     for i in xrange(0, len(ct) + pad_size(len(ct), bs), bs):
217         blk = ct[i:i + bs]
218         pt.append(strxor(encrypter(r[0]), blk))
219         r = r[1:] + [blk]
220     return b"".join(pt)
221
222
223 def _mac_shift(bs, data, xor_lsb=0):
224     num = (bytes2long(data) << 1) ^ xor_lsb
225     return long2bytes(num, bs)[-bs:]
226
227
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)
233     return k1, k2
234
235
236 def mac(encrypter, bs, data):
237     """MAC (known here as CMAC, OMAC1) mode of operation
238
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
242
243     Implementation is based on PyCrypto's CMAC one, that is in public domain.
244     """
245     k1, k2 = _mac_ks(encrypter, bs)
246     if len(data) % bs == 0:
247         tail_offset = len(data) - bs
248     else:
249         tail_offset = len(data) - (len(data) % bs)
250     prev = bs * b'\x00'
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,
257     ))