🕌

APIキーを管理してくれるシェルスクリプトを書いてみた

に公開

今回は私が普段利用しているシェルスクリプトを紹介しようと思います。

紹介するもの!

今回紹介するのはAPIキーを管理してくれる機能になります。私は普段ブログを書くにあたり様々なサービスを利用しており、各サービスごとにAPIキーの利用があります。例えばAPIキーを利用しているサービスは以下になります。

  • Datadog
  • Gemini
  • OpenAI
  • Traceloop

これらのAPIキーを利用するときに、毎回ウェブサイトに行ってチェックするのも嫌ですし、平文でローカルに保存するのもよろしくないです。かといってAPIキーを暗記するなんて普通に考えてできないので実質不可能です。

これらの課題を解決するためにシェルスクリプトでAPIキーを管理するための機能を作りました。以前に作ったのでプロンプトは残ってないですが、claude codeとやり取りしながら実装しました。実装されている機能は以下になります。

  • APIキーの暗号化・復号化
  • APIキーの登録
  • APIキーを現在のシェルの環境変数に登録する
  • 登録されているAPIキーの一覧取得(登録名のみ)
  • APIの平文を取得する
  • 登録しているAPIキーの削除

実装紹介

仕組みの説明

まずはざっくり仕組みを説明します。シェルスクリプトは$HOME/.config/zsh/api_key_manager.zshに管理されており、APIキーは$HOME/.config/zsh/api_keysに保存されるようになっています。APIキーはAES256を用いて暗号化され、暗号化する時に任意のパスワードを利用する必要があります。パスワードを利用することにより、仮に暗号化されたコードの場所がバレてしまってもキーの復号をできないようにしています。

暗号化・復号化

APIキーの暗号化と復号化は以下で実施しています。

# Configuration
API_KEY_DIR="${HOME}/.config/zsh/api_keys"
API_KEY_INDEX="${API_KEY_DIR}/.index"

# Ensure directory exists
[[ ! -d "$API_KEY_DIR" ]] && mkdir -p "$API_KEY_DIR"

# Function to encrypt data using openssl
_encrypt_data() {
    local data="$1"
    local key_name="$2"
    echo "$data" | openssl enc -aes-256-cbc -salt -pbkdf2 -out "${API_KEY_DIR}/${key_name}.enc" 2>/dev/null
}

# Function to decrypt data using openssl
_decrypt_data() {
    local key_name="$1"
    local key_file="${API_KEY_DIR}/${key_name}.enc"
    [[ -f "$key_file" ]] && openssl enc -aes-256-cbc -d -pbkdf2 -in "$key_file" 2>/dev/null
}

APIキーは*.encファイルで保存され、利用時はパスワードが求められます。

APIキーの登録

以下にて新たなAPIキーを登録することができます。実行時にパスワードが聞かれるので、入力すると一覧にAPIキーを登録することができます。

register_api_key() {
    local key_name="$1"
    local key_content="$2"
    
    if [[ -z "$key_name" || -z "$key_content" ]]; then
        echo "Usage: register_api_key <key_name> <key_content>" >&2
        return 1
    fi
    
    # Check if key already exists
    if [[ -f "${API_KEY_DIR}/${key_name}.enc" ]]; then
        echo "Error: API key with name '$key_name' already exists" >&2
        return 1
    fi
    
    # Encrypt and save the key
    echo "Enter encryption password for '$key_name':"
    if _encrypt_data "$key_content" "$key_name"; then
        # Add to index
        echo "$key_name" >> "$API_KEY_INDEX"
        # Sort and remove duplicates
        sort -u "$API_KEY_INDEX" -o "$API_KEY_INDEX"
        echo "API key '$key_name' registered successfully"
    else
        echo "Error: Failed to encrypt API key" >&2
        return 1
    fi
}

指定したAPIキーたちを環境変数に登録する

以下を実行すると、指定したAPIキーの名前とその値をまとめて環境変数に登録できます。set_api_keys_env AAA BBB CCCのように複数指定するとAAA、BBB、CCCそれぞれについてパスワードが聞かれた上で登録することができます。

set_api_keys_env() {
    if [[ $# -eq 0 ]]; then
        echo "Usage: set_api_keys_env <key_name[:env_var_name]> ..." >&2
        echo "Example: set_api_keys_env openai:OPENAI_API_KEY github:GITHUB_TOKEN" >&2
        return 1
    fi
    
    local success_count=0
    local total_count=$#
    
    for key_spec in "$@"; do
        # Parse key_name:env_var_name format
        local key_name="${key_spec%%:*}"
        local env_var_name="${key_spec#*:}"
        
        # If no colon, use key name as env var name
        [[ "$env_var_name" == "$key_spec" ]] && env_var_name="$key_name"
        
        # Check if key exists
        if [[ ! -f "${API_KEY_DIR}/${key_name}.enc" ]]; then
            echo "Error: API key '$key_name' not found, skipping" >&2
            continue
        fi
        
        # Decrypt and set as environment variable
        echo "Enter decryption password for '$key_name':"
        local decrypted_key=$(_decrypt_data "$key_name")
        
        if [[ -n "$decrypted_key" ]]; then
            export "$env_var_name"="$decrypted_key"
            echo "✓ Environment variable '$env_var_name' set successfully"
            ((success_count++))
        else
            echo "Error: Failed to decrypt API key '$key_name'" >&2
        fi
    done
    
    echo "Successfully set $success_count out of $total_count environment variables"
}

登録されているAPIキーの一覧を取得する

以下を実行すると登録されているAPIキーの一覧が取得できます。この実行ではパスワードの入力は求められません。

get_api_keys() {
    if [[ ! -f "$API_KEY_INDEX" ]]; then
        echo "No API keys registered"
        return 0
    fi
    
    echo "Registered API keys:"
    cat "$API_KEY_INDEX" | while read -r key_name; do
        if [[ -f "${API_KEY_DIR}/${key_name}.enc" ]]; then
            echo "  - $key_name"
        fi
    done
}

私の環境で実行するとこんな感じになります。

get_api_keys

# 結果
Registered API keys:
  - DATADOG_API_KEY
  - GEMINI_API_KEY
  - OPENAI_API_KEY
  - TRACELOOP_API_KEY

APIキーを取得する

指定したAPIキーの内容を取得するには以下を呼び出します。こちらをget_api_key_row AAAのようにすると、パスワードを入力し正しい値であればAPIキーが平文でコンソールに表示されます。間違っていても表示されますが文字化けして何かわからない情報になります。

get_api_key_row() {
    local key_name="$1"
    
    if [[ -z "$key_name" ]]; then
        echo "Usage: get_api_key_row <key_name>" >&2
        return 1
    fi
    
    # Check if key exists
    if [[ ! -f "${API_KEY_DIR}/${key_name}.enc" ]]; then
        echo "Error: API key '$key_name' not found" >&2
        return 1
    fi
    
    # Decrypt and display
    echo "Enter decryption password for '$key_name':"
    local decrypted_key=$(_decrypt_data "$key_name")
    
    if [[ -n "$decrypted_key" ]]; then
        echo "API Key '$key_name': $decrypted_key"
    else
        echo "Error: Failed to decrypt API key" >&2
        return 1
    fi
}

APIキーを削除する

最後に以下を実行するとAPIキーを削除できます。実行すると本当に削除するか確認が表示され、y/Yを入力すると削除が実行されます。なお復元はできないです。

delete_api_key() {
    local key_name="$1"
    
    if [[ -z "$key_name" ]]; then
        echo "Usage: delete_api_key <key_name>" >&2
        return 1
    fi
    
    # Check if key exists
    if [[ ! -f "${API_KEY_DIR}/${key_name}.enc" ]]; then
        echo "Error: API key '$key_name' not found" >&2
        return 1
    fi
    
    # Confirm deletion
    echo -n "Are you sure you want to delete API key '$key_name'? (y/N): "
    read -r confirm
    
    if [[ "$confirm" =~ ^[Yy]$ ]]; then
        # Remove encrypted file
        rm -f "${API_KEY_DIR}/${key_name}.enc"
        
        # Remove from index
        if [[ -f "$API_KEY_INDEX" ]]; then
            grep -v "^${key_name}$" "$API_KEY_INDEX" > "${API_KEY_INDEX}.tmp"
            mv "${API_KEY_INDEX}.tmp" "$API_KEY_INDEX"
        fi
        
        echo "API key '$key_name' deleted successfully"
    else
        echo "Deletion cancelled"
    fi
}

まとめ

今回は私が普段使っているAPIキー管理方法を共有しました。完全な平文で保存するよりはまだマシだと思ったのと、毎回APIキーわからなくなって新しいものを発行するよりはこのような方法を用意したほうがいいと思って作ってみました。しかし一般的にはパスワード管理ツールでやるのが得策だと思うので、興味ある人はぜひ試してもらいたいと思いつつ、業務で利用する場合は控えたほうがいいかもです(これはあくまで個人PCでのみ使っている手法です)。

Discussion