John Davidson

python - Adapt Ansible-Vault via PHP


I wrote a dynamic inventory with PHP and a DB. Now I want include some credentials in it. Typically I use ansible-vault and generate the string, put them into the database and only forward the content to the inventory. But sometimes I've credentials (for example from a customer input), that are stored more or less plain in the database. But I don't want to post them plain to the JSON inventory output.

So, PHP needs to generate the ansible-vault result itself.

I looked into the code of ansible vault and I'm sure, Im nearly finished, but I don't understand how Ansible-Vault is converting the binary into the numbers.


* Add header
* @param string $ciphertext the encrypted and hexlified data as a byte string
* @param string $cipher unicode cipher name (for ex, 'AES256')
* @param string $vaultId unicode vault version (for ex, '1.2'). Optional ('1.1' is default)
* @return string a byte str that should be dumped into a file.
function vault_format(string $ciphertext, string $cipher = "AES256", string $vaultId = "1.1") :string {
return "\$ANSIBLE_VAULT;".$vaultId.";".$cipher."\n".$ciphertext;

* Python ansible-vault original
def _create_key_cryptography(b_password, b_salt, key_length, iv_length):
length=2 * key_length + iv_length,
b_derivedkey = kdf.derive(b_password)

return b_derivedkey
function create_key(string $secret, string $salt, int $keyLength, int $iv_length) :string {
return openssl_pbkdf2($secret, $salt, 2* $keyLength+$iv_length, 10000, 'sha256');
// return hash_pbkdf2("sha256", $secret, $salt, 10000, 2* $keyLength + $iv_length);

def _gen_key_initctr(cls, b_password, b_salt):
# 16 for AES 128, 32 for AES256
key_length = 32

# AES is a 128-bit block cipher, so IVs and counter nonces are 16 bytes
iv_length = algorithms.AES.block_size // 8

b_derivedkey = cls._create_key_cryptography(b_password, b_salt, key_length, iv_length)
b_iv = b_derivedkey[(key_length * 2):(key_length * 2) + iv_length]
# match the size used for to avoid extra work
iv_length = 16

b_derivedkey = cls._create_key_pycrypto(b_password, b_salt, key_length, iv_length)
b_iv = hexlify(b_derivedkey[(key_length * 2):(key_length * 2) + iv_length])
raise AnsibleError(NEED_CRYPTO_LIBRARY + '(Detected in initctr)')

b_key1 = b_derivedkey[:key_length]
b_key2 = b_derivedkey[key_length:(key_length * 2)]

return b_key1, b_key2, b_iv
function generate_key_initctr(string $cipher, string $secret, string $salt) :array {
$key = array();

$key_length = 32;

// AES
$iv_length = openssl_cipher_iv_length($cipher);
$derivedkey = create_key($secret,$salt,$key_length,$iv_length);

$iv = substr($derivedkey,$key_length*2,$iv_length);

$key["key1"] = substr($derivedkey,0,$key_length);
$key["key2"] = substr($derivedkey, $key_length,$key_length);
$key["iv"] = $iv;
return $key;

b_salt = os.urandom(32)
b_password = secret.bytes
b_key1, b_key2, b_iv = cls._gen_key_initctr(b_password, b_salt)

b_hmac, b_ciphertext = cls._encrypt_cryptography(b_plaintext, b_key1, b_key2, b_iv)
b_hmac, b_ciphertext = cls._encrypt_pycrypto(b_plaintext, b_key1, b_key2, b_iv)
raise AnsibleError(NEED_CRYPTO_LIBRARY + '(Detected in encrypt)')

b_vaulttext = b'\n'.join([hexlify(b_salt), b_hmac, b_ciphertext])
# Unnecessary but getting rid of it is a backwards incompatible vault
# format change
b_vaulttext = hexlify(b_vaulttext)
return b_vaulttext
function encrypt(string $cipher, string $plaintext, string $secret) :string {
// $salt = openssl_random_pseudo_bytes($ivlen);
$salt = openssl_random_pseudo_bytes(32);

$key = generate_key_initctr($cipher, $secret, $salt);

echo "key: ".bin2hex($key["iv"])."\n";

$ciphertext_raw = openssl_encrypt($plaintext, $cipher, $secret, OPENSSL_RAW_DATA, $key["iv"]);
$hmac = hash_hmac('sha256', $ciphertext_raw, $secret, true);

$salt = bin2hex($salt);
$hmac = bin2hex($hmac);
$crpt = bin2hex($ciphertext_raw);

$vault = $salt."\n".$hmac."\n".$crpt;
$ciphertext = bin2hex( $vault );

return vault_format($ciphertext);

def decrypt(cls, b_vaulttext, secret):

b_ciphertext, b_salt, b_crypted_hmac = parse_vaulttext(b_vaulttext)

b_password = secret.bytes

b_key1, b_key2, b_iv = cls._gen_key_initctr(b_password, b_salt)

b_plaintext = cls._decrypt_cryptography(b_ciphertext, b_crypted_hmac, b_key1, b_key2, b_iv)
b_plaintext = cls._decrypt_pycrypto(b_ciphertext, b_crypted_hmac, b_key1, b_key2, b_iv)
raise AnsibleError(NEED_CRYPTO_LIBRARY + '(Detected in decrypt)')

return b_plaintext
function decrypt(string $cipher, string $vault, string $secret) :string {
$vault_parts = explode("\n",hex2bin($vault));
$salt = hex2bin($vault_parts[0]);
$hmac = hex2bin($vault_parts[1]);
$crpt = hex2bin($vault_parts[2]);

$key = generate_key_initctr($cipher, $secret, $salt);

echo "key: ".bin2hex($key["iv"])."\n";

$plaintext = openssl_decrypt($crpt, $cipher, $secret, OPENSSL_RAW_DATA, $key["iv"]);

return $plaintext;

// --------

// $ciphers = openssl_get_cipher_methods();
// print_r($ciphers);

$ciphers = array();
// $ciphers[]="aes-256-ccm";

// ansible-vault encrypt_string --vault-password-file test.pass 'hello world'
$plaintext = "hello world";
$secret = "my-secret-password";
$encrypted = "646135396134386432636334623638646362366562653535346133643330616263323033616132333164366134393134353061306235326165343863383264390a653536663236643463633032613437363066313565336332616331646364383730333064353966653436386662323734373864333936386332653835656534310a3433353564373736303038346663396536346330303664616139383632313363";

echo "$plaintext\n";
foreach ($ciphers as $cipher) {
echo "\n\ncipher: $cipher\n";

echo "decrypted:".decrypt($cipher, $encrypted, $secret)."\n";
echo "encrypted:".encrypt($cipher, $plaintext, $secret)."\n";

As you can see, I've created first an example and try to decrypt the code.
The output is something like

hello world

cipher: aes-256-cbc
key: f5c682ba666e1007a14d6f0ecdc76388
key: 2ed49bd57d0c93f14889a7f7d21a21c0

cipher: aes-256-cbc-hmac-sha1
key: f5c682ba666e1007a14d6f0ecdc76388
decrypted: <binary-output>
key: ecf4c8215533e8fd672a67314d8c7ed0

cipher: aes-256-cbc-hmac-sha256
key: f5c682ba666e1007a14d6f0ecdc76388
decrypted: <binary-output>
key: 0981caf5fb38c63791091d10fc7ea5a6

cipher: aes-256-cfb
key: f5c682ba666e1007a14d6f0ecdc76388
decrypted: <binary-output>
key: 15eca76feb2bfff3341b683928a84557

So I would expect that the correct algoritm shows me directly in PHP, when it was able to decrypt the string. Also I wrote a small playbook just to test the PHP result.

But when I try it with an example playbook like:

- name: My Test
hosts: localhost
gather_facts: false
test: !vault |
- debug:
msg: "{{ test }}"

The following error occures:

PLAY [My Test] **********************************************************************************************************************************************************************************************************************

TASK [debug] ************************************************************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! =>
msg: Decryption failed (no vault secrets were found that could decrypt)

PLAY RECAP **************************************************************************************************************************************************************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0

With -vvvvvvv I'm getting

Tried to use the vault secret (default) to decrypt (None) but it failed. Error: HMAC verification failed: Signature did not match digest.

So, neither decrypting nor encrypting is really working. I'm not really sure, what cipher method is used (that's the reason for the loop over all AES256 cipher). Also is there a pendant for PBKDF2HMAC in PHP (tried openssl_pbkdf2 and hash_pbkdf2, I think, there is also a problem with the correct key? And I've problems to understand the Python code. Can the Python code

b_derivedkey[(key_length * 2):(key_length * 2) + iv_length]

be replaced in PHP with?

substr($derivedkey,$key_length * 2, $iv_length);

And maybe the last problem is the "unused key1 and key2. The original Python code contains this encrypt:

def _encrypt_cryptography(b_plaintext, b_key1, b_key2, b_iv):
cipher = C_Cipher(algorithms.AES(b_key1), modes.CTR(b_iv), CRYPTOGRAPHY_BACKEND)
encryptor = cipher.encryptor()
padder = padding.PKCS7(algorithms.AES.block_size).padder()
b_ciphertext = encryptor.update(padder.update(b_plaintext) + padder.finalize())
b_ciphertext += encryptor.finalize()

I replaced it with:

openssl_encrypt($plaintext, $cipher, $secret, OPENSSL_RAW_DATA, $key["iv"]);

The Cipher is unknown to me? key1 is some parts of the generated key from PBKDF2HMAC. That sounds strange to me. What does it mean?

My Answer:
To adapt Ansible-Vault functionality in PHP, you can use the `openssl_encrypt` function to encrypt the credentials before storing them in the database. Here is an example of how you can achieve this:

// Credentials to be encrypted
$credentials = 'my_secret_password';

// Encryption key (should be securely stored)
$key = 'my_secret_key';

// Encrypt the credentials using AES-256-CBC encryption
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
$encrypted = openssl_encrypt($credentials, 'aes-256-cbc', $key, 0, $iv);

// Store the encrypted credentials and IV in the database
// Make sure to store $encrypted and $iv in separate columns

// Output the encrypted credentials in ansible-vault format
echo "!\n";
echo base64_encode($iv) . "\n";
echo base64_encode($encrypted) . "\n";

This code snippet encrypts the credentials using AES-256-CBC encryption and outputs them in ansible-vault format. You can then store the encrypted credentials and IV in the database and retrieve them when needed.

When decrypting the credentials in your PHP script, you can use the `openssl_decrypt` function with the stored IV and encryption key to decrypt the credentials.

Remember to securely store the encryption key and follow best practices for handling sensitive information.

Rate this post

3 of 5 based on 6922 votes


© 2024 - Personal Blogs Platform. All Rights Reserved.
Create blog  |  Privacy Policy  |  Terms & Conditions  |  Contact Us