README | 2 ++ doc/NEWS | 4 ++++ doc/index | 2 ++ pygost/sespake.py | 213 +++++++++++++++++++++++++++++++++++++++++++++++++++++ pygost/test_sespake.py | 680 +++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/README b/README index 0e9aee070cfd1fd2ec09cb8f48fc9b373ec921e21bfc5d1e150f6c62bf238ebe..7314a4535592c04dc4f2c128aa89a74b52f712128a498dfd9947162d5f2a762c 100644 --- a/README +++ b/README @@ -24,6 +24,8 @@ * GOST R 34.12-2015 64-bit block cipher Магма (Magma) * GOST R 34.13-2015 padding methods and block cipher modes of operation (ECB, CTR, OFB, CBC, CFB, MAC), ISO 10126 padding * MGM AEAD mode for 64 and 128 bit ciphers (RFC 9058) +* SESPAKE password-authenticated key exchange protocol + (RFC 8133, ТК26 Р 50.1.115-2016) * CTR-ACPKM, OMAC-ACPKM-Master modes of operation (ТК26 Р 1323565.1.017-2018) * KExp15/KImp15 key export/import functions (ТК26 Р 1323565.1.017-2018) * KDF_GOSTR3411_2012_256, KDF_TREE_GOSTR3411_2012_256 (ТК26 Р 50.1.113-2016) diff --git a/doc/NEWS b/doc/NEWS index ff79f8a848fa1a7651c8c046aa7cdd77b8abdd80a928d59527024662e351e948..9978c555de73157d9b4b9625d7edf8a1c1bd24e0862dbe78664731d0e6d61a52 100644 --- a/doc/NEWS +++ b/doc/NEWS @@ -1,3 +1,7 @@ +A 5.14 +5.14 + Added SESPAKE (RFC 8133) algorithm support. + A 5.13 5.13 Ability to use masked 34.10 private keys. diff --git a/doc/index b/doc/index index 8e61084677e6e787ab6995637cc4f3153df54f12dd37767cbf6ed3096517be47..683ca62bfb642322ec92609b3d714f35701c58209d74b86da3757cf608641c5a 100644 --- a/doc/index +++ b/doc/index @@ -44,6 +44,8 @@ * GOST R 34.13-2015 padding methods and block cipher modes of operation (ECB, CTR, OFB, CBC, CFB, MAC), ISO 10126 padding * MGM AEAD mode for 64 and 128 bit ciphers => https://datatracker.ietf.org/doc/html/rfc9058.html RFC 9058 +* SESPAKE password-authenticated key exchange protocol (ТК26 Р 50.1.115-2016) + => https://datatracker.ietf.org/doc/html/rfc8133.html RFC 8133 * CTR-ACPKM, OMAC-ACPKM-Master modes of operation (ТК26 Р 1323565.1.017-2018) * KExp15/KImp15 key export/import functions (ТК26 Р 1323565.1.017-2018) * KDF_GOSTR3411_2012_256, KDF_TREE_GOSTR3411_2012_256 (ТК26 Р 50.1.113-2016) diff --git a/pygost/sespake.py b/pygost/sespake.py new file mode 100644 index 0000000000000000000000000000000000000000..3d014624bed9a98ae7e909af96e650cd29d778556840c9a7a96cb2555d1a4193 --- /dev/null +++ b/pygost/sespake.py @@ -0,0 +1,213 @@ +# coding: utf-8 +# PyGOST -- Pure Python GOST cryptographic functions library +# Copyright (C) 2015-2026 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, 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 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"""Security Evaluated Standardized Password-Authenticated Key Exchange + +:rfc:`8133` +https://eprint.iacr.org/2015/1237.pdf +https://habr.com/ru/articles/282043/ +""" + +from os import urandom +import hmac + +from pygost import gost34112012256 +from pygost import gost34112012512 +from pygost.pbkdf2 import pbkdf2 +from pygost.utils import bytes2long +from pygost.utils import hexdec +from pygost.utils import long2bytes + +Qs = { + "id-GostR3410-2001-CryptoPro-A-ParamSet": ( + bytes2long(hexdec("a69d51caf1a309fa9e9b66187759b0174c274e080356f23cfcbfe84d396ad7bb")), + bytes2long(hexdec("5d26f29ecc2e9ac0404dcf7986fa55fe94986362170f54b9616426a659786dac")), + ), + "id-GostR3410-2001-CryptoPro-B-ParamSet": ( + bytes2long(hexdec("3d715a874a4b17cb3b517893a9794a2b36c89d2ffc693f01ee4cc27e7f49e399")), + bytes2long(hexdec("1c5a641fcf7ce7e87cdf8cea38f3db3096eace2fad158384b53953365f4fe7fe")), + ), + "id-GostR3410-2001-CryptoPro-C-ParamSet": ( + bytes2long(hexdec("1e36383e43bb6cfa2917167d71b7b5dd3d6d462b43d7c64282ae67dfbec2559d")), + bytes2long(hexdec("137478a9f721c73932ea06b45cf72e37eb78a63f29a542e563c614650c8b6399")), + ), + "id-tc26-gost-3410-2012-512-paramSetA": ( + bytes2long(hexdec("2a17f8833a32795327478871b5c5e88aefb91126c64b4b8327289bea62559425d18198f133f400874328b220c74497cd240586cb249e158532cb8090776cd61c")), + bytes2long(hexdec("728f0c4a73b48da41ce928358fad26b47a6e094e9362bae82559f83cddc4ec3a4676bd3707edeaf4cd85e99695c64c241edc622be87dc0cf87f51f4367f723c5")), + ), + "id-tc26-gost-3410-2012-512-paramSetB": ( + bytes2long(hexdec("7e1fae8285e035bec244bef2d0e5ebf436633cf50e55231dea9c9cf21d4c8c33df85d4305de92971f0a4b4c07e00d87bdbc720eb66e49079285aaf12e0171149")), + bytes2long(hexdec("2cc89998b875d4463805ba0d858a196592db20ab161558ff2f4ef7a85725d20953967ae621afdeae89bb77c83a2528ef6fce02f68bda4679d7f2704947dbc408")), + ), + "id-tc26-gost-3410-2012-256-paramSetA": ( + bytes2long(hexdec("b51adf93a40ab15792164fad3352f95b66369eb2a4ef5efae32829320363350e")), + bytes2long(hexdec("74a358cc08593612f5955d249c96afb7e8b0bb6d8bd2bbe491046650d822be18")), + # U: ebe97afffe0d0f88b8b0114b8de430ac2b34564e4420af24728e7305bc48aeaa + # V: 828f2dcf8f06612b4fea4da72ca509c0f76dd37df424ea22bfa6f4f65748c1e4 + ), + "id-tc26-gost-3410-2012-512-paramSetC": ( + bytes2long(hexdec("489c91784e02e98f19a803abca319917f37689e5a18965251ce2ff4e8d8b298f5ba7470f9e0e713487f96f4a8397b3d09a270c9d367eb5e0e6561adeeb51581d")), + bytes2long(hexdec("684ea885aca64eaf1b3fee36c0852a3be3bd8011b0ef18e203ff87028d6eb5db2c144a0dcc71276542bfd72ca2a43fa4f4939da66d9a60793c704a8c94e16f18")), + # U: 3a3496f97e96b3849a4fa7db60fd93858bde89958e4beebd05a6b3214216b37c9d9a560076e7ea59714828b18fbfef996ffc98bf3dc9f2d3cb0ed36a0d6ace88 + # V: 52d884c8bf0ad6c5f7b3973e32a668daa1f1ed092eff138dae6203b2ccdec56147464d35fec4b727b2480eb143074712c76550c7a54ff3ea26f70059480dcb50 + ), +} + + +class A: + def __init__( + self, + curve, + q, + pw, + salt, + idA, + idB, + idAlg=b"", + ind=b"\x01", + fIters=2000, + fpw=None, + alpha=None, + ): + self.curve = curve + self.idA = idA + self.idAlg = idAlg + self.salt = salt + self.ind = ind + self.idB = idB + if fpw is None: + fpw = pbkdf2(gost34112012512.new, pw, salt, fIters, 64) + self.qpw = self.curve.exp( + bytes2long(fpw[:self.curve.point_size][::-1]), q[0], q[1]) + self.alpha = ( + bytes2long(urandom(self.curve.point_size)) % self.curve.q + ) if alpha is None else alpha + alphaP = self.curve.exp(self.alpha) + tmp = self.curve.exp(self.curve.q-1, self.qpw[0], self.qpw[1]) + self.u1 = self.curve._add(alphaP[0], alphaP[1], tmp[0], tmp[1]) + + def ka(self, u2, dataA=b""): + if not self.curve.contains(u2): + raise ValueError("u2 is not on curve") + self.u2 = u2 + self.dataA = dataA + tmp = self.curve.exp(self.curve.q-1, self.qpw[0], self.qpw[1]) + qA = self.curve._add(self.u2[0], self.u2[1], tmp[0], tmp[1]) + # TODO: check zero point + tmp = self.curve.exp( + (self.curve.cofactor * self.alpha) % self.curve.q, qA[0], qA[1]) + self.k = gost34112012256.new( + long2bytes(tmp[0], self.curve.point_size)[::-1] + + long2bytes(tmp[1], self.curve.point_size)[::-1] + ).digest() + u1 = ( + long2bytes(self.u1[0], self.curve.point_size)[::-1] + + long2bytes(self.u1[1], self.curve.point_size)[::-1] + ) + u2 = ( + long2bytes(self.u2[0], self.curve.point_size)[::-1] + + long2bytes(self.u2[1], self.curve.point_size)[::-1] + ) + macA = hmac.new(key=self.k, msg=b"".join(( + b"\x01", self.idA, self.ind, self.salt, u1, u2, self.idAlg, + self.dataA, + )), digestmod=gost34112012256).digest() + return macA + + def kc(self, macB, dataB=b""): + u1 = ( + long2bytes(self.u1[0], self.curve.point_size)[::-1] + + long2bytes(self.u1[1], self.curve.point_size)[::-1] + ) + u2 = ( + long2bytes(self.u2[0], self.curve.point_size)[::-1] + + long2bytes(self.u2[1], self.curve.point_size)[::-1] + ) + _macB = hmac.new(key=self.k, msg=b"".join(( + b"\x02", self.idB, self.ind, self.salt, u1, u2, self.idAlg, + self.dataA, dataB, + )), digestmod=gost34112012256).digest() + if not hmac.compare_digest(_macB, macB): + raise ValueError("wrong macB") + + +class B: + def __init__( + self, + curve, + q, + pw, + idA, + idB, + idAlg=b"", + ind=b"\x01", + salt=None, + saltLen=16, + fIters=2000, + fpw=None, + ): + self.curve = curve + self.q = q + self.idA = idA + self.idB = idB + self.idAlg = idAlg + self.ind = ind + if salt is None: + salt = urandom(saltLen) + self.salt = salt + if fpw is None: + fpw = pbkdf2(gost34112012512.new, pw, self.salt, fIters, 64) + self.fpw = fpw + self.qpw = self.curve.exp( + bytes2long(fpw[:self.curve.point_size][::-1]), q[0], q[1]) + + def ka(self, u1, beta=None): + if not self.curve.contains(u1): + raise ValueError("u1 is not on curve") + self.u1 = u1 + qB = self.curve._add(self.u1[0], self.u1[1], self.qpw[0], self.qpw[1]) + # TODO: check zero point + if beta is None: + beta = bytes2long(urandom(self.curve.point_size)) % self.curve.q + tmp = self.curve.exp((self.curve.cofactor * beta) % self.curve.q, qB[0], qB[1]) + self.k = gost34112012256.new( + long2bytes(tmp[0], self.curve.point_size)[::-1] + + long2bytes(tmp[1], self.curve.point_size)[::-1] + ).digest() + betaP = self.curve.exp(beta) + self.u2 = self.curve._add(betaP[0], betaP[1], self.qpw[0], self.qpw[1]) + return self.u2 + + def kc(self, macA, dataA=b"", dataB=b""): + u1 = ( + long2bytes(self.u1[0], self.curve.point_size)[::-1] + + long2bytes(self.u1[1], self.curve.point_size)[::-1] + ) + u2 = ( + long2bytes(self.u2[0], self.curve.point_size)[::-1] + + long2bytes(self.u2[1], self.curve.point_size)[::-1] + ) + _macA = hmac.new(key=self.k, msg=b"".join(( + b"\x01", self.idA, self.ind, self.salt, u1, u2, self.idAlg, + dataA, + )), digestmod=gost34112012256).digest() + if not hmac.compare_digest(_macA, macA): + raise ValueError("wrong macA") + self.dataA = dataA + macB = hmac.new(key=self.k, msg=b"".join(( + b"\x02", self.idB, self.ind, self.salt, u1, u2, self.idAlg, + self.dataA, dataB, + )), digestmod=gost34112012256).digest() + return macB diff --git a/pygost/test_sespake.py b/pygost/test_sespake.py new file mode 100644 index 0000000000000000000000000000000000000000..7154c0a4520ae3e804c971110fc48377c046086e7afab96904e1ff85f541cd30 --- /dev/null +++ b/pygost/test_sespake.py @@ -0,0 +1,680 @@ +# coding: utf-8 +# PyGOST -- Pure Python GOST cryptographic functions library +# Copyright (C) 2015-2026 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, 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 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from os import urandom +from random import randint +from unittest import TestCase +import hmac + +from pygost import gost34112012256 +from pygost import gost34112012512 +from pygost import sespake +from pygost.gost3410 import CURVES +from pygost.pbkdf2 import pbkdf2 +from pygost.utils import bytes2long +from pygost.utils import hexdec +from pygost.utils import hexenc +from pygost.utils import long2bytes + + +class TestQs(TestCase): + def runTest(self): + """Test RFC's Qs generation code results + + Calculation code is taken from RFC too. Its style is altered. + Ported to Python3. Native Streebog and utilities are used. + """ + def XGCD(a, b): + """XGCD(a,b) returns a list of form [g,x,y], where g is GCD(a,b) and + x,y satisfy the equation g = ax + by.""" + a1 = 1 + b1 = 0 + a2 = 0 + b2 = 1 + aneg = 1 + bneg = 1 + swap = False + if a < 0: + a = -a + aneg=-1 + if b < 0: + b = -b + bneg=-1 + if b > a: + swap = True + a, b = b, a + while True: + quot = -(a // b) + a = a % b + a1 = a1 + quot*a2 + b1 = b1 + quot*b2 + if a == 0: + if swap: + return (b, b2*bneg, a2*aneg) + return (b, a2*aneg, b2*bneg) + quot = -(b // a) + b = b % a + a2 = a2 + quot*a1 + b2 = b2 + quot*b1 + if b == 0: + if swap: + return (a, b1*bneg, a1*aneg) + return (a, a1*aneg, b1*bneg) + + def getMultByMask(elems, mask): + n = len(elems) + r = 1 + for i in range(n): + if mask & 1: + r *= elems[n - 1 - i] + mask = mask >> 1 + return r + + def subF(P, other, p): + return (P - other) % p + + def divF(P, other, p): + return mulF(P, invF(other, p), p) + + def addF(P, other, p): + return (P + other) % p + + def mulF(P, other, p): + return (P * other) % p + + def invF(R, p): + assert (R != 0) + return XGCD(R, p)[1] % p + + def negF(R, p): + return (-R) % p + + def powF(R, m, p): + assert R is not None + if m == 0: + assert R != 0 + return 1 + if m < 0: + t = invF(R, p) + return powF(t, (-m), p) + i = m.bit_length() - 1 + r = 1 + while i > 0: + if (m >> i) & 1: + r = (r * R) % p + r = (r * r) % p + i -= 1 + if m & 1: + r = (r * R) % p + return r + + def add(Px, Py, Qx, Qy, p, a, b): + if (Qx is None) and (Qy is None): + return (Px, Py) + if (Px is None) and (Py is None): + return (Qx, Qy) + if (Px == Qx) and (Py == negF(Qy, p)): + return (None, None) + if (Px == Qx) and (Py == Qy): + assert Py != 0 + return duplicate(Px, Py, p, a) + l = divF(subF(Qy, Py, p), subF(Qx, Px, p), p) + resX = subF(subF(powF(l, 2, p), Px, p), Qx, p) + resY = subF(mulF(l, subF(Px, resX, p), p), Py, p) + return (resX, resY) + + def duplicate(Px, Py, p, a): + if (Px is None) and (Py is None): + return (None, None) + if Py == 0: + return (None, None) + l = divF(addF(mulF(powF(Px, 2, p), 3, p), a, p), mulF(Py, 2, p), p) + resX = subF(powF(l, 2, p), mulF(Px, 2, p), p) + resY = subF(mulF(l, subF(Px, resX, p), p), Py, p) + return (resX, resY) + + def mul(Px, Py, s, p, a, b): + assert (Px is not None) and (Py is not None) + X = Px + Y = Py + i = s.bit_length() - 1 + resX = None + resY = None + while i > 0: + if (s >> i) & 1: + resX, resY = add(resX, resY, X, Y, p, a, b) + resX, resY = duplicate(resX, resY, p, a) + i -= 1 + if s & 1: + resX, resY = add(resX, resY, X, Y, p, a, b) + return (resX, resY) + + def Ord(Px, Py, m, q, p, a, b): + assert (Px is not None) and (Py is not None) + assert (m is not None) and (q is not None) + assert mul(Px, Py, m, p, a, b) == (None, None) + X = Px + Y = Py + r = m + for mask in range(1 << len(q)): + t = getMultByMask(q, mask) + Rx, Ry = mul(X, Y, t, p, a, b) + if (Rx is None) and (Ry is None): + r = min(r, t) + return r + + def isQuadraticResidue(R, p): + if R == 0: + assert False + temp = powF(R, ((p - 1) // 2), p) + if temp == (p - 1): + return False + assert temp == 1 + return True + + def getRandomQuadraticNonresidue(p): + from random import randint + r = (randint(2, p - 1)) % p + while isQuadraticResidue(r, p): + r = (randint(2, p - 1)) % p + return r + + def ModSqrt(R, p): + assert R is not None + assert isQuadraticResidue(R, p) + if p % 4 == 3: + res = powF(R, (p + 1) // 4, p) + if powF(res, 2, p) != R: + res = None + return (res, negF(res, p)) + ainvF = invF(R, p) + s = p - 1 + alpha = 0 + while (s % 2) == 0: + alpha += 1 + s = s // 2 + b = powF(getRandomQuadraticNonresidue(p), s, p) + r = powF(R, (s + 1) // 2, p) + bj = 1 + for k in range(0, alpha - 1): # alpha >= 2 because p % 4 = 1 + d = 2 ** (alpha - k - 2) + x = powF(mulF(powF(mulF(bj, r, p), 2, p), ainvF, p), d, p) + if x != 1: + bj = mulF(bj, powF(b, (2 ** k), p), p) + res = mulF(bj, r, p) + return (res, negF(res, p)) + + def generateQs(p, point_size, a, b, q, cofactor, Px, Py): + seed = 0 + while True: + hashSrc = ( + long2bytes(Px, point_size)[::-1] + + long2bytes(Py, point_size)[::-1] + + long2bytes(seed, 4)[::-1] + ) + if point_size == 32: + QxRaw = gost34112012256.new(hashSrc).digest() + else: + QxRaw = gost34112012512.new(hashSrc).digest() + Qx = bytes2long(QxRaw[::-1]) % p + R = addF(addF(powF(Qx, 3, p), mulF(Qx, a, p), p), b, p) + if (R == 0) or not isQuadraticResidue( R, p ): + seed += 1 + continue + Qy = min(ModSqrt(R, p)) + orderDivisors = (q,) if (cofactor == 1) else (2, 2, q) + if cofactor * Ord( + Qx, Qy, cofactor*q, orderDivisors, p, a, b + ) == cofactor*q: + yield ((Qx, Qy), seed) + seed += 1 + + seeds = { + "id-GostR3410-2001-CryptoPro-A-ParamSet": 0x01, + "id-GostR3410-2001-CryptoPro-B-ParamSet": 0x00, + "id-GostR3410-2001-CryptoPro-C-ParamSet": 0x06, + "id-tc26-gost-3410-2012-512-paramSetA": 0x01, + "id-tc26-gost-3410-2012-512-paramSetB": 0x00, + "id-tc26-gost-3410-2012-256-paramSetA": 0x01, + "id-tc26-gost-3410-2012-512-paramSetC": 0x13, + } + for name, (x, y) in sespake.Qs.items(): + curve = CURVES[name] + (qx, qy), seed = next(generateQs( + curve.p, + curve.point_size, + curve.a, + curve.b, + curve.q, + curve.cofactor, + curve.x, + curve.y, + )) + self.assertEqual(qx, x) + self.assertEqual(qy, y) + self.assertEqual(seed, seeds[name]) + + +class TestSymmetric(TestCase): + def _test(self, curve, q): + for i in range(10): + pw = urandom(randint(4, 8)) + idA = urandom(randint(0, 4)) + idB = urandom(randint(0, 4)) + idAlg = urandom(randint(0, 4)) + ind = urandom(1) + b = sespake.B(curve, q, pw, idA=idA, idB=idB, idAlg=idAlg, ind=ind, fIters=2) + a = sespake.A(curve, q, pw, b.salt, idA, idB, idAlg, ind, fIters=2) + u2 = b.ka(a.u1) + macA = a.ka(u2) + macB = b.kc(macA) + a.kc(macB) + self.assertEqual(a.k, b.k) + + def test_256A(self): + curveName = "id-tc26-gost-3410-2012-256-paramSetA" + self._test(CURVES[curveName], sespake.Qs[curveName]) + + def test_512C(self): + curveName = "id-tc26-gost-3410-2012-512-paramSetC" + self._test(CURVES[curveName], sespake.Qs[curveName]) + + +class TestExamples(TestCase): + fpw = hexdec("bd04673f7149b18e98155bd1e2724e71d0099aa25174f792d3326c6f181270671c6213e3930efdda26451792c6208122ee60d200520d695dfd9f5f0fd5aba702") + salt = hexdec("2923be84e16cd6ae529049f1f1bbe9eb") + idA = 4 * b"\x00" + idB = 4 * b"\x00" + + def test_fpw(self): + fpw = pbkdf2(gost34112012512.new, b"123456", self.salt, 2000, 64) + self.assertSequenceEqual(fpw, self.fpw) + + def _test( + self, + curveName, + qpwExpected, + alpha, + alphaPExpected, + u1Expected, + beta, + kBSrcExpected, + kBExpected, + betaPExpected, + u2Expected, + macAExpected, + macBExpected, + ): + curve = CURVES[curveName] + q = sespake.Qs[curveName] + qpw = curve.exp(bytes2long(self.fpw[:curve.point_size][::-1]), q[0], q[1]) + self.assertSequenceEqual(hexenc(long2bytes(qpw[0])), qpwExpected[0]) + self.assertSequenceEqual(hexenc(long2bytes(qpw[1])), qpwExpected[1]) + + alphaP = curve.exp(alpha) + self.assertSequenceEqual(hexenc(long2bytes(alphaP[0])), alphaPExpected[0]) + self.assertSequenceEqual(hexenc(long2bytes(alphaP[1])), alphaPExpected[1]) + tmp = curve.exp(curve.q-1, qpw[0], qpw[1]) + u1 = curve._add(alphaP[0], alphaP[1], tmp[0], tmp[1]) + self.assertSequenceEqual(hexenc(long2bytes(u1[0])), u1Expected[0]) + self.assertSequenceEqual(hexenc(long2bytes(u1[1])), u1Expected[1]) + + self.assertTrue(curve.contains(u1)) + qB = curve._add(u1[0], u1[1], qpw[0], qpw[1]) + self.assertEqual(qB, alphaP) + tmp = curve.exp((curve.cofactor * beta) % curve.q, qB[0], qB[1]) + self.assertSequenceEqual(hexenc(long2bytes(tmp[0])[::-1]), kBSrcExpected[0]) + self.assertSequenceEqual(hexenc(long2bytes(tmp[1])[::-1]), kBSrcExpected[1]) + kB = gost34112012256.new( + long2bytes(tmp[0], curve.point_size)[::-1] + + long2bytes(tmp[1], curve.point_size)[::-1] + ).digest() + self.assertSequenceEqual(hexenc(kB), kBExpected) + betaP = curve.exp(beta) + self.assertSequenceEqual(hexenc(long2bytes(betaP[0])), betaPExpected[0]) + self.assertSequenceEqual(hexenc(long2bytes(betaP[1])), betaPExpected[1]) + u2 = curve._add(betaP[0], betaP[1], qpw[0], qpw[1]) + self.assertSequenceEqual(hexenc(long2bytes(u2[0])), u2Expected[0]) + self.assertSequenceEqual(hexenc(long2bytes(u2[1])), u2Expected[1]) + + self.assertTrue(curve.contains(u2)) + tmp = curve.exp(curve.q-1, qpw[0], qpw[1]) + qA = curve._add(u2[0], u2[1], tmp[0], tmp[1]) + tmp = curve.exp((curve.cofactor * alpha) % curve.q, qA[0], qA[1]) + kA = gost34112012256.new( + long2bytes(tmp[0], curve.point_size)[::-1] + + long2bytes(tmp[1], curve.point_size)[::-1] + ).digest() + self.assertEqual(kA, kB) + + macA = hmac.new( + key=kA, + msg=b"".join(( + b"\x01", self.idA, b"\x01", self.salt, + long2bytes(u1[0], curve.point_size)[::-1], + long2bytes(u1[1], curve.point_size)[::-1], + long2bytes(u2[0], curve.point_size)[::-1], + long2bytes(u2[1], curve.point_size)[::-1], + )), + digestmod=gost34112012256, + ).digest() + self.assertSequenceEqual(hexenc(macA), macAExpected) + + macB = hmac.new( + key=kB, + msg=b"".join(( + b"\x02", self.idB, b"\x01", self.salt, + long2bytes(u1[0], curve.point_size)[::-1], + long2bytes(u1[1], curve.point_size)[::-1], + long2bytes(u2[0], curve.point_size)[::-1], + long2bytes(u2[1], curve.point_size)[::-1], + )), + digestmod=gost34112012256, + ).digest() + self.assertSequenceEqual(hexenc(macB), macBExpected) + self._test_lib( + curveName, + alpha, + beta, + u1Expected, + u2Expected, + macAExpected, + macBExpected, + kBExpected, + ) + + def _test_lib( + self, + curveName, + alpha, + beta, + u1Expected, + u2Expected, + macAExpected, + macBExpected, + kExpected, + ): + curve = CURVES[curveName] + q = sespake.Qs[curveName] + a = sespake.A( + curve, + q, + b"cached", + self.salt, + self.idA, + self.idB, + fpw=self.fpw, + alpha=alpha, + ) + self.assertSequenceEqual(hexenc(long2bytes(a.u1[0])), u1Expected[0]) + self.assertSequenceEqual(hexenc(long2bytes(a.u1[1])), u1Expected[1]) + b = sespake.B( + curve, + q, + b"cached", + idA=self.idA, + idB=self.idB, + salt=self.salt, + fpw=self.fpw, + ) + u2 = b.ka(a.u1, beta=beta) + self.assertSequenceEqual(hexenc(long2bytes(u2[0])), u2Expected[0]) + self.assertSequenceEqual(hexenc(long2bytes(u2[1])), u2Expected[1]) + macA = a.ka(u2) + self.assertSequenceEqual(hexenc(macA), macAExpected) + macB = b.kc(macA) + self.assertSequenceEqual(hexenc(macB), macBExpected) + a.kc(macB) + self.assertEqual(a.k, b.k) + self.assertSequenceEqual(hexenc(a.k), kExpected) + + def test_256B(self): + self._test( + "id-GostR3410-2001-CryptoPro-A-ParamSet", + qpwExpected=( + "59495655d1e7c7424c622485f575ccf121f3122d274101e8ab734cc9c9a9b45e", + "48d1c311d33c9b701f3b03618562a4a07a044e3af31e3999e67b487778b53c62", + ), + alpha=0x1f2538097d5a031fa68bbb43c84d12b3de47b7061c0d5e24993e0c873cdba6b3, + alphaPExpected=( + "bbc77cf42dc1e62d06227935379b4aa4d14fea4f565ddf4cb4fa4d31579f9676", + "8e16604a4afdf28246684d4996274781f6cb80abbba1414c1513ec988509dabf", + ), + u1Expected=( + "204f564383b2a76081b907f3fca8795e806be2c2ed228730b5b9e37074229e8d", + "e84f9e442c61dde37b601a7f37e7ca11c56183fa071dfa9320ede3e7521f9d41", + ), + beta=0xdc497d9ef6324912fd367840ee509a2032aedb1c0a890d133b45f596fccbd45d, + kBSrcExpected=( + "2e01a3d84fdb7e947bb8929be9363df5f725d6401aa559d41a6724f8d5f18e2c", + "a0dba93105cddaf4bfaea3906fdd719dbeb297b6a17f4fbd96dcc723ea3472a9", + ), + kBExpected="1a626554921dc2e92b4dd8d67dbe5a5662e56299373f06799535ad26094ecaa3", + betaPExpected=( + "6097341c1be388e83e7ca2df47fab86e2271fd942e5b7b2eb2409e49f742bc29", + "c81aa48bdb4ca6fa0ef18b9788ae25fe30857aa681b3942217f9fed151bab7d0", + ), + u2Expected=( + "dc137a2f1d4a35aebc0ecbf6d3486def8480bfdc752a86dd4f207d7d1910e22d", + "7532f0ce99dcc772a4d77861dae57c138f07ae304a727907fb0aafdb624ed572", + ), + macAExpected="237a03c35f4917ce86b3589445f11e1a6f108b2fdd0aa9e810664b255960b579", + macBExpected="9ee0e8733b069850804d9798731dcd1cffe87a3b151f0ae83ea96afb4ffc31e4", + ) + + def test_256C(self): + self._test( + "id-GostR3410-2001-CryptoPro-B-ParamSet", + qpwExpected=( + "6dc2ae26bc691fca5a73d9c452790d15e34ba5404d92955b914c8d2662abb985", + "3b02aaa9dd65ae30c335ced12f3154bbac059f66b088306747453edf6e5db077", + ), + alpha=0x499d72b90299cab0da1f8be19d9122f622a13b32b730c46bd0664044f2144fad, + alphaPExpected=( + "61d6f916db717222d74877f179f7ebef7cd4d24d8c1f523c048e34a1df30f8dd", + "3ec48863049cfcfe662904082e78503f4973a4e105e2f1b18c69a5e7fb209000", + ), + u1Expected=( + "21f5437af33d2a1171a070226b4ae82d3765cd0eebff1ecefe158ebc50c63ab1", + "5c9553b5d11aaaece738ad9a9f8cb4c100ad4fa5e089d3cbccea8c0172eb7ecc", + ), + beta=0x0f69ff614957ef83668edc2d7ed614be76f7b253db23c5cc9c52bf7df8f4669d, + kBSrcExpected=( + "50140a5ded3343efc8257b79e646d9f0df43828c04919bd460c97ad14ba3a86b", + "00c406b5744d8eb149dc8e7fc84064d85320253e57a9b6b13d0d38fea8ee5e0a", + ), + kBExpected="a626de01b1680ff7513009122bcee1896883394f96030172455c9ae060cce44a", + betaPExpected=( + "33bc6f7e9c0ba10cfb2b72546c327171295508ea97f8c8ba9f890f2478ab4d6c", + "75d57b396c396f492f057e9222ccc686437a2aad464e452ef426fc8eeed1a4a6", + ), + u2Expected=( + "089ddee718ee8a224a7f37e22cffd731c25fcbf58860364ee322412cdcef99ac", + "0ece03d4e395a6354c571871bef425a532d5d463b0f8fd427f91a43e20cda55c", + ), + macAExpected="b91f43902afa90d3e5c691cbdc438a1ebf547f4c2cb41443cc38797be247a7d0", + macBExpected="79d55483fd99b12bcca5edc6bbe1d7b915ce0451b0891e775d4a61cb16e33fcc", + ) + + def test_256D(self): + self._test( + "id-GostR3410-2001-CryptoPro-C-ParamSet", + qpwExpected=( + "945821daf91e158b839939630655a3b21ff3e146d27041e86c05650eb3b46b59", + "3a0c2816ac97421fa0e879605f17f0c9c3eb734cff196937f6284438d70bdc48", + ), + alpha=0x3a54ac3f19ad9d0b1eac8acdcea70e581f1dac33d13feafd81e762378639c1a8, + alphaPExpected=( + "96b7f09c94d297c257a7da48364c0076e59e48d221cba604ae111ca3933b446a", + "54e4953d86b77ecceb578500931e822300f7e091f79592ca202a020d762c34a6", + ), + u1Expected=( + "81bbd6fca464d2e2404a66d786ce4a777e739a89aeb68c2dac99d53273b75387", + "6b6dbd922ea7e060998f8b230ab6ef07ad2ec86b2bf66391d82a30612eadd411", + ), + beta=0x448781782bf7c0e52a1dd9e6758fd3482d90d3cfccf42232cf357e59a4d49fd4, + kBSrcExpected=( + "16a12d88547e1c9006baa008e8cbecc9d16891edc836cfb75f8eb956fa761194", + "d28e25dad3818d163c494b059a8c70a5a1b88a7f80a2ee3549301846542c470b", + ), + kBExpected="be7e7e47b41116f2c77e3b8fce403072ca82450d65defc71a95649e4deeaecee", + betaPExpected=( + "4b9c0ab55a938121f282f48a2cc4396eb16e7e0068b495b0c1dd4667786a3eb7", + "223460aa8e09383e9df9844c5a0f2766484738e5b30128a171b69a77d9509b96", + ), + u2Expected=( + "2ed9b903254003a672e89ebebc9e31503726ad124bb5fc0a726ee0e6fcce323e", + "4cf5e1042190120391ec8db62fe25e9e26ec60fb0b78b242199839c295fcd022", + ), + macAExpected="d3b41ae2c9431136063e6d08a61be963bd5ed6a1fff937fa8b090a98e162bfed", + macBExpected="d6b39a4499bed3e04facf955502d16b2cb674a205fac3cd83d54ec2fd5fce258", + ) + + def test_512A(self): + self._test( + "id-tc26-gost-3410-2012-512-paramSetA", + qpwExpected=( + "0c0ab53d0e0a9c607cad758f558915a0a7dc5dc87b45e9a58fddf30ec3385960283e030cd322d9e46b070637785fd49d2cd711f46807a24c40af9a42c8e2d740", + "df93a8012b86d3a3d4f8a4d487da15fc739eb31b20b3b0e8c8c032aaf8072c6337cf7d5b404719e5b4407c41d9a3216a08ca69c271484e9ed72b8aaa52e28b8b", + ), + alpha=0x3ce54325db52fe798824aead11bb16fa766857d04a4af7d468672f16d90e7396046a46f815693e85b1ce5464da9270181f82333b0715057bbe8d61d400505f0e, + alphaPExpected=( + "b93093eb0fcc463239b7df276e09e592fcfc9b635504ea4531655d76a0a3078e2b4e51cfe2fa400cc5de9fbe369db204b3e8ed7edd85ee5cca654c1aed70e396", + "809770b8d910ea30bd2fa89736e91dc31815d2d9b31128077eedc371e9f69466f497dc64dd5b1fadc587f860ee256109138c4a9cd96b628e65a8f590520fc882", + ), + u1Expected=( + "e7510a9edd37b869566c81052e2515e1563fdfe79f1d782d6200f33c3cc2764d40d0070b73ad5a47bae9a8f2289c1b07dac26a1a2ff9d3ecb0a8a94a4f179f13", + "ba333b912570777b626a5337bc7f727952460eeba2775707fe4537372e902df5636080b25399751bf48fb154f3c2319a91857c23f39f89ef54a8f043853f82de", + ), + beta=0xb5c286a79aa8e97ec0e19bc1959a1d15f12f8c97870ba9d68cc12811a56a3bb11440610825796a49d468cdc9c2d02d76598a27973d5960c5f50bce28d8d345f4, + kBSrcExpected=( + "8459c20cb5c532416db928eb50c0520fb21b9cd39a4e7606b221be15ca1d02da0815dec44979c08c7d2307af247dda1f89ec812069f5d9cde306aff0bc3fd26e", + "d201b95352a25606b643e888302efc8d3e951e3eb4684adb5c057b8f8c89b6cc0deed100065b518a1c717f7682ff612bbc798ec7b2490fb7003f943387371c1d", + ), + kBExpected="5324def848b663cc26422f5e45eec34c51d24361b16560ca58a3d3284586cb7a", + betaPExpected=( + "238b38644e440452a99fa6b93d9fd7da0cb83c32d3c1e3cfe5df5c3eb0f9db91e588daedc849ea2fb867ae855a21b4077353c0794716a6480995113d8c20c7af", + "b2273d5734c1897f8d15a7008b862938c8c74ca7e877423d95243eb7ebd02fd2c456cf9fc956f078a59aa86f19dd1075e5167e4ed35208718ea93161c530ed14", + ), + u2Expected=( + "c33844126216e81b372001e77c1fe9c7547f9223cf7bb865c4472ec18be0c79a678cc5ae4028e3f3620cce355514f1e589f8a0c433ceafcbd2ee87884d953411", + "8b520d083aaf257e8a54ec90cbadbaf4feed2c2d868c82ff04fcbb9ef6f38e56f6baf9472d477414da7e36f538ed223d2e2ee02fae1a20a98c5a9fcf03b6f30d", + ), + macAExpected="e8ef9ea8f1e6b12668e58cd22dd8eec64a16710039faa6b603992220fafe5614", + macBExpected="61143460836b235cecd0b49b587ea45d513c3a38783f1c9d3b05970a956a55ba", + ) + + def test_512B(self): + self._test( + "id-tc26-gost-3410-2012-512-paramSetB", + qpwExpected=( + "7d03e65b8050d1e12cbb601a17b9273b0e728f5021cd47c8a4dd822e4627ba5f9c696286a2cdda9a065509866b4dededc4a118409604ad549f87a60afa621161", + "16037dad45421ec50b00d50bdc6ac3b85348bc1d3a2f85db27c3373580fef87c2c743b7ed30f22be22958044e716f93a61ca3213a361a2797a16a3ae62957377", + ), + alpha=0x715e893fa639bf341296e0623e6d29dadf26b163c278767a7982a989462a3863fe12aef8bd403d59c4dc4720570d4163db0805c7c10c4e818f9cb785b04b9997, + alphaPExpected=( + "10c479ea1c04d3c2c02b0576a9c42d96226ff033c1191436777f66916030d87d02fb93738ed7669d07619ffce7c1f3c4db5e5df49e2186d6fa1e2eb5767602b9", + "039f6044191404e707f26d59d979136a831cce43e1c5f0600d1ddf8f39d0ca3d52fbd943bf04ddced1aa2ce8f5ebd7487acdef239c07d015084d796784f35436", + ), + u1Expected=( + "45c05cce8290762f2470b719b4306d62b2911ceb144f7f72ef11d10498c7e921ff163fe72044b4e7332ad8cbec3c12117820f53a60762315bceb5bc6da5cf1e0", + "5be483e382d0f5f0748c4f6a5045d99e62755b5acc9554ec4a5b2093e121a2dd5c6066bc9ede39373ba19899208bb419e38b39bbdedeb0b09a5caaeaa984d02e", + ), + beta=0x30fa8c2b4146c2dbbe82bed04d7378877e8c06753bd0a0ff71ebf2befe8da8f3dc0836468e2ce7c5c961281b6505140f8407413f03c2cb1d201ea1286ce30e6d, + kBSrcExpected=( + "3f0402e40a9d5963205bcdf4fd8977919bbaf480f8e4fbd1255aece6ed57264bd0a287984f59d10204b5f45e4d77f3cf8a63b31beb2df59f8af73c209cca8b50", + "b418d801e490ae133f04f4f3f4d8fe8e19646a1baf44d236fcc21b7f4d8fc6a1e29d6b69acceed4e62abb20dad78acf4feb0ed838ed91e9212aba389714e560c", + ), + kBExpected="d590e05ef5aece8b7cfbfc71be455f29a5cc666f85cdb17e7cc716c59ff170e9", + betaPExpected=( + "34c0149e7bb91ae377b02573fcc48af7bfb7b16deb8f9ce870f384688e3241a3a868588cc0ef4364cca67d17e3260cd82485c202adc76f895d5df673b1788e67", + "608e944929bd643569ed5189db871453f13333a1eaf82b2fe1be8100e775f13dd9925bd317b63bfaf05024d4a738852332b64501195c1b2ef789e34f23ddafc5", + ), + u2Expected=( + "0535f95463444c4594b5a2e14b35760491c670925060b4bebc97de3a3076d1a581f89026e04282b040925d9250201024aca4b2713569b6c3916a6f3344b840ad", + "40e6c2e55aec31e7bcb6ea0242857fc6dfb5409803edf4ca20141f72cc3c7988706e076765f4f004340e5294a7f8e53ba59cb67502f0044558c854a7d63fe900", + ), + macAExpected="de46bb4c8ce08a6ef3b8dfaccc1a39b08d8c27b6cb0fcf592386a648f4e5bd8c", + macBExpected="ecb11de2061c55f1d11459cb51ce31409999992fcaa1222fb14fceab96ee7aac", + ) + + def test_256A(self): + self._test( + "id-tc26-gost-3410-2012-256-paramSetA", + qpwExpected=( + "dbf99827078956812fa48c6e695df589def1d18a2d4d35a96d75bf6854237629", + "9fddd48bfbc57bee1da0cff282884f284d471b388893c48f5ecb02fc18d67589", + ), + alpha=0x147b72f6684fb8fd1b418a899f7dbecaf5fce60b13685baa95328654a7f0707f, + alphaPExpected=( + "33fbac14eae538275a769417829c431bd9fa622b6f02427ef55bd60ee6bc2888", + "22f2ebcf960a82e6cdb4042d3ddda511b2fba925383c2273d952ea2d406eae46", + ), + u1Expected=( + "e569ab544e3a13c41077de97d659a1b7a13f61ddd808b633a5621fe2583a2c43", + "a21a743a08f4d715661297ecd6f86553a808925bf34802bf7ec34c548a40b2c0", + ), + beta=0x30d5cfadaa0e31b405e6734c03ec4c5df0f02f4ba25c9a3b320ee6453567b4cb, + kBSrcExpected=( + "a339a0b89cef1a6ffd4ca128049e0684df4a9775b689a337841bf7d791207f35", + "118628f7288eaa0f7ec81da20a24ff1e6993c63d9dd26a90b74dd1a266280663", + ), + kBExpected="7df71ac327ed517d0de403e817c6204bc19165b9d1002b9f1088a6cda6eacf27", + betaPExpected=( + "2b2d89fab735433970564f2f28cfa1b57d640cb902bc6334a538f44155022cb2", + "10ef6a82eef1e70f942aa81d6b4ce5dec0ddb9447512962874870e6f2849a96f", + ), + u2Expected=( + "190d2f283f7e861065db53227d7fbdf429cebf93791262cb29569bdf63c86ca4", + "b3f1715721e9221897ccde046c9b843a8386dbf7818a112f15a02bc820ac8f6d", + ), + macAExpected="f929b61a3c833985b829f268557fa811009f820ab1a730b5aa334c3e6ba3177f", + macBExpected="a2928a5cf620bbc4900de403f7fc59a5e980b68be046d0b5d9b4ae6abfa80bd6", + ) + + def test_512C(self): + self._test( + "id-tc26-gost-3410-2012-512-paramSetC", + qpwExpected=( + "0185ae6271a81bb7f236a955f7caa26fb63849813c0287d96c83a15ae6b6a86467ab13b6d88ce8cd7dc2e5b97ff5f28fac2c108f2a3cf3db5515c9e6d7d210e8", + "ed0220f92ef771a71c64ecc77986db7c03d37b3e2ab3e83f32ce5e074a762ec08253c9e2102b87532661275c4b1d16d2789cdabc58acfdf7318de70ab64f09b8", + ), + alpha=0x332f930421d14cfe260042159f18e49fd5a54167e94108ad80b1de60b13de7999a34d611e63f3f870e5110247df8ec7466e648acf385e52ccb889abf491edff0, + alphaPExpected=( + "561655966d52952e805574f4281f1ed3a2d498932b00cba9decb42837f09835bffbfe2d84d6b6b242fe7b57f92e1a6f2413e12ddd6383e4437e13d72693469ad", + "f6b18328b2715bd7f4178615273a36135bc0bf62f7d8bb9f080164ad36470ad03660f51806c64c6691badef30f793720f8e3feaed631d6a54a4c372dcbf80e82", + ), + u1Expected=( + "40645b4b9a908d74def98886a336f98bae6ada4c1ac9b7594a33d5e4a16486c5533c7f3c5dd84797ab5b4340bfc70caf1011b69a01a715e5b9b5432d5151cbd7", + "267fbb18d0b79559d1875909f2a15f7b49ecd8ed166cf7f4fcd1f448915504835e80d52be8d34ada5b5e159cf52979b1bcfe8f5048dc443a0983aa19192b8407", + ), + beta=0x38481771e7d054f96212686b613881880bd8a6c89ddbc656178f014d2c093432a033ee10415f13a160d44c2ad61e6e2e05a7f7ec286bcea3ea4d4d53f8634fa2, + kBSrcExpected=( + "4f4d64b5d07008e9e685874f882c3e1e60a6675eed421fc234163fdeb44c6918b7bcceab88a0f3fb788da8db101851ff1a416822ba37c353cec4c5a52395b772", + "ac93c054e3f4055ced6ff0bee4a6a24ed68b86fefa70de4a2b16085142a4dff05d32ec7ddfe304f5c704fdfa060f64e9e832140025f392e50350770e3fb62cac", + ), + kBExpected="a08384a62f4be1ae4898fca36daa3faa451b3ec5b59ce375f89e929f4b13258c", + betaPExpected=( + "b7c5818687083433bc1aff61cb5ca79e38232025e0c1f123b8651e62173ce6873f3e6ffe7281c2e45f4f524f66b0c263616ed08fd210ac4355ca3292b51d71c3", + "497f14205dbdc89bddaf50520ed3b1429ad30777310186be5e68070f016a44e0c766db08e8ac23fbdfde6d675aa4df591eb18ba0d348df7aa40973a2f1dcfa55", + ), + u2Expected=( + "b772fd97d6fdec1da0771bc059b3e5adf9858311031eae5aec6a6ec8104b4105c45a6c65689a8ee636c687db62cc0afc9a48ca66e381286cc73f374c1dd8f445", + "c64f69425ffeb2995130e85a08edc3a686ec28ee6e8469f7f09bd3bcbdd843ac573578da6ba1cb3f5f069f205233853f06255c4b28586c9a1643537497b1018c", + ), + macAExpected="1263f2890e90ee426b9ba08ab9ea7f1fff26e1605cc65de296969115e5317687", + macBExpected="6dfd06045d6d97a0e419b00e0035b9d2e3ab098b7ca4ad525460fab62185aa57", + )