You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/08/09 15:55:42 UTC
(superset) 01/02: test: add reproduction tests for this fix (checkpoint)
This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch fix-encryption-default-gcm
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 953b60f5e694679ce8b19dd3bbc7a1c1f852eeca
Author: Amin Ghadersohi <am...@gmail.com>
AuthorDate: Sun Aug 9 08:05:31 2026 -0700
test: add reproduction tests for this fix (checkpoint)
---
.../unit_tests/utils/test_encrypt_cbc_iv_reuse.py | 80 ++++++++++++++++++++++
1 file changed, 80 insertions(+)
diff --git a/tests/unit_tests/utils/test_encrypt_cbc_iv_reuse.py b/tests/unit_tests/utils/test_encrypt_cbc_iv_reuse.py
new file mode 100644
index 00000000000..ad208813755
--- /dev/null
+++ b/tests/unit_tests/utils/test_encrypt_cbc_iv_reuse.py
@@ -0,0 +1,80 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Characterization tests for the IV behavior of Superset's default
+app-encryption engine (``sqlalchemy_utils`` ``AesEngine``, AES-CBC).
+
+``AesEngine._initialize_engine`` (see
+``sqlalchemy_utils.types.encrypted.encrypted_type``) derives its IV as the
+first 16 bytes of the SHA-256 digest of the configured key: ``self.iv =
+self.secret_key[:16]``. That derivation is a pure function of the key alone,
+so every encryption performed under the same key reuses the same IV, unlike
+``AesGcmEngine``, which samples a fresh random IV (``os.urandom``) on every
+call and embeds it in the output. These tests pin that observable difference:
+same-plaintext encryptions are identical under the default engine and distinct
+under the GCM engine.
+"""
+
+from sqlalchemy import String
+from sqlalchemy.engine import make_url
+
+from superset.utils.encrypt import (
+ DEFAULT_ENCRYPTION_ENGINE_NAME,
+ EncryptedType,
+ resolve_encryption_engine,
+)
+
+DIALECT = make_url("sqlite://").get_dialect()
+SECRET_KEY = "k" * 32
+
+
+def _field(engine: type) -> EncryptedType:
+ return EncryptedType(String(1024), key=lambda: SECRET_KEY, engine=engine)
+
+
+def test_default_encryption_engine_name_resolves_to_cbc() -> None:
+ """The engine name the codebase falls back to when config is unset resolves
+ to the unauthenticated AES-CBC engine, not the authenticated AES-GCM one.
+ """
+ from sqlalchemy_utils.types.encrypted.encrypted_type import AesEngine
+
+ assert resolve_encryption_engine(DEFAULT_ENCRYPTION_ENGINE_NAME) is AesEngine
+
+
+def test_default_engine_repeats_ciphertext_for_repeated_plaintext() -> None:
+ """Encrypting the same plaintext twice under one key produces identical
+ ciphertext with the default engine, because its IV is a deterministic
+ function of the key rather than freshly sampled per call.
+ """
+ field = _field(resolve_encryption_engine(DEFAULT_ENCRYPTION_ENGINE_NAME))
+
+ first = field.process_bind_param("hunter2", DIALECT)
+ second = field.process_bind_param("hunter2", DIALECT)
+
+ assert first == second
+
+
+def test_gcm_engine_varies_ciphertext_for_repeated_plaintext() -> None:
+ """Contrast case: the authenticated engine samples a fresh random IV each
+ call, so encrypting the same plaintext twice under one key produces
+ different ciphertext.
+ """
+ field = _field(resolve_encryption_engine("aes-gcm"))
+
+ first = field.process_bind_param("hunter2", DIALECT)
+ second = field.process_bind_param("hunter2", DIALECT)
+
+ assert first != second