summaryrefslogtreecommitdiff
path: root/pygost/wrap.py
blob: 301023eda18228d35ca1b00d2916afc30495e30a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# coding: utf-8
# PyGOST -- Pure Python GOST cryptographic functions library
# Copyright (C) 2015-2020 Sergey Matveev <stargrave@stargrave.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""Key wrap.

:rfc:`4357` key wrapping (28147-89 and CryptoPro).
"""

from hmac import compare_digest
from struct import pack
from struct import unpack

from pygost.gost28147 import cfb_encrypt
from pygost.gost28147 import DEFAULT_SBOX
from pygost.gost28147 import ecb_decrypt
from pygost.gost28147 import ecb_encrypt
from pygost.gost28147_mac import MAC
from pygost.gost3413 import ctr
from pygost.gost3413 import mac


def wrap_gost(ukm, kek, cek, sbox=DEFAULT_SBOX):
    """28147-89 key wrapping

    :param ukm: UKM
    :type ukm: bytes, 8 bytes
    :param kek: key encryption key
    :type kek: bytes, 32 bytes
    :param cek: content encryption key
    :type cek: bytes, 32 bytes
    :returns: wrapped key
    :rtype: bytes, 44 bytes
    """
    cek_mac = MAC(kek, data=cek, iv=ukm, sbox=sbox).digest()[:4]
    cek_enc = ecb_encrypt(kek, cek, sbox=sbox)
    return ukm + cek_enc + cek_mac


def unwrap_gost(kek, data, sbox=DEFAULT_SBOX):
    """28147-89 key unwrapping

    :param kek: key encryption key
    :type kek: bytes, 32 bytes
    :param data: wrapped key
    :type data: bytes, 44 bytes
    :returns: unwrapped CEK
    :rtype: 32 bytes
    """
    if len(data) != 44:
        raise ValueError("Invalid data length")
    ukm, cek_enc, cek_mac = data[:8], data[8:8 + 32], data[-4:]
    cek = ecb_decrypt(kek, cek_enc, sbox=sbox)
    if MAC(kek, data=cek, iv=ukm, sbox=sbox).digest()[:4] != cek_mac:
        raise ValueError("Invalid MAC")
    return cek


def wrap_cryptopro(ukm, kek, cek, sbox=DEFAULT_SBOX):
    """CryptoPro key wrapping

    :param ukm: UKM
    :type ukm: bytes, 8 bytes
    :param kek: key encryption key
    :type kek: bytes, 32 bytes
    :param cek: content encryption key
    :type cek: bytes, 32 bytes
    :returns: wrapped key
    :rtype: bytes, 44 bytes
    """
    return wrap_gost(ukm, diversify(kek, bytearray(ukm)), cek, sbox=sbox)


def unwrap_cryptopro(kek, data, sbox=DEFAULT_SBOX):
    """CryptoPro key unwrapping

    :param kek: key encryption key
    :type kek: bytes, 32 bytes
    :param data: wrapped key
    :type data: bytes, 44 bytes
    :returns: unwrapped CEK
    :rtype: 32 bytes
    """
    if len(data) < 8:
        raise ValueError("Invalid data length")
    return unwrap_gost(
        diversify(kek, bytearray(data[:8]), sbox=sbox),
        data,
        sbox=sbox,
    )


def diversify(kek, ukm, sbox=DEFAULT_SBOX):
    out = kek
    for i in range(8):
        s1, s2 = 0, 0
        for j in range(8):
            k, = unpack("<i", out[j * 4:j * 4 + 4])
            if (ukm[i] >> j) & 1:
                s1 += k
            else:
                s2 += k
        iv = pack("<I", s1 % 2 ** 32) + pack("<I", s2 % 2 ** 32)
        out = cfb_encrypt(out, out, iv=iv, sbox=sbox)
    return out


def kexp15(encrypter_key, encrypter_mac, bs, key, iv):
    """KExp15 key exporting

    :param encrypter_key: encrypting function for key encryption,
                          that takes block as an input
    :param encrypter_mac: encrypting function for key authentication
    :param int bs: cipher's blocksize, bytes
    :param bytes key: key to export
    :param bytes iv: half blocksize-sized initialization vector
    """
    key_mac = mac(encrypter_mac, bs, iv + key)
    return ctr(encrypter_key, bs, key + key_mac, iv)


def kimp15(encrypter_key, encrypter_mac, bs, kexp, iv):
    """KImp15 key importing

    :param encrypter_key: encrypting function for key decryption,
                          that takes block as an input
    :param encrypter_mac: encrypting function for key authentication
    :param int bs: cipher's blocksize, bytes
    :param bytes kexp: key to import
    :param bytes iv: half blocksize-sized initialization vector
    """
    key_and_key_mac = ctr(encrypter_key, bs, kexp, iv)
    key, key_mac = key_and_key_mac[:-bs], key_and_key_mac[-bs:]
    if not compare_digest(mac(encrypter_mac, bs, iv + key), key_mac):
        raise ValueError("invalid authentication tag")
    return key