[MYSQL] 离谱! 用shell实现mysql_config_editor功能. mysql免密登录不再安全了!

2024年 3月 6日 62.6k 0

导读

mysql的日常使用中, 可能会遇到 配置免密登录的环境. mysql的免密是在客户端配置的, 将IP端口,账号密码等信息加密保存在~/.mylogin.cnf文件中. 使用起来十分方便. 只需要指定名称即可连接到指定服务器, 比如 mysql --login-path=root

配置也比较简单, 可使用如下命令配置

mysql_config_editor set --login-path=root -h127.0.0.1 -P3314 -uroot -p

但是要交互的输入密码就很烦. 不支持STDIN输入密码. 这让自动化脚本就不那么自动了, 虽然也可以使用expect之类的自动输入密码, 但有的环境没有这个包. 说白了, 用起来就不那么舒服了.

所以决定自己写个mysql_config_editor.

  1. C语言版, 官方写了, 改改也能用, 但还要编译才能跑, 比较麻烦.
  2. python版, 也有大佬写了. 但python没得内置的加密包, 还得调用第三方包, 或者调用openssl来做, 但也很麻烦
  3. 那就使用shell来实现吧.
    尽管shell读写二进制数据很难受… 十分地难受.

原理解析

通过c/python版的源码我们可以得知 mysql_config_editor加密后的格式如下

对象 大小(字节) 描述
flag 4 填充符
key 20 key(不是realkey)
linesize 4 记录这一行数据的大小
data linesize 加密后的数据
n 依次类推就不细看了
linesize 4 记录这一行数据的大小
data linesize 加密后的数据

使用key创建一个AES KEY (20字节, 亦或. 是不是很眼熟-_- 很mysql连接用到的加密很像)

既然KEY也放在文件里面的, 那我们就可以根据这个KEY使用openssl解密出相关的内容了.
注: 加密模式为ecb (my_aes_128_ecb). 不用关心, 都交给openssl去做. 填充字符啥的, 也不用关心, openssl都会去做的.

为了考虑兼容性, 这里没有使用xxd, 而是使用的od, 怕有些环境没得vim… openssl的话是每个环境都有的(除非你没得ssh)

由于shell不支持二进制数据变量, 所以得存到临时文件, 要使用变量的话, 可以转为HEX 保存.
不多说了, 直接看例子吧

使用例子

脚本见文末, 本脚本是为了其它自动化脚本服务的, 所以没有写接口参数, 没得--help之类的功能. 主打一个能用就行.

解密

解密就比较简单了, 直接读取key, 然后生成AES KEY (官方保存的是原始的KEY). 根据这个AES KEY做AES解密. 就可以得到数据了.
用法如下

sh mysql_config_editor.sh decode 你的mylogin.cnf文件

例子如下:

08:38:03 [root@ddcw21 ~]#sh mysql_config_editor.sh decode ~/.mylogin.cnf

[root]
user = "root"
password = "123456"
host = "127.0.0.1"
port = 3314

08:38:17 [root@ddcw21 ~]#

这个其实也可以使用 mysql_config_editor print --all 来查看的, 但密码是加密的.

加密

加密的话, 也比较简单, 就是生成随机的KEY, 写入文件, 然后根据这个KEY生成AES KEY去加密剩下的数据即可.
自动填充的数据都是由openssl实现的, 所以没啥好关注的.
用法如下:

sh mysql_config_editor.sh encode ~/.mylogin.cnf 账号密码等信息的文本文件

例子如下:

08:42:29 [root@ddcw21 ~]#cat mylogin.txt
[root]
user = "root"
password = "123456"
host = "127.0.0.1"
port = 3314

08:42:33 [root@ddcw21 ~]#sh mysql_config_editor.sh encode ~/.mylogin.cnf mylogin.txt
FINISH. FILENAME: /root/.mylogin.cnf

08:42:49 [root@ddcw21 ~]#mysql --login-path=root -e 'select @@version'
+-----------+
| @@version |
+-----------+
| 8.0.28 |
+-----------+

总结

有了这个工具后, 以后就能自动配置mysql免密登录. 玩意忘了密码, 还能查看免密文件记录的密码.
但能查看~/.mylogin.cnf中记录的密码了. 那mysql_config_editor还安全么…

附源码

墨天轮地址: https://www.modb.pro/doc/126086
github地址:https://github.com/ddcw/ddcw/tree/master/shells/mysql_config_editor
也可以直接复制如下shell源码:

#!/usr/bin/env bash
#write by ddcw @https://github.com/ddcw
#mysql_config_editor的shell版, 不用再交互了. 支持加密解密~/.mylogin.cnf
#用法:
#解密 sh mysql_config_editor.sh decode ~/.mylogin.cnf /tmp/t20240305_out.txt
#加密 sh mysql_config_editor.sh encode ~/.mylogin.cnf /tmp/t20240305_out.txt

#/tmp/t20240305_out.txt 格式如下:
aa='
[root]
user = "root"
password = "123456"
host = "127.0.0.1"
port = 3314
socket = "/data/mysql_3314/run/mysql.sock"
'

OP=$1 #操作, 只有decode, encode
BFILE=$2 #二进制文件, 就是.mylogin.cnf
TFILE=$3 #文本文件

tmpfile1="/tmp/.tmpfileformysql_config_editorbyddcw1" #临时文件, 用于中间交互数据的.
tmpfile2="/tmp/.tmpfileformysql_config_editorbyddcw2" #临时文件,保存结果数据的

# 输入: 二进制密钥的十六进制字符串形式
# 输出: aes key 放到 tmpfile2
realkey() {
keyhex=$1 # 传入的是二进制密钥的十六进制字符串形式

# 初始化一个空的十六进制字符串表示的密钥
rkey_hex=""
for (( i=0; i/dev/null
}

#读hex
readhex(){
start=$1
size=$2
readsize ${start} ${size}
dd if=${BFILE} bs=1 skip=${start} count=${size} 2>/dev/null | od -An -tx1 | tr -d '\n ' | sed ':a;N;$!ba;s/\n//g' > ${tmpfile2}
}

#读int32
readuint32(){
start=$1
size=4
od -An -t u4 -j ${start} -N ${size} ${BFILE} | awk '{print $1}' > ${tmpfile2}
}

openssl_decode(){
key=$1
hex_string=$2
: > ${tmpfile1}
for (( i=0; i> "${tmpfile1}"
done
openssl enc -aes-128-ecb -d -K ${key} -in ${tmpfile1} -out ${tmpfile2}

}

openssl_encode(){
key=$1 #HEX
hex_string=$2
: > ${tmpfile2}
: > ${tmpfile1}
for (( i=0; i> "${tmpfile1}"
done
printf '\n' >> "${tmpfile1}" #要换行, 不然识别不了
openssl enc -aes-128-ecb -K ${key} -in ${tmpfile1} -out ${tmpfile2}
}

write_hex(){
hex_string=$1
filename=$2
for (( i=0; i> ${filename}
done

}

write_int32(){
int_value=$1
printf "\\x$(printf '%08x' $int_value | cut -c7-8)" >> ${BFILE}
printf "\\x$(printf '%08x' $int_value | cut -c5-6)" >> ${BFILE}
printf "\\x$(printf '%08x' $int_value | cut -c3-4)" >> ${BFILE}
printf "\\x$(printf '%08x' $int_value | cut -c1-2)" >> ${BFILE}
}

encode(){
#echo -e "${TFILE} --> ${BFILE}"
: > ${BFILE}
key=$(head -c 20 /dev/urandom | od -v -A n -t x1 | tr -d ' \n ')
realkey ${key}
real_key=`cat ${tmpfile2}`
#写个0 填充
write_int32 0
write_hex ${key} ${BFILE}

#循环处理TFILE文件了
while IFS= read -r line || [[ -n "$line" ]]; do
if [ -z "$line" ]; then
continue #跳过空行
fi
byte_count=$(echo -n "$line" | wc -c) #实际大小
((byte_count++))
value=$(echo -n "$line" | od -v -A n -t x1 | tr -d ' \n')
pad_len=$(( (byte_count/ 16 + 1) * 16 )) #进1
write_int32 ${pad_len} #写大小 4bytes
openssl_encode ${real_key} ${value} #写数据
cat ${tmpfile2} >> ${BFILE}

done ${TFILE}"
#: > ${TFILE}
offset=4 #开始4字节不管了
readhex ${offset} 20
offset=$[ ${offset} + 20 ]
realkey `cat ${tmpfile2}`
real_key=`cat ${tmpfile2}`
#echo "real_key" ${real_key}
maxsize=`stat -c "%s" ${BFILE}`
while [ 1 -eq 1 ];do
#echo "OFFSET START: ${offset}"
readuint32 ${offset} 4
offset=$[ ${offset} + 4 ]
keysize=`cat ${tmpfile2}`
#echo "SIZE: ${keysize}"
if [ "${keysize}" == "" ] || [ ${offset} -ge ${maxsize} ];then
break
fi
readhex ${offset} ${keysize}
offset=$[ ${offset} + ${keysize} ]
hexstring=`cat ${tmpfile2}`
#echo "real_key: ${real_key} hexstring: ${hexstring}"
openssl_decode "${real_key}" "${hexstring}"
#cat ${tmpfile2} >> ${TFILE}
cat ${tmpfile2}
#echo "OFFSET STOP: ${offset}"
done
#cat ${TFILE}
echo ""

}
if [ "${OP^^}" == "DECODE" ];then
if [ ! -f ${BFILE} ];then
echo "${BFILE} not exists"
exit 1
fi
decode
elif [ "${OP^^}" == "ENCODE" ];then
if [ ! -f ${TFILE} ];then
echo "${TFILE} not exists"
exit 1
fi
if [ -f ${BFILE} ];then
mv ${BFILE} /tmp/.mylogin.cnf.backbyddcw_`date +%s` #存在的话, 就备份一手, 免得操作有问题
fi
encode
else
echo "unknown ${@}"
fi

相关文章

Oracle如何使用授予和撤销权限的语法和示例
Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
社区版oceanbase安装
Oracle 导出CSV工具-sqluldr2
ETL数据集成丨快速将MySQL数据迁移至Doris数据库

发布评论