[PR] あなたが Kindle で読みたいその本、Kindle に対応したら Twitter でお知らせします。

Ansible Vault 機能のソースコードを読んでみた

Posted on

Ansible 1.5 から DB のパスワードや API の認証情報といった機密情報を暗号化する Vault 機能が提供されています。

あらかじめ設定したパスワードを使って機密ファイルを共通鍵認証で暗号化する機能ですが、そもそも Vault 機能を信じても大丈夫なのでしょうか? 機密情報を任せるのであれば、どういった暗号アルゴリズムで処理されているのかちゃんと理解しておきたいものです。

というわけで、ansible-vault コマンドの実装を読んでみました。今回読んだのは 5/25 にリリースされた v2.1.0.0-1 です。 GitHub 上のソースコードへリンクを貼っているので、詳しく読みたい方は参考にしてください。

Ansible のソースコードを読み解く

ansible-vault コマンドの実体は lib/ansible/cli/vault.py です。

これは CLI のインターフェイスのみで、実際の処理は import された ansible.parsing.vault のパッケージで行われています。

暗号化や復号化といった処理はすべて lib/ansible/parsing/vault/__init__.py に集約されています。処理を追いかけると ValutLib クラスの encrypt 関数で暗号アルゴリズムが選択されることがわかります。対応している暗号アルゴリズムは AESAES256 で、デフォルトは AES256 が使われます。

class VaultLib:

    def encrypt(self, data):
        (snip)

        if not self.cipher_name or self.cipher_name not in CIPHER_WRITE_WHITELIST:
            self.cipher_name = u"AES256"

        try:
            Cipher = CIPHER_MAPPING[self.cipher_name]
        except KeyError:
            raise AnsibleError(u"{0} cipher could not be found".format(self.cipher_name))
        this_cipher = Cipher()

        # encrypt data
        b_enc_data = this_cipher.encrypt(b_data, self.b_password)

最後の this_cipher.encryptVaultAES256 クラスの encrypt 関数が実行されます。

class VaultAES256:

    def encrypt(self, data, password):

        salt = os.urandom(32)
        key1, key2, iv = self.gen_key_initctr(password, salt)

        # PKCS#7 PAD DATA http://tools.ietf.org/html/rfc5652#section-6.3
        bs = AES.block_size
        padding_length = (bs - len(data) % bs) or bs
        data += to_bytes(padding_length * chr(padding_length), encoding='ascii', errors='strict')

        # COUNTER.new PARAMETERS
        # 1) nbits (integer) - Length of the counter, in bits.
        # 2) initial_value (integer) - initial value of the counter. "iv" from gen_key_initctr

        ctr = Counter.new(128, initial_value=int(iv, 16))

        # AES.new PARAMETERS
        # 1) AES key, must be either 16, 24, or 32 bytes long -- "key" from gen_key_initctr
        # 2) MODE_CTR, is the recommended mode
        # 3) counter=<CounterObject>

        cipher = AES.new(key1, AES.MODE_CTR, counter=ctr)

        # ENCRYPT PADDED DATA
        cryptedData = cipher.encrypt(data)

ここが今回の肝になるソースコードです。大まかな流れとしては、

  1. 渡ってきたパスワードと urandom で生成したソルトから key1, key2, iv を生成
  2. ブロック長に足りない部分を padding 処理
  3. Counter.newAES-CTR モードで必要なカウンターブロックを計算
  4. AES.new して AES-CTR モードで暗号化

AESCrypto.Cipher から import されたものです。

# AES IMPORTS
try:
    from Crypto.Cipher import AES as AES
    HAS_AES = True
except ImportError:
    HAS_AES = False

Crypto.Cipher は何だろうと調べてみたら PyCrypto というパッケージでした。

というわけで、Vault 機能は独自方式で暗号化されているわけではなく、PyCrypto パッケージを使って AES256 で暗号化されている ことがわかりました。

まとめ

今回ソースコードを読んでみて Vault 機能は信用しても大丈夫だと自信を持って言えるようになりました。 AES の CTR モードのように新しい知見も得られたので、ソースコードを読んでみるのは大切ですね。