声明:本项目仅提供加密策略,开发者无法保证您的个人信息是百分百安全的,请勿将您的个人信息分享给不值得信任的平台、个人。
本项目是一个基于Python的相亲助手工具,通过Gradio库提供了直观的可视化界面。用户可以通过简单的操作完成个人介绍的加密,以及上传加密文件后的大模型匹配分析。
简单来说
- 你可以详细地描述自己的个人信息、身体状况、对异性的要求,无论这些信息多么隐私都没有关系,然后将这些信息加密并写入TXT。
- 当你拿到两份加密信息(例如进行相亲的两个人的个人信息),你可以将他们输入到本项目中,后台会进行解密,并根据两份信息判断这两个人适不适合在一起。整个过程中,没人能够直接地观察到个人信息,最终反馈的结果也仅为“合适”或“不合适”。
这种策略能够在避免向陌生人/刚认识不久的人暴露过多个人隐私的前提下,尽可能多地展示自己以帮助相亲的双方了解对方是否有自己讨厌的爱好、行为等情形。
本项目的介绍内容由以下几部分组成:
- 项目的快速体验、使用说明(由开发者在Aistudio进行部署)
- 源码与技术细节
- 源码获取
- 在Aistudio进行部署的注意事项
- 加解密
- 使用Ernie-bot进行判断
- 更多展望
题外话:本项目受到了智能体社交的Idea的启发,但是因为开发者并不希望构建并维护一个沟通平台,所以采用了构造了一个平台依赖程度较低的项目。也许不久之后,我们就可以在应用商店看到智能体社交的APP了,在这类APP中我们也许可以在保有隐私的基础下,进行更多的预社交活动~
1. 快速体验
本项目的部署链接:相亲判官——在保护隐私的前提下进行相亲信息交互_AI应用-飞桨AI Studio星河社区
1.1. 生成个人信息
输入个人信息、喜好,点击按钮,即可生成密文。下载后请保持文件名不要更改。
1.2. 判断相亲双方是否合适
分别上传两个人的信息,自己的信息可以自己生成,相亲对象的信息可以让对方生成后发给你~点击判断,既可以获得结果。
注意:本项目仅供参考或娱乐,最终决定应当由相亲双方谨慎考虑后决定。
2. 源码与技术细节
本项目源码可以从GitHub - Liyulingyue/BlindDateChecker: 一个简单相亲判断器,给定两个人的信息,判断这两个人是不是合适 获取。Github在国内可能会连接不顺畅,但是拼运气大概每天的2/3的时间内是能连接上的。
本项目也提供了一个源代码的压缩包,但可能不是最新版本。
2.1. 源码获取
通过git clone
的方式即可获取源码,运行主程序为main.py
。
BlindDateChecker/CheckerTools
中Crypt
为加密相关函数,GradioTools
为gradio相关代码,LLM
为大模型处理相关函数。
In [ ]
! git clone https://github.com/Liyulingyue/BlindDateChecker.git
In [ ]
# 还需要安装对应的库,最新代码依赖的包参考requirements.txt
! pip install erniebot
! pip install pycryptodome
2.2. 在Aistudio进行部署的注意事项
- 修改
main.py
为main.gradio.py
- 删去
requirements.txt
中的gradio
依赖(如果涉及到gbk或者utf8字符格式,导致打不开文件,可以下载后本地编辑,或者通过代码打开文件进行编辑) - 在
BlindDateChecker/CheckerTools/GradioTools/gr_judge_btn.py
的fn_judge
函数中,增加如下代码,将临时文件路径转化为真实路径:
upload1 = os.path.join(os.path.dirname(__file__), upload1.name)
upload2 = os.path.join(os.path.dirname(__file__), upload2.name)
- 修改
BlindDateChecker/CheckerTools/GradioTools/configure.py
路径下的一言key,该key在Aistudio主页获取 - 修改
BlindDateChecker/CheckerTools/Crypt/configure.py
路径下的配置信息,改成不一样的数字即可,sort_key
是数字1-8的随机组合,convert_key
可以随便填写
最后,点击右上角部署,选择部署类型为gradio,根目录为BlindDateChecker
,执行文件为main.gradio.py
2.3. 加解密
本节只说如何进行加密,解密只是逆序执行。
本节的加密流程如下:
- 随机生成八位数密钥
- 通过密钥对用户信息(明文)进行加密,即可获得密文
- 通过配置的密文对密钥信息进行加密,得到密钥密文
- 创建TXT文件,将密钥密文作为文件名,将密文作为文件内容
之后需要使用文件内容的时候,只需要反过来执行,先根据文件名解析密钥,再通过密钥解析获得明文。整个过程中,用户只能获得密文和密钥密文,无法直接观察到个人信息。唯一能够轻松观察到明文的人只有程序开发者/部署工程师/维护工程师,因此,只要开发/维护/管理人员不监守自盗,用户信息基本能够做到保密。
2.3.1. 对密钥进行加密
对密钥进行加密的步骤如下:
- 重排序:根据配置,将密钥顺序重排,例如密钥为32145678,配置为21345678,根据配置,重排后第一位的元素是原密钥中第2位的元素。
- 补位:根据配置,将密钥补充为16位数字,例如密钥为32145678,配置为12345678,交错组合后,密钥被补充为1(3)2(2)3(1)4(4)5(5)6(6)7(7)8(8)。
- 进制转换:采用A代表0,Z代表25,BA代表26,BB代表27,将每两位数字转化为26位字母。
整体来看,只有重排序过程起到了加密,后面两个步骤对加密没有什么帮助。但如果将补位中,统一补在上位改为随机补在上下中的一位,则能够起到很好的加密。
不过作者为了省事所以暂时先省略了这些逻辑。
下面的代码展示了加解密过程
In [1]
# 重排
import random
# 加密函数
def encrypt(key, data):
if len(str(data)) != 8 or len(key) != 8 or not all("1" <= k <= "8" for k in key):
raise ValueError("Both key and data must be sequences of 8 digits, and key must contain numbers from 1 to 8.")
# 将数据转换为字符串并确保它是八位数
data_str = str(data).zfill(8)
# 根据密钥重新排列数据
encrypted_text = [None] * 8
for i, k in enumerate(key):
encrypted_text[int(k) - 1] = data_str[i]
# 连接重新排列后的字符以形成加密文本
return ''.join(encrypted_text)
# 解密函数
def decrypt(key, encrypted_text):
if len(encrypted_text) != 8 or len(key) != 8 or not all("1" <= k <= "8" for k in key):
raise ValueError(
"Both key and encrypted_text must be sequences of 8 characters, and key must contain numbers from 1 to 8.")
# 根据密钥反向映射以恢复原始数据
decrypted_text = [None] * 8
for i, k in enumerate(key):
decrypted_text[i] = encrypted_text[int(k) - 1]
# 连接字符以形成解密后的数据,并转换为整数
return ''.join(decrypted_text)
# 示例用法
# 生成一个1-8的随机组合序列作为密钥
key = ''.join([str(x) for x in random.sample(range(1, 9), 8)])
print("Key:", key)
# 八位数密文(这里为了演示,我们用一个固定的数)
data = "42894361"
print("Original Data:", data)
# 使用正确的密钥解密
decrypted_data = decrypt(key, data)
print("Encrypted Text:", decrypted_data)
# 加密一个明文数据(这里为了演示,我们用之前解密的数)
plain_text = decrypted_data
encrypted_data = encrypt(key, plain_text)
print("Decrypted Data:", encrypted_data)
Key: 76435128 Original Data: 42894361 Encrypted Text: 63984421 Decrypted Data: 42894361
In [2]
# 补位与转化进制
import random
import string
# 加密函数
def encrypt(key, data):
if len(key) != 8 or len(data) != 8:
raise ValueError("Both key and data must be 8-digit integers.")
# 将密钥和数据交错拼接起来
interleaved = ''.join([c1 + c2 for c1, c2 in zip(key.zfill(8), data.zfill(8))])
# 将数字转换为26进制的字母表示
encrypted = ''
for i in range(0, 16, 2):
num = int(interleaved[i:i + 2])
if num < 26:
encrypted += "A"+string.ascii_uppercase[num] # 0-25 转换为 A-Z
else:
# 对于大于25的数,需要特殊处理为两位字母
first_digit = num // 26 # 计算高位数字
second_digit = num % 26 # 计算低位数字
encrypted += string.ascii_uppercase[first_digit] + string.ascii_uppercase[second_digit]
return encrypted
# 解密函数
def decrypt(key, encrypted_text):
if len(key) != 8 or len(encrypted_text) != 16:
raise ValueError("Invalid input for decryption.")
# 用于存储解密出的数字的列表
decrypted_nums = []
for i in range(0, 16, 2):
char1 = encrypted_text[i]
char2 = encrypted_text[i+1]
num1 = string.ascii_uppercase.index(char1)
num2 = string.ascii_uppercase.index(char2)
num = num1*26+num2
de_num = num%10
decrypted_nums.append(str(de_num))
# 将加密数据的数字序列转换回原始的八位数整数
decrypted_data = ''.join(decrypted_nums)
if len(decrypted_data) != 8:
raise ValueError("Decrypted data must be 8-digit integers.")
return decrypted_data
# 示例用法:
key = "13415926" # 人工配置的八位数整数密钥
shuffle_key = "12345678"
data = str(random.randint(10000000, 99999999)) # 随机生成的八位数整数待加密数据
print("Original Data:", data)
encrypted_text = encrypt(key, data)
print("Encrypted Text:", encrypted_text)
decrypted_data = decrypt(key, encrypted_text)
print("Decrypted Data:", decrypted_data)
assert decrypted_data == data # 确保加密和解密是可逆的(对于给定的密钥)
Original Data: 15686789 Encrypted Text: ALBJBUASCEDTBCCR Decrypted Data: 15686789
2.3.2. 对文本进行加密
对文本进行加密时采用库pycryptodome,代码如下:
In [4]
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
import os
def encrypt(plaintext, password):
# plaintext为str,password为str,输出为btypes,因此输出必须采用with open('example.txt','rw+')来进行读写
# 确保密码长度是16的倍数(AES-128)
if len(password) > 16:
password = password[:16]
elif len(password) < 16:
password += '\0' * (16 - len(password))
key = password.encode() # 在实际应用中,应该使用更安全的密钥派生函数
plaintext = plaintext.encode()
iv = get_random_bytes(16) # 初始化向量
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))
iv_ciphertext = iv+ciphertext
return iv_ciphertext
def decrypt(iv_ciphertext, password):
# password为str,iv_ciphertext为btypes,输出为str,因此iv_ciphertext必须采用with open('example.txt','rw+')来进行读写
# 确保密码长度是16的倍数(AES-128)
if len(password) > 16:
password = password[:16]
elif len(password) < 16:
password += '\0' * (16 - len(password))
key = password.encode() # 在实际应用中,应该使用更安全的密钥派生函数
iv = iv_ciphertext[:16]
ciphertext = iv_ciphertext[16:]
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)
plaintext = plaintext.decode()
return plaintext
password = '12345678' # 注意:密码应该是安全的,并且在实际应用中不应该硬编码
plaintext = "加密文字!"
print("Original Data:", plaintext)
encrypted_text = encrypt(plaintext, password)
print("Encrypted Text:", encrypted_text)
decrypted_text = decrypt(encrypted_text, "12345678")
print("Decrypted Data:", decrypted_text)
assert decrypted_text == plaintext # 确保加密和解密是可逆的(对于给定的密钥)
Original Data: 加密文字! Encrypted Text: b'\x96\x7f\xe6\xbb\x1a\xb7\xab\xe5\xde\xe8K+L\xe2,\xf7\xa8\xb6\x87A\xa9s8\xe3\x8b\xcb\xae\xe6aE\xd23' Decrypted Data: 加密文字!
2.4. 使用ErnieBot进行判断
本质上,应当根据个人信息,生成两个数字人进行对话。最后采访两个数字人对对方的态度决定是否合适。不过为了方便,这次直接采用提示词工程进行。为了方便调用,下面的代码进行了一点点封装,便于直接获取想要的信息。
记得从aistudio首页获取信息,替换api_key。
In [7]
import erniebot
import json
class ErnieClass(object):
"""
ErnieBot API封装类
Args:
access_token (str): 用于访问ErnieBot API的access token。
api_type (str, optional): 使用的ErnieBot API的类型。默认为"aistudio"。
Returns:
None
"""
def __init__(self, access_token, api_type="aistudio"):
erniebot.api_type = 'aistudio'
erniebot.access_token = access_token
def get_llm_answer(self, prompt):
response = erniebot.ChatCompletion.create(
model='ernie-bot',
messages=[{'role': 'user', 'content': prompt}],
top_p=0,
temperature=0.1,
)
result = response.get_result()
return result
def extract_json_from_llm_answer(self, result, start_str="```json", end_str="```", replace_list=["\n"]):
s_id = result.index(start_str)
e_id = result.index(end_str, s_id+len(start_str))
json_str = result[s_id+len(start_str):e_id]
for replace_str in replace_list:
json_str = json_str.replace(replace_str,"")
json_dict = json.loads(json_str)
return json_dict
def get_llm_json_answer(self, prompt):
result = self.get_llm_answer(prompt)
json_dict = self.extract_json_from_llm_answer(result)
return json_dict
access_token = "***********************************" #请从aistudio首页获取key
ernie = ErnieClass(access_token=access_token)
decrypted_data1 = "大家好,我是刘婷,今年29岁,现在是一家时尚杂志的编辑。我的性格比较外向,喜欢社交和聚会,业余时间我会和朋友一起逛街、看电影或者旅行。我认为生活应该是多姿多彩的,充满了欢笑和惊喜。对于我的另一半,我希望他能够陪我一起享受生活,给我足够的关心和浪漫。我希望我们的生活能够充满乐趣和爱意,而不是单调和乏味。"
decrypted_data2 = "大家好,我是王磊,今年32岁,目前在一家大型企业担任高级工程师。我的性格比较内向,喜欢安静的环境,业余时间多用来研究新技术和编程。我认为生活应该简单而有条理,不喜欢太过复杂和混乱的事物。对于我的另一半,我希望她能够温柔、贤惠,能够理解和支持我的工作。我不希望她过多地干涉我的生活,更希望我们能够有各自的空间和自由。"
prompt = f"""
你是一个相亲判断机器人,你将获得两个人的个人信息,对对方的要求等信息。请你综合两个人的信息进行判断。判断结果通过json的格式返回。
返回内容是一个字典{"{"}'判断结果':bool{"}"}。其中,判断结果为True时,表明双方较为合适,有进一步发展的可能;False表明双方不适合。
相亲人1的信息是:{decrypted_data1}。
相亲人2的信息是:{decrypted_data2}。
"""
json_dict = ernie.get_llm_json_answer(prompt)
check_result = json_dict["判断结果"]
print(check_result) # False表明两个人不合适,True表示两个人合适
'ernie-bot' will be deprecated in the future. Please use 'ernie-3.5' instead.
False
2.5. 更多展望
如果你在使用过程中遇到问题或有改进建议,请通过GitHub Issues或其他任何渠道与我们联系。 欢迎提交Pull Request参与项目贡献,共同完善和优化项目功能。 本项目目前处于开发阶段,如果对这个项目感兴趣,可以提交Pull Request参与项目贡献后续计划包括:
- 加密算法优化
- [ ] 增加更安全的加密策略
- 大模型
- [ ] 如过你不希望使用文心大模型,而是其他模型,欢迎提交Pull Request参与开发
- [ ] 欢迎补充调用本地大模型的代码
- 判断结果优化
- [ ] 优化Prompt
- [ ] 补充判断方法:调用Agent,根据两个人的自我介绍生成数字人,进行100轮对话,判断是否合适