]> Cypherpunks.ru repositories - pygost.git/commitdiff
Magma block cipher 3.3
authorSergey Matveev <stargrave@stargrave.org>
Sat, 10 Jun 2017 20:49:16 +0000 (23:49 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Sat, 10 Jun 2017 20:49:16 +0000 (23:49 +0300)
NEWS
README
pygost/gost3412.py
pygost/gost3413.py
pygost/stubs/pygost/gost3412.pyi
pygost/test_gost3412.py
pygost/test_gost3413.py
www.texi

diff --git a/NEWS b/NEWS
index fd1faa5f55d554280cfc5e541c2bba5b1747b4a1..7c6ca3fddd7e0f73650eb0b8bd4381262f8f4031 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,6 @@
 3.3:
     * GOST3412Kuz renamed to GOST3412Kuznezhik
+    * GOST3412Magma implements GOST R 34.12-2015 Magma 64-bit block cipher
 
 3.2:
     34.13-2015 block cipher modes of operation implementations.
diff --git a/README b/README
index 60c3864e70c01ed40d6846011887e6fdb3a05703..57009b01c796184a824f55b9d3b844296d185eb6 100644 (file)
--- a/README
+++ b/README
@@ -17,6 +17,7 @@ GOST is GOvernment STandard of Russian Federation (and Soviet Union).
 * 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.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)
 * PEP247-compatible hash/MAC functions
index 663318609d47d0d0214e2f907263a730d7d525c5..fe9600741054b01dfaa279041fc2f97d32edbe4f 100644 (file)
 #
 # 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 34.12-2015 128-bit block cipher Кузнечик (Kuznechik)
-
-:rfc:`7801`. Pay attention that 34.12-2015 also defines 64-bit block
-cipher Магма (Magma) -- it is **not** implemented here, but in gost28147
-module.
+"""GOST 34.12-2015 64 and 128 bit block ciphers (:rfc:`7801`)
 
 Several precalculations are performed during this module importing.
 """
 
+from pygost.gost28147 import block2ns as gost28147_block2ns
+from pygost.gost28147 import decrypt as gost28147_decrypt
+from pygost.gost28147 import encrypt as gost28147_encrypt
+from pygost.gost28147 import ns2block as gost28147_ns2block
 from pygost.utils import strxor
 from pygost.utils import xrange  # pylint: disable=redefined-builtin
 
@@ -152,3 +152,30 @@ class GOST3412Kuznechik(object):
         for i in range(9, 0, -1):
             blk = [PIinv[v] for v in Linv(bytearray(strxor(self.ks[i], blk)))]
         return bytes(strxor(self.ks[0], blk))
+
+
+class GOST3412Magma(object):
+    """GOST 34.12-2015 64-bit block cipher Магма (Magma)
+    """
+    def __init__(self, key):
+        """
+        :param key: encryption/decryption key
+        :type key: bytes, 32 bytes
+        """
+        # Backward compatibility key preparation for 28147-89 key schedule
+        self.key = b"".join(key[i * 4:i * 4 + 4][::-1] for i in range(8))
+        self.sbox = "Gost28147_tc26_ParamZ"
+
+    def encrypt(self, blk):
+        return gost28147_ns2block(gost28147_encrypt(
+            self.sbox,
+            self.key,
+            gost28147_block2ns(blk[::-1]),
+        ))[::-1]
+
+    def decrypt(self, blk):
+        return gost28147_ns2block(gost28147_decrypt(
+            self.sbox,
+            self.key,
+            gost28147_block2ns(blk[::-1]),
+        ))[::-1]
index bd8cc49ad7e8c2ae20fa6cf5f6ced910b60a68a9..c0201c97ae8d2947aaf149494d444463c6c83a90 100644 (file)
@@ -132,12 +132,12 @@ def ofb(encrypter, bs, data, iv):
 
     For decryption you use the same function again.
     """
-    if len(iv) != 2 * bs:
+    if len(iv) < 2 * bs or len(iv) % bs != 0:
         raise ValueError("Invalid IV size")
-    r = [iv[:bs], iv[bs:]]
+    r = [iv[i:i + bs] for i in range(0, len(iv), bs)]
     result = []
     for i in xrange(0, len(data) + pad_size(len(data), bs), bs):
-        r = [r[1], encrypter(r[0])]
+        r = r[1:] + [encrypter(r[0])]
         result.append(strxor(r[1], data[i:i + bs]))
     return b"".join(result)
 
@@ -152,13 +152,13 @@ def cbc_encrypt(encrypter, bs, pt, iv):
     """
     if not pt or len(pt) % bs != 0:
         raise ValueError("Plaintext is not blocksize aligned")
-    if len(iv) != 2 * bs:
+    if len(iv) < 2 * bs or len(iv) % bs != 0:
         raise ValueError("Invalid IV size")
-    r = [iv[:bs], iv[bs:]]
+    r = [iv[i:i + bs] for i in range(0, len(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]]
+        r = r[1:] + [ct[-1]]
     return b"".join(ct)
 
 
@@ -172,14 +172,14 @@ def cbc_decrypt(decrypter, bs, ct, iv):
     """
     if not ct or len(ct) % bs != 0:
         raise ValueError("Ciphertext is not blocksize aligned")
-    if len(iv) != 2 * bs:
+    if len(iv) < 2 * bs or len(iv) % bs != 0:
         raise ValueError("Invalid IV size")
-    r = [iv[:bs], iv[bs:]]
+    r = [iv[i:i + bs] for i in range(0, len(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]
+        r = r[1:] + [blk]
     return b"".join(pt)
 
 
@@ -191,13 +191,13 @@ def cfb_encrypt(encrypter, bs, pt, iv):
     :param bytes pt: plaintext
     :param bytes iv: double blocksize-sized initialization vector
     """
-    if len(iv) != 2 * bs:
+    if len(iv) < 2 * bs or len(iv) % bs != 0:
         raise ValueError("Invalid IV size")
-    r = [iv[:bs], iv[bs:]]
+    r = [iv[i:i + bs] for i in range(0, len(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]]
+        r = r[1:] + [ct[-1]]
     return b"".join(ct)
 
 
@@ -209,14 +209,14 @@ def cfb_decrypt(encrypter, bs, ct, iv):
     :param bytes ct: ciphertext
     :param bytes iv: double blocksize-sized initialization vector
     """
-    if len(iv) != 2 * bs:
+    if len(iv) < 2 * bs or len(iv) % bs != 0:
         raise ValueError("Invalid IV size")
-    r = [iv[:bs], iv[bs:]]
+    r = [iv[i:i + bs] for i in range(0, len(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]
+        r = r[1:] + [blk]
     return b"".join(pt)
 
 
index c67a7daa7c9c7c682e783a180a4d19368283e0b8..1648cf4f85f955b6ac770b4f8c46d219ecb33c0e 100644 (file)
@@ -4,3 +4,11 @@ class GOST3412Kuznechik(object):
     def encrypt(self, blk: bytes) -> bytes: ...
 
     def decrypt(self, blk: bytes) -> bytes: ...
+
+
+class GOST3412Magma(object):
+    def __init__(self, key: bytes) -> None: ...
+
+    def encrypt(self, blk: bytes) -> bytes: ...
+
+    def decrypt(self, blk: bytes) -> bytes: ...
index 6ab4c90d7076de2e3cc2e1d7a31cd7737afc681d..e0b078608884b1e36cf122d550c8e72ecbc25158 100644 (file)
@@ -19,6 +19,7 @@ from unittest import TestCase
 
 from pygost.gost3412 import C
 from pygost.gost3412 import GOST3412Kuznechik
+from pygost.gost3412 import GOST3412Magma
 from pygost.gost3412 import L
 from pygost.gost3412 import PI
 from pygost.utils import hexdec
@@ -121,3 +122,17 @@ class KuznechikTest(TestCase):
     def test_decrypt(self):
         ciph = GOST3412Kuznechik(self.key)
         self.assertEqual(ciph.decrypt(self.ciphertext), self.plaintext)
+
+
+class MagmaTest(TestCase):
+    key = hexdec("ffeeddccbbaa99887766554433221100f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")
+    plaintext = hexdec("fedcba9876543210")
+    ciphertext = hexdec("4ee901e5c2d8ca3d")
+
+    def test_encrypt(self):
+        ciph = GOST3412Magma(self.key)
+        self.assertEqual(ciph.encrypt(self.plaintext), self.ciphertext)
+
+    def test_decrypt(self):
+        ciph = GOST3412Magma(self.key)
+        self.assertEqual(ciph.decrypt(self.ciphertext), self.plaintext)
index 31a56f3203f9ad55c5237c625b05bfbadd9343f8..f1574eef6f17c73e247b967d199534dbc884b5a7 100644 (file)
@@ -3,6 +3,7 @@ from random import randint
 from unittest import TestCase
 
 from pygost.gost3412 import GOST3412Kuznechik
+from pygost.gost3412 import GOST3412Magma
 from pygost.gost3413 import _mac_ks
 from pygost.gost3413 import cbc_decrypt
 from pygost.gost3413 import cbc_encrypt
@@ -169,3 +170,146 @@ class GOST3412KuznechikModesTest(TestCase):
             data = urandom(randint(0, 16 * 2))
             ciph = GOST3412Kuznechik(urandom(32))
             mac(ciph.encrypt, 16, data)
+
+
+class GOST3412MagmaModesTest(TestCase):
+    key = hexdec("ffeeddccbbaa99887766554433221100f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff")
+    ciph = GOST3412Magma(key)
+    plaintext = ""
+    plaintext += "92def06b3c130a59"
+    plaintext += "db54c704f8189d20"
+    plaintext += "4a98fb2e67a8024c"
+    plaintext += "8912409b17b57e41"
+    iv = hexdec("1234567890abcdef234567890abcdef134567890abcdef12")
+
+    def test_ecb_vectors(self):
+        ciphtext = ""
+        ciphtext += "2b073f0494f372a0"
+        ciphtext += "de70e715d3556e48"
+        ciphtext += "11d8d9e9eacfbc1e"
+        ciphtext += "7c68260996c67efb"
+        self.assertSequenceEqual(
+            hexenc(ecb_encrypt(self.ciph.encrypt, 8, hexdec(self.plaintext))),
+            ciphtext,
+        )
+        self.assertSequenceEqual(
+            hexenc(ecb_decrypt(self.ciph.decrypt, 8, hexdec(ciphtext))),
+            self.plaintext,
+        )
+
+    def test_ecb_symmetric(self):
+        for _ in range(100):
+            pt = pad2(urandom(randint(0, 16 * 2)), 16)
+            ciph = GOST3412Magma(urandom(32))
+            ct = ecb_encrypt(ciph.encrypt, 8, pt)
+            self.assertSequenceEqual(ecb_decrypt(ciph.decrypt, 8, ct), pt)
+
+    def test_ctr_vectors(self):
+        ciphtext = ""
+        ciphtext += "4e98110c97b7b93c"
+        ciphtext += "3e250d93d6e85d69"
+        ciphtext += "136d868807b2dbef"
+        ciphtext += "568eb680ab52a12d"
+        iv = self.iv[:4]
+        self.assertSequenceEqual(
+            hexenc(ctr(self.ciph.encrypt, 8, hexdec(self.plaintext), iv)),
+            ciphtext,
+        )
+        self.assertSequenceEqual(
+            hexenc(ctr(self.ciph.encrypt, 8, hexdec(ciphtext), iv)),
+            self.plaintext,
+        )
+
+    def test_ctr_symmetric(self):
+        for _ in range(100):
+            pt = urandom(randint(0, 16 * 2))
+            iv = urandom(4)
+            ciph = GOST3412Magma(urandom(32))
+            ct = ctr(ciph.encrypt, 8, pt, iv)
+            self.assertSequenceEqual(ctr(ciph.encrypt, 8, ct, iv), pt)
+
+    def test_ofb_vectors(self):
+        iv = self.iv[:16]
+        ciphtext = ""
+        ciphtext += "db37e0e266903c83"
+        ciphtext += "0d46644c1f9a089c"
+        ciphtext += "a0f83062430e327e"
+        ciphtext += "c824efb8bd4fdb05"
+        self.assertSequenceEqual(
+            hexenc(ofb(self.ciph.encrypt, 8, hexdec(self.plaintext), iv)),
+            ciphtext,
+        )
+        self.assertSequenceEqual(
+            hexenc(ofb(self.ciph.encrypt, 8, hexdec(ciphtext), iv)),
+            self.plaintext,
+        )
+
+    def test_ofb_symmetric(self):
+        for _ in range(100):
+            pt = urandom(randint(0, 16 * 2))
+            iv = urandom(8 * 2)
+            ciph = GOST3412Magma(urandom(32))
+            ct = ofb(ciph.encrypt, 8, pt, iv)
+            self.assertSequenceEqual(ofb(ciph.encrypt, 8, ct, iv), pt)
+
+    def test_cbc_vectors(self):
+        ciphtext = ""
+        ciphtext += "96d1b05eea683919"
+        ciphtext += "aff76129abb937b9"
+        ciphtext += "5058b4a1c4bc0019"
+        ciphtext += "20b78b1a7cd7e667"
+        self.assertSequenceEqual(
+            hexenc(cbc_encrypt(self.ciph.encrypt, 8, hexdec(self.plaintext), self.iv)),
+            ciphtext,
+        )
+        self.assertSequenceEqual(
+            hexenc(cbc_decrypt(self.ciph.decrypt, 8, 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(8 * 2)
+            ciph = GOST3412Magma(urandom(32))
+            ct = cbc_encrypt(ciph.encrypt, 8, pt, iv)
+            self.assertSequenceEqual(cbc_decrypt(ciph.decrypt, 8, ct, iv), pt)
+
+    def test_cfb_vectors(self):
+        iv = self.iv[:16]
+        ciphtext = ""
+        ciphtext += "db37e0e266903c83"
+        ciphtext += "0d46644c1f9a089c"
+        ciphtext += "24bdd2035315d38b"
+        ciphtext += "bcc0321421075505"
+        self.assertSequenceEqual(
+            hexenc(cfb_encrypt(self.ciph.encrypt, 8, hexdec(self.plaintext), iv)),
+            ciphtext,
+        )
+        self.assertSequenceEqual(
+            hexenc(cfb_decrypt(self.ciph.encrypt, 8, hexdec(ciphtext), iv)),
+            self.plaintext,
+        )
+
+    def test_cfb_symmetric(self):
+        for _ in range(100):
+            pt = urandom(randint(0, 16 * 2))
+            iv = urandom(8 * 2)
+            ciph = GOST3412Magma(urandom(32))
+            ct = cfb_encrypt(ciph.encrypt, 8, pt, iv)
+            self.assertSequenceEqual(cfb_decrypt(ciph.encrypt, 8, ct, iv), pt)
+
+    def test_mac_vectors(self):
+        k1, k2 = _mac_ks(self.ciph.encrypt, 8)
+        self.assertSequenceEqual(hexenc(k1), "5f459b3342521424")
+        self.assertSequenceEqual(hexenc(k2), "be8b366684a42848")
+        self.assertSequenceEqual(
+            hexenc(mac(self.ciph.encrypt, 8, hexdec(self.plaintext))[:4]),
+            "154e7210",
+        )
+
+    def test_mac_applies(self):
+        for _ in range(100):
+            data = urandom(randint(0, 16 * 2))
+            ciph = GOST3412Magma(urandom(32))
+            mac(ciph.encrypt, 8, data)
index 6cb880b6d6cf47818c6f7baa2810d6118df609b4..5e9791ee2417a7c7ed211c384f9d1b45ba4eb2d2 100644 (file)
--- a/www.texi
+++ b/www.texi
@@ -49,6 +49,7 @@ Currently supported algorithms are:
     (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.12-2015 64-bit block cipher Магма (Magma)
 @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
@@ -92,6 +93,8 @@ mailing list. Announcements also go to this mailing list.
 @item 3.3
     @itemize
     @item @code{GOST3412Kuz} renamed to @code{GOST3412Kuznezhik}
+    @item @code{GOST3412Magma} implements GOST R 34.12-2015 Magma 64-bit
+        block cipher
     @end itemize
 
 @item 3.2