Warning: I am not an expert. This library is purely for my own curiosity. Do not use it in production.
- We try to use as much third party code as possible. This library is basically just a wrapper around CryptoPP with some glue code to read and write ansible vault files.
- We use SecureArray and SecureString which clear the array when released, whenever we store sensitive values such as the vault content, password, salt, hmac, key, and iv.
- Does not support the Vault ID part of the ansible vault format.
- Apart from some basic unit tests this is basically untested code. It is basically experimental code, do not trust it with your secrets!
- libansible-vault-cpp is just a library that can encrypt/decrypt ansible vault files, it is not a replacement for the ansible-vault tool, that tool is a fully featured tool for working with ansible vault files.
- https://docs.ansible.com/ansible/latest/cli/ansible-vault.html
- https://github.com/Wedjaa/JavaAnsibleVault
- https://github.com/vermut/intellij-encryption
- https://github.com/pbthorste/avtool
- https://github.com/AGWA/git-crypt
- https://github.com/getsops/sops
- https://github.com/dani-garcia/vaultwarden
- https://www.hashicorp.com/products/vault
sudo yum install cryptopp-devel gtest-devel
gcc
cmake .
make
Clang
rm -rf CMakeFiles CMakeCache.txt cmake_install.cmake
CC=/usr/bin/clang CXX=/usr/bin/clang++ cmake .
make
Generates libansible-vault-cpp.so[.0.1] and ansible-vault-cpp_test.
./ansible-vault-cpp_test
cd fuzz
cmake .
make
There are some issues with fuzzing because the format is slightly complicated:
- There are 3 sections, the header has to have the right format, the salt, password, and encrypted content all have to match
- Apart from the few samples that we supply to the fuzzer as a starting point, it is basically never going to generate inputs that are a valid ansible vault file, parsing is almost always going to early exit
fuzz_ansible_vault_password: The input is interpretted as a password and is used to decrypt a standard ansible vault file, some sample passwords are supplied from fuzz/sample_passwords/
fuzz_ansible_vault_decrypt: The input is interpretted as a standard ansible vault file, two attempts to decrypt it with two passwords are tried, some invalid and valid files are supplied from fuzz/sample_ansible_vault/
Running fuzz_ansible_vault_password for example:
mkdir -p ./corpus/fuzz_ansible_vault_password/
./fuzz_ansible_vault_password -runs=500000 -fork=4 -max_len=1000 ./corpus/fuzz_ansible_vault_password ./sample_passwords
Running fuzz_ansible_vault_decrypt for example:
mkdir -p ./corpus/fuzz_ansible_vault_decrypt/
./fuzz_ansible_vault_decrypt -runs=100000000 -fork=4 -max_len=4000 ./corpus/fuzz_ansible_vault_decrypt/ ./sample_ansible_vault/
Set a maximum length of the test inputs:
-max_len=1000
Set the number of concurrent processes:
-fork=4
Set a maximum number of iterations to run:
-runs=500
NOTE: These examples are lacking error checking
Link to the library in your cmake file:
TARGET_LINK_LIBRARIES(myapplication ansible-vault-cpp)
Encrypt with the library
#include <iostream>
#include <fstream>
#include "ansible_vault.h"
void EncryptVaultFile()
{
// NOTE: SecureString is meant to be pervasive. Sensitive data and passwords should be cleared from memory before release
const std::string plain_text_str = "My encrypted text.\nAnd another line.\n"; // Take care of where you get this string from and store it
const vault::SecureString plain_text(plain_text_str.c_str(), plain_text_str.length());
const std::string password_str = "mytestpassword"; // Take care of where you get this string from and store it
const vault::SecureString password(password_str.c_str(), password_str.length());
const std::string salt_utf8 = "ed3496252ad601cf571ac38eab55544f";
vault::SecureArray<uint8_t, 32> salt;
CopyStringToBytes(salt_utf8, salt);
std::ostringstream encrypted;
const vault::ENCRYPT_RESULT encryption_result = vault::encrypt(plain_text, password, salt, encrypted);
if (encryption_result != vault::ENCRYPT_RESULT::OK) {
std::cerr<<"Error encrypting the string"<<std::endl;
} else {
std::cout<<"Successfully encrypted the string"<<std::endl;
// Write the ansible vault content out to a file
{
std::ofstream out("myvault");
out<<encrypted.str()<<std::endl;
}
std::cout<<"Wrote output to file"<<std::endl;
}
}
Decrypt with the library
#include <iostream>
#include <fstream>
#include "ansible_vault.h"
void DecryptVaultFile()
{
std::string encrypted;
{
std::ifstream file("myvault");
encrypted = ((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
}
// NOTE: SecureString is meant to be pervasive. Sensitive data and passwords should be cleared from memory before release
const std::string password_str = "mytestpassword"; // Take care of where you get this string from and store it
const vault::SecureString password(password_str.c_str(), password_str.length());
vault::SecureString decrypted;
const vault::DECRYPT_RESULT decryption_result = vault::decrypt(encrypted, password, decrypted);
if (decryption_result != vault::DECRYPT_RESULT::OK) {
std::cerr<<"Error decrypting the vault"<<std::endl;
} else {
std::cout<<"Successfully decrypted the vault"<<std::endl;
std::cout<<"Vault content: \""<<decrypted.c_str()<<"\""<<std::endl;
}
}
$ANSIBLE_VAULT;1.1;AES256
65333363656231663530393762613031336662613262326666386233643763636339366235626334
3236636366366131383962323463633861653061346538360a386566363337383133613761313566
31623761656437393862643936373564313565663633636366396231653131386364336534626338
3430343561626237660a333562616537623035396539343634656439356439616439376630396438
3730
$ANSIBLE_VAULT;1.2;AES256;myvaultid
30613233633461343837653833666333643061636561303338373661313838333565653635353162
3263363434623733343538653462613064333634333464660a663633623939393439316636633863
61636237636537333938306331383339353265363239643939666639386530626330633337633833
6664656334373166630a363736393262666465663432613932613036303963343263623137386239
6330
The encryption process is basically:
- Generate a salt
- Use PKCS5 PBKDF2 HMAC with the salt and password to derive the encryption key, HMAC key, and IV
- Derived encryption key and IV are used to key a block cypher for AES256 encryption
- Create HMAC with SHA256 from the encrypted data
- Combine hex(salt) + '\n' + hex(hmachash) + '\n' + hex(encrypted)
- hex(combined)
Decryption is basically the reverse:
- unhex(combined)
- Split parts salt, hmachash, encrypted
- unhex(salt), unhex(hmachash), unhex(encrypted)
- Use PKCS5 PBKDF2 HMAC with the salt and password to derive the encryption key, HMAC key, and IV
- Verify HMAC with SHA256 for the the encrypted data matches the expected hmachash
- Derived encryption key and IV are used to key a block cypher for AES256 decryption
Standard Red Hat ansible-vault Executable Usage for Testing Compatibility (Not libansible-vault-cpp)
https://stackoverflow.com/questions/43467180/how-to-decrypt-string-with-ansible-vault-2-3-0
Password: 123
echo '$ANSIBLE_VAULT;1.2;AES256;dev
30613233633461343837653833666333643061636561303338373661313838333565653635353162
3263363434623733343538653462613064333634333464660a663633623939393439316636633863
61636237636537333938306331383339353265363239643939666639386530626330633337633833
6664656334373166630a363736393262666465663432613932613036303963343263623137386239
6330' | ansible-vault decrypt
Encrypt:
ansible-vault encrypt vars/vault.yaml
Decrypt:
ansible-vault decrypt vars/vault.yaml
Encrypt:
echo "mypassword" > password.txt
echo "My plain text file\nMultiple lines\n" > plaintext.txt
ansible-vault encrypt --vault-password-file password.txt --output output_encrypted.txt plaintext.txt
OR
(Asks for password, which is "mypassword")
echo "My plain text file\nMultiple lines\n" > plaintext.txt
ansible-vault encrypt --output output_encrypted.txt plaintext.txt
Decrypt:
ansible-vault decrypt --vault-password-file password.txt --output output_decrypted.txt output_encrypted.txt
OR
(Asks for password, which is "mypassword")
ansible-vault decrypt --output sample_decrypted.txt test/data/sample.txt