]> Cypherpunks.ru repositories - pygost.git/blob - pygost/wrap.py
Fix wrap_cryptopro's sbox handling
[pygost.git] / pygost / wrap.py
1 # coding: utf-8
2 # PyGOST -- Pure Python GOST cryptographic functions library
3 # Copyright (C) 2015-2022 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 """Key wrap.
17
18 :rfc:`4357` key wrapping (28147-89 and CryptoPro).
19 """
20
21 from hmac import compare_digest
22 from struct import pack
23 from struct import unpack
24
25 from pygost.gost28147 import cfb_encrypt
26 from pygost.gost28147 import DEFAULT_SBOX
27 from pygost.gost28147 import ecb_decrypt
28 from pygost.gost28147 import ecb_encrypt
29 from pygost.gost28147_mac import MAC
30 from pygost.gost3413 import ctr
31 from pygost.gost3413 import mac
32
33
34 def wrap_gost(ukm, kek, cek, sbox=DEFAULT_SBOX):
35     """28147-89 key wrapping
36
37     :param ukm: UKM
38     :type ukm: bytes, 8 bytes
39     :param kek: key encryption key
40     :type kek: bytes, 32 bytes
41     :param cek: content encryption key
42     :type cek: bytes, 32 bytes
43     :returns: wrapped key
44     :rtype: bytes, 44 bytes
45     """
46     cek_mac = MAC(kek, data=cek, iv=ukm, sbox=sbox).digest()[:4]
47     cek_enc = ecb_encrypt(kek, cek, sbox=sbox)
48     return ukm + cek_enc + cek_mac
49
50
51 def unwrap_gost(kek, data, sbox=DEFAULT_SBOX):
52     """28147-89 key unwrapping
53
54     :param kek: key encryption key
55     :type kek: bytes, 32 bytes
56     :param data: wrapped key
57     :type data: bytes, 44 bytes
58     :returns: unwrapped CEK
59     :rtype: 32 bytes
60     """
61     if len(data) != 44:
62         raise ValueError("Invalid data length")
63     ukm, cek_enc, cek_mac = data[:8], data[8:8 + 32], data[-4:]
64     cek = ecb_decrypt(kek, cek_enc, sbox=sbox)
65     if MAC(kek, data=cek, iv=ukm, sbox=sbox).digest()[:4] != cek_mac:
66         raise ValueError("Invalid MAC")
67     return cek
68
69
70 def wrap_cryptopro(ukm, kek, cek, sbox=DEFAULT_SBOX):
71     """CryptoPro key wrapping
72
73     :param ukm: UKM
74     :type ukm: bytes, 8 bytes
75     :param kek: key encryption key
76     :type kek: bytes, 32 bytes
77     :param cek: content encryption key
78     :type cek: bytes, 32 bytes
79     :returns: wrapped key
80     :rtype: bytes, 44 bytes
81     """
82     return wrap_gost(
83         ukm,
84         diversify(kek, bytearray(ukm), sbox=sbox),
85         cek,
86         sbox=sbox,
87     )
88
89
90 def unwrap_cryptopro(kek, data, sbox=DEFAULT_SBOX):
91     """CryptoPro key unwrapping
92
93     :param kek: key encryption key
94     :type kek: bytes, 32 bytes
95     :param data: wrapped key
96     :type data: bytes, 44 bytes
97     :returns: unwrapped CEK
98     :rtype: 32 bytes
99     """
100     if len(data) < 8:
101         raise ValueError("Invalid data length")
102     return unwrap_gost(
103         diversify(kek, bytearray(data[:8]), sbox=sbox),
104         data,
105         sbox=sbox,
106     )
107
108
109 def diversify(kek, ukm, sbox=DEFAULT_SBOX):
110     out = kek
111     for i in range(8):
112         s1, s2 = 0, 0
113         for j in range(8):
114             k, = unpack("<i", out[j * 4:j * 4 + 4])
115             if (ukm[i] >> j) & 1:
116                 s1 += k
117             else:
118                 s2 += k
119         iv = pack("<I", s1 % 2 ** 32) + pack("<I", s2 % 2 ** 32)
120         out = cfb_encrypt(out, out, iv=iv, sbox=sbox)
121     return out
122
123
124 def kexp15(encrypter_key, encrypter_mac, bs, key, iv):
125     """KExp15 key exporting
126
127     :param encrypter_key: encrypting function for key encryption,
128                           that takes block as an input
129     :param encrypter_mac: encrypting function for key authentication
130     :param int bs: cipher's blocksize, bytes
131     :param bytes key: key to export
132     :param bytes iv: half blocksize-sized initialization vector
133     """
134     key_mac = mac(encrypter_mac, bs, iv + key)
135     return ctr(encrypter_key, bs, key + key_mac, iv)
136
137
138 def kimp15(encrypter_key, encrypter_mac, bs, kexp, iv):
139     """KImp15 key importing
140
141     :param encrypter_key: encrypting function for key decryption,
142                           that takes block as an input
143     :param encrypter_mac: encrypting function for key authentication
144     :param int bs: cipher's blocksize, bytes
145     :param bytes kexp: key to import
146     :param bytes iv: half blocksize-sized initialization vector
147     """
148     key_and_key_mac = ctr(encrypter_key, bs, kexp, iv)
149     key, key_mac = key_and_key_mac[:-bs], key_and_key_mac[-bs:]
150     if not compare_digest(mac(encrypter_mac, bs, iv + key), key_mac):
151         raise ValueError("Invalid authentication tag")
152     return key