X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=pygost%2Fgost3413.py;h=32be5a33f33f6a628bf2aac239f15e160f6e1df7;hb=31b08d5a78505f0ae1a144e58d023d84eda2cc6e;hp=bd8cc49ad7e8c2ae20fa6cf5f6ced910b60a68a9;hpb=43fdce36120844bc0fc38e0d5664dfc7090c119a;p=pygost.git diff --git a/pygost/gost3413.py b/pygost/gost3413.py index bd8cc49..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-2017 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,51 +128,80 @@ 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: double blocksize-sized initialization vector + :param bytes iv: blocksize-sized initialization vector For decryption you use the same function again. """ - if len(iv) != 2 * bs: + if len(iv) < bs or len(iv) % bs != 0: raise ValueError("Invalid IV size") - r = [iv[:bs], iv[bs:]] + r = [iv[i:i + bs] for i in range(0, len(iv), bs)] result = [] for i in xrange(0, len(data) + pad_size(len(data), bs), bs): - r = [r[1], encrypter(r[0])] - result.append(strxor(r[1], data[i:i + bs])) + r = r[1:] + [encrypter(r[0])] + result.append(strxor(r[-1], data[i:i + bs])) return b"".join(result) 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: double blocksize-sized initialization vector + :param bytes iv: blocksize-sized initialization vector """ if not pt or len(pt) % bs != 0: raise ValueError("Plaintext is not blocksize aligned") - if len(iv) != 2 * bs: + if len(iv) < bs or len(iv) % bs != 0: raise ValueError("Invalid IV size") - r = [iv[:bs], iv[bs:]] + r = [iv[i:i + bs] for i in range(0, len(iv), bs)] ct = [] for i in xrange(0, len(pt), bs): ct.append(encrypter(strxor(r[0], pt[i:i + bs]))) - r = [r[1], ct[-1]] + r = r[1:] + [ct[-1]] return b"".join(ct) @@ -166,57 +209,57 @@ 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: double blocksize-sized initialization vector + :param bytes iv: blocksize-sized initialization vector """ if not ct or len(ct) % bs != 0: raise ValueError("Ciphertext is not blocksize aligned") - if len(iv) != 2 * bs: + if len(iv) < bs or len(iv) % bs != 0: raise ValueError("Invalid IV size") - r = [iv[:bs], iv[bs:]] + r = [iv[i:i + bs] for i in range(0, len(iv), bs)] pt = [] for i in xrange(0, len(ct), bs): blk = ct[i:i + bs] pt.append(strxor(r[0], decrypter(blk))) - r = [r[1], blk] + r = r[1:] + [blk] return b"".join(pt) 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: double blocksize-sized initialization vector + :param bytes iv: blocksize-sized initialization vector """ - if len(iv) != 2 * bs: + if len(iv) < bs or len(iv) % bs != 0: raise ValueError("Invalid IV size") - r = [iv[:bs], iv[bs:]] + r = [iv[i:i + bs] for i in range(0, len(iv), bs)] ct = [] for i in xrange(0, len(pt) + pad_size(len(pt), bs), bs): ct.append(strxor(encrypter(r[0]), pt[i:i + bs])) - r = [r[1], ct[-1]] + r = r[1:] + [ct[-1]] return b"".join(ct) 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: double blocksize-sized initialization vector + :param bytes iv: blocksize-sized initialization vector """ - if len(iv) != 2 * bs: + if len(iv) < bs or len(iv) % bs != 0: raise ValueError("Invalid IV size") - r = [iv[:bs], iv[bs:]] + r = [iv[i:i + bs] for i in range(0, len(iv), bs)] pt = [] for i in xrange(0, len(ct) + pad_size(len(ct), bs), bs): blk = ct[i:i + bs] pt.append(strxor(encrypter(r[0]), blk)) - r = [r[1], blk] + r = r[1:] + [blk] return b"".join(pt) @@ -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,