MySQL の新機能 mysql_config_editor ソース コード分析
mysql5.6 から、mysql は暗号化ツール mysql_config_editor を開始しました。その前に、my.cnf にアカウントとパスワードを平文で記述しておき、mysql クライアントを使用してログインするときに、アカウントとパスワードを指定せずにデータベースにログインできるようにします。 mysql_config_editor ツールを使用して、暗号化されたアカウントのパスワードをバイナリ ファイルに置きます。ログイン時に、クライアントはファイルを復号化してデータベースにログインします。暗号化と復号化はメモリ内で実行されるため、ファイルの内容を平文で表示することはできません。ファイルのアクセス許可を適切に維持している限り、悪意のある人がデータベースのパスワードを解読することを防ぐことができます。
mysql_config_editor の使用プロセスは次のとおりです: mysql_config_editor set --login-path=client --host=localhost - -user=localuser --password
このようにして、ローカル データ ソース情報を構成します。 login-path: mysql クライアント経由でログインするときの ID を指定します。 host: ユーザーに接続するデータベース: ローカル経由 データベースに接続するときは、アカウントのパスワードを使用します: ローカル接続経由で接続するときに使用するデータベースのパスワードを指定します (ここで入力したパスワードがpassword1であると仮定します)
もちろん、リモート接続経由で接続する場合は、特定のポート情報も追加します。このようにして、データベースにログインするときは、次のコマンドを実行するだけでデータベースに接続できます: mysql --login-path=client
このようにして、ローカル データベースに接続します。
mysql_config_editor の詳細を見てみましょう: このツールには set/remove/print/reset/help が含まれているため、set 関数の実装のみを分析します: set 関数は、主に set_command 関数を通じて実装されます。アカウントやパスワードなどのデータ ソース情報を構成し、その情報をバイナリ ファイルに保存します:
<ol style="margin:0 1px 0 0px;padding-left:40px;" start="1" class="dp-css"><li>static int set_command(void)<br /> </li><li>{<br /></li><li>DBUG_ENTER("set_command");<br /></li><li><br /></li><li>DYNAMIC_STRING file_buf, path_buf;<br /></li><li>init_dynamic_string(&path_buf, "", MY_LINE_MAX, MY_LINE_MAX);<br /></li><li>init_dynamic_string(&file_buf, "", file_size, 3 * MY_LINE_MAX);<br /></li><li><br /></li><li>if (tty_password)<br /></li><li>opt_password= get_tty_password(NullS); <br /></li><li>if (file_size)<br /></li><li>{<br /></li><li>if (read_and_decrypt_file(&file_buf) == -1) //如果文件存在,就读取文件,并将文件的密文解密后存放到file_buf中.<br /></li><li>goto error;<br /></li><li>}<br /></li><li><br /></li><li>dynstr_append(&path_buf, "["); /* --login=path */ <br /></li><li>if (opt_login_path)<br /></li><li>dynstr_append(&path_buf, opt_login_path);<br /></li><li>else<br /></li><li>dynstr_append(&path_buf, "client");<br /></li><li>dynstr_append(&path_buf, "]");<br /></li><li><br /></li><li>if (opt_user) /* --user */<br /></li><li>{<br /></li><li>dynstr_append(&path_buf, "\nuser = ");<br /></li><li>dynstr_append(&path_buf, opt_user);<br /></li><li>}<br /></li><li><br /></li><li>if (opt_password) /* --password */<br /></li><li>{<br /></li><li>dynstr_append(&path_buf, "\npassword = ");<br /></li><li>dynstr_append(&path_buf, opt_password);<br /></li><li>}<br /></li><li><br /></li><li>if (opt_host) /* --host */<br /></li><li>{<br /></li><li>dynstr_append(&path_buf, "\nhost = ");<br /></li><li>dynstr_append(&path_buf, opt_host);<br /></li><li>}<br /></li><li><br /></li><li>if (opt_socket)<br /></li><li>{<br /></li><li>dynstr_append(&path_buf, "\nsocket = ");<br /></li><li>dynstr_append(&path_buf, opt_socket);<br /></li><li>}<br /></li><li><br /></li><li>if (opt_port)<br /></li><li>{<br /></li><li>dynstr_append(&path_buf, "\nport = ");<br /></li><li>dynstr_append(&path_buf, opt_port);<br /></li><li>}<br /></li><li><br /></li><li>dynstr_append(&path_buf, "\n");<br /></li><li><br /></li><li>/* Warn if login path already exists */<br /></li><li>if (opt_warn && ((locate_login_path (&file_buf, opt_login_path)) //判断该login-path是否已经存在<br /></li><li>!= NULL))<br /></li><li>{<br /></li><li>int choice;<br /></li><li>printf ("WARNING : \'%s\' path already exists and will be "<br /></li><li>"overwritten. \n Continue? (Press y|Y for Yes, any "<br /></li><li>"other key for No) : ",<br /></li><li>opt_login_path);<br /></li><li>choice= getchar();<br /></li><li><br /></li><li>if (choice != (int) 'y' && choice != (int) 'Y’) //如果login-path存在是否选择覆盖<br /></li><li>goto done; /* skip */<br /></li><li>}<br /></li><li><br /></li><li>/* Remove the login path. */<br /></li><li>remove_login_path(&file_buf, opt_login_path); //从原来文件中读取的内容中,删掉该login-path信息<br /></li><li><br /></li><li>/* Append the new login path to the file buffer. */<br /></li><li>dynstr_append(&file_buf, path_buf.str); //将该login-path的信息加到file_buf的末尾<br /></li><li><br /></li><li>if (encrypt_and_write_file(&file_buf) == -1) //将包含新的log-path的所有信息和原来的信息加密写入文件<br /></li><li>goto error;<br /></li><li><br /></li><li>done:<br /></li><li>dynstr_free(&file_buf);<br /></li><li>dynstr_free(&path_buf);<br /></li><li>DBUG_RETURN(0);<br /></li><li><br /></li><li>error:<br /></li><li>dynstr_free(&file_buf);<br /></li><li>dynstr_free(&path_buf);<br /></li><li>DBUG_RETURN(-1);<br /></li><li>} </li></ol>
コードの具体的なロジックは次のとおりです:
ここでは、関係するいくつかの重要な関数に焦点を当てます: read_and_decrypt_file (ファイルの内容を読み取り、復号化して動的文字バッファーに入れます)locate_login_path (ログイン パスが既に存在するかどうかを確認します)remove_login_path (ログインの場合) -path が存在します) を削除し、login-path) dynstr_append(&file_buf, path_buf.str); 新しいログインパスを file_buf の末尾に追加します encrypt_and_write_file(&file_buf) file_buf 内の情報をデコードしてファイルに書き込みます
まず、次のように暗号化されたファイル形式を見てみましょう:
ここでは、暗号化されたファイルの最初の 4 バイトが ' であるため、暗号化されたファイルがすでに存在していると仮定します。
- static char* locate_login_path(DYNAMIC_STRING *file_buf, const char *path_name)
- {
- DBUG_ENTER("locate_login_path");
-
- char *addr= NULL;
- DYNAMIC_STRING dy_path_name;
-
- init_dynamic_string(&dy_path_name, "", 512, 512); // 初始化dy_path_name动态字符串
-
- //将dy_path_name 设置为[path_name]
- dynstr_append(&dy_path_name, "\n[“);
- dynstr_append(&dy_path_name, path_name);
- dynstr_append(&dy_path_name, "]");
-
- //检查第一个login-path是否就是要寻找的login-path
- /* First check if it is the very first login path. */
- if (file_buf->str == strstr(file_buf->str, dy_path_name.str + 1))
- addr= file_buf->str;
- /* If not, scan through the file. */
- else
- {
- addr= strstr(file_buf->str, dy_path_name.str);
- if (addr)
- addr ++; /* Move past '\n' */
- }
-
- dynstr_free(&dy_path_name);
- DBUG_RETURN(addr); //返回找到的login-path在file_buf的首地址
- }
该函数主要是寻找login-path是否能已经存在,如果已经存在,返回该login-path在file_buf中的首地址。
如果该login-path已经存在,那么我们可能会选择remove该login-path,然后在添加该login-path。
接下来我们看看removelogin-path的实现:
- static void remove_login_path(DYNAMIC_STRING *file_buf, const char *path_name)
- {
- DBUG_ENTER("remove_login_path");
-
- char *start=NULL, *end= NULL;
- int to_move, len, diff;
- if((start= locate_login_path(file_buf, path_name)) == NULL) //如果该login-path不存在,直接结束
- /* login path was not found, skip.. */
- goto done;
-
- end= strstr(start, "\n[“); //end为从start开始寻找,下一个login-path的起始位置
-
- if (end) //如果该login-path是file_buf中间的某一个login-path
- {
- end ++; /* Move past '\n' */
- len= ((diff= (start - end)) > 0) ? diff : - diff;
- to_move= file_buf->length - (end - file_buf->str);
- }
- else //如果该login-path是该file_buf中最后一个log-path
- {
- *start= '\0';
- file_buf->length= ((diff= (file_buf->str - start)) > 0) ? diff : - diff;
- goto done;
- }
-
- while(to_move —) //将该login-path之后的login-path整体前移,覆盖move掉的login-path
- *(start ++)= *(end ++);
-
- *start= '\0';
- file_buf->length -= len;
-
- done:
- DBUG_VOID_RETURN;
- }
该函数主要是覆盖已经存在的login-path相关的字符串。 函数:dynstr_append(&file_buf, path_buf.str) ,将新添加的login-path内容,添加到file_buf的末尾。
最后来看看最重要,也是最核心的加密函数encrypt_and_write_file的实现:
<ol style="margin:0 1px 0 0px;padding-left:40px;" start="1" class="dp-css"><li>static int encrypt_and_write_file(DYNAMIC_STRING *file_buf)<br /> </li><li>{<br /></li><li>DBUG_ENTER("encrypt_and_write_file");<br /></li><li>my_bool done= FALSE;<br /></li><li>char cipher[MY_LINE_MAX], *tmp= NULL;<br /></li><li>uint bytes_read=0, len= 0;<br /></li><li>int enc_len= 0; // Can be negative.<br /></li><li><br /></li><li>if (reset_login_file(0) == -1) //清空文件,并重新生成随机加密秘钥,并将对称加密秘钥写入文件头部<br /></li><li>goto error;<br /></li><li>/* Move past key first. */<br /></li><li>if (my_seek(g_fd, MY_LOGIN_HEADER_LEN, SEEK_SET, MYF(MY_WME))<br /></li><li>!= (MY_LOGIN_HEADER_LEN))<br /></li><li>goto error; /* Error while seeking. */<br /></li><li><br /></li><li>tmp= &file_buf->str[bytes_read];<br /></li><li>while(! done)<br /></li><li>{<br /></li><li>len= 0;<br /></li><li><br /></li><li>while(*tmp++ != '\n’) //读取file_buf中的每一行内容<br /></li><li>if (len < (file_buf->length - bytes_read))<br /></li><li>len ++;<br /></li><li>else<br /></li><li>{<br /></li><li>done= TRUE; <br /></li><li>break;<br /></li><li>}<br /></li><li><br /></li><li>if (done)<br /></li><li>break;<br /></li><li><br /></li><li>if ((enc_len= encrypt_buffer(&file_buf->str[bytes_read],++len,cipher+MAX_CIPHER_STORE_LEN))<0) //对读到的这一行内容进行加密,并将密文存放到cipher + MAX_CIPHER_STORE_LEN的地址处</li><li>goto error;<br /></li><li><br /></li><li>bytes_read += len;<br /></li><li><br /></li><li>if (enc_len > MY_LINE_MAX)<br /></li><li>goto error;<br /></li><li><br /></li><li>/* Store cipher length first. */<br /></li><li>int4store(cipher, enc_len); //将密文的长度存放到cipher的头部<br /></li><li><br /></li><li>if ((my_write(g_fd, (const uchar *)cipher, enc_len + MAX_CIPHER_STORE_LEN,<br /></li><li>MYF(MY_WME))) != (enc_len + MAX_CIPHER_STORE_LEN)) //将该行加密过的密文写到文件<br /></li><li>goto error;<br /></li><li>}<br /></li><li>verbose_msg("Successfully written encrypted data to the login file.\n");<br /></li><li>/* Update file_size */<br /></li><li>file_size= bytes_read; //更新文件大小<br /></li><li><br /></li><li>DBUG_RETURN(0);<br /></li><li><br /></li><li>error:<br /></li><li>my_perror("couldn't encrypt the file");<br /></li><li>DBUG_RETURN(-1);<br /></li><li>} </li></ol>
该函数主要功能如下:
- 读取file_buf中一行
- 对读取到的行,根据产生的KEY进行加密,将加密后的内容存放到cipher+MAX_CIPHER_STORE_LEN地址处
- 将密文的长度存放到cipher和cipher+MAX_CIPHER_STORE_LEN之间的地址
- 将cipher写入文件
- 更新文件大小
上述1~5一直循环至file_buf中的内容全部加密,并全部写入到文件中为止!
下一节会讲到具体采用的加密算法,并会通过相关的解密算法,编写程序对该文件进行解密操作!!