]> Cypherpunks.ru repositories - pygost.git/blobdiff - pygost/gost3413.py
Unify docstring's leading space presence
[pygost.git] / pygost / gost3413.py
index d26f69cc5e387b5230d2c259b3a692c931df3aa4..32be5a33f33f6a628bf2aac239f15e160f6e1df7 100644 (file)
@@ -1,11 +1,10 @@
 # coding: utf-8
 # PyGOST -- Pure Python GOST cryptographic functions library
-# Copyright (C) 2015-2019 Sergey Matveev <stargrave@stargrave.org>
+# 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, either version 3 of the License, or
-# (at your option) any later version.
+# 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
@@ -14,7 +13,7 @@
 #
 # 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.
 """
@@ -25,6 +24,9 @@ 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
     """
@@ -75,8 +77,8 @@ def pad3(data, 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:
@@ -91,7 +93,7 @@ def ecb_decrypt(decrypter, bs, ct):
     """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:
@@ -102,11 +104,23 @@ def ecb_decrypt(decrypter, bs, ct):
     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
 
@@ -114,19 +128,48 @@ def ctr(encrypter, bs, data, iv):
     """
     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
 
@@ -145,8 +188,8 @@ def ofb(encrypter, bs, data, iv):
 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
     """
@@ -166,7 +209,7 @@ def cbc_decrypt(decrypter, bs, ct, iv):
     """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
     """
@@ -186,8 +229,8 @@ def cbc_decrypt(decrypter, bs, ct, iv):
 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
     """
@@ -204,8 +247,8 @@ def cfb_encrypt(encrypter, bs, pt, iv):
 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
     """
@@ -225,9 +268,13 @@ def _mac_shift(bs, data, xor_lsb=0):
     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
@@ -236,8 +283,8 @@ def _mac_ks(encrypter, bs):
 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.
@@ -247,10 +294,73 @@ def mac(encrypter, bs, data):
         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):
+        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,