+3.2:
+ 34.13-2015 block cipher modes of operation implementations.
+
3.1:
Fixed mypy stubs related to PEP247-successors.
* 28147-89 CryptoPro key meshing for CFB mode (RFC 4357)
* RFC 4491 (using GOST algorithms with X.509) compatibility helpers
* GOST R 34.12-2015 128-bit block cipher Кузнечик (Kuznechik) (RFC 7801)
-* GOST R 34.13-2015 padding methods
+* GOST R 34.13-2015 padding methods and block cipher modes of operation
+ (ECB, CTR, OFB, CBC, CFB, MAC)
* PEP247-compatible hash/MAC functions
Known problems: low performance and non time-constant calculations.
>>> verify(curve, pub, dgst, signature, mode=2012)
True
-Other examples can be found in docstrings.
+Other examples can be found in docstrings and unittests.
PyGOST is free software: see the file COPYING for copying conditions.
PYTHONPATH=.. $(PYTHON) -m unittest test_gost3410_vko
PYTHONPATH=.. $(PYTHON) -m unittest test_wrap
PYTHONPATH=.. $(PYTHON) -m unittest test_gost3412
+ PYTHONPATH=.. $(PYTHON) -m unittest test_gost3413
from pygost.gost3413 import pad2
from pygost.gost3413 import pad_size
+from pygost.gost3413 import unpad2
from pygost.utils import hexdec
from pygost.utils import strxor
from pygost.utils import xrange # pylint: disable=redefined-builtin
data[i - BLOCKSIZE:i],
))
if pad:
- last_block = bytearray(plaintext[-1])
- 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")
- plaintext[-1] = bytes(last_block[:pad_index])
+ plaintext[-1] = unpad2(plaintext[-1], BLOCKSIZE)
return b"".join(plaintext)
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
+
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
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):
"""Padding method 3
"""
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
+ :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
+ :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 ctr(encrypter, bs, data, iv):
+ """Counter mode of operation
+
+ :param encrypter: Encrypting function, that takes block as an input
+ :param int bs: cipher's blocksize
+ :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")
+ stream = []
+ ctr_value = 0
+ for _ in xrange(0, len(data) + pad_size(len(data), bs), bs):
+ stream.append(encrypter(iv + long2bytes(ctr_value, bs // 2)))
+ ctr_value += 1
+ return strxor(b"".join(stream), data)
+
+
+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 bytes data: plaintext/ciphertext
+ :param bytes iv: double blocksize-sized initialization vector
+
+ For decryption you use the same function again.
+ """
+ if len(iv) != 2 * bs:
+ raise ValueError("Invalid IV size")
+ r = [iv[:bs], 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
+ :param bytes pt: already padded plaintext
+ :param bytes iv: double blocksize-sized initialization vector
+ """
+ if not pt or len(pt) % bs != 0:
+ raise ValueError("Plaintext is not blocksize aligned")
+ if len(iv) != 2 * bs:
+ raise ValueError("Invalid IV size")
+ r = [iv[:bs], 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
+ :param bytes ct: ciphertext
+ :param bytes iv: double blocksize-sized initialization vector
+ """
+ if not ct or len(ct) % bs != 0:
+ raise ValueError("Ciphertext is not blocksize aligned")
+ if len(iv) != 2 * bs:
+ raise ValueError("Invalid IV size")
+ r = [iv[:bs], 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
+ :param bytes pt: plaintext
+ :param bytes iv: double blocksize-sized initialization vector
+ """
+ if len(iv) != 2 * bs:
+ raise ValueError("Invalid IV size")
+ r = [iv[:bs], 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
+ :param bytes ct: ciphertext
+ :param bytes iv: double blocksize-sized initialization vector
+ """
+ if len(iv) != 2 * bs:
+ raise ValueError("Invalid IV size")
+ r = [iv[:bs], 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:]
+
+
+def _mac_ks(encrypter, bs):
+ Rb = 0b10000111 if bs == 16 else 0b11011
+ _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
+ :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,
+ ))
+from typing import Callable
+
+
def pad_size(data_size: int, blocksize: int) -> int: ...
def pad2(data: bytes, blocksize: int) -> bytes: ...
+def unpad2(data: bytes, blocksize: int) -> bytes: ...
+
+
def pad3(data: bytes, blocksize: int) -> bytes: ...
+
+
+def ecb_encrypt(encrypter: Callable[[bytes], bytes], bs: int, pt: bytes) -> bytes: ...
+
+
+def ecb_decrypt(decrypter: Callable[[bytes], bytes], bs: int, ct: bytes) -> bytes: ...
+
+
+def ctr(encrypter: Callable[[bytes], bytes], bs: int, data: bytes, iv: bytes) -> bytes: ...
+
+
+def ofb(encrypter: Callable[[bytes], bytes], bs: int, data: bytes, iv: bytes) -> bytes: ...
+
+
+def cbc_encrypt(encrypter: Callable[[bytes], bytes], bs: int, pt: bytes, iv: bytes) -> bytes: ...
+
+
+def cbc_decrypt(decrypter: Callable[[bytes], bytes], bs: int, ct: bytes, iv: bytes) -> bytes: ...
+
+
+def cfb_encrypt(encrypter: Callable[[bytes], bytes], bs: int, pt: bytes, iv: bytes) -> bytes: ...
+
+
+def cfb_decrypt(encrypter: Callable[[bytes], bytes], bs: int, ct: bytes, iv: bytes) -> bytes: ...
+
+
+def mac(encrypter: Callable[[bytes], bytes], bs: int, data: bytes) -> bytes: ...
--- /dev/null
+from os import urandom
+from random import randint
+from unittest import TestCase
+
+from pygost.gost3412 import GOST3412Kuz
+from pygost.gost3413 import _mac_ks
+from pygost.gost3413 import cbc_decrypt
+from pygost.gost3413 import cbc_encrypt
+from pygost.gost3413 import cfb_decrypt
+from pygost.gost3413 import cfb_encrypt
+from pygost.gost3413 import ctr
+from pygost.gost3413 import ecb_decrypt
+from pygost.gost3413 import ecb_encrypt
+from pygost.gost3413 import mac
+from pygost.gost3413 import ofb
+from pygost.gost3413 import pad2
+from pygost.gost3413 import unpad2
+from pygost.utils import hexdec
+from pygost.utils import hexenc
+
+
+class Pad2Test(TestCase):
+ def test_symmetric(self):
+ for _ in range(100):
+ for blocksize in (8, 16):
+ data = urandom(randint(0, blocksize * 3))
+ self.assertSequenceEqual(
+ unpad2(pad2(data, blocksize), blocksize),
+ data,
+ )
+
+
+class GOST3412KuzModesTest(TestCase):
+ key = hexdec("8899aabbccddeeff0011223344556677fedcba98765432100123456789abcdef")
+ ciph = GOST3412Kuz(key)
+ plaintext = ""
+ plaintext += "1122334455667700ffeeddccbbaa9988"
+ plaintext += "00112233445566778899aabbcceeff0a"
+ plaintext += "112233445566778899aabbcceeff0a00"
+ plaintext += "2233445566778899aabbcceeff0a0011"
+ iv = hexdec("1234567890abcef0a1b2c3d4e5f0011223344556677889901213141516171819")
+
+ def test_ecb_vectors(self):
+ ciphtext = ""
+ ciphtext += "7f679d90bebc24305a468d42b9d4edcd"
+ ciphtext += "b429912c6e0032f9285452d76718d08b"
+ ciphtext += "f0ca33549d247ceef3f5a5313bd4b157"
+ ciphtext += "d0b09ccde830b9eb3a02c4c5aa8ada98"
+ self.assertSequenceEqual(
+ hexenc(ecb_encrypt(self.ciph.encrypt, 16, hexdec(self.plaintext))),
+ ciphtext,
+ )
+ self.assertSequenceEqual(
+ hexenc(ecb_decrypt(self.ciph.decrypt, 16, hexdec(ciphtext))),
+ self.plaintext,
+ )
+
+ def test_ecb_symmetric(self):
+ for _ in range(100):
+ pt = pad2(urandom(randint(0, 16 * 2)), 16)
+ ciph = GOST3412Kuz(urandom(32))
+ ct = ecb_encrypt(ciph.encrypt, 16, pt)
+ self.assertSequenceEqual(ecb_decrypt(ciph.decrypt, 16, ct), pt)
+
+ def test_ctr_vectors(self):
+ ciphtext = ""
+ ciphtext += "f195d8bec10ed1dbd57b5fa240bda1b8"
+ ciphtext += "85eee733f6a13e5df33ce4b33c45dee4"
+ ciphtext += "a5eae88be6356ed3d5e877f13564a3a5"
+ ciphtext += "cb91fab1f20cbab6d1c6d15820bdba73"
+ iv = self.iv[:8]
+ self.assertSequenceEqual(
+ hexenc(ctr(self.ciph.encrypt, 16, hexdec(self.plaintext), iv)),
+ ciphtext,
+ )
+ self.assertSequenceEqual(
+ hexenc(ctr(self.ciph.encrypt, 16, hexdec(ciphtext), iv)),
+ self.plaintext,
+ )
+
+ def test_ctr_symmetric(self):
+ for _ in range(100):
+ pt = urandom(randint(0, 16 * 2))
+ iv = urandom(8)
+ ciph = GOST3412Kuz(urandom(32))
+ ct = ctr(ciph.encrypt, 16, pt, iv)
+ self.assertSequenceEqual(ctr(ciph.encrypt, 16, ct, iv), pt)
+
+ def test_ofb_vectors(self):
+ ciphtext = ""
+ ciphtext += "81800a59b1842b24ff1f795e897abd95"
+ ciphtext += "ed5b47a7048cfab48fb521369d9326bf"
+ ciphtext += "66a257ac3ca0b8b1c80fe7fc10288a13"
+ ciphtext += "203ebbc066138660a0292243f6903150"
+ self.assertSequenceEqual(
+ hexenc(ofb(self.ciph.encrypt, 16, hexdec(self.plaintext), self.iv)),
+ ciphtext,
+ )
+ self.assertSequenceEqual(
+ hexenc(ofb(self.ciph.encrypt, 16, hexdec(ciphtext), self.iv)),
+ self.plaintext,
+ )
+
+ def test_ofb_symmetric(self):
+ for _ in range(100):
+ pt = urandom(randint(0, 16 * 2))
+ iv = urandom(16 * 2)
+ ciph = GOST3412Kuz(urandom(32))
+ ct = ofb(ciph.encrypt, 16, pt, iv)
+ self.assertSequenceEqual(ofb(ciph.encrypt, 16, ct, iv), pt)
+
+ def test_cbc_vectors(self):
+ ciphtext = ""
+ ciphtext += "689972d4a085fa4d90e52e3d6d7dcc27"
+ ciphtext += "2826e661b478eca6af1e8e448d5ea5ac"
+ ciphtext += "fe7babf1e91999e85640e8b0f49d90d0"
+ ciphtext += "167688065a895c631a2d9a1560b63970"
+ self.assertSequenceEqual(
+ hexenc(cbc_encrypt(self.ciph.encrypt, 16, hexdec(self.plaintext), self.iv)),
+ ciphtext,
+ )
+ self.assertSequenceEqual(
+ hexenc(cbc_decrypt(self.ciph.decrypt, 16, hexdec(ciphtext), self.iv)),
+ self.plaintext,
+ )
+
+ def test_cbc_symmetric(self):
+ for _ in range(100):
+ pt = pad2(urandom(randint(0, 16 * 2)), 16)
+ iv = urandom(16 * 2)
+ ciph = GOST3412Kuz(urandom(32))
+ ct = cbc_encrypt(ciph.encrypt, 16, pt, iv)
+ self.assertSequenceEqual(cbc_decrypt(ciph.decrypt, 16, ct, iv), pt)
+
+ def test_cfb_vectors(self):
+ ciphtext = ""
+ ciphtext += "81800a59b1842b24ff1f795e897abd95"
+ ciphtext += "ed5b47a7048cfab48fb521369d9326bf"
+ ciphtext += "79f2a8eb5cc68d38842d264e97a238b5"
+ ciphtext += "4ffebecd4e922de6c75bd9dd44fbf4d1"
+ self.assertSequenceEqual(
+ hexenc(cfb_encrypt(self.ciph.encrypt, 16, hexdec(self.plaintext), self.iv)),
+ ciphtext,
+ )
+ self.assertSequenceEqual(
+ hexenc(cfb_decrypt(self.ciph.encrypt, 16, hexdec(ciphtext), self.iv)),
+ self.plaintext,
+ )
+
+ def test_cfb_symmetric(self):
+ for _ in range(100):
+ pt = urandom(randint(0, 16 * 2))
+ iv = urandom(16 * 2)
+ ciph = GOST3412Kuz(urandom(32))
+ ct = cfb_encrypt(ciph.encrypt, 16, pt, iv)
+ self.assertSequenceEqual(cfb_decrypt(ciph.encrypt, 16, ct, iv), pt)
+
+ def test_mac_vectors(self):
+ k1, k2 = _mac_ks(self.ciph.encrypt, 16)
+ self.assertSequenceEqual(hexenc(k1), "297d82bc4d39e3ca0de0573298151dc7")
+ self.assertSequenceEqual(hexenc(k2), "52fb05789a73c7941bc0ae65302a3b8e")
+ self.assertSequenceEqual(
+ hexenc(mac(self.ciph.encrypt, 16, hexdec(self.plaintext))[:8]),
+ "336f4d296059fbe3",
+ )
+
+ def test_mac_applies(self):
+ for _ in range(100):
+ data = urandom(randint(0, 16 * 2))
+ ciph = GOST3412Kuz(urandom(32))
+ mac(ciph.encrypt, 16, data)
(using GOST algorithms with X.509) compatibility helpers
@item GOST R 34.12-2015 128-bit block cipher Кузнечик (Kuznechik)
(@url{https://tools.ietf.org/html/rfc7801.html, RFC 7801})
-@item GOST R 34.13-2015 padding methods
+@item GOST R 34.13-2015 padding methods and block cipher modes of operation
+ (ECB, CTR, OFB, CBC, CFB, MAC)
@item PEP247-compatible hash/MAC functions
@end itemize
@unnumbered News
@table @strong
+@item 3.2
+34.13-2015 block cipher modes of operation implementations.
+
@item 3.1
Fixed mypy stubs related to PEP247-successors.