Open EasonC13 opened 1 year ago
Hi, I don't think it's a good feature because of security reasons. Is it fundamentally possible to transfer the secret key so that the admin is not able to find out what the key is?
Hi @karfly
Yes, we can do it by cryptography.
Please check the following proof of concept code.
We can get the master key when receiving an API call while keeping it unidentified and unobtainable without the telegram API call from the user.
Users will need to set their API key again when they change chat information such as first name, last name, or username.
The hacker can't get the master key without the chat information and the bot's private key.
Moreover, we do need to warn users about the possibility that their keys will get hacked and recommend they set Usage Limits at https://platform.openai.com/account/billing/limits.
import hashlib
from base64 import urlsafe_b64encode
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.backends import default_backend
import pymongo
db = pymongo.database.Database
col = db["users_openAI_keys"]
def Sha512Hash(text):
hashed = hashlib.sha512(text.encode("utf-8")).hexdigest()
return hashed
def encrypt_text(key, text: bytes):
fernet = Fernet(key)
return fernet.encrypt(text)
def decrypt_text(key, encrypted_text: bytes):
fernet = Fernet(key)
return fernet.decrypt(encrypted_text)
def generate_fernet_key(seed: bytes):
# Use the same salt for key recovery.
salt = b'*)X\x1bz"z\xd0\x8a\xdc\x91\xef*\x9c\x00\x0b'
kdf = PBKDF2HMAC(
algorithm=hashes.SHA512(),
length=32,
salt=salt,
iterations=100,
backend=default_backend(),
)
return urlsafe_b64encode(kdf.derive(seed))
def get_users_master_key(
update: telegram.update.Update, context: callbackcontext.CallbackContext
):
"""
We can get the master key when receiving an API call
while keeping it unidentified and unobtainable
without the telegram API call from the user.
Users will need to set their API key again
when they change chat information
such as first name, last name, or username.
The hacker can't get the master key
without the chat information and the bot's private key.
"""
master_key = Sha512Hash(f"{context.bot.private_key}_{str(update.message.chat)}")
return master_key
def set_users_openAI_key(
openAI_API_key: str,
update: telegram.update.Update,
context: callbackcontext.CallbackContext,
):
master_key = get_users_master_key(update, context)
fernet_key = generate_fernet_key(master_key.encode())
openAI_API_key_encrypted = encrypt_text(fernet_key, openAI_API_key.encode())
col.insert_one(
{
"key": Sha512Hash(master_key), # double hash as index
"openAI_API_key": openAI_API_key_encrypted,
}
)
def get_users_openAI_key(
update: telegram.update.Update,
context: callbackcontext.CallbackContext,
):
master_key = get_users_master_key(update, context)
result = col.find_one(
{
"key": Sha512Hash(master_key), # double hash as index
}
)
fernet_key = generate_fernet_key(master_key.encode())
openAI_API_key = decrypt_text(fernet_key, result["openAI_API_key"])
return openAI_API_key.decode()
Hi Want to know the follow-up about this proof-of-concept code.
What do you guys think?
I'll answer later, sorry. Right now I'm fully focused on message streaming and DALL-E 2 support
Hi, this repo is awesome!
Curious if the team is interested in including a version that requires users to upload their OpenAI API key first to use ChatGPT. Maybe the host could enable this feature by setting a boolean in config.
By doing so, the host can open the bot to others who don't have the resource to host but want to use ChatGPT and pay the bill by themself.
If yes, I can contribute to that part.
Regarding the security issue, we could use encryption methods to encrypt the key users uploaded by their userID and username. Hence, the host can't access their key in Database directly. Maybe need to hash the user id and username to create a mapping to de-identify users in the database as well.
Thank you.