背景
dbeaver查看连接密码,可以通过输入主密码进行查看。但是免费版是没有对应功能的。在某次次破解失效了,想要转到免费版使用,但之前存储的密码因为加密,没办法迁移到免费版,会解析出问题。
搜索学习
解密示例:
openssl aes-128-cbc -d -K babb4a9f774ab853c96c2d653dfe544a -iv 00000000000000000000000000000000 -in "credentials-config.json"
加密示例:
openssl aes-128-cbc -e -nosalt -K babb4a9f774ab853c96c2d653dfe544a -iv 00000000000000000000000000000000 -in credentials.json -out credentials-config.json
其中"babb4a9f774ab853c96c2d653dfe544a"为默认密码的16进制转义,在github:dbeaver源码中为
private static final byte[] LOCAL_KEY_CACHE = new byte[] { -70, -69, 74, -97, 119, 74, -72, 83, -55, 108, 45, 101, 61, -2, 84, 74 };
解密成功!
源码解析
github:源代码连接
分析其dbeaver加密代码和上面openssl参数可以看到使用的aes cdc加密方式
密码转义和创建密钥
使用传递过来的字符串生成密钥,其中有个bug,只取前16位byte,如果密码超过16位(前端页面无限制),则也只有前16位生效。
解密代码
先读取传递过来的value(也就是文件内容),读取前16位作为iv,后面的内容是真正的文本内容,使用密钥进行解密。
加密代码
生成新的iv,并以密钥进行加密,最后将iv拼接到加密后的内容前,整体返回iv+密文。
python复现
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import sys
import os
import json
default_paths = [
'~/Library/DBeaverData/workspace6/General/.dbeaver/credentials-config.json',
'~/.local/share/DBeaverData/workspace6/General/.dbeaver/credentials-config.json',
'~/.local/share/.DBeaverData/workspace6/General/.dbeaver/credentials-config.json',
'~/AppData/Roaming/DBeaverData/workspace6/General/.dbeaver/credentials-config.json',
]
PASSWORD_DECRYPTION_KEY = bytes([186, 187, 74, 159, 119, 74, 184, 83, 201, 108, 45, 101, 61, 254, 84, 74])
# password补位或默认密码
def pad_password(password:str) -> bytes:
# 不足16位的补位,超过16位的截取前16位
if password is not None:
password_encode=password.encode('utf-8')
secret_key = password_encode[:16].ljust(16, b'\0')
else:
secret_key = PASSWORD_DECRYPTION_KEY
return secret_key
# 解密
def decryptValue(iv:bytes,metadata:bytes,password=None) -> bytes:
# 不足16位的补位,超过16位的截取前16位
secret_key = pad_password(password)
# 获取AES密钥对象
decryptor = AES.new(secret_key, AES.MODE_CBC,iv)
# 解密
padded_output = decryptor.decrypt(metadata)
# 去除补位
output=unpad(padded_output, AES.block_size)
return output
# 加密
def encryptValue(metadata:bytes,password=None,iv=None) -> bytes:
# 不足16位的补位,超过16位的截取前16位
secret_key = pad_password(password)
# 获取AES密钥对象
if iv is not None:
encryptor = AES.new(secret_key, AES.MODE_CBC,iv)
else:
encryptor = AES.new(secret_key, AES.MODE_CBC)
# 获取iv
iv = encryptor.iv
# 补位
padded_input = pad(metadata, AES.block_size)
# 加密
input = encryptor.encrypt(padded_input)
# 返回iv和加密后的值
return iv,input
def read_file(path):
if not os.path.exists(path):
print('文件不存在')
sys.exit(1)
with open(path, 'rb') as f:
data = f.read()
return data
# 选择默认路径或手动输入路径
def choice_path():
for path in default_paths:
path = os.path.expanduser(path)
if os.path.exists(path):
break
if path is None:
print('未找到默认路径,请手动输入路径')
path = input('请输入路径:')
return path
print(f'找到默认路径:{path},是否使用默认路径?(y/n)')
choice = input('请输入:')
if choice.lower() == 'n':
path = input('请输入路径:')
return path
def main():
# 判断默认路径是否存在
path = choice_path()
# 读取文件
data = read_file(path)
print(data)
iv,metadata = data[:16],data[16:]
# 解密
de_data = decryptValue(iv,metadata,password='123')
print(json.dumps(json.loads(de_data), indent=4, ensure_ascii=False))
#转换为json文件
with open('data.json', 'wb') as f:
f.write(de_data)
print('解密成功,已生成data.json文件')
# 进行重新加密,如果是从自定义加密修改为默认加密或新加密
# iv,en_data= encryptValue(de_data,password='123',iv=iv)
if __name__ == '__main__':
main()
疑问
为什么iv为任何值都可以解密?所以iv的作用是什么?