aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohnny Hsieh <32300164+mnixry@users.noreply.github.com>2023-12-29 21:56:29 +0800
committerGitHub <noreply@github.com>2023-12-29 10:56:29 -0300
commit91541cf726e0e9144155b6c92c5cd92f4661a4ac (patch)
tree0599bedc4230c8aadd58185481e80bb195f4aec2
parentd911018f0141358a7d752201bf1d92a9873d0a02 (diff)
downloadpyca-cryptography-91541cf726e0e9144155b6c92c5cd92f4661a4ac.zip
pyca-cryptography-91541cf726e0e9144155b6c92c5cd92f4661a4ac.tar.gz
pyca-cryptography-91541cf726e0e9144155b6c92c5cd92f4661a4ac.tar.bz2
Add support for GCM mode of SM4 cipher (#10072)
* Add support for SM4-GCM cipher ref: #7503 ref: https://github.com/openssl/openssl/issues/13667 * Update SM4 GCM tests to use external test vector * Cite SM4 test vectors sources in document * Add tests for SM4ModeGCM finalize_with_tag * Update CHANGELOG.rst
-rw-r--r--CHANGELOG.rst4
-rw-r--r--docs/development/test-vectors.rst3
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py2
-rw-r--r--tests/hazmat/primitives/test_sm4.py92
-rw-r--r--vectors/cryptography_vectors/ciphers/SM4/rfc8998.txt11
5 files changed, 109 insertions, 3 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 9ecc48b..9ade854 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -65,6 +65,10 @@ Changelog
for :class:`~cryptography.x509.Certificate` chains. These APIs should be
considered unstable and not subject to our stability guarantees until
documented as such in a future release.
+* Added support for
+ :class:`~cryptography.hazmat.primitives.ciphers.algorithms.SM4`
+ :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM`
+ when using OpenSSL 3.0 or greater.
.. _v41-0-7:
diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst
index a0a0261..73eaeb5 100644
--- a/docs/development/test-vectors.rst
+++ b/docs/development/test-vectors.rst
@@ -969,6 +969,8 @@ Symmetric ciphers
* SEED (CBC) from :rfc:`4196`.
* SEED (CFB, OFB) generated by this project.
See: :doc:`/development/custom-vectors/seed`
+* SM4 (CBC, CFB, CTR, ECB, OFB) from `draft-ribose-cfrg-sm4-10`_.
+* SM4 (GCM) from :rfc:`8998`.
Two factor authentication
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1046,6 +1048,7 @@ header format (substituting the correct information):
.. _`GnuTLS example keys`: https://gitlab.com/gnutls/gnutls/-/commit/ad2061deafdd7db78fd405f9d143b0a7c579da7b
.. _`NESSIE IDEA vectors`: https://www.cosic.esat.kuleuven.be/nessie/testvectors/bc/idea/Idea-128-64.verified.test-vectors
.. _`NESSIE`: https://en.wikipedia.org/wiki/NESSIE
+.. _`draft-ribose-cfrg-sm4-10`: https://tools.ietf.org/html/draft-ribose-cfrg-sm4-10
.. _`Ed25519 website`: https://ed25519.cr.yp.to/software.html
.. _`NIST SP-800-38B`: https://csrc.nist.gov/publications/detail/sp/800-38b/archive/2005-05-01
.. _`NIST PKI Testing`: https://csrc.nist.gov/Projects/PKI-Testing
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index 58cef90..0e14bfb 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -269,7 +269,7 @@ class Backend:
),
)
self.register_cipher_adapter(AES, XTS, _get_xts_cipher)
- for mode_cls in [ECB, CBC, OFB, CFB, CTR]:
+ for mode_cls in [ECB, CBC, OFB, CFB, CTR, GCM]:
self.register_cipher_adapter(
SM4, mode_cls, GetCipherByName("sm4-{mode.name}")
)
diff --git a/tests/hazmat/primitives/test_sm4.py b/tests/hazmat/primitives/test_sm4.py
index 53893ee..695987b 100644
--- a/tests/hazmat/primitives/test_sm4.py
+++ b/tests/hazmat/primitives/test_sm4.py
@@ -7,9 +7,10 @@ import os
import pytest
-from cryptography.hazmat.primitives.ciphers import algorithms, modes
+from cryptography.exceptions import InvalidTag
+from cryptography.hazmat.primitives.ciphers import algorithms, base, modes
-from ...utils import load_nist_vectors
+from ...utils import load_nist_vectors, load_vectors_from_file
from .utils import generate_encrypt_test
@@ -91,3 +92,90 @@ class TestSM4ModeCTR:
lambda key, **kwargs: algorithms.SM4(binascii.unhexlify(key)),
lambda iv, **kwargs: modes.CTR(binascii.unhexlify(iv)),
)
+
+
+@pytest.mark.supported(
+ only_if=lambda backend: backend.cipher_supported(
+ algorithms.SM4(b"\x00" * 16), modes.GCM(b"\x00" * 16)
+ ),
+ skip_message="Does not support SM4 GCM",
+)
+class TestSM4ModeGCM:
+ @pytest.mark.parametrize(
+ "vector",
+ load_vectors_from_file(
+ os.path.join("ciphers", "SM4", "rfc8998.txt"),
+ load_nist_vectors,
+ ),
+ )
+ def test_encryption(self, vector, backend):
+ key = binascii.unhexlify(vector["key"])
+ iv = binascii.unhexlify(vector["iv"])
+ associated_data = binascii.unhexlify(vector["aad"])
+ tag = binascii.unhexlify(vector["tag"])
+ plaintext = binascii.unhexlify(vector["plaintext"])
+ ciphertext = binascii.unhexlify(vector["ciphertext"])
+
+ cipher = base.Cipher(algorithms.SM4(key), modes.GCM(iv))
+ encryptor = cipher.encryptor()
+ encryptor.authenticate_additional_data(associated_data)
+ computed_ct = encryptor.update(plaintext) + encryptor.finalize()
+ assert computed_ct == ciphertext
+ assert encryptor.tag == tag
+
+ @pytest.mark.parametrize(
+ "vector",
+ load_vectors_from_file(
+ os.path.join("ciphers", "SM4", "rfc8998.txt"),
+ load_nist_vectors,
+ ),
+ )
+ def test_decryption(self, vector, backend):
+ key = binascii.unhexlify(vector["key"])
+ iv = binascii.unhexlify(vector["iv"])
+ associated_data = binascii.unhexlify(vector["aad"])
+ tag = binascii.unhexlify(vector["tag"])
+ plaintext = binascii.unhexlify(vector["plaintext"])
+ ciphertext = binascii.unhexlify(vector["ciphertext"])
+
+ cipher = base.Cipher(algorithms.SM4(key), modes.GCM(iv, tag))
+ decryptor = cipher.decryptor()
+ decryptor.authenticate_additional_data(associated_data)
+ computed_pt = decryptor.update(ciphertext) + decryptor.finalize()
+ assert computed_pt == plaintext
+
+ cipher_no_tag = base.Cipher(algorithms.SM4(key), modes.GCM(iv))
+ decryptor = cipher_no_tag.decryptor()
+ decryptor.authenticate_additional_data(associated_data)
+ computed_pt = decryptor.update(
+ ciphertext
+ ) + decryptor.finalize_with_tag(tag)
+ assert computed_pt == plaintext
+
+ @pytest.mark.parametrize(
+ "vector",
+ load_vectors_from_file(
+ os.path.join("ciphers", "SM4", "rfc8998.txt"),
+ load_nist_vectors,
+ ),
+ )
+ def test_invalid_tag(self, vector, backend):
+ key = binascii.unhexlify(vector["key"])
+ iv = binascii.unhexlify(vector["iv"])
+ associated_data = binascii.unhexlify(vector["aad"])
+ tag = binascii.unhexlify(vector["tag"])
+ ciphertext = binascii.unhexlify(vector["ciphertext"])
+
+ cipher = base.Cipher(algorithms.SM4(key), modes.GCM(iv, tag))
+ decryptor = cipher.decryptor()
+ decryptor.authenticate_additional_data(associated_data)
+ decryptor.update(ciphertext[:-1])
+ with pytest.raises(InvalidTag):
+ decryptor.finalize()
+
+ cipher_no_tag = base.Cipher(algorithms.SM4(key), modes.GCM(iv))
+ decryptor = cipher_no_tag.decryptor()
+ decryptor.authenticate_additional_data(associated_data)
+ decryptor.update(ciphertext[:-1])
+ with pytest.raises(InvalidTag):
+ decryptor.finalize_with_tag(tag)
diff --git a/vectors/cryptography_vectors/ciphers/SM4/rfc8998.txt b/vectors/cryptography_vectors/ciphers/SM4/rfc8998.txt
new file mode 100644
index 0000000..9f2e8af
--- /dev/null
+++ b/vectors/cryptography_vectors/ciphers/SM4/rfc8998.txt
@@ -0,0 +1,11 @@
+# Vectors from rfc8998.txt. Reformatted to work with the NIST loader
+# SM4 GCM
+
+# A.2
+COUNT = 1
+KEY = 0123456789abcdeffedcba9876543210
+IV = 00001234567800000000abcd
+AAD = feedfacedeadbeeffeedfacedeadbeefabaddad2
+TAG = 83de3541e4c2b58177e065a9bf7b62ec
+PLAINTEXT = aaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbccccccccccccccccddddddddddddddddeeeeeeeeeeeeeeeeffffffffffffffffeeeeeeeeeeeeeeeeaaaaaaaaaaaaaaaa
+CIPHERTEXT = 17f399f08c67d5ee19d0dc9969c4bb7d5fd46fd3756489069157b282bb200735d82710ca5c22f0ccfa7cbf93d496ac15a56834cbcf98c397b4024a2691233b8d