X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=pygost%2Fgost3413.py;h=32be5a33f33f6a628bf2aac239f15e160f6e1df7;hb=31b08d5a78505f0ae1a144e58d023d84eda2cc6e;hp=a31a3c2e32832f0e10ec10cd01d7244180999f98;hpb=34c9c9a4f95eecfee75fc36d75d2ee45d1054a25;p=pygost.git diff --git a/pygost/gost3413.py b/pygost/gost3413.py index a31a3c2..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-2016 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,14 +13,22 @@ # # 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. """ +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 + """Calculate required pad size to full up blocksize """ if data_size < blocksize: return blocksize - data_size @@ -35,7 +42,7 @@ def pad1(data, blocksize): Just fill up with zeros if necessary. """ - return data + b'\x00' * pad_size(len(data), blocksize) + return data + b"\x00" * pad_size(len(data), blocksize) def pad2(data, blocksize): @@ -43,7 +50,20 @@ def pad2(data, blocksize): Add one bit and then fill up with zeros. """ - return data + b'\x80' + b'\x00' * pad_size(len(data) + 1, blocksize) + return data + b"\x80" + b"\x00" * pad_size(len(data) + 1, blocksize) + + +def unpad2(data, blocksize): + """Unpad method 2 + """ + last_block = bytearray(data[-blocksize:]) + pad_index = last_block.rfind(b"\x80") + if pad_index == -1: + raise ValueError("Invalid padding") + for c in last_block[pad_index + 1:]: + if c != 0: + raise ValueError("Invalid padding") + return data[:-(blocksize - pad_index)] def pad3(data, blocksize): @@ -52,3 +72,296 @@ def pad3(data, blocksize): if pad_size(len(data), blocksize) == 0: return data return pad2(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, bytes + :param bytes pt: already padded plaintext + """ + if not pt or len(pt) % bs != 0: + raise ValueError("Plaintext is not blocksize aligned") + ct = [] + for i in xrange(0, len(pt), bs): + ct.append(encrypter(pt[i:i + bs])) + return b"".join(ct) + + +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, bytes + :param bytes ct: ciphertext + """ + if not ct or len(ct) % bs != 0: + raise ValueError("Ciphertext is not blocksize aligned") + pt = [] + for i in xrange(0, len(ct), bs): + pt.append(decrypter(ct[i:i + bs])) + return b"".join(pt) + + +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, bytes + :param bytes data: plaintext/ciphertext + :param bytes iv: half blocksize-sized initialization vector + + For decryption you use the same function again. + """ + 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 = (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, bytes + :param bytes data: plaintext/ciphertext + :param bytes iv: blocksize-sized initialization vector + + For decryption you use the same function again. + """ + if len(iv) < bs or len(iv) % bs != 0: + raise ValueError("Invalid IV size") + 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])) + 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, bytes + :param bytes pt: already padded plaintext + :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) < bs or len(iv) % bs != 0: + raise ValueError("Invalid IV size") + 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]] + return b"".join(ct) + + +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, bytes + :param bytes ct: ciphertext + :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) < bs or len(iv) % bs != 0: + raise ValueError("Invalid IV size") + 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] + 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, bytes + :param bytes pt: plaintext + :param bytes iv: blocksize-sized initialization vector + """ + if len(iv) < bs or len(iv) % bs != 0: + raise ValueError("Invalid IV size") + 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]] + 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, bytes + :param bytes ct: ciphertext + :param bytes iv: blocksize-sized initialization vector + """ + if len(iv) < bs or len(iv) % bs != 0: + raise ValueError("Invalid IV size") + 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] + return b"".join(pt) + + +def _mac_shift(bs, data, xor_lsb=0): + num = (bytes2long(data) << 1) ^ xor_lsb + return long2bytes(num, bs)[-bs:] + + +Rb64 = 0b11011 +Rb128 = 0b10000111 + + +def _mac_ks(encrypter, bs): + 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 + + +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, bytes + :param bytes data: data to authenticate + + Implementation is based on PyCrypto's CMAC one, that is in public domain. + """ + k1, k2 = _mac_ks(encrypter, bs) + if len(data) % bs == 0: + tail_offset = len(data) - bs + else: + tail_offset = len(data) - (len(data) % bs) + 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, + ))