# coding: utf-8
# PyGOST -- Pure Python GOST cryptographic functions library
-# Copyright (C) 2015-2017 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2015-2024 Sergey Matveev <stargrave@stargrave.org>
#
# 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
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-""" GOST R 34.11-2012 (Streebog) hash function common files
+"""GOST R 34.11-2012 (Streebog) hash function common files
This is implementation of :rfc:`6986`. Most function and variable names are
taken according to specification's terminology.
from pygost.iface import PEP247
from pygost.utils import hexdec
from pygost.utils import strxor
-from pygost.utils import xrange # pylint: disable=redefined-builtin
+from pygost.utils import xrange
BLOCKSIZE = 64
-# pylint: disable=bad-whitespace,bad-continuation
Pi = bytearray((
252, 238, 221, 17, 207, 110, 49, 22, 251, 196, 250,
218, 35, 197, 4, 77, 233, 119, 240, 219, 147, 46,
6, 14, 22, 30, 38, 46, 54, 62,
7, 15, 23, 31, 39, 47, 55, 63,
)
-# pylint: disable=bad-whitespace,bad-continuation
C = [hexdec("".join(s))[::-1] for s in (
(
)]
+def _lcache():
+ cache = []
+ for byteN in xrange(8):
+ cache.append([0 for _ in xrange(256)])
+ for byteN in xrange(8):
+ for byteVal in xrange(256):
+ res64 = 0
+ val = byteVal
+ for bitN in xrange(8):
+ if val & 0x80 > 0:
+ res64 ^= A[(7 - byteN) * 8 + bitN]
+ val <<= 1
+ cache[byteN][byteVal] = res64
+ return cache
+
+
+# Trade memory for CPU for part of L() calculations
+LCache = _lcache()
+
+
def add512bit(a, b):
- """ Add two 512 integers
- """
- a = bytearray(a)
- b = bytearray(b)
- cb = 0
- res = bytearray(64)
- for i in range(64):
- cb = a[i] + b[i] + (cb >> 8)
- res[i] = cb & 0xff
- return res
+ a = int.from_bytes(a, "little")
+ b = int.from_bytes(b, "little")
+ r = (a + b) % (1 << 512)
+ return r.to_bytes(512 // 8, "little")
def g(n, hsh, msg):
def L(data):
res = []
for i in range(8):
- val = unpack("<Q", data[i * 8:i * 8 + 8])[0]
res64 = 0
- for j in range(BLOCKSIZE):
- if val & 0x8000000000000000:
- res64 ^= A[j]
- val <<= 1
+ for j in range(8):
+ res64 ^= LCache[j][data[8 * i + j]]
res.append(pack("<Q", res64))
return b"".join(res)
class GOST34112012(PEP247):
- """ GOST 34.11-2012 big-endian hash
+ """GOST 34.11-2012 big-endian hash
>>> m = GOST34112012(digest_size=32)
>>> m.update("foo")
:param digest_size: hash digest size to compute
:type digest_size: 32 or 64 bytes
"""
- self.data = data
self._digest_size = digest_size
+ self.hsh = BLOCKSIZE * (b"\x01" if digest_size == 32 else b"\x00")
+ self.chk = bytearray(BLOCKSIZE * b"\x00")
+ self.n = 0
+ self.buf = b""
+ self.update(data)
def copy(self):
- return GOST34112012(copy(self.data), self.digest_size)
+ obj = GOST34112012()
+ obj._digest_size = self._digest_size
+ obj.hsh = self.hsh
+ obj.chk = copy(self.chk)
+ obj.n = self.n
+ obj.buf = self.buf
+ return obj
@property
def digest_size(self):
return self._digest_size
+ def _update_block(self, block):
+ self.hsh = g(self.n, self.hsh, block)
+ self.chk = add512bit(self.chk, block)
+ self.n += 512
+
def update(self, data):
- """ Append data that has to be hashed
+ """Update state with the new data
"""
- self.data += data
+ if len(self.buf) > 0:
+ chunk_len = BLOCKSIZE - len(self.buf)
+ self.buf += data[:chunk_len]
+ data = data[chunk_len:]
+ if len(self.buf) == BLOCKSIZE:
+ self._update_block(self.buf)
+ self.buf = b""
+ while len(data) >= BLOCKSIZE:
+ self._update_block(data[:BLOCKSIZE])
+ data = data[BLOCKSIZE:]
+ self.buf += data
def digest(self):
- """ Get hash of the provided data
+ """Get hash of the provided data
"""
- hsh = BLOCKSIZE * (b"\x01" if self.digest_size == 32 else b"\x00")
- chk = bytearray(BLOCKSIZE * b"\x00")
- n = 0
- data = self.data
- for i in xrange(0, len(data) // BLOCKSIZE * BLOCKSIZE, BLOCKSIZE):
- block = data[i:i + BLOCKSIZE]
- hsh = g(n, hsh, block)
- chk = add512bit(chk, block)
- n += 512
+ data = self.buf
# Padding
- padblock_size = len(data) * 8 - n
+ padblock_size = len(data) * 8
data += b"\x01"
- padlen = BLOCKSIZE - len(data) % BLOCKSIZE
+ padlen = BLOCKSIZE - len(data)
if padlen != BLOCKSIZE:
data += b"\x00" * padlen
- hsh = g(n, hsh, data[-BLOCKSIZE:])
- n += padblock_size
- chk = add512bit(chk, data[-BLOCKSIZE:])
+ hsh = g(self.n, self.hsh, data)
+ n = self.n + padblock_size
+ chk = add512bit(self.chk, data)
hsh = g(0, hsh, pack("<Q", n) + 56 * b"\x00")
hsh = g(0, hsh, chk)
return hsh[-self._digest_size:]