Eventually you already stumbled upon the ESXi way of storing it’s local passwords. Maybe because you ran into the /etc/shadow file and realized it’s just how any Linux executes this task. Or maybe because you planned to perform a scripted install using a kickstart file, and wondered what format that --iscrypted option of the rootpw parameter expects. Whatever the reason you’re here: we’ll talk in more detail about it in this post.

Crypt is one of those system internals of any Linux that’s just there, doing it’s job, and yet - measured on todays standard - horribly documented. If you really want to understand what’s going on, you’ll quickly find yourself digging around in some ooooold C code.

Digging deeper

It doesn’t end there. Because Crypt is supporting many different hash algorithms they invented the Modular Crypt Format, a format specification used for the Crypt hash string. If you ever saw the contents of a typical Linux shadow file (this is true for ESXi as well), then you’ve probably already seen something like this:

$1$mysalt$4Lz7hS.y2V54mV2gJXEKR

The spec defines the format as ${identifier}$ followed by implementation specific data, sections separated by $. The example show above it’s the md5_crypt implementation. You can identify it because of the 1 used as identifier. It is then followed by the salt, ‘mysalt’ in this example, and finally by the md5-hashed password (can you guess it?). Here’s a short overview of some of the most common identifiers:

Hash Algo. Identifier
MD5 $1
SHA-256 $5
SHA-512 $6

Modular is nice in general. But by now, because of many different versions of Crypt that support different hash algorithms, the Crypt space has become a real crypt for tons of deprecated legacy hash algorithms such as des_crypt or md5_crypt and multiple others. As if this wouldn’t be enough, the Modular Crypt Format is beeing replaced by the more formally specified PHC string format.

So, ok: depending on the OS and version you’re on, different algorithms are used and supported. For ESXi 6.7 it’s sha512_crypt. Notice that this is not simply a SHA512 hash of your password. The resulting string contains metadata about itself as well as an salt and must satisfy some other requirements. You can read all about it in the Linux source or in the specification.

If you’re on a up-to-date Linux system or on ESXi 6.7, you may use system tools such as crypt itself to create a valid sha512_crypt hash. But what if you’re on a different OS? What if your crypt doesn’t support the SHA512 algorithm? What if you want to automate things (and can’t use esxcli or other options)?

Sidenote: if you happen to run across one of those websites that offer to create a password hash for you I hope I don’t have to explain why this is a very, very bad idea.

Python to the rescue

So, we want a OS independent and possibly automated method to generate ESXi compliant passwords. Python is a good pick for this task because:

  • It’s OS independent
  • Is pre-installed in ESXi

and most importantly:

  • Kicks ass

So, let’s go. First, we’ll have to grab passlib, a fantastic module for Python that comes packed with all the hashes you’ll ever need, yet with no dependencies by default. You might find others suggesting to just use the Python standard crypt module for this task. But, as you can read from the documentation, the Python crypt module is only available on UNIX platforms. Go ahead and install passlib (manual install if on ESXi):

pip install passlib

Also we’ll be using the standard Python secrets module to create secure random passwords, rather then just using random (which is not guaranteed to provide enough entropy). This is optional in case you want to choose your password by yourself of course.

import string
import secrets
from passlib.hash import sha512_crypt as sha512

# Charset for the password should have some complexity
symbols = "!#$%&()+-.:[email protected]"
passset = string.ascii_letters + string.digits + symbols

# Charset for the salt is limited to [./0-9A-Za-z] by the crypt spec
saltset = string.ascii_letters + string.digits

# Create a random password with some guarantees
# 16 characters long
# Stats and ends with a letter (optional)
# At least one lower and one upper letter
# At least two digits
# At least one symbol
while True:
  passwd = ''.join(secrets.choice(passset) for i in range(16))
    if (any(passwd.startswith(c) for c in string.ascii_letters)
      and any(passwd.endswith(c) for c in string.ascii_letters)
      and any(c.islower() for c in passwd)
      and any(c.isupper() for c in passwd)
      and any(c in symbols for c in passwd)
      and sum(c.isdigit() for c in passwd) >= 2):
      break

# Salt must be between 0 and 16 characters from saltset
saltwd = ''.join(secrets.choice(saltset) for i in range(12))

# ESXi is using the sha512_crypt variant
# Rounds are set to 5000 as an requirements for implicit_rounds, see passlib documentation
if (passwd and saltwd):
    sha512_passwd = sha512.encrypt(secret=passwd, salt=saltwd, rounds=5000)
    sha512.verify(secret=passwd, hash=sha512_passwd)

The generated string should look something like this:

>>> print(sha512_passwd)
$6$Zl0IGes7S59O$ny69awyOpYEOybUclZ4wMR9WdgqI9WB27eJ8zcSoXzvIrVGjvn3.GgXSn.2grp3iG.G3fKP1rSqj8hovNxiZ4/

That’s it for today. While the code is trivial, I hope I could provide some insights on crypt and how ESXi is using it. Happy automation!