]> Cypherpunks.ru repositories - pygost.git/blobdiff - pygost/gost3413.py
(un)pad_iso10126
[pygost.git] / pygost / gost3413.py
index 3a539d2e4b1f9097e78774ea3cf263df648a9d5a..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-2020 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
     """
@@ -75,7 +80,7 @@ def ecb_encrypt(encrypter, bs, pt):
     """ECB encryption mode of operation
 
     :param encrypter: encrypting function, that takes block as an input
     """ECB encryption mode of operation
 
     :param encrypter: encrypting function, that takes block as an input
-    :param int bs: cipher's blocksize
+    :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
 
     :param encrypter: encrypting function, that takes block as an input
     """Counter mode of operation
 
     :param encrypter: encrypting function, that takes block as an input
-    :param int bs: cipher's blocksize
+    :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
 
     :param encrypter: encrypting function, that takes block as an input
 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 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
 
@@ -145,7 +191,7 @@ def cbc_encrypt(encrypter, bs, pt, iv):
     """CBC encryption mode of operation
 
     :param encrypter: encrypting function, that takes block as an input
     """CBC encryption mode of operation
 
     :param encrypter: encrypting function, that takes block as an input
-    :param int bs: cipher's blocksize
+    :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
     """
@@ -186,7 +232,7 @@ def cfb_encrypt(encrypter, bs, pt, iv):
     """CFB encryption mode of operation
 
     :param encrypter: encrypting function, that takes block as an input
     """CFB encryption mode of operation
 
     :param encrypter: encrypting function, that takes block as an input
-    :param int bs: cipher's blocksize
+    :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
     """
@@ -204,7 +250,7 @@ def cfb_decrypt(encrypter, bs, ct, iv):
     """CFB decryption mode of operation
 
     :param encrypter: encrypting function, that takes block as an input
     """CFB decryption mode of operation
 
     :param encrypter: encrypting 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
     """
@@ -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
@@ -236,7 +286,7 @@ def mac(encrypter, bs, data):
     """MAC (known here as CMAC, OMAC1) mode of operation
 
     :param encrypter: encrypting function, that takes block as an input
     """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 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,11 +296,97 @@ 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:]
+    return encrypter(strxor(
+        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):
     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:]
         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,
     ))
     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]