Support multi-bot secure key exchange
This commit is contained in:
parent
b95c6593c8
commit
377e561764
30
README.md
30
README.md
|
@ -16,21 +16,37 @@ from bwb.molly import bwb
|
||||||
|
|
||||||
### Handshaking
|
### Handshaking
|
||||||
|
|
||||||
On boot up, send `000000handshake [secret]` to BWB chat:
|
Boot up:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
secret = bwb.init()
|
client.send_message(BOT_WITH_BOT, '000000init ' + bwb.init())
|
||||||
await client.send_message(BWB, '000000handshake ' + secret)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
When you see a `000000handshake [secret data]`:
|
On `000000init [data]`:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
bwb.init(secret_data)
|
client.send_message(BOT_WITH_BOT, '000000handshake ' + bwb.handshake(data))
|
||||||
await client.send_message(BWB, bwb.wrap('🤝'))
|
|
||||||
```
|
```
|
||||||
|
|
||||||
When you see and authed '🤝', reply with *unauthed* '🤝'.
|
On `000000handshake [data]`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
client.send_message(BOT_WITH_BOT, bwb.wrap('secret ' + bwb.secret(data)))
|
||||||
|
bwb.set_otp(bwb.init_secret)
|
||||||
|
```
|
||||||
|
|
||||||
|
On _OTP authed_ `123456secret [data]`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
bwb.set_secret(data)
|
||||||
|
client.send_message(BOT_WITH_BOT, bwb.wrap('🤝'))
|
||||||
|
```
|
||||||
|
|
||||||
|
On _OTP authed_ `123456🤝`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
client.send_message(BOT_WITH_BOT, '🤝')
|
||||||
|
```
|
||||||
|
|
||||||
### Interaction
|
### Interaction
|
||||||
|
|
||||||
|
|
|
@ -13,20 +13,27 @@ from cryptography.hazmat.primitives.ciphers.algorithms import AES
|
||||||
from cryptography.hazmat.primitives.ciphers.modes import CBC
|
from cryptography.hazmat.primitives.ciphers.modes import CBC
|
||||||
from cryptography.hazmat.primitives.padding import PKCS7
|
from cryptography.hazmat.primitives.padding import PKCS7
|
||||||
|
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
|
||||||
|
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.ec import ECDH
|
||||||
|
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
||||||
|
|
||||||
OTP_LOOKAHEAD = 25
|
OTP_LOOKAHEAD = 25
|
||||||
|
|
||||||
class common:
|
class common:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.init_secret = ''
|
||||||
|
self.key = None
|
||||||
|
self.master_pub = None
|
||||||
self.otp = None
|
self.otp = None
|
||||||
self.secret = ''
|
self.otp_secret = ''
|
||||||
self.otp_count = 0
|
self.otp_count = 0
|
||||||
|
|
||||||
def init(self, secret=None):
|
def set_otp(self, otp_secret):
|
||||||
secret = secret or ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(12))
|
self.otp = HOTP(otp_secret.encode(), 6, SHA1(), backend=default_backend(), enforce_key_length=False)
|
||||||
self.otp = HOTP(secret.encode(), 6, SHA1(), backend=default_backend(), enforce_key_length=False)
|
self.otp_secret = otp_secret
|
||||||
self.secret = secret
|
|
||||||
self.otp_count = 0
|
self.otp_count = 0
|
||||||
return secret
|
return True
|
||||||
|
|
||||||
def get_otp(self, target):
|
def get_otp(self, target):
|
||||||
if not self.otp:
|
if not self.otp:
|
||||||
|
@ -64,7 +71,7 @@ class common:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def to_b58(self, text):
|
def to_b58(self, text):
|
||||||
return 'l' + base58.b58encode(text.encode()).decode()
|
return 'l' + base58.b58encode(text).decode()
|
||||||
|
|
||||||
def from_b58(self, text):
|
def from_b58(self, text):
|
||||||
if not text.startswith('l'):
|
if not text.startswith('l'):
|
||||||
|
@ -74,8 +81,10 @@ class common:
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def enc(self, text):
|
def enc(self, text, key=None):
|
||||||
if not self.secret:
|
if not key:
|
||||||
|
key = self.otp_secret.encode()
|
||||||
|
if not key:
|
||||||
raise RuntimeError('bwb not init yet')
|
raise RuntimeError('bwb not init yet')
|
||||||
|
|
||||||
padder = PKCS7(AES.block_size).padder()
|
padder = PKCS7(AES.block_size).padder()
|
||||||
|
@ -83,7 +92,7 @@ class common:
|
||||||
padded += padder.finalize()
|
padded += padder.finalize()
|
||||||
|
|
||||||
digest = Hash(SHA256(), default_backend())
|
digest = Hash(SHA256(), default_backend())
|
||||||
digest.update(self.secret.encode())
|
digest.update(key)
|
||||||
key = digest.finalize()
|
key = digest.finalize()
|
||||||
|
|
||||||
iv = os.urandom(4)
|
iv = os.urandom(4)
|
||||||
|
@ -94,8 +103,10 @@ class common:
|
||||||
|
|
||||||
return 'I' + base58.b58encode(iv + ct).decode()
|
return 'I' + base58.b58encode(iv + ct).decode()
|
||||||
|
|
||||||
def dec(self, ciphertext):
|
def dec(self, ciphertext, key=None):
|
||||||
if not self.secret:
|
if not key:
|
||||||
|
key = self.otp_secret.encode()
|
||||||
|
if not key:
|
||||||
return False # so we can run every message through
|
return False # so we can run every message through
|
||||||
if not ciphertext.startswith('I'):
|
if not ciphertext.startswith('I'):
|
||||||
return False
|
return False
|
||||||
|
@ -106,7 +117,7 @@ class common:
|
||||||
ct = ciphertext[4:]
|
ct = ciphertext[4:]
|
||||||
|
|
||||||
digest = Hash(SHA256(), default_backend())
|
digest = Hash(SHA256(), default_backend())
|
||||||
digest.update(self.secret.encode())
|
digest.update(key)
|
||||||
key = digest.finalize()
|
key = digest.finalize()
|
||||||
|
|
||||||
cipher = Cipher(AES(key), CBC(iv * 4), default_backend())
|
cipher = Cipher(AES(key), CBC(iv * 4), default_backend())
|
||||||
|
@ -119,6 +130,7 @@ class common:
|
||||||
|
|
||||||
return unpadded.decode()
|
return unpadded.decode()
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
|
print(e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def wrap(self, text, target=None, b58=False, enc=False):
|
def wrap(self, text, target=None, b58=False, enc=False):
|
||||||
|
@ -149,3 +161,32 @@ class common:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_pub(self):
|
||||||
|
self.key = X25519PrivateKey.generate()
|
||||||
|
pub_key = self.key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
|
||||||
|
return base58.b58encode(pub_key).decode()
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
self.init_secret = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(14))
|
||||||
|
return self.get_pub()
|
||||||
|
|
||||||
|
def handshake(self, text):
|
||||||
|
self.master_pub = X25519PublicKey.from_public_bytes(base58.b58decode(text))
|
||||||
|
slave_pub = self.get_pub()
|
||||||
|
self.set_otp(slave_pub)
|
||||||
|
return slave_pub
|
||||||
|
|
||||||
|
def secret(self, text):
|
||||||
|
if not self.init_secret: return
|
||||||
|
self.set_otp(text)
|
||||||
|
slave_pub = X25519PublicKey.from_public_bytes(base58.b58decode(text))
|
||||||
|
shared_key = self.key.exchange(slave_pub)
|
||||||
|
derived_key = HKDF(SHA256(), length=32, salt=b'Qot.', info=None, backend=default_backend()).derive(shared_key)
|
||||||
|
return self.enc(self.init_secret, derived_key)
|
||||||
|
|
||||||
|
def set_secret(self, text):
|
||||||
|
shared_key = self.key.exchange(self.master_pub)
|
||||||
|
derived_key = HKDF(SHA256(), length=32, salt=b'Qot.', info=None, backend=default_backend()).derive(shared_key)
|
||||||
|
otp_secret = self.dec(text, derived_key)
|
||||||
|
return self.set_otp(otp_secret)
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -5,7 +5,7 @@ with io.open('README.md', encoding='utf-8') as fh:
|
||||||
long_description = fh.read()
|
long_description = fh.read()
|
||||||
|
|
||||||
setuptools.setup(name='bwb',
|
setuptools.setup(name='bwb',
|
||||||
version='1.1.0',
|
version='2.0.0',
|
||||||
description='bwb',
|
description='bwb',
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type='text/markdown',
|
long_description_content_type='text/markdown',
|
||||||
|
|
Loading…
Reference in New Issue
Block a user