Hey Chef, What's the Length of your Encrypted Password?

Josh Pitts

TL;DR

This post takes a quick look at Chef Data-Bags and SaltStack Pillar (GPG.Renderer) and identifies methods to determine if encrypted information leaks details about the plaintext, such as password length, that could aid an attacker.

Introduction

Does your organization, or one you are testing/auditing, use Chef Data Bags or SaltStack Pillar with the GPG.renderer to secure secrets for deployment and operations? If so, you have probably looked at these encrypted blobs of data and thought, "Good they are encrypted!" or "Darn they are encrypted!" Either way, you move on to something else more interesting. Well, let's take a closer look at the encryption algorithms and determine if we can learn something useful.

Chef Encrypted Data Bags

Chef Data Bags are JSON formatted files that store important data for just about anything that TechOps/DevOps (*Ops) would deploy to endpoint clients/servers. Encrypted data bags are just that, where the JSON formatted data goes from this: 

And with this command:

To this:

By default, data bags are encrypted using AES-256-CBC using the OpenSSL library via the knife command (written in Ruby). Already we have a couple issues: we know it's an encrypted password and we know the service (mysql). Leaking this information is not too bad, but people put these in their code repositories, then they put them online.

AES is a block cipher, so we can find the length of the encrypted data within ranges of AES block sizes (128 bits). First we find the minimum size of data for an encrypted data bag. For this I'll use a 0 byte length password such as:

With the encrypted result:

The base64 encoded data is 32 bytes long after decoding. In typical PKCS7 padding fashion, 32 bytes continues to be the encrypted data size until we hit the next block of plaintext size, and in this case it is at 13 bytes of plaintext data.

Plaintext:

 

Encrypted:

The decoded base64 string is 48 bytes as expected (AES has a 128 bit block size == 16 bytes).

Now we can extrapolate this out to plaintext size ranges:

Or, if written in Python:

 

The preceding snippet of code was added to a recursive JSON parsing script to find values of data that might be below 13 characters in length. 

This script is useful to find encrypted passwords that I consider weak. This is important because these passwords are typically used for service or administrative accounts, and as a best practice should be more than 13 characters. In my experience analyzing passwords over the years, I’ve learned that if the password is less than 13 characters, it was manually generated, not complex and easy to remember.

You can get the script here, pull requests welcome.

Also, as the encrypted data blob size increases, the estimated plaintext size margin of error decreases. This can allow an attacker to accurately guess the size of x509 certificates, SSH keys, and other data within the encrypted data bags.

SaltStack Pillar via GPG Render

SaltStack is also for *Ops style client and server management and it's written in Python. I'm going to walk through the default example for single secret encryption from SALT.RENDERS.GPG to determine if we can glean information from the encrypted data that might be useful to an attacker. The GPG Render uses GnuPG with default settings. After creating a GPG key, this is the first example:

The output would be as follows:

If you have previously used GPG/PGP, you’d be familiar with this output — it's an ASCII armored PGP message.

GPG default settings include RSA 2048 key, AES-256-CFB, and zlib compression. For this study, we're going to focus on AES-256-CFB and zlib compression. The pseudo code example of the symmetric packet:

AES-256-CFB_16(zlib(plaintext + MDC[20 byte SHA1]))

  • AES-256-CFB – Cipher Feedback Mode, 16 octets (16 bytes, or 128 bits).
  • Zlib - DEFLATE based compression
  • MDC - Modification Detection Code Packet – 20 byte SHA1 hash to verify the plaintext

 

To verify the default settings for any key, just type the following:

These settings correspond to the following diagram found on the gnupg mailing list, where first listed option is the first selection:

One way to view the format of a PGP message is via PGPDump. Just pipe the output of GPG

into PGPdump directly:

The "Symmetrically Encrypted and MDC Packet" is the part I'll be focusing on. In this instance, the data is 70 bytes. Just like with Chef Encrypted Data Bags, I'll need to find how small this packet can be with the default settings. To do so, I'll pass zero data to GPG and encrypt it.

The packet is 59 bytes vs 70 bytes with the data from before, "supersecret," being 11 bytes in length. 70 – 11 = 59.

Compression wasn't effective on this simple string of text. At what point does zlib compression become effective? Zlib uses DEFLATE to remove duplicate strings, based off of LZ77, and bit reduction via Huffman coding. It's an easy algorithm to understand, but easier to observe when the compression is applied/effective via manual testing with gzip.

The following is a test of repeating characters:

Compression becomes effective with the string “passssword” after four repeating characters. Note that a null byte string is still 20 bytes in length.

The following is a test of a four-character repeating pattern:

From this test you can see that compression becomes effective only after the pattern is complete and is four characters long.

The following is a test of two-character repeating pattern:

Again, compression becomes effective only after three repeating two character patterns.

Just a quick test in GPG to make sure the behavior is the same:

“Password” is 8 characters so 59 + 9 = 67 bytes.

“Passssword” is 10 characters and 59 + 10 != 67 bytes, with a two byte loss from compression just like the gzip example.

What does this all mean?

For years we've been drilling into our security community the concept of complex passwords and passphrases. If you subscribe to that concept, use GPG for password storage, as SaltStack recommends, the length of your password can be accurately guessed if your encrypted files are compromised.

For example, I've seen this XKCD comic many times:

And to see if compression is effective:

It's not: 28 + 59 = 87.

The same applies for complex passwords.

The key point here, if you are auditing or get access to GPG encrypted secrets and want to see their password hygiene without asking for passwords, you now have that capability. Pay attention to any Symmetric Encrypted Packets less than 71 bytes (~12 character password if complex). However, if they are using another symmetric encryption algorithm or compression, you will need use the same approach that I demonstrated to find the minimum length for that algorithm and compression method. If there is a short password in use, then an attacker could successfully brute force it.

For example, an encrypted SaltStack Pillar on GitHub:

Comes out to a likely 6 characters in length (65 – 59 = 6).

If you are using SaltStack and want to keep the length of your secrets secure, encrypt the entire pillar dictionary. One downside of this however, is that all the secrets are decrypted at once. Or if you are concerned with single use pillars for individual passwords, use long passphrases as the new NIST sp800-63 publication suggests.

Questions or feedback? Reach out to us at rex@okta.com

Josh Pitts
Principal Hacker, Offsec Team

Josh Pitts is a Principal Hacker at Okta on our offsec team. He has over 15 years' experience conducting physical and IT security assessments, IT security operations support, penetration testing, malware analysis, reverse engineering, and forensics. Josh also served in the US Marines working in SIGINT.