【暗号化】パスワード管理ツールを作成（shell script / bash）

# パスワードマネージャー
煩雑になりがちなサービスのIDとパスワードを一元管理しましょう！

シェルスクリプトで、簡易なパスワードマネージャーをつくりました。GnuPGを使って、暗号化しています。

### 環境
- MAC OS
- シェルスクリプト
- ターミナル

## ご利用方法

### 1. GnuPGをインストール/設定
   - GnuPG: ファイルを暗号化するための無料ソフトウェアです。
   - 下記のコマンドを実行すると、インストールできます。（MAC, Linux環境）
```shell
 sudo apt install gnupg
```
  - PGPの設定: https://wiki.archlinux.jp/index.php/GnuPG
  
### 2. password_manager.zshをダウンロード
  - GitHubリポジト(https://github.com/oshibas/apprentice/tree/main/week3-4/quest) の「<>code」プルダウンから、「Download Zip」をクリック。

### 3. password_manager.zshを実行

  - 下記のコマンドを入力してください。
  - ファイルに実行権限を付与し、ファイルを起動します。
```shell
 chmod +x password_manager.sh
 ./password_manager.sh
```
### 4. 動作内容
  1. あなたのGPGキーで設定したメールアドレスを入力してください。
  2. 次の選択肢から入力してください(Add Password/Get Password/Exit)
  3. 終了したい場合は、 exit と入力してください。



# パスワードマネージャーの作り方 

難易度別に、３段階をふみました。

ステップ1: パスワードを保存しよう！
ステップ2: パスワードを検索しよう！
ステップ3: パスワードを暗号化しよう！

## ステップ1: パスワードを保存しよう！

### 内容
 パスワードを保存する処理

### 仕様
以下の情報をファイルに保存する。
シェルスクリプトを実行するたび、入力された情報がファイルに追記される。

- サービス名
- ユーザー名
- パスワード

### 実行時のアウトプット

```bash
パスワードマネージャーへようこそ！
サービス名を入力してください：
ユーザー名を入力してください：
パスワードを入力してください：
# 入力が完了したら
Thank you!
```

<details><summary><b>　ステップ1のbash　</b></summary><div>

```bash
#!/bin/bash

echo "パスワードマネージャーへようこそ！"
echo "サービス名を入力してください："
# サービス名をservice_name変数に代入する
read service_name

echo "ユーザー名を入力してください："
# ユーザー名をusername変数に代入する
read username

echo "パスワードを入力してください："
# -s: 入力されたパスワードを表示させない
read -s password

# ユーザーの入力をpass.txtに書き込む
echo "$service_name:$username:$password" >> pass.txt

echo "Thank you!"
```

### ポイント

<b>宣言文:</b>
はじめに、bashで書きますよ~！という宣言文を入れます。
「シェバング」というそうです。
`#!/bin/bash`

<b>実行時にテキストを表示:</b>
`echo`コマンド
例:`echo "パスワードマネージャーへようこそ！"`

<b>ユーザーから受け取った文字列を、変数に代入: </b>
`read`コマンド `<変数名>`
ここではサービス名を受け取り、代入します。
例:`read service_name`

<b>変数を別のテキストファイルに書き込む:</b>
`echo <$変数名>:<$変数名>:<$変数名> >> <テキストファイル名>`
`:`を変数名にはさむと、複数の変数を1行に結合します。
`>>`このファイルに書き込んでね！という意味の、「リダイレクト演算子」というものです。
例:`echo "$service_name:$username:$password" >> pass.txt`

</div></details>

# ステップ2: パスワードを検索しよう！

内容: パスワードを保存し、`検索する`処理

### 仕様
下記のステップ1の使用に加えて、検索機能を追加しました。

`選択式の質問が表示され、ユーザーの入力に応じて分岐処理を行う。`
シェルスクリプトを実行するたび、入力された情報がファイルに追記される。

- サービス名
- ユーザー名
- パスワード

### 実行時のアウトプット
```bash
パスワードマネージャーへようこそ！
次の選択肢から入力してください(Add Password/Get Password/Exit)：

# Add Password が入力された場合
サービス名を入力してください：
ユーザー名を入力してください：
パスワードを入力してください：

パスワードの追加は成功しました。
次の選択肢から入力してください(Add Password/Get Password/Exit)：

# Get Password が入力された場合
サービス名を入力してください：
## サービス名が保存されていなかった場合
そのサービスは登録されていません。
## サービス名が保存されていた場合
サービス名：hoge
ユーザー名：fuga
パスワード：piyo

次の選択肢から入力してください(Add Password/Get Password/Exit)：

# Exit が入力された場合
Thank you!
## プログラムが終了

# Add Password/Get Password/Exit 以外が入力された場合
入力が間違えています。Add Password/Get Password/Exit から入力してください。
```

<details><summary><b>　ステップ2のbash　</b></summary><div>

```bash
#!/bin/bash
# 無限ループ内でユーザーにメニューを表示し、選択肢に応じて適切な処理を行う
while true; do
    echo "パスワードマネージャーへようこそ！"
    echo "次の選択肢から入力してください(Add Password/Get Password/Exit):"
    # ユーザーからの入力を受け取り、choice変数に代入する
    read choice

    case $choice in
        "Add Password")
            read -p "サービス名を入力してください: " serviceName
            read -p "ユーザー名を入力してください: " userName
            # -s: 入力されたパスワードを表示させない
            read -s -p "パスワードを入力してください: " password
            # 入力された情報をpasswords.txtファイルに追記する
            echo "$serviceName:$userName:$password" >> passwords.txt
            echo "パスワードの追加は成功しました。"
            ;;
        "Get Password")
            read -p "サービス名を入力してください: " serviceName
            # serviceNameに対応するpassをpasswords.txtファイルから取得
            password=$(grep "^$serviceName:" passwords.txt | cut -d: -f3)

            if [ -z "$password" ]; then
                echo "そのサービスは登録されていません。"
            else
                echo "サービス名：$serviceName"
                # サービス名に対応するユーザー名を表示
                echo "ユーザー名：$(grep "^$serviceName:" passwords.txt | cut -d: -f2)"
                # サービス名に対応するパスワードを表示
                echo "パスワード：$password"
            fi
            ;;
        "Exit")
            echo "Thank you!"
            exit
            ;;
        *)
            echo "入力が間違えています。Add Password/Get Password/Exitから入力してください。"
            ;;
    esac
done
```

### ポイント

<b>while文:</b>
```shell
while 条件式
do
 コマンド
done
```
`while true; do` から始まる行は、無限ループを表しています。
ずっと`true`（真）なので、「条件を満たしている間は同じ処理を繰り返す（無限にループしちゃうよ！）」という意味です。

<b>ユーザーの入力を変数に代入:</b>
`read choice` の行は、ユーザーからの入力を受け取り、その入力を `choice` 変数に代入するもの。

<b>case文:</b>
```shell
case 値 in
 条件・値1 ) コマンド1 ;;
 条件・値2 ) コマンド2 ;;
 条件・値3 ) コマンド3 ;;
esac
```
`;;` を忘れないでね。

`choice` の値に応じて、下記のいずれかの処理を行う。
- Add Password
- Get Password
- Exit
`case` 文の中で、各処理を実装しています。

<b>Add Password の場合: </b>
`read` コマンドを使用してサービス名、ユーザー名、パスワードをユーザーに入力させ、それらの情報を `passwords.txt` に追記するためのコマンドを実行。
`echo` コマンドを使用してパスワードの追加が成功したことをユーザーに通知します。

<b>Get Password の場合:</b>
`read` コマンドを使用してサービス名をユーザーに入力させ、それに対応するパスワードを `passwords.txt` ファイルから取得し、それをユーザーに表示するためのコマンドを実行します。

`grep` コマンドと `cut` コマンドを使用して、`serviceName` に対応する行を `passwords.txt` から取得し、パスワードを取り出します。

`grep`コマンドは、指定された正規表現に一致するテキスト行を検索するために使用されます。

`cut`コマンドは、テキストファイルから行、フィールド、バイトなどの指定されたセクションを抽出するために使用されます。

`grep "^$serviceName:" passwords.txt`は、`passwords.txt`ファイル内で`$serviceName`で始まる行を検索し、`cut -d: -f3`はその行から3番目のフィールド（パスワード）を抽出しています。

</div></details>

# ステップ3: パスワードを暗号化しよう！

### 内容
 パスワードを暗号化する処理をほどこしました。

### 仕様
以下の情報をファイルに保存します。
シェルスクリプトを実行するたび、入力された情報がファイルに追記されます。

- サービス名
- ユーザー名
- パスワード

<details><summary><b>　ステップ3のbash　</b></summary><div>

```bash
#!/bin/bash

# 無限ループ内でユーザーにメニューを表示し、選択肢に応じて適切な処理を行う
while true; do
    echo "パスワードマネージャーへようこそ！"
    echo "終了したい場合は Exit と入力してください。"

    echo "あなたのGPGキーで設定したメールアドレスを入力してください。"
    # ユーザーからの入力を受け取り、gpg_email変数に代入する。
    read gpg_email
    # 「?」演算子: 変数が設定されていない場合にエラーメッセージを表示。
    gpg_email="${gpg_email:?GPGのメールアドレスを設定してください。}"

    echo "次の選択肢から入力してください(Add Password/Get Password/Exit):"
    read choice

    case $choice in
        "Add Password")
            # -pオプションでメッセージ（プロンプト）を表示
            # ユーザーからの入力を受け取り、変数に代入する
            read -p "サービス名を入力してください: " serviceName
            # 空文字の場合、エラーメッセージを表示する
            serviceName="${serviceName:?サービス名を設定してください。}"

            read -p "ユーザー名を入力してください: " userName
            userName="${userName:?ユーザー名を設定してください。}"

            # -s: 入力されたパスワードを表示させない
            read -s -p "パスワードを入力してください: " password
            password="${password:?パスワードを設定してください。}"

            # 復号化したデータを一時ファイルに保存
            # 標準エラー出力(2)を/dev/nullに捨てる。つまり、エラーメッセージを表示しない。
            gpg -d password.gpg > password.txt 2> /dev/null
            # パスワードをpassword.txtに書き込む
            echo "$serviceName:$userName:$password" >> passwords.txt

           # 入力ファイルを暗号化して出力ファイルに保存する
           gpg -r "$gpg_email" -e -o password.gpg password.txt

            echo "パスワードの追加は成功しました。"
            # ;; 各処理の終了を示す。省略可能。
            ;;

        "Get Password")
            read -p "サービス名を入力してください: " serviceName
            # 復号化したデータを一時ファイルに保存
            gpg -d password.gpg > password.txt 2> /dev/null
            # serviceNameに対応するpassをpasswords.txtファイルから取得
            # cut -d: -f3: 「:」で区切られた3番目のフィールド（パスワード）を取得
            password=$(grep "^$serviceName:" passwords.txt | cut -d: -f3)

            if [ -z "$password" ]; then
                echo "そのサービスは登録されていません。"
            else
                echo "サービス名：$serviceName"
                # サービス名に対応するユーザー名を表示
                # cut -d: -f2: 「:」で区切られた2番目のフィールド（ユーザー名）を取得。
                echo "ユーザー名：$(grep "^$serviceName:" passwords.txt | cut -d: -f2)"
                # サービス名に対応するパスワードを表示。
                echo "パスワード：$password"
            fi
            ;;
        "Exit")
            echo "Thank you!"
            exit
            ;;

        # *)どの値にも一致しなかった場合の処理
        *)
            echo "入力が間違えています。Add Password/Get Password/Exitから入力してください。"
            ;;
    # case文の終了を示す。esac=caseを逆読みしてる。
    esac
# whileループの終了を示す。
done

    # 一時ファイルを削除する。
    rm password.txt
```

### ポイント

 1. ユーザーのGPGキーを、コマンド入力から受け取ります。
 2. 空文字を入力した場合、エラーメッセージを表示します。
 3. セキュリティを強化。

</div></details>