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