X-Git-Url: http://www.git.cypherpunks.ru/?p=pygost.git;a=blobdiff_plain;f=pygost%2Fgost3413.py;h=495a3f9e343826ed430616f88b63c4ead6ca5b60;hp=3a539d2e4b1f9097e78774ea3cf263df648a9d5a;hb=4277661e11e63906d923827e5c124877f1dc0c8d;hpb=a63f78066756e6959b7bcbc52738f2ff4a03a308 diff --git a/pygost/gost3413.py b/pygost/gost3413.py index 3a539d2..495a3f9 100644 --- a/pygost/gost3413.py +++ b/pygost/gost3413.py @@ -24,6 +24,9 @@ from pygost.utils import strxor from pygost.utils import xrange +KEY_SIZE = 32 + + def pad_size(data_size, blocksize): """Calculate required pad size to full up blocksize """ @@ -75,7 +78,7 @@ 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 int bs: cipher's blocksize, bytes :param bytes pt: already padded plaintext """ if not pt or len(pt) % bs != 0: @@ -90,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: @@ -101,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 * (KEY_SIZE // 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 int bs: cipher's blocksize, bytes :param bytes data: plaintext/ciphertext :param bytes iv: half blocksize-sized initialization vector @@ -113,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 int bs: cipher's blocksize, bytes :param bytes data: plaintext/ciphertext :param bytes iv: blocksize-sized initialization vector @@ -145,7 +189,7 @@ 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 int bs: cipher's blocksize, bytes :param bytes pt: already padded plaintext :param bytes iv: blocksize-sized initialization vector """ @@ -165,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,7 +230,7 @@ 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 int bs: cipher's blocksize, bytes :param bytes pt: plaintext :param bytes iv: blocksize-sized initialization vector """ @@ -204,7 +248,7 @@ 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 int bs: cipher's blocksize, bytes :param bytes ct: ciphertext :param bytes iv: blocksize-sized initialization vector """ @@ -224,8 +268,12 @@ 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 + 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) @@ -236,7 +284,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 - :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. @@ -254,3 +302,66 @@ def mac(encrypter, bs, data): 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, + (KEY_SIZE + bs) * sections, + ) + for i in xrange(0, tail_offset, bs): + if i % section_size == 0: + keymat, keymats = keymats[:KEY_SIZE + bs], keymats[KEY_SIZE + bs:] + key, k1 = keymat[:KEY_SIZE], keymat[KEY_SIZE:] + encrypter = algo_class(key).encrypt + prev = encrypter(strxor(data[i:i + bs], prev)) + tail = data[tail_offset:] + if len(tail) == bs: + key, k1 = keymats[:KEY_SIZE], keymats[KEY_SIZE:] + 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, + ))