Support multi-bot secure key exchange

master
Tanner Collin 5 years ago
parent b95c6593c8
commit 377e561764
  1. 30
      README.md
  2. 67
      bwb/common.py
  3. 2
      setup.py

@ -16,21 +16,37 @@ from bwb.molly import bwb
### Handshaking
On boot up, send `000000handshake [secret]` to BWB chat:
Boot up:
```text
secret = bwb.init()
await client.send_message(BWB, '000000handshake ' + secret)
client.send_message(BOT_WITH_BOT, '000000init ' + bwb.init())
```
When you see a `000000handshake [secret data]`:
On `000000init [data]`:
```text
bwb.init(secret_data)
await client.send_message(BWB, bwb.wrap('🤝'))
client.send_message(BOT_WITH_BOT, '000000handshake ' + bwb.handshake(data))
```
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

@ -13,20 +13,27 @@ from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import CBC
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
class common:
def __init__(self):
self.init_secret = ''
self.key = None
self.master_pub = None
self.otp = None
self.secret = ''
self.otp_secret = ''
self.otp_count = 0
def init(self, secret=None):
secret = secret or ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(12))
self.otp = HOTP(secret.encode(), 6, SHA1(), backend=default_backend(), enforce_key_length=False)
self.secret = secret
def set_otp(self, otp_secret):
self.otp = HOTP(otp_secret.encode(), 6, SHA1(), backend=default_backend(), enforce_key_length=False)
self.otp_secret = otp_secret
self.otp_count = 0
return secret
return True
def get_otp(self, target):
if not self.otp:
@ -64,7 +71,7 @@ class common:
return False
def to_b58(self, text):
return 'l' + base58.b58encode(text.encode()).decode()
return 'l' + base58.b58encode(text).decode()
def from_b58(self, text):
if not text.startswith('l'):
@ -74,8 +81,10 @@ class common:
except BaseException as e:
return False
def enc(self, text):
if not self.secret:
def enc(self, text, key=None):
if not key:
key = self.otp_secret.encode()
if not key:
raise RuntimeError('bwb not init yet')
padder = PKCS7(AES.block_size).padder()
@ -83,7 +92,7 @@ class common:
padded += padder.finalize()
digest = Hash(SHA256(), default_backend())
digest.update(self.secret.encode())
digest.update(key)
key = digest.finalize()
iv = os.urandom(4)
@ -94,8 +103,10 @@ class common:
return 'I' + base58.b58encode(iv + ct).decode()
def dec(self, ciphertext):
if not self.secret:
def dec(self, ciphertext, key=None):
if not key:
key = self.otp_secret.encode()
if not key:
return False # so we can run every message through
if not ciphertext.startswith('I'):
return False
@ -106,7 +117,7 @@ class common:
ct = ciphertext[4:]
digest = Hash(SHA256(), default_backend())
digest.update(self.secret.encode())
digest.update(key)
key = digest.finalize()
cipher = Cipher(AES(key), CBC(iv * 4), default_backend())
@ -119,6 +130,7 @@ class common:
return unpadded.decode()
except BaseException as e:
print(e)
return False
def wrap(self, text, target=None, b58=False, enc=False):
@ -149,3 +161,32 @@ class common:
return True
else:
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)

@ -5,7 +5,7 @@ with io.open('README.md', encoding='utf-8') as fh:
long_description = fh.read()
setuptools.setup(name='bwb',
version='1.1.0',
version='2.0.0',
description='bwb',
long_description=long_description,
long_description_content_type='text/markdown',

Loading…
Cancel
Save