From 6ef3f1e598a70eeeb0de96c71d20d49922b2e707 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Wed, 9 Oct 2024 19:39:03 +0900 Subject: [PATCH] Add utility to replace crypt.crypt The crypt module was removed in Python 3.13. Add the utility function to replace crypt.crypt which is used by a few projects. Note that the function internally calls libcrypt library, so that we don't have to re-implement its algorithm in python layer. Change-Id: Ibe6748068b8145fdc7ece384c94a923d1032de5f --- bindep.txt | 4 ++++ oslo_utils/secretutils.py | 24 ++++++++++++++++++++++++ oslo_utils/tests/test_secretutils.py | 9 +++++++++ 3 files changed, 37 insertions(+) diff --git a/bindep.txt b/bindep.txt index 785a6cbc..cc894088 100644 --- a/bindep.txt +++ b/bindep.txt @@ -9,3 +9,7 @@ python3-devel [platform:rpm] qemu-img [platform:redhat test] qemu-tools [platform:suse test] qemu-utils [platform:dpkg test] + +libxcrypt-compat [platform:redhat crypt] +libcrypt1 [platform:suse crypt] +libcrypt1 [platform:dpkg crypt] diff --git a/oslo_utils/secretutils.py b/oslo_utils/secretutils.py index 2c5970dd..62b8bfa6 100644 --- a/oslo_utils/secretutils.py +++ b/oslo_utils/secretutils.py @@ -18,6 +18,8 @@ Secret utilities. .. versionadded:: 3.5 """ +import ctypes +import ctypes.util import hashlib import hmac @@ -40,3 +42,25 @@ def md5(string=b'', usedforsecurity=True): See https://bugs.python.org/issue9216 """ return hashlib.md5(string, usedforsecurity=usedforsecurity) # nosec + + +if ctypes.util.find_library("crypt"): + _libcrypt = ctypes.CDLL(ctypes.util.find_library("crypt"), use_errno=True) + _crypt = _libcrypt.crypt + _crypt.argtypes = (ctypes.c_char_p, ctypes.c_char_p) + _crypt.restype = ctypes.c_char_p +else: + _crypt = None + + +def crypt_password(key, salt): + """Encrtpt password string and generate the value in /etc/shadow format + + This is provided as a replacement of crypt.crypt method because crypt + module was removed in Python 3.13. + + .. versionadded:: 7.5 + """ + if _crypt is None: + raise RuntimeError('libcrypt is not available') + return _crypt(key.encode('utf-8'), salt.encode('utf-8')).decode('utf-8') diff --git a/oslo_utils/tests/test_secretutils.py b/oslo_utils/tests/test_secretutils.py index 4b364820..ce6ecdd8 100644 --- a/oslo_utils/tests/test_secretutils.py +++ b/oslo_utils/tests/test_secretutils.py @@ -77,3 +77,12 @@ class SecretUtilsTest(testscenarios.TestWithScenarios, TypeError, secretutils.md5, None, usedforsecurity=True) self.assertRaises( TypeError, secretutils.md5, None, usedforsecurity=False) + + def test_password_crypt(self): + self.assertEqual( + '$5$mysalt$fcnMdhaFpUmeWtGOgVuImueZGL1v0Q1kUVbV2NbFOX4', + secretutils.crypt_password('mytopsecret', '$5$mysalt$')) + self.assertEqual( + '$6$mysalt$jTEJ24XtvcWmav/sTQb1tYqmk1kBQD/sxcMIxEPUcie' + 'J8L9AuCTWxYlxGz.XtIQYWspWkUXQz9zPIFTSKubP6.', + secretutils.crypt_password('mytopsecret', '$6$mysalt$'))