X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=pygost%2Fgost3413.py;h=32be5a33f33f6a628bf2aac239f15e160f6e1df7;hb=31b08d5a78505f0ae1a144e58d023d84eda2cc6e;hp=a350915ba3631f61892c918318a35c00b64d9092;hpb=4deec9f06b1b54b1cbf4027a49976fcbd4e20e57;p=pygost.git diff --git a/pygost/gost3413.py b/pygost/gost3413.py index a350915..32be5a3 100644 --- a/pygost/gost3413.py +++ b/pygost/gost3413.py @@ -1,11 +1,10 @@ # coding: utf-8 # PyGOST -- Pure Python GOST cryptographic functions library -# Copyright (C) 2015-2018 Sergey Matveev +# Copyright (C) 2015-2020 Sergey Matveev # # 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 . -""" 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,