# coding: utf-8
# PyGOST -- Pure Python GOST cryptographic functions library
-# Copyright (C) 2015-2020 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2015-2023 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
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-""" GOST R 34.13-2015: Modes of operation for block ciphers
+"""GOST R 34.13-2015: Modes of operation for block ciphers
This module currently includes only padding methods.
"""
+from os import urandom
+
from pygost.utils import bytes2long
from pygost.utils import long2bytes
from pygost.utils import strxor
from pygost.utils import xrange
+KEYSIZE = 32
+
+
def pad_size(data_size, blocksize):
"""Calculate required pad size to full up blocksize
"""
def ecb_encrypt(encrypter, bs, pt):
"""ECB encryption mode of operation
- :param encrypter: Encrypting function, that takes block as an input
- :param int bs: cipher's blocksize
+ :param encrypter: encrypting function, that takes block as an input
+ :param int bs: cipher's blocksize, bytes
:param bytes pt: already padded plaintext
"""
if not pt or len(pt) % bs != 0:
"""ECB decryption mode of operation
:param decrypter: Decrypting function, that takes block as an input
- :param int bs: cipher's blocksize
+ :param int bs: cipher's blocksize, bytes
:param bytes ct: ciphertext
"""
if not ct or len(ct) % bs != 0:
return b"".join(pt)
-def ctr(encrypter, bs, data, iv):
+def acpkm(encrypter, bs):
+ """Perform ACPKM key derivation
+
+ :param encrypter: encrypting function, that takes block as an input
+ :param int bs: cipher's blocksize, bytes
+ """
+ return b"".join([
+ encrypter(bytes(bytearray(range(d, d + bs))))
+ for d in range(0x80, 0x80 + bs * (KEYSIZE // bs), bs)
+ ])
+
+
+def ctr(encrypter, bs, data, iv, _acpkm=None):
"""Counter mode of operation
- :param encrypter: Encrypting function, that takes block as an input
- :param int bs: cipher's blocksize
+ :param encrypter: encrypting function, that takes block as an input
+ :param int bs: cipher's blocksize, bytes
:param bytes data: plaintext/ciphertext
:param bytes iv: half blocksize-sized initialization vector
"""
if len(iv) != bs // 2:
raise ValueError("Invalid IV size")
+ if len(data) > bs * (1 << (8 * (bs // 2 - 1))):
+ raise ValueError("Too big data")
stream = []
ctr_value = 0
+ ctr_max_value = 1 << (8 * (bs // 2))
+ if _acpkm is not None:
+ acpkm_algo_class, acpkm_section_size_in_bs = _acpkm
+ acpkm_section_size_in_bs //= bs
for _ in xrange(0, len(data) + pad_size(len(data), bs), bs):
+ if (
+ _acpkm is not None and
+ ctr_value != 0 and
+ ctr_value % acpkm_section_size_in_bs == 0
+ ):
+ encrypter = acpkm_algo_class(acpkm(encrypter, bs)).encrypt
stream.append(encrypter(iv + long2bytes(ctr_value, bs // 2)))
- ctr_value += 1
+ ctr_value = (ctr_value + 1) % ctr_max_value
return strxor(b"".join(stream), data)
+def ctr_acpkm(algo_class, encrypter, section_size, bs, data, iv):
+ """CTR-ACPKM mode of operation
+
+ :param algo_class: pygost.gost3412's algorithm class
+ :param encrypter: encrypting function, that takes block as an input
+ :param int section_size: ACPKM'es section size (N), in bytes
+ :param int bs: cipher's blocksize, bytes
+ :param bytes data: plaintext/ciphertext
+ :param bytes iv: half blocksize-sized initialization vector
+
+ For decryption you use the same function again.
+ """
+ if section_size % bs != 0:
+ raise ValueError("section_size must be multiple of bs")
+ return ctr(encrypter, bs, data, iv, _acpkm=(algo_class, section_size))
+
+
def ofb(encrypter, bs, data, iv):
"""OFB mode of operation
- :param encrypter: Encrypting function, that takes block as an input
- :param int bs: cipher's blocksize
+ :param encrypter: encrypting function, that takes block as an input
+ :param int bs: cipher's blocksize, bytes
:param bytes data: plaintext/ciphertext
:param bytes iv: blocksize-sized initialization vector
def cbc_encrypt(encrypter, bs, pt, iv):
"""CBC encryption mode of operation
- :param encrypter: Encrypting function, that takes block as an input
- :param int bs: cipher's blocksize
+ :param encrypter: encrypting function, that takes block as an input
+ :param int bs: cipher's blocksize, bytes
:param bytes pt: already padded plaintext
:param bytes iv: blocksize-sized initialization vector
"""
"""CBC decryption mode of operation
:param decrypter: Decrypting function, that takes block as an input
- :param int bs: cipher's blocksize
+ :param int bs: cipher's blocksize, bytes
:param bytes ct: ciphertext
:param bytes iv: blocksize-sized initialization vector
"""
def cfb_encrypt(encrypter, bs, pt, iv):
"""CFB encryption mode of operation
- :param encrypter: Encrypting function, that takes block as an input
- :param int bs: cipher's blocksize
+ :param encrypter: encrypting function, that takes block as an input
+ :param int bs: cipher's blocksize, bytes
:param bytes pt: plaintext
:param bytes iv: blocksize-sized initialization vector
"""
def cfb_decrypt(encrypter, bs, ct, iv):
"""CFB decryption mode of operation
- :param encrypter: Encrypting function, that takes block as an input
- :param int bs: cipher's blocksize
+ :param encrypter: encrypting function, that takes block as an input
+ :param int bs: cipher's blocksize, bytes
:param bytes ct: ciphertext
:param bytes iv: blocksize-sized initialization vector
"""
return long2bytes(num, bs)[-bs:]
+Rb64 = 0b11011
+Rb128 = 0b10000111
+
+
def _mac_ks(encrypter, bs):
- Rb = 0b10000111 if bs == 16 else 0b11011
- _l = encrypter(bs * b'\x00')
+ Rb = Rb128 if bs == 16 else Rb64
+ _l = encrypter(bs * b"\x00")
k1 = _mac_shift(bs, _l, Rb) if bytearray(_l)[0] & 0x80 > 0 else _mac_shift(bs, _l)
k2 = _mac_shift(bs, k1, Rb) if bytearray(k1)[0] & 0x80 > 0 else _mac_shift(bs, k1)
return k1, k2
def mac(encrypter, bs, data):
"""MAC (known here as CMAC, OMAC1) mode of operation
- :param encrypter: Encrypting function, that takes block as an input
- :param int bs: cipher's blocksize
+ :param encrypter: encrypting function, that takes block as an input
+ :param int bs: cipher's blocksize, bytes
:param bytes data: data to authenticate
Implementation is based on PyCrypto's CMAC one, that is in public domain.
tail_offset = len(data) - bs
else:
tail_offset = len(data) - (len(data) % bs)
- prev = bs * b'\x00'
+ prev = bs * b"\x00"
for i in xrange(0, tail_offset, bs):
prev = encrypter(strxor(data[i:i + bs], prev))
tail = data[tail_offset:]
strxor(pad3(tail, bs), prev),
k1 if len(tail) == bs else k2,
))
+
+
+def acpkm_master(algo_class, encrypter, key_section_size, bs, keymat_len):
+ """ACPKM-Master key derivation
+
+ :param algo_class: pygost.gost3412's algorithm class
+ :param encrypter: encrypting function, that takes block as an input
+ :param int key_section_size: ACPKM'es key section size (T*), in bytes
+ :param int bs: cipher's blocksize, bytes
+ :param int keymat_len: length of key material to produce
+ """
+ return ctr_acpkm(
+ algo_class,
+ encrypter,
+ key_section_size,
+ bs,
+ data=b"\x00" * keymat_len,
+ iv=b"\xFF" * (bs // 2),
+ )
+
+
+def mac_acpkm_master(algo_class, encrypter, key_section_size, section_size, bs, data):
+ """OMAC-ACPKM-Master
+
+ :param algo_class: pygost.gost3412's algorithm class
+ :param encrypter: encrypting function, that takes block as an input
+ :param int key_section_size: ACPKM'es key section size (T*), in bytes
+ :param int section_size: ACPKM'es section size (N), in bytes
+ :param int bs: cipher's blocksize, bytes
+ :param bytes data: data to authenticate
+ """
+ if len(data) % bs == 0:
+ tail_offset = len(data) - bs
+ else:
+ tail_offset = len(data) - (len(data) % bs)
+ prev = bs * b"\x00"
+ sections = len(data) // section_size
+ if len(data) % section_size != 0:
+ sections += 1
+ keymats = acpkm_master(
+ algo_class,
+ encrypter,
+ key_section_size,
+ bs,
+ (KEYSIZE + bs) * sections,
+ )
+ for i in xrange(0, tail_offset, bs):
+ if i % section_size == 0:
+ keymat, keymats = keymats[:KEYSIZE + bs], keymats[KEYSIZE + bs:]
+ key, k1 = keymat[:KEYSIZE], keymat[KEYSIZE:]
+ encrypter = algo_class(key).encrypt
+ prev = encrypter(strxor(data[i:i + bs], prev))
+ tail = data[tail_offset:]
+ if len(tail) == bs:
+ key, k1 = keymats[:KEYSIZE], keymats[KEYSIZE:]
+ encrypter = algo_class(key).encrypt
+ k2 = long2bytes(bytes2long(k1) << 1, size=bs)
+ if bytearray(k1)[0] & 0x80 != 0:
+ k2 = strxor(k2, long2bytes(Rb128 if bs == 16 else Rb64, size=bs))
+ return encrypter(strxor(
+ strxor(pad3(tail, bs), prev),
+ k1 if len(tail) == bs else k2,
+ ))
+
+
+def pad_iso10126(data, blocksize):
+ """ISO 10126 padding
+
+ Does not exist in 34.13, but added for convenience.
+ It uses urandom call for getting the randomness.
+ """
+ pad_len = blocksize - len(data) % blocksize
+ if pad_len == 0:
+ pad_len = blocksize
+ return b"".join((data, urandom(pad_len - 1), bytes((pad_len,))))
+
+
+def unpad_iso10126(data, blocksize):
+ """Unpad :py:func:`pygost.gost3413.pad_iso10126`
+ """
+ if len(data) % blocksize != 0:
+ raise ValueError("Data length is not multiple of blocksize")
+ pad_len = bytearray(data)[-1]
+ if pad_len > blocksize:
+ raise ValueError("Padding length is bigger than blocksize")
+ return data[:-pad_len]