On 03/02/2014 08:49 PM, David Anderson wrote:
Greetings,

I've been looking at the crypto that underlies ansible-vault, and I'm worried. Specifically, it seems to me that the vault as implemented is not safe for credential storage.

If you haven't read the implementation, this is essentially what happens when you encrypt a file with ansible-vault:

  * The plaintext is prepended with a SHA256 hash of itself,
  * A random salt is generated,
  * The vault password and salt are used to derive an AES key and IV,
    using a python implementation of openssl's EVP_BytesToKey(),
  * The plaintext-with-hash is encrypted with AES in CBC mode, using
    the above key and IV,
  * This ciphertext is hexlified and stored.


Looking at the comments in the code, this is a python reproduction of what `openssl aes-256-cbc -salt` does, except for the SHA256 hash bit which is an "aftermarket" integrity measure.

The biggest concern I have about this code is that these blobs are /much/ easier to bruteforce than the "AES-256" would lead you to believe. This is because openssl's EVP_BytesToKey() is a poor KDF, one which does not make the key derivation expensive in CPU and/or memory. As a result, it is very cheap to test a candidate password: four MD5 operations on small inputs, a few AES-256 block operations, and a SHA-256 hash. All these operations are easy to hardware-accelerate, either with modern CPUs (AES-NI) or GPUs.

This dramatically reduces the search space from 32 bytes if brute-forcing the AES key (64 bytes if the IV isn't included in the ciphertext, which I haven't checked), to the number of bytes in the password. Those bytes are also very likely to be in a small set of values (letters+numbers, maybe symbols if you're lucky), further reducing the search space for bruteforcing.

A good KDF would close this avenue of attack by making the key derivation so expensive that it's cheaper to bruteforce the AES key. Unfortunately, EVP_BytesToKey() is not such a KDF. Its documentation in OpenSSL even recommends using better KDFs, such as PBKDF2 or scrypt, for designs which don't specifically require BytesToKey.

So, it seems to me that ansible-vault blobs are not safe to expose to untrusted people, because brute-forcing them in an offline attack is much easier than it would seem. This is a problem, because if only trusted people have access to the blobs, you might as well just have the sensitive data in cleartext.


Further concerns about the implementation:

  * SHA-256 is used as an authentication code, but isn't one.
  * The encoding is constructed as "mac-then-encrypt", whereas
    encrypt-then-mac is the safer default, because it minimizes your
    code's exposure to hostile inputs. This is relatively minor
    compared to the effective lack of MAC.
  * The hash check on decryption is not constant-time, which opens up
    a timing side-channel.
  * The core of the implementation seems lifted verbatim from a pair
    of Stack Overflow answers. This is concerning in two ways:
      o The question being answered was "how do I reproduce this one
        specific behavior of the openssl CLI in python?", not "What's
        a good way to securely store sensitive data at rest, where
        attackers can perform offline attacks at will?"
      o The unit tests only verify that the implementation is
        internally consistent (M == decrypt(encrypt(M)) essentially),
        not that it matches the openssl behavior it's copying. While
        the primitives are delegated to pycrypto, there could be bugs
        lurking in the glue around the primitives. Since this is a
        non-standard combination of primitives, there are no canonical
        test inputs you can check against.


I should say that I'm not a crypto expert, merely an enthusiastic amateur. However, my spidey sense is tingling pretty hard in light of all the above. I'm kinda hoping that I've overlooked something obvious that makes this all safe, but that's a lot of distinct concerns to address :/.


Not wishing to be just a downer, I have suggestions for safer vault implementations:

  * Derive keys with PBKDF#2, use NaCl's secretbox() for encryption
    and decryption. Secretbox implements correct and fast
    authenticated encryption, and PBKDF#2 will severely slow down
    trivial bruteforcing attacks. Pynacl provides Python bindings for
    NaCl, and pycrypto provides PBKDF#2.
  * If a dependency on pynacl is not desired, use AES-GCM to perform
    authenticated encryption. AES-GCM will be available in the
    upcoming pycrypto release.
  * For something that uses only current pycrypto, AES-CTR combined
    with an HMAC-SHA256 authentication code. However, this is starting
    to drift back into the territory of manually gluing primitives
    together in new and exciting ways (although AES-CTR+HMAC-SHA256 is
    not exactly off the beaten path), which increases the risk.

I'd be more than happy to provide a vault implementation for the first option, and the commandline plumbing to enable selection of vault implementations, if it would be helpful. I wouldn't trust myself to implement the other two without oversight from an expert, unfortunately.

- Dave
--
You received this message because you are subscribed to the Google Groups "Ansible Project" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/d/msgid/ansible-project/CAMx%2Br7W47tp%3Dmn6EwsO_WcuZfs7ujAgzYBQXhV2VE1M%3Dqaz4GA%40mail.gmail.com.
For more options, visit https://groups.google.com/groups/opt_out.

Hi David, thanks for the review! I was really hoping to get this sort of feedback prior to the release, but we can always improve vault in future releases.

As I am self admittedly -not- a cryptologist, I wrote a framework that would allow for others to contribute better cipher methods. If you have something in mind, please write up a pull request for a new cipher class in vault.py and I will gladly review/merge.

I would really be interested in seeing an alternative to what you've described in:

"A good KDF would close this avenue of attack by making the key derivation so expensive that it's cheaper to bruteforce the AES key."

--
You received this message because you are subscribed to the Google Groups "Ansible 
Project" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/ansible-project/53148D97.6070700%40gmail.com.
For more options, visit https://groups.google.com/groups/opt_out.

Reply via email to