Created
May 3, 2022 19:09
-
-
Save kgaughan/9b7b318600f0c860771a4de55d1286d3 to your computer and use it in GitHub Desktop.
Decrypting ansible-vault files without ansible-vault
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import binascii | |
import sys | |
import typing | |
from cryptography.hazmat.primitives import ciphers, hashes, hmac, padding | |
from cryptography.hazmat.primitives.ciphers import algorithms | |
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC | |
def vault_parse(lines: typing.List[str]) -> typing.Tuple[bytes, bytes, bytes]: | |
parts = binascii.unhexlify("".join(lines[1:])).decode("ascii").split("\n") | |
return ( | |
binascii.unhexlify(parts[0]), # salt | |
binascii.unhexlify(parts[1]), # encrypted HMAC | |
binascii.unhexlify(parts[2]), # ciphertext | |
) | |
def vault_create_derived_key( | |
passwd: bytes, | |
salt: bytes, | |
key_length: int, | |
iv_length: int, | |
) -> bytes: | |
kdf = PBKDF2HMAC( | |
algorithm=hashes.SHA256(), | |
length=2 * key_length + iv_length, | |
salt=salt, | |
iterations=10000, | |
) | |
return kdf.derive(passwd) | |
def vault_initialise_key_init_iv( | |
passwd: bytes, | |
salt: bytes, | |
) -> typing.Tuple[bytes, bytes, bytes]: | |
key_length = 32 # AES256 | |
iv_length = algorithms.AES.block_size // 8 | |
derived_key = vault_create_derived_key(passwd, salt, key_length, iv_length) | |
iv = derived_key[(key_length * 2) : (key_length * 2) + iv_length] | |
key1 = derived_key[:key_length] | |
key2 = derived_key[key_length : (key_length * 2)] | |
return key1, key2, iv | |
def vault_decrypt(contents: typing.List[str], passwd: bytes) -> bytes: | |
""" | |
Decrypts your vault token found at the parameter passed in. | |
The token is decrypted with ansible-vault. | |
Returns the unencrypted github token. | |
""" | |
# This is very brute force | |
if len(contents) == 0 or contents[0] != "$ANSIBLE_VAULT;1.1;AES256": | |
sys.exit("Cannot parse vault") | |
# This is the reverse of the algorithm given at | |
# https://docs.ansible.com/ansible/latest/user_guide/vault.html | |
salt, encrypted_hmac, ciphertext = vault_parse(contents) | |
key1, key2, iv = vault_initialise_key_init_iv(passwd, salt) | |
# Validate ciphertext against the HMAC | |
hmac_o = hmac.HMAC(key2, hashes.SHA256()) | |
hmac_o.update(ciphertext) | |
hmac_o.verify(encrypted_hmac) | |
# Decrypt the ciphertext using AES256+CTR | |
cipher = ciphers.Cipher( | |
ciphers.algorithms.AES(key1), | |
ciphers.modes.CTR(iv), | |
) | |
decryptor = cipher.decryptor() | |
unpadder = padding.PKCS7(128).unpadder() | |
return ( | |
unpadder.update(decryptor.update(ciphertext) + decryptor.finalize()) | |
+ unpadder.finalize() | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note that this isn't entirely my work, and it's a bit rough and ready. It's derived from the source of Ansible itself, so should be considered subject to the same license.