From f5a840b7bb15bbb658680214280b801269349d93 Mon Sep 17 00:00:00 2001 From: Akira Ajisaka Date: Mon, 23 Mar 2026 17:35:23 +0900 Subject: [PATCH 1/3] Fix static zero IV in InternalSecurityAccessor Replace the hardcoded IV with a per-encryption random IV. Co-Authored-By: Claude Opus 4.6 --- .../InternalSecurityAccessor.scala | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/InternalSecurityAccessor.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/InternalSecurityAccessor.scala index afc1dde1fd0..03b6a22285a 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/InternalSecurityAccessor.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/InternalSecurityAccessor.scala @@ -17,6 +17,7 @@ package org.apache.kyuubi.service.authentication +import java.security.SecureRandom import javax.crypto.Cipher import javax.crypto.spec.{IvParameterSpec, SecretKeySpec} @@ -32,23 +33,17 @@ class InternalSecurityAccessor(conf: KyuubiConf, val isServer: Boolean) { val cryptoKeyAlgorithm = conf.get(ENGINE_SECURITY_CRYPTO_KEY_ALGORITHM) val cryptoCipher = conf.get(ENGINE_SECURITY_CRYPTO_CIPHER_TRANSFORMATION) + private val random = new SecureRandom() private val tokenMaxLifeTime: Long = conf.get(ENGINE_SECURITY_TOKEN_MAX_LIFETIME) private val provider: EngineSecuritySecretProvider = EngineSecuritySecretProvider.create(conf) - private val (encryptor, decryptor) = + private val (secretKeySpec, encryptor, decryptor) = initializeForAuth(cryptoCipher, normalizeSecret(provider.getSecret())) - private def initializeForAuth(cipher: String, secret: String): (Cipher, Cipher) = { + private def initializeForAuth(cipher: String, secret: String): (SecretKeySpec, Cipher, Cipher) = { val secretKeySpec = new SecretKeySpec(secret.getBytes, cryptoKeyAlgorithm) - val nonce = new Array[Byte](cryptoIvLength) - val iv = new IvParameterSpec(nonce) - val _encryptor = Cipher.getInstance(cipher) - _encryptor.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv) - val _decryptor = Cipher.getInstance(cipher) - _decryptor.init(Cipher.DECRYPT_MODE, secretKeySpec, iv) - - (_encryptor, _decryptor) + (secretKeySpec, _encryptor, _decryptor) } def issueToken(): String = { @@ -69,11 +64,17 @@ class InternalSecurityAccessor(conf: KyuubiConf, val isServer: Boolean) { } private[authentication] def encrypt(value: String): String = synchronized { - byteArrayToHexString(encryptor.doFinal(value.getBytes)) + val nonce = new Array[Byte](cryptoIvLength) + random.nextBytes(nonce) + encryptor.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(nonce)) + byteArrayToHexString(nonce ++ encryptor.doFinal(value.getBytes)) } private[authentication] def decrypt(value: String): String = synchronized { - new String(decryptor.doFinal(hexStringToByteArray(value))) + val bytes = hexStringToByteArray(value) + val nonce = bytes.take(cryptoIvLength) + decryptor.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(nonce)) + new String(decryptor.doFinal(bytes.drop(cryptoIvLength))) } private def normalizeSecret(secret: String): String = { From 96351c86d29cc209c154cc63ad46240647c55f86 Mon Sep 17 00:00:00 2001 From: Akira Ajisaka Date: Mon, 6 Apr 2026 10:51:09 +0900 Subject: [PATCH 2/3] Move random into object --- .../service/authentication/InternalSecurityAccessor.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/InternalSecurityAccessor.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/InternalSecurityAccessor.scala index 03b6a22285a..5a69c1d5bc7 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/InternalSecurityAccessor.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/InternalSecurityAccessor.scala @@ -33,7 +33,6 @@ class InternalSecurityAccessor(conf: KyuubiConf, val isServer: Boolean) { val cryptoKeyAlgorithm = conf.get(ENGINE_SECURITY_CRYPTO_KEY_ALGORITHM) val cryptoCipher = conf.get(ENGINE_SECURITY_CRYPTO_CIPHER_TRANSFORMATION) - private val random = new SecureRandom() private val tokenMaxLifeTime: Long = conf.get(ENGINE_SECURITY_TOKEN_MAX_LIFETIME) private val provider: EngineSecuritySecretProvider = EngineSecuritySecretProvider.create(conf) private val (secretKeySpec, encryptor, decryptor) = @@ -65,7 +64,7 @@ class InternalSecurityAccessor(conf: KyuubiConf, val isServer: Boolean) { private[authentication] def encrypt(value: String): String = synchronized { val nonce = new Array[Byte](cryptoIvLength) - random.nextBytes(nonce) + InternalSecurityAccessor.random.nextBytes(nonce) encryptor.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(nonce)) byteArrayToHexString(nonce ++ encryptor.doFinal(value.getBytes)) } @@ -114,6 +113,7 @@ class InternalSecurityAccessor(conf: KyuubiConf, val isServer: Boolean) { object InternalSecurityAccessor extends Logging { @volatile private var _engineSecurityAccessor: InternalSecurityAccessor = _ + private val random: SecureRandom = new SecureRandom() def initialize(conf: KyuubiConf, isServer: Boolean): Unit = { if (_engineSecurityAccessor == null) { From abfe628643535cf0ff011b104389de43aa9c89b1 Mon Sep 17 00:00:00 2001 From: Akira Ajisaka Date: Tue, 7 Apr 2026 16:06:55 +0900 Subject: [PATCH 3/3] Throw IllegalArgumentException for malformed tokens. Specify UTF-8 charset --- .../authentication/InternalSecurityAccessor.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/InternalSecurityAccessor.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/InternalSecurityAccessor.scala index 5a69c1d5bc7..c44f0bdacb7 100644 --- a/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/InternalSecurityAccessor.scala +++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/service/authentication/InternalSecurityAccessor.scala @@ -17,6 +17,7 @@ package org.apache.kyuubi.service.authentication +import java.nio.charset.StandardCharsets import java.security.SecureRandom import javax.crypto.Cipher import javax.crypto.spec.{IvParameterSpec, SecretKeySpec} @@ -39,7 +40,8 @@ class InternalSecurityAccessor(conf: KyuubiConf, val isServer: Boolean) { initializeForAuth(cryptoCipher, normalizeSecret(provider.getSecret())) private def initializeForAuth(cipher: String, secret: String): (SecretKeySpec, Cipher, Cipher) = { - val secretKeySpec = new SecretKeySpec(secret.getBytes, cryptoKeyAlgorithm) + val secretKeySpec = + new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), cryptoKeyAlgorithm) val _encryptor = Cipher.getInstance(cipher) val _decryptor = Cipher.getInstance(cipher) (secretKeySpec, _encryptor, _decryptor) @@ -66,14 +68,18 @@ class InternalSecurityAccessor(conf: KyuubiConf, val isServer: Boolean) { val nonce = new Array[Byte](cryptoIvLength) InternalSecurityAccessor.random.nextBytes(nonce) encryptor.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(nonce)) - byteArrayToHexString(nonce ++ encryptor.doFinal(value.getBytes)) + byteArrayToHexString(nonce ++ encryptor.doFinal(value.getBytes(StandardCharsets.UTF_8))) } private[authentication] def decrypt(value: String): String = synchronized { val bytes = hexStringToByteArray(value) + if (bytes.length <= cryptoIvLength) { + throw new IllegalArgumentException( + "Malformed engine access token: ciphertext is shorter than the IV length") + } val nonce = bytes.take(cryptoIvLength) decryptor.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(nonce)) - new String(decryptor.doFinal(bytes.drop(cryptoIvLength))) + new String(decryptor.doFinal(bytes.drop(cryptoIvLength)), StandardCharsets.UTF_8) } private def normalizeSecret(secret: String): String = {