NEWS | 1 + README | 1 + pygost/gost341194.py | 35 +++++++++++++++++++++++++++++++++++ pygost/stubs/pygost/gost341194.pyi | 3 +++ pygost/test_gost341194.py | 47 +++++++---------------------------------------- diff --git a/NEWS b/NEWS index 0e9ad1a2cc331a8707f3bd45ad0ac86f8b0eb124f6525e3a0988bdb4a34f742a..ca918d7be7db794467884ec38b93890fd07f858d7d8b7982ff78fe0089b8e0b1 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,7 @@ * gost3410.prv_unmarshal, gost3410.pub_marshal, gost3410.pub_unmarshal helpers added, removing the need of x509 module at all * gost3410.verify expects (pubX, pubY) tuple, instead of two separate pubX, pubY arguments + * 34.11-94 based PBKDF2 function added 2.4: Fixed 34.13 mypy stub diff --git a/README b/README index 1d2a1a5bd0b42450c44978f2918d23c1e3853e564e0837f0519880a5f41f3936..a10f05fff6788863abfe2ad6428d4961be0fd299bbe9a2bcf42ad5d631ad8bfa 100644 --- a/README +++ b/README @@ -6,6 +6,7 @@ * GOST 28147-89 (RFC 5830) block cipher with ECB, CNT (CTR), CFB, MAC, CBC (RFC 4357) modes of operation * various 28147-89-related S-boxes included * GOST R 34.11-94 hash function (RFC 5831) +* GOST R 34.11-94 based PBKDF2 function * GOST R 34.11-2012 Стрибог (Streebog) hash function (RFC 6986) * GOST R 34.10-2001 (RFC 5832) public key signature function * GOST R 34.10-2012 (RFC 7091) public key signature function diff --git a/pygost/gost341194.py b/pygost/gost341194.py index 81810422d3d02721ba564609adaac626a41d91cc83e66159e71bf328733f88ab..68b3112d124f120d647e6b04d9b9456773770e3fdee4cbc3b2bec4afcc27c9de 100644 --- a/pygost/gost341194.py +++ b/pygost/gost341194.py @@ -29,8 +29,10 @@ from pygost.gost28147 import encrypt from pygost.gost28147 import ns2block from pygost.gost28147 import validate_sbox from pygost.iface import PEP247 +from pygost.utils import bytes2long from pygost.utils import hexdec from pygost.utils import hexenc +from pygost.utils import long2bytes from pygost.utils import strxor from pygost.utils import xrange @@ -183,3 +185,36 @@ def new(data=b"", sbox=DEFAULT_SBOX): return GOST341194(data, sbox) + + +# This implementation is based on Python 3.5.2 source code's one. +# PyGOST does not register itself in hashlib anyway, so use it instead. +def pbkdf2(password, salt, iterations, dklen): + """PBKDF2 implementation for GOST R 34.11-94 + + Based on http://tc26.ru/methods/containers_v1/Addition_to_PKCS5_v1_0.pdf + """ + inner = GOST341194(sbox="GostR3411_94_CryptoProParamSet") + outer = GOST341194(sbox="GostR3411_94_CryptoProParamSet") + password = password + b"\x00" * (inner.block_size - len(password)) + inner.update(strxor(password, len(password) * b"\x36")) + outer.update(strxor(password, len(password) * b"\x5C")) + + def prf(msg): + icpy = inner.copy() + ocpy = outer.copy() + icpy.update(msg) + ocpy.update(icpy.digest()) + return ocpy.digest() + + dkey = b'' + loop = 1 + while len(dkey) < dklen: + prev = prf(salt + long2bytes(loop, 4)) + rkey = bytes2long(prev) + for _ in xrange(iterations - 1): + prev = prf(prev) + rkey ^= bytes2long(prev) + loop += 1 + dkey += long2bytes(rkey, inner.digest_size) + return dkey[:dklen] diff --git a/pygost/stubs/pygost/gost341194.pyi b/pygost/stubs/pygost/gost341194.pyi index b1598518ce1212b86483369958fbe6989ebf2a28998ab31d30a76741e29ecd50..8b12b431f4c6f1898526d1a8e514cecd2262583fe8eb479475268d8ea78c4922 100644 --- a/pygost/stubs/pygost/gost341194.pyi +++ b/pygost/stubs/pygost/gost341194.pyi @@ -14,3 +14,6 @@ def hexdigest(self) -> str: ... def new(data: bytes=..., sbox: str=...) -> GOST341194: ... + + +def pbkdf2(password: bytes, salt: bytes, iterations: int, dklen: int) -> bytes: ... diff --git a/pygost/test_gost341194.py b/pygost/test_gost341194.py index d3ed2de0e140aa7a703dbc2d774222d3c1ac796d8751628c6d32353118421d59..60844d98a9334a112e6eeceba72216813f799098de9c0479671f97c98a133b53 100644 --- a/pygost/test_gost341194.py +++ b/pygost/test_gost341194.py @@ -21,11 +21,8 @@ import hmac from pygost import gost341194 from pygost.gost341194 import GOST341194 -from pygost.utils import bytes2long +from pygost.gost341194 import pbkdf2 from pygost.utils import hexenc -from pygost.utils import long2bytes -from pygost.utils import strxor -from pygost.utils import xrange class TestCopy(TestCase): @@ -153,67 +150,37 @@ "1c4ac7614691bbf427fa2316216be8f10d92edfd37cd1027514c1008f649c4e8", ) -# This implementation is based on Python 3.5.2 source code. -# PyGOST does not register itself in hashlib anyway, so use -# pbkdf2_hmac directly. -def pbkdf2_hmac(password, salt, iterations, dklen): - inner = GOST341194(sbox="GostR3411_94_CryptoProParamSet") - outer = GOST341194(sbox="GostR3411_94_CryptoProParamSet") - password = password + b'\x00' * (inner.block_size - len(password)) - inner.update(strxor(password, len(password) * b"\x36")) - outer.update(strxor(password, len(password) * b"\x5C")) - - def prf(msg): - icpy = inner.copy() - ocpy = outer.copy() - icpy.update(msg) - ocpy.update(icpy.digest()) - return ocpy.digest() - - dkey = b'' - loop = 1 - while len(dkey) < dklen: - prev = prf(salt + long2bytes(loop, 4)) - rkey = bytes2long(prev) - for _ in xrange(iterations - 1): - prev = prf(prev) - rkey ^= bytes2long(prev) - loop += 1 - dkey += long2bytes(rkey, inner.digest_size) - return dkey[:dklen] - - class TestPBKDF2(TestCase): """http://tc26.ru/methods/containers_v1/Addition_to_PKCS5_v1_0.pdf test vectors """ def test_1(self): self.assertEqual( - hexenc(pbkdf2_hmac(b"password", b"salt", 1, 32)), + hexenc(pbkdf2(b"password", b"salt", 1, 32)), "7314e7c04fb2e662c543674253f68bd0b73445d07f241bed872882da21662d58", ) def test_2(self): self.assertEqual( - hexenc(pbkdf2_hmac(b"password", b"salt", 2, 32)), + hexenc(pbkdf2(b"password", b"salt", 2, 32)), "990dfa2bd965639ba48b07b792775df79f2db34fef25f274378872fed7ed1bb3", ) def test_3(self): self.assertEqual( - hexenc(pbkdf2_hmac(b"password", b"salt", 4096, 32)), + hexenc(pbkdf2(b"password", b"salt", 4096, 32)), "1f1829a94bdff5be10d0aeb36af498e7a97467f3b31116a5a7c1afff9deadafe", ) @skip("it takes too long") def test_4(self): self.assertEqual( - hexenc(pbkdf2_hmac(b"password", b"salt", 16777216, 32)), + hexenc(pbkdf2(b"password", b"salt", 16777216, 32)), "a57ae5a6088396d120850c5c09de0a525100938a59b1b5c3f7810910d05fcd97", ) def test_5(self): self.assertEqual( - hexenc(pbkdf2_hmac( + hexenc(pbkdf2( b"passwordPASSWORDpassword", b"saltSALTsaltSALTsaltSALTsaltSALTsalt", 4096, @@ -224,7 +191,7 @@ ) def test_6(self): self.assertEqual( - hexenc(pbkdf2_hmac( + hexenc(pbkdf2( b"pass\x00word", b"sa\x00lt", 4096,