From 6efb627903a68c7d1eea5b6cf080a367b2584251 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 6 Jan 2026 17:30:11 +0100 Subject: [PATCH 1/9] fix typo --- radicale/auth/htpasswd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/radicale/auth/htpasswd.py b/radicale/auth/htpasswd.py index dd66dfbd..1c4b2b55 100644 --- a/radicale/auth/htpasswd.py +++ b/radicale/auth/htpasswd.py @@ -123,9 +123,9 @@ class Auth(auth.BaseAuth): self._has_bcrypt = True if self._encryption == "autodetect": if self._htpasswd_bcrypt_use == 0: - logger.info("auth htpasswd encryption is 'radicale.auth.htpasswd_encryption.%s' and bycrypt module found, but currently not required", self._encryption) + logger.info("auth htpasswd encryption is 'radicale.auth.htpasswd_encryption.%s' and bcrypt module found, but currently not required", self._encryption) else: - logger.info("auth htpasswd encryption is 'radicale.auth.htpasswd_encryption.%s' and bycrypt module found (bcrypt entries found: %d)", self._encryption, self._htpasswd_bcrypt_use) + logger.info("auth htpasswd encryption is 'radicale.auth.htpasswd_encryption.%s' and bcrypt module found (bcrypt entries found: %d)", self._encryption, self._htpasswd_bcrypt_use) if self._encryption == "bcrypt": self._verify = functools.partial(self._bcrypt, bcrypt) else: From 46526cbb712e02bb43d2117f968aebb93e1565ee Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 6 Jan 2026 17:31:24 +0100 Subject: [PATCH 2/9] add check for bcrypt vs. passlib/libpass version --- radicale/utils.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/radicale/utils.py b/radicale/utils.py index 152f384e..17326ee5 100644 --- a/radicale/utils.py +++ b/radicale/utils.py @@ -27,6 +27,8 @@ from importlib import import_module, metadata from string import ascii_letters, digits, punctuation from typing import Callable, Sequence, Tuple, Type, TypeVar, Union +from packaging.version import Version + from radicale import config from radicale.log import logger @@ -85,6 +87,10 @@ def load_plugin(internal_types: Sequence[str], module_name: str, def package_version(name): + if name == "passlib": + # passlib(libpass) requires special handling as module name is unchanged, but metadata has new name + import passlib + return passlib.__version__ return metadata.version(name) @@ -99,6 +105,30 @@ def vobject_supports_vcard4() -> bool: return False +def passlib_libpass_supports_bcrypt() -> Tuple[bool, str]: + """Check if passlib/libpass version supports bcrypt version.""" + info = "" + try: + version_bcrypt = package_version("bcrypt") + version_bcrypt_check = "5.0.0" + version_passlib = package_version("passlib") + version_passlib_check = "1.9.3" + if Version(version_bcrypt) >= Version(version_bcrypt_check): + # bcrypt >= 5.0.0 has issues with passlib(libpass) < 1.9.3 + if Version(version_passlib) < Version(version_passlib_check): + info = "bcrypt module version %r >= %r and passlib(libpass) module version %r < %r found => incompatible, downgrade bcrypt or upgrade passlib(libpass)" % (version_bcrypt, version_bcrypt_check, version_passlib, version_passlib_check) + return (False, info) + else: + info = "bcrypt module version %r >= %r and passlib(libpass) module version %r >= %r found => ok" % (version_bcrypt, version_bcrypt_check, version_passlib, version_passlib_check) + return (True, info) + else: + info = "bcrypt module version %r < %r and passlib(libpass) module version %r found => ok" % (version_bcrypt, version_bcrypt_check, version_passlib) + return (True, info) + except Exception: + info = "bcrypt module version or passlib(libpass) module version %r not found => problem" + return (False, info) + + def packages_version(): versions = [] versions.append("python=%s.%s.%s" % (sys.version_info[0], sys.version_info[1], sys.version_info[2])) From 731716856cf2cf329d9a4b8f7c990732d7bd2618 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 6 Jan 2026 17:32:24 +0100 Subject: [PATCH 3/9] check whether bcrypt is usuable with passlib/libpass version --- radicale/auth/htpasswd.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/radicale/auth/htpasswd.py b/radicale/auth/htpasswd.py index 1c4b2b55..47f01727 100644 --- a/radicale/auth/htpasswd.py +++ b/radicale/auth/htpasswd.py @@ -61,7 +61,7 @@ from typing import Any, Tuple from passlib.hash import apr_md5_crypt, sha256_crypt, sha512_crypt -from radicale import auth, config, logger +from radicale import auth, config, logger, utils class Auth(auth.BaseAuth): @@ -120,12 +120,22 @@ class Auth(auth.BaseAuth): "The htpasswd encryption method 'bcrypt' or 'autodetect' requires " "the bcrypt module (entries found: %d)." % self._htpasswd_bcrypt_use) from e else: - self._has_bcrypt = True + [bcrypt_usable, info] = utils.passlib_libpass_supports_bcrypt() + if bcrypt_usable: + self._has_bcrypt = True + logger.debug(info) + else: + logger.warning(info) if self._encryption == "autodetect": if self._htpasswd_bcrypt_use == 0: logger.info("auth htpasswd encryption is 'radicale.auth.htpasswd_encryption.%s' and bcrypt module found, but currently not required", self._encryption) else: logger.info("auth htpasswd encryption is 'radicale.auth.htpasswd_encryption.%s' and bcrypt module found (bcrypt entries found: %d)", self._encryption, self._htpasswd_bcrypt_use) + if not bcrypt_usable: + raise RuntimeError("The htpasswd encryption 'autodetect' requires the bcrypt module but not usuable") + else: + if not bcrypt_usable: + raise RuntimeError("The htpasswd encryption method 'bcrypt' requires the bcrypt module but not usuable") if self._encryption == "bcrypt": self._verify = functools.partial(self._bcrypt, bcrypt) else: From 84bf14fd8e602110a01d80bdf69fcc0d7ba3fe89 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 6 Jan 2026 17:33:02 +0100 Subject: [PATCH 4/9] add note about bcrypt vs. passlib/libpass --- radicale/auth/htpasswd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radicale/auth/htpasswd.py b/radicale/auth/htpasswd.py index 47f01727..3066abd3 100644 --- a/radicale/auth/htpasswd.py +++ b/radicale/auth/htpasswd.py @@ -43,7 +43,7 @@ out-of-the-box: - SHA256 (htpasswd -2 ...) - SHA512 (htpasswd -5 ...) -When bcrypt is installed: +When bcrypt is installed (bcrypt >= 5.0.0 requires passlib/libpass >= 1.9.3): - BCRYPT (htpasswd -B ...) -- Requires htpasswd 2.4.x When argon2 is installed: From e2532ff2a7c5809338a77a2b20320f0fcf3ade66 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 6 Jan 2026 17:34:04 +0100 Subject: [PATCH 5/9] display module versions on test start --- radicale/tests/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/radicale/tests/__init__.py b/radicale/tests/__init__.py index 9a57e6fd..44e86fc0 100644 --- a/radicale/tests/__init__.py +++ b/radicale/tests/__init__.py @@ -23,6 +23,8 @@ Tests for Radicale. import base64 import logging +import os +import platform import shutil import sys import tempfile @@ -36,7 +38,7 @@ import defusedxml.ElementTree as DefusedET import vobject import radicale -from radicale import app, config, types, xmlutils +from radicale import app, config, types, utils, xmlutils RESPONSES = Dict[str, Union[int, Dict[str, Tuple[int, ET.Element]], vobject.base.Component]] @@ -52,6 +54,11 @@ class BaseTest: application: app.Application def setup_method(self) -> None: + if os.environ.get("PYTHONPATH"): + info = "with PYTHONPATH=%r " % os.environ.get("PYTHONPATH") + else: + info = "" + logging.info("Testing Radicale %s(%s) as %s on %s", info, utils.packages_version(), utils.user_groups_as_string(), platform.platform()) self.configuration = config.load() self.colpath = tempfile.mkdtemp() self.configure({ From 27eedc098d02a9cb49664eb8abf91f7140941e16 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 6 Jan 2026 17:34:26 +0100 Subject: [PATCH 6/9] skip bcrypt tests if not usable --- radicale/tests/test_auth.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/radicale/tests/test_auth.py b/radicale/tests/test_auth.py index 86b61062..7e4c6c14 100644 --- a/radicale/tests/test_auth.py +++ b/radicale/tests/test_auth.py @@ -30,7 +30,7 @@ from typing import Iterable, Tuple, Union import pytest -from radicale import xmlutils +from radicale import utils, xmlutils from radicale.tests import BaseTest @@ -121,38 +121,47 @@ class TestBaseAuthRequests(BaseTest): self._test_htpasswd("autodetect", "tmp:$6$3Qhl8r6FLagYdHYa$UCH9yXCed4A.J9FQsFPYAOXImzZUMfvLa0lwcWOxWYLOF5sE/lF99auQ4jKvHY2vijxmefl7G6kMqZ8JPdhIJ/") @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") def test_htpasswd_bcrypt_2a(self) -> None: self._test_htpasswd("bcrypt", "tmp:$2a$10$Mj4A9vMecAp/K7.0fMKoVOk1SjgR.RBhl06a52nvzXhxlT3HB7Reu") - @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed") + @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed or incompatibe") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") def test_htpasswd_bcrypt_2a_autodetect(self) -> None: self._test_htpasswd("autodetect", "tmp:$2a$10$Mj4A9vMecAp/K7.0fMKoVOk1SjgR.RBhl06a52nvzXhxlT3HB7Reu") @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") def test_htpasswd_bcrypt_2b(self) -> None: self._test_htpasswd("bcrypt", "tmp:$2b$12$7a4z/fdmXlBIfkz0smvzW.1Nds8wpgC/bo2DVOb4OSQKWCDL1A1wu") @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") def test_htpasswd_bcrypt_2b_autodetect(self) -> None: self._test_htpasswd("autodetect", "tmp:$2b$12$7a4z/fdmXlBIfkz0smvzW.1Nds8wpgC/bo2DVOb4OSQKWCDL1A1wu") @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") def test_htpasswd_bcrypt_2y(self) -> None: self._test_htpasswd("bcrypt", "tmp:$2y$05$oD7hbiQFQlvCM7zoalo/T.MssV3VNTRI3w5KDnj8NTUKJNWfVpvRq") @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") def test_htpasswd_bcrypt_2y_autodetect(self) -> None: self._test_htpasswd("autodetect", "tmp:$2y$05$oD7hbiQFQlvCM7zoalo/T.MssV3VNTRI3w5KDnj8NTUKJNWfVpvRq") @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") def test_htpasswd_bcrypt_C10(self) -> None: self._test_htpasswd("bcrypt", "tmp:$2y$10$bZsWq06ECzxqi7RmulQvC.T1YHUnLW2E3jn.MU2pvVTGn1dfORt2a") @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") def test_htpasswd_bcrypt_C10_autodetect(self) -> None: self._test_htpasswd("bcrypt", "tmp:$2y$10$bZsWq06ECzxqi7RmulQvC.T1YHUnLW2E3jn.MU2pvVTGn1dfORt2a") @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") def test_htpasswd_bcrypt_unicode(self) -> None: self._test_htpasswd("bcrypt", "😀:$2y$10$Oyz5aHV4MD9eQJbk6GPemOs4T6edK6U9Sqlzr.W1mMVCS8wJUftnW", "unicode") From 3eb5f32d3da9e8fef11bfa99562575c8af807ffe Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 6 Jan 2026 17:34:43 +0100 Subject: [PATCH 7/9] update copyright year --- radicale/auth/htpasswd.py | 2 +- radicale/tests/__init__.py | 2 +- radicale/tests/test_auth.py | 2 +- radicale/utils.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/radicale/auth/htpasswd.py b/radicale/auth/htpasswd.py index 3066abd3..3b473f28 100644 --- a/radicale/auth/htpasswd.py +++ b/radicale/auth/htpasswd.py @@ -3,7 +3,7 @@ # Copyright © 2008 Pascal Halter # Copyright © 2008-2017 Guillaume Ayoub # Copyright © 2017-2019 Unrud -# Copyright © 2024-2025 Peter Bieringer +# Copyright © 2024-2026 Peter Bieringer # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/radicale/tests/__init__.py b/radicale/tests/__init__.py index 44e86fc0..5b637159 100644 --- a/radicale/tests/__init__.py +++ b/radicale/tests/__init__.py @@ -1,7 +1,7 @@ # This file is part of Radicale - CalDAV and CardDAV server # Copyright © 2012-2017 Guillaume Ayoub # Copyright © 2017-2023 Unrud -# Copyright © 2024-2025 Peter Bieringer +# Copyright © 2024-2026 Peter Bieringer # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/radicale/tests/test_auth.py b/radicale/tests/test_auth.py index 7e4c6c14..e15fa567 100644 --- a/radicale/tests/test_auth.py +++ b/radicale/tests/test_auth.py @@ -2,7 +2,7 @@ # Copyright © 2012-2016 Jean-Marc Martins # Copyright © 2012-2017 Guillaume Ayoub # Copyright © 2017-2022 Unrud -# Copyright © 2024-2025 Peter Bieringer +# Copyright © 2024-2026 Peter Bieringer # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/radicale/utils.py b/radicale/utils.py index 17326ee5..139c6d69 100644 --- a/radicale/utils.py +++ b/radicale/utils.py @@ -2,7 +2,7 @@ # Copyright © 2014 Jean-Marc Martins # Copyright © 2012-2017 Guillaume Ayoub # Copyright © 2017-2018 Unrud -# Copyright © 2024-2025 Peter Bieringer +# Copyright © 2024-2026 Peter Bieringer # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by From 7a107ff65e879fcc10d587e6b42b07689501b594 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 6 Jan 2026 17:38:21 +0100 Subject: [PATCH 8/9] extend doc related to bcrypt vs. passlib/libpass --- DOCUMENTATION.md | 1 + 1 file changed, 1 insertion(+) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index d46667f3..0b4c6636 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -1037,6 +1037,7 @@ Available methods: * `bcrypt` This uses a modified version of the Blowfish stream cipher, which is considered very secure. The installation of Python's **bcrypt** module is required for this to work. + Also consider version of passlib(libpass): bcrypt >= 5.0.0 requires passlib(libpass) >= 1.9.3 * `md5` Use an iterated MD5 digest of the password with salt (nowadays insecure). From f676dd76214a7028672b575989fd04854c5a0d03 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 6 Jan 2026 17:41:35 +0100 Subject: [PATCH 9/9] cosmetics --- radicale/tests/test_auth.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/radicale/tests/test_auth.py b/radicale/tests/test_auth.py index e15fa567..dfa631f0 100644 --- a/radicale/tests/test_auth.py +++ b/radicale/tests/test_auth.py @@ -121,47 +121,47 @@ class TestBaseAuthRequests(BaseTest): self._test_htpasswd("autodetect", "tmp:$6$3Qhl8r6FLagYdHYa$UCH9yXCed4A.J9FQsFPYAOXImzZUMfvLa0lwcWOxWYLOF5sE/lF99auQ4jKvHY2vijxmefl7G6kMqZ8JPdhIJ/") @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed") - @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib(libpass) module") def test_htpasswd_bcrypt_2a(self) -> None: self._test_htpasswd("bcrypt", "tmp:$2a$10$Mj4A9vMecAp/K7.0fMKoVOk1SjgR.RBhl06a52nvzXhxlT3HB7Reu") @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed or incompatibe") - @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib(libpass) module") def test_htpasswd_bcrypt_2a_autodetect(self) -> None: self._test_htpasswd("autodetect", "tmp:$2a$10$Mj4A9vMecAp/K7.0fMKoVOk1SjgR.RBhl06a52nvzXhxlT3HB7Reu") @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed") - @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib(libpass) module") def test_htpasswd_bcrypt_2b(self) -> None: self._test_htpasswd("bcrypt", "tmp:$2b$12$7a4z/fdmXlBIfkz0smvzW.1Nds8wpgC/bo2DVOb4OSQKWCDL1A1wu") @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed") - @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib(libpass) module") def test_htpasswd_bcrypt_2b_autodetect(self) -> None: self._test_htpasswd("autodetect", "tmp:$2b$12$7a4z/fdmXlBIfkz0smvzW.1Nds8wpgC/bo2DVOb4OSQKWCDL1A1wu") @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed") - @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib(libpass) module") def test_htpasswd_bcrypt_2y(self) -> None: self._test_htpasswd("bcrypt", "tmp:$2y$05$oD7hbiQFQlvCM7zoalo/T.MssV3VNTRI3w5KDnj8NTUKJNWfVpvRq") @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed") - @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib(libpass) module") def test_htpasswd_bcrypt_2y_autodetect(self) -> None: self._test_htpasswd("autodetect", "tmp:$2y$05$oD7hbiQFQlvCM7zoalo/T.MssV3VNTRI3w5KDnj8NTUKJNWfVpvRq") @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed") - @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib(libpass) module") def test_htpasswd_bcrypt_C10(self) -> None: self._test_htpasswd("bcrypt", "tmp:$2y$10$bZsWq06ECzxqi7RmulQvC.T1YHUnLW2E3jn.MU2pvVTGn1dfORt2a") @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed") - @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib(libpass) module") def test_htpasswd_bcrypt_C10_autodetect(self) -> None: self._test_htpasswd("bcrypt", "tmp:$2y$10$bZsWq06ECzxqi7RmulQvC.T1YHUnLW2E3jn.MU2pvVTGn1dfORt2a") @pytest.mark.skipif(has_bcrypt == 0, reason="No bcrypt module installed") - @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib/libpass module") + @pytest.mark.skipif(not utils.passlib_libpass_supports_bcrypt()[0], reason="bcrypt module incompatible with passlib(libpass) module") def test_htpasswd_bcrypt_unicode(self) -> None: self._test_htpasswd("bcrypt", "😀:$2y$10$Oyz5aHV4MD9eQJbk6GPemOs4T6edK6U9Sqlzr.W1mMVCS8wJUftnW", "unicode")