前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >[MYSQL] 离谱! 用shell实现mysql_config_editor功能. mysql免密登录不再安全了(修改:2024.03.07)

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

原创
作者头像
大大刺猬
修改2024-03-07 18:41:24
修改2024-03-07 18:41:24
5330
举报
文章被收录于专栏:大大刺猬大大刺猬

导读

: mysql 5.7.338.0.23 之前的版本, 存在BUG 95597 即: 密码存在 # 字符, 会被当做注释. 修复: 使用双引号将字符串引起来

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

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

代码语言:shell
复制
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解密. 就可以得到数据了.

用法如下

代码语言:shell
复制
sh mysql_config_editor.sh decode 你的mylogin.cnf文件

例子如下:

代码语言:shell
复制
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实现的, 所以没啥好关注的.

用法如下:

代码语言:shell
复制
sh mysql_config_editor.sh encode ~/.mylogin.cnf 账号密码等信息的文本文件

例子如下:

代码语言:shell
复制
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源码:

代码语言: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<16; i++ )); do
		rkey_hex+="00"
	done
	
	for (( i=0; i<${#keyhex}; i+=2 )); do
		byte_hex=${keyhex:$i:2}
		dec_val=$((16#$byte_hex))
		index=$((i/2%16))
		index_hex_start=$((index*2))
		rkey_byte_hex=${rkey_hex:$index_hex_start:2}
		
		rkey_byte_dec=$((16#$rkey_byte_hex))
		xor_byte_dec=$((rkey_byte_dec^dec_val))
		
		rkey_hex=$(printf "%s%.2x%s" "${rkey_hex:0:$index_hex_start}" "$xor_byte_dec" "${rkey_hex:$index_hex_start+2}")
	done
	echo $rkey_hex > ${tmpfile2}
}

#从 ${BFILE} start读n字节到 ${tmpfile2}
readsize(){
	start=$1
	size=$2
	dd if=${BFILE} bs=1 skip=${start} count=${size} of=${tmpfile2} 2>/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<${#hex_string}; i+=2 )); do
		byte="\\x${hex_string:$i:2}"
		printf "$byte" >> "${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<${#hex_string}; i+=2 )); do
		byte="\\x${hex_string:$i:2}"
		printf "$byte" >> "${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<${#hex_string}; i+=2 )); do
		byte="\\x${hex_string:$i:2}"
		printf "$byte" >> ${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}
	echo "FINISH. FILENAME: ${BFILE}"
	chmod 600 ${BFILE}
}

decode(){
	echo ""
	#echo -e "${BFILE} --> ${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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 导读
  • 原理解析
  • 使用例子
    • 解密
    • 加密
  • 总结
  • 附源码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档