summaryrefslogtreecommitdiff
path: root/pygost/wrap.py
blob: a3c206ca4c52a02c61e35fb55806fed0d5a36ae3 (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
# 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 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


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