グローバルナビゲーションへ

本文へ

フッターへ

お役立ち情報Blog



SSL/TLSを使用してMySQLサーバへ暗号化接続する方法を検証してみた

ブラウザでSSLで暗号化されていないサイトへアクセスすると警告がでるようになるなど、ネットワーク通信の通信データにおけるセキュリティ意識が年々高まっていると感じます。

データベースにおいてもグローバルネットワークを通して通信する場合には十分な注意を払う必要があります。
今回はMySQLサーバへの接続にSSL/TLSを使用して暗号化接続を行う方法について検証した内容をご紹介したいと思います。

検証前の確認(暗号化された接続の有効化

検証前にMySQLサーバ側で暗号化された接続が有効化されている必要があります。 有効化の確認は以下のSQLで確認できます。

MySQL [(none)]> SHOW VARIABLES LIKE 'have_%ssl';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| have_openssl  | YES   |
| have_ssl      | YES   |
+---------------+-------+
2 rows in set (0.03 sec)

ValueがYESになっていれば有効です。

検証に使用した各種バージョンは以下の通りです。

  • MySQLサーバ MySQL 8.0.16
  • MySQLクライアント MariaDB 5.5 (CentOS7系のデフォルト)
  • MySQLクライアント MySQL 8.0.18
  • PHP 7.2

MySQLクライアントのTLS接続オプション

SSL/TLS接続を有効にするMySQLのオプションを確認していきましょう。

–ssl, –skip-ssl

SSL/TLSを使用してサーバに接続できるようになります。
MySQL5.6以下では–ssl-modeオプションがないので、代わりに–sslオプションを使用します。

Note
MySQL 8.0では、クライアント側オプション–sslは削除されました。クライアントプログラムの場合は、–ssl-modeを代わりに使用します。

注意しなければならないのは、この指定だけでは暗号化されていない接続が使用されるケースがあるという事です。 MySQL公式のドキュメントによると推奨されるオプションセットとして、サーバ側で少なくとも–ssl-certと–ssl-keyを、クライアント側では–ssl-caを使用するように記載されています。
詳細については公式のドキュメントを参照してください。

–ssl-mode

MySQL5.7で追加されたオプションです。
MySQLクライアントのバージョン5.7からはデフォルトのモードがPREFERREDでMySQLサーバでTLS接続が有効化されている場合は自動的に暗号化された接続になるようです。

mode 説明
DISABLED 暗号化されていない接続を確立します。
PREFERRED サーバが暗号化された接続をサポートしている場合は暗号化された接続を確立し、 暗号化された接続を確立できない場合は暗号化されていない接続にフォールバックします。 default
REQUIRED サーバーが暗号化された接続をサポートしている場合、暗号化された接続を確立します。 暗号化された接続を確立できない場合、接続の試行は失敗します。
VERIFY_CA REQUIREDに似ていますが、構成されたCA証明書に対してサーバー認証局(CA)証明書をさらに検証します。 有効な一致するCA証明書が見つからない場合、接続の試行は失敗します。
VERIFY_IDENTITY VERIFY_CAに似ていますが、クライアントがサーバーへの接続に使用するホスト名を、 サーバーがクライアントに送信する証明書のIDと照合してホスト名のID検証を追加で実行します。

–ssl-ca

PEM形式の認証局(CA)証明書ファイルのパス名。サーバー側では、このオプションは–sslを意味します。

–ssl-capath

PEM形式の信頼できるSSL認証局(CA)証明書ファイルを含むディレクトリのパス名。サーバー側では、このオプションは–sslを意味します。

–ssl-cipher

TLSv1.2までのTLSプロトコルを使用する接続の許容暗号化暗号リスト。
コロン区切りで複数指定する事が可能です。

e.g.

--ssl-cipher=DHE-RSA-AES128-GCM-SHA256:AES128-SHA
MySQL 8.0 リファレンスマニュアル

MySQLでTLS接続

2つのバージョンのMySQLクライアントでMySQLサーバに接続してみます。

接続が暗号化されているかは以下のSQLコマンドで確認できます。

MySQL [(none)]> SHOW STATUS LIKE 'Ssl_cipher';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Ssl_cipher    |       |
+---------------+-------+
1 row in set (0.04 sec)

Valueが空の場合は暗号化されていません。

他にもSTATUS又は\sコマンドでも確認が可能です。

MySQL [(none)]> \s
...
SSL:                    Not in use
...

SSLが Not in use の場合は暗号化されていません。

TLSオプションを明示的に指定しない場合(MariaDB 5.5)

$ mysql -h <HOST> -p <PORT> -u <MYSQL_USER> -p
MySQL [(none)]> SHOW STATUS LIKE 'Ssl_cipher';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Ssl_cipher    |       |
+---------------+-------+
1 row in set (0.03 sec)

Valueの中身が空なので暗号化されていません。

TLSオプションを明示的に指定する場合(MariaDB 5.5)

MariaDB 5.5では–ssl-modeがないので、–sslオプションを使用して接続します。

$ mysql -h <HOST> -p <PORT> -u <MYSQL_USRE> -p --ssl
MySQL [(none)]> SHOW STATUS LIKE 'Ssl_cipher';
+---------------+--------------------+
| Variable_name | Value              |
+---------------+--------------------+
| Ssl_cipher    | DHE-RSA-AES256-SHA |
+---------------+--------------------+
1 row in set (0.03 sec)

接続の暗号化が確認できました。

TLSオプションを明示的に指定しない場合(MySQL 8.0)

$ mysql -h <HOST> -p <PORT> -u <MYSQL_USRE> -p
mysql [(none)] > SHOW STATUS LIKE 'Ssl_cipher';
+---------------+--------------------+
| Variable_name | Value              |
+---------------+--------------------+
| Ssl_cipher    | DHE-RSA-AES128-SHA |
+---------------+--------------------+
1 row in set (0.03 sec)

MySQLクライアントが8.0の場合は–ssl-modeのデフォルトがPREFERREDのため、明示的に指定しなくても接続が暗号化されています。

TLSオプションを明示的にDISABLEDで指定する場合(MySQL 8.0)

–ssl-modeオプションにDISABLEDを指定した場合も確認してみます。

$ mysql -h <HOST> -p <PORT> -u <MYSQL_USRE> -p --ssl-mode=DISABLED
mysql [(none)] > SHOW STATUS LIKE 'Ssl_cipher';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Ssl_cipher    |       |
+---------------+-------+
1 row in set (0.04 sec)

暗号化せずに接続していることが分かります。

MySQLユーザに接続の暗号化を強制する場合にはCREATE USER句のSSL/TLSオプションを使用することで設定が可能です。 詳細については公式のドキュメントを参照してください。

MySQL 8.0 リファレンスマニュアル

TLSプロトコルと暗号の構成

サーバがサポートする暗号化アルゴリズム(Cipher)の確認は以下のSQLで確認ができます。

mysql [(none)] > SHOW SESSION STATUS LIKE 'Ssl_cipher_list';
+-----------------+---------------------------------------------------------------------------------------------------+
| Variable_name   | Value                                                                                             |
+-----------------+---------------------------------------------------------------------------------------------------+
| Ssl_cipher_list | AES256-SHA:AES128-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA |
+-----------------+---------------------------------------------------------------------------------------------------+
1 row in set (0.02 sec)

Ssl_cipher_listのValueの左から順にクライアントとサーバの両方で対応しているものが使用されます。

MySQL 8.0のクライアントでTLS接続した際にはSsl_cipherの値はDHE-RSA-AES128-SHAでした。試しに別の暗号化アルゴリズムに変えてみましょう。

$ mysql -h <HOST> -p <PORT> -u <MYSQL_USRE> -p --ssl-cipher=AES256-SHA:AES128-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA<

現在のセッションTLSプロトコルと暗号を確認するには以下のSQLで確認します。

mysql [(none)] > SELECT * FROM performance_schema.session_status WHERE VARIABLE_NAME IN ('Ssl_version','Ssl_cipher');
+---------------+----------------+
| VARIABLE_NAME | VARIABLE_VALUE |
+---------------+----------------+
| Ssl_cipher    | AES256-SHA     |
| Ssl_version   | TLSv1.2        |
+---------------+----------------+
2 rows in set (0.03 sec)

暗号化アルゴリズムが変わっている事が確認できます。

PHPアプリケーション(PDO)を使ったSSL/TLS接続

ここまでMySQLクライアントを使用して確認してきましたが、PHPアプリケーションからMySQLサーバへの接続を暗号する方法についても確認していきます。

PHP マニュアル

PDOのSSLサポート(オプション)にはMySQLクライアントでいう–ssl-mode(–ssl)がありません。PEM形式の認証局(CA)証明書ファイルが必要になりますので予め手元に用意しておきます。

MySQLサーバにDBaaSを使用されている場合は以下のURLより証明書を入手可能です。

PDOのコンストラクタにSSLオプションを明示的に有効化していないケース

<?php
declare(strict_types=1);

$settings = new \stdClass();
$settings->host     = '<HOST>';
$settings->username = '<MYSQL_USERNAME>';
$settings->password = '<MYSQL_PASSWORD>';
$settings->dbname   = '<MYSQL_DBNAME>';
$settings->charset  = '<MYSQL_CHARSET>';

$pdo = new \PDO(
    "mysql:host={$settings->host}; dbname={$settings->dbname}; charset={$settings->charset}",
    $settings->username,
    $settings->password,
    [
        \PDO::ATTR_EMULATE_PREPARES => false,
        \PDO::ATTR_ERRMODE          => \PDO::ERRMODE_EXCEPTION,
    ]
);

$stmt = $pdo->query(
    <<<SQL
SELECT * FROM performance_schema.session_status
    WHERE VARIABLE_NAME IN ('Ssl_version', 'Ssl_cipher');
SQL
);

foreach($stmt->fetchAll(\PDO::FETCH_OBJ) as $row) {
    echo "{$row->VARIABLE_NAME}:\t{$row->VARIABLE_VALUE}", PHP_EOL;
}

実行結果

$ php tls-connection-test.php
Ssl_cipher:
Ssl_version:<

暗号化せずに接続していることが分かります。

PDOのコンストラクタにPDO::MYSQL_ATTR_SSL_CAを指定

<?php
declare(strict_types=1);

$settings = new \stdClass();
$settings->host     = '<HOST>';
$settings->username = '<MYSQL_USERNAME>';
$settings->password = '<MYSQL_PASSWORD>';
$settings->dbname   = '<MYSQL_DBNAME>';
$settings->charset  = '<MYSQL_CHARSET>';

$pdo = new \PDO(
    "mysql:host={$settings->host}; dbname={$settings->dbname}; charset={$settings->charset}",
    $settings->username,
    $settings->password,
    [
        \PDO::ATTR_EMULATE_PREPARES => false,
        \PDO::ATTR_ERRMODE          => \PDO::ERRMODE_EXCEPTION,
        \PDO::MYSQL_ATTR_SSL_CA     => __DIR__.'/ca.pem', // 予め用意したCA証明書のパスを指定します。
    ]
);

$stmt = $pdo->query(
    <<<SQL
SELECT * FROM performance_schema.session_status
    WHERE VARIABLE_NAME IN ('Ssl_version', 'Ssl_cipher');
SQL
SQL
);

foreach($stmt->fetchAll(\PDO::FETCH_OBJ) as $row) {
    echo "{$row->VARIABLE_NAME}:\t{$row->VARIABLE_VALUE}", PHP_EOL;
}

PDOのコンストラクタ第四引数のオプションに \PDO::MYSQL_ATTR_SSL_CAを追加します。

実行結果

$ php tls-connection-test.php
Ssl_cipher:     DHE-RSA-AES256-SHA
Ssl_version:    TLSv1.2

接続の暗号化が確認できました。

PDOのコンストラクタにPDO::MYSQL_ATTR_SSL_CIPHERを指定

<?php
declare(strict_types=1);

$settings = new \stdClass();
$settings->host     = '<HOST>';
$settings->username = '<MYSQL_USERNAME>';
$settings->password = '<MYSQL_PASSWORD>';
$settings->dbname   = '<MYSQL_DBNAME>';
$settings->charset  = '<MYSQL_CHARSET>';

$pdo = new \PDO(
    "mysql:host={$settings->host}; dbname={$settings->dbname}; charset={$settings->charset}",
    $settings->username,
    $settings->password,
    [
        \PDO::ATTR_EMULATE_PREPARES => false,
        \PDO::ATTR_ERRMODE          => \PDO::ERRMODE_EXCEPTION,
        \PDO::MYSQL_ATTR_SSL_CA     => __DIR__.'/ca.pem',
        \PDO::MYSQL_ATTR_SSL_CIPHER => 'AES256-SHA:AES128-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA', // Ssl_cipher_listで使用可能なCipherをコロン区切りで指定します
    ]
);

$stmt = $pdo->query(
    <<<SQL
SELECT * FROM performance_schema.session_status
    WHERE VARIABLE_NAME IN ('Ssl_version', 'Ssl_cipher');
SQL
);

foreach($stmt->fetchAll(\PDO::FETCH_OBJ) as $row) {
    echo "{$row->VARIABLE_NAME}:\t{$row->VARIABLE_VALUE}", PHP_EOL;
}

PDOのコンストラクタ第四引数のオプションに\PDO::MYSQL_ATTR_SSL_CIPHERを追加します。

実行結果

$ php tls-connection-test.php
Ssl_cipher:     AES256-SHA
Ssl_version:    TLSv1.2

暗号化アルゴリズムが変わっている事が確認できました。

まとめ

簡易的な検証ですがMySQLサーバへの暗号化接続方法について確認しました。

暗号化しない接続と比べると暗号化する処理が追加になる分MySQLサーバへの接続コストが高くなります。実際の運用に適応する場合は事前に動作検証を行ってからご検討ください。

この記事を書いた人

美髭公
美髭公事業開発部 web application engineer
2013年にアーティスに入社。システムエンジニアとしてアーティスCMSを使用したWebサイトや受託システムの構築・保守に携わる。環境構築が好き。
この記事のカテゴリ

FOLLOW US

最新の情報をお届けします