Skip to content

Instantly share code, notes, and snippets.

@cfra
Created February 16, 2026 14:43
Show Gist options
  • Select an option

  • Save cfra/a839043328c0f690b43eed44ac7130d5 to your computer and use it in GitHub Desktop.

Select an option

Save cfra/a839043328c0f690b43eed44ac7130d5 to your computer and use it in GitHub Desktop.
Create crypt(3) passwords with Python >= 3.13
#!/usr/bin/env python
#
# Copyright 2026 Christian Franke <nobody@nowhere.ws>
#
# Licensed 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.
#
# This small script was created as Python 3.13 dropped the crypt module and
# Python is my ad-hoc way to call the crypt(3) function of the OS.
#
# The script can either be imported as module, then it exposes the crypt
# and crypt_gensalt methods of the underlying OS.
#
# It can also be executed directly, in this case, it will either take a password
# passed as argument or one provided interactively and hash it.
#
import cffi
import getpass
import os
import sys
_ffi = cffi.FFI()
_ffi.cdef("char *crypt(const char *phrase, const char *setting);")
_ffi.cdef("char *crypt_gensalt(const char *prefix, unsigned long count, const char *rbytes, int nrbytes);")
_crypt = _ffi.dlopen("crypt")
def crypt(phrase, setting=None):
if hasattr(phrase, "encode"):
phrase = phrase.encode('utf8')
if hasattr(setting, "encode"):
setting = setting.encode('ascii')
rv = _crypt.crypt(phrase, setting)
if rv == _ffi.NULL:
raise OSError(_ffi.errno, os.strerror(_ffi.errno))
return _ffi.string(rv).decode('ascii')
def crypt_gensalt(prefix, count):
if hasattr(prefix, "encode"):
prefix = prefix.encode('ascii')
if prefix is None:
prefix = _ffi.NULL
rv = _crypt.crypt_gensalt(prefix, count, _ffi.NULL, 0)
if rv == _ffi.NULL:
raise OSError(_ffi.errno, os.strerror(_ffi.errno))
return _ffi.string(rv).decode('ascii')
if __name__ == '__main__':
if len(sys.argv) == 2:
password = sys.argv[1]
else:
password = getpass.getpass()
print(crypt(password, crypt_gensalt("$6$", 65536)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment