]> Cypherpunks.ru repositories - pygost.git/blobdiff - pygost/gost3413.py
(un)pad_iso10126
[pygost.git] / pygost / gost3413.py
index 15f83061ffd1d598c2663df0677a911b97df1165..27f0c30dc79a75de4e66f8e284455221474fa215 100644 (file)
@@ -1,6 +1,6 @@
 # coding: utf-8
 # PyGOST -- Pure Python GOST cryptographic functions library
 # coding: utf-8
 # PyGOST -- Pure Python GOST cryptographic functions library
-# Copyright (C) 2015-2019 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2015-2022 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
 #
 # 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/>.
 #
 # 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.
 """
 
 
 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
 
 
 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 pad_size(data_size, blocksize):
     """Calculate required pad size to full up blocksize
     """
@@ -74,8 +79,8 @@ def pad3(data, blocksize):
 def ecb_encrypt(encrypter, bs, pt):
     """ECB encryption mode of operation
 
 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:
     :param bytes pt: already padded plaintext
     """
     if not pt or len(pt) % bs != 0:
@@ -90,7 +95,7 @@ def ecb_decrypt(decrypter, bs, ct):
     """ECB decryption mode of operation
 
     :param decrypter: Decrypting function, that takes block as an input
     """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:
     :param bytes ct: ciphertext
     """
     if not ct or len(ct) % bs != 0:
@@ -101,11 +106,23 @@ def ecb_decrypt(decrypter, bs, ct):
     return b"".join(pt)
 
 
     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
 
     """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
 
     :param bytes data: plaintext/ciphertext
     :param bytes iv: half blocksize-sized initialization vector
 
@@ -113,19 +130,48 @@ def ctr(encrypter, bs, data, iv):
     """
     if len(iv) != bs // 2:
         raise ValueError("Invalid IV size")
     """
     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
     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):
     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)))
         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)
 
 
     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
 
 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
 
     :param bytes data: plaintext/ciphertext
     :param bytes iv: blocksize-sized initialization vector
 
@@ -144,8 +190,8 @@ def ofb(encrypter, bs, data, iv):
 def cbc_encrypt(encrypter, bs, pt, iv):
     """CBC encryption mode of operation
 
 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
     """
     :param bytes pt: already padded plaintext
     :param bytes iv: blocksize-sized initialization vector
     """
@@ -165,7 +211,7 @@ def cbc_decrypt(decrypter, bs, ct, iv):
     """CBC decryption mode of operation
 
     :param decrypter: Decrypting function, that takes block as an input
     """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
     """
     :param bytes ct: ciphertext
     :param bytes iv: blocksize-sized initialization vector
     """
@@ -185,8 +231,8 @@ def cbc_decrypt(decrypter, bs, ct, iv):
 def cfb_encrypt(encrypter, bs, pt, iv):
     """CFB encryption mode of operation
 
 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
     """
     :param bytes pt: plaintext
     :param bytes iv: blocksize-sized initialization vector
     """
@@ -203,8 +249,8 @@ def cfb_encrypt(encrypter, bs, pt, iv):
 def cfb_decrypt(encrypter, bs, ct, iv):
     """CFB decryption mode of operation
 
 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
     """
     :param bytes ct: ciphertext
     :param bytes iv: blocksize-sized initialization vector
     """
@@ -224,9 +270,13 @@ def _mac_shift(bs, data, xor_lsb=0):
     return long2bytes(num, bs)[-bs:]
 
 
     return long2bytes(num, bs)[-bs:]
 
 
+Rb64 = 0b11011
+Rb128 = 0b10000111
+
+
 def _mac_ks(encrypter, bs):
 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
     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
@@ -235,8 +285,8 @@ def _mac_ks(encrypter, bs):
 def mac(encrypter, bs, data):
     """MAC (known here as CMAC, OMAC1) mode of operation
 
 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.
     :param bytes data: data to authenticate
 
     Implementation is based on PyCrypto's CMAC one, that is in public domain.
@@ -246,7 +296,7 @@ def mac(encrypter, bs, data):
         tail_offset = len(data) - bs
     else:
         tail_offset = len(data) - (len(data) % bs)
         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:]
     for i in xrange(0, tail_offset, bs):
         prev = encrypter(strxor(data[i:i + bs], prev))
     tail = data[tail_offset:]
@@ -254,3 +304,89 @@ def mac(encrypter, bs, data):
         strxor(pad3(tail, bs), prev),
         k1 if len(tail) == bs else k2,
     ))
         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]