summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Lidén Borell <samuel@slbdata.se>2011-04-26 18:51:34 (GMT)
committerSamuel Lidén Borell <samuel@slbdata.se>2011-04-26 18:51:34 (GMT)
commit3030d6a64a93370c943aa3ee06a0f8b5e74cdc5d (patch)
tree17641b379cef46bdeb2fd0c7f8134a67ef86fdbc
parentb8ec1a473f9b12f31d42bc4a1a832e9790561294 (diff)
downloadfishlim-3030d6a64a93370c943aa3ee06a0f8b5e74cdc5d.zip
fishlim-3030d6a64a93370c943aa3ee06a0f8b5e74cdc5d.tar.gz
fishlim-3030d6a64a93370c943aa3ee06a0f8b5e74cdc5d.tar.bz2
Add a Python implementation from Nam T. Nguyen
-rw-r--r--python/README12
-rw-r--r--python/fish.py254
-rw-r--r--python/irccrypt.py782
3 files changed, 1048 insertions, 0 deletions
diff --git a/python/README b/python/README
new file mode 100644
index 0000000..ec5f158
--- /dev/null
+++ b/python/README
@@ -0,0 +1,12 @@
+
+This is a Python implementation of FiSH for XChat. It's made by Nam T.
+Nguyen and uses irccrypt.py by Björn Edström.
+
+Unlike FiSHLiM it also support key exchange (DH1080) and a more secure
+block chiper mode (CBC).
+
+I keep it here in case the original source would disappear from the
+Internet. The URL is:
+
+http://www.vithon.org/forum/Thread/show/54/FiSH_encryption_for_X_Chat_Python.html
+
diff --git a/python/fish.py b/python/fish.py
new file mode 100644
index 0000000..3c8b9f6
--- /dev/null
+++ b/python/fish.py
@@ -0,0 +1,254 @@
+###
+#
+# FiSH/Mircryption clone for X-Chat in 100% Python
+#
+# Requirements: PyCrypto, and Python 2.5+
+#
+# Copyright 2010 Nam T. Nguyen
+# Released under the BSD license
+#
+# irccrypt module is copyright 2009 Bjorn Edstrom
+# with modification from Nam T. Nguyen
+#
+###
+from __future__ import with_statement
+
+__module_name__ = 'fish'
+__module_version__ = '1.0'
+__module_description__ = 'fish encryption in pure python'
+
+import irccrypt
+import pickle
+import os
+import threading
+
+KEY_MAP = {}
+LOCK_MAP = {}
+
+class SecretKey(object):
+ def __init__(self, dh, key=None):
+ self.dh = dh
+ self.key = key
+ self.cbc_mode = False
+
+def set_processing():
+ id = xchat.get_info('server')
+ LOCK_MAP[id] = True
+
+def unset_processing():
+ id = xchat.get_info('server')
+ LOCK_MAP[id] = False
+
+def is_processing():
+ id = xchat.get_info('server')
+ return LOCK_MAP.get(id, False)
+
+def get_id(ctx):
+ return (ctx.get_info('server'), ctx.get_info('channel'))
+
+def get_nick(full):
+ if full[0] == ':':
+ full = full[1 : ]
+ return full[ : full.index('!')]
+
+def unload(userdata):
+ tmp_map = {}
+ for id, key in KEY_MAP.iteritems():
+ if key.key:
+ tmp_map[id] = key
+ with open(os.path.join(xchat.get_info('xchatdir'),
+ 'fish.pickle'), 'wb') as f:
+ pickle.dump(tmp_map, f)
+ print 'fish unloaded'
+
+def decrypt(key, inp):
+ decrypt_clz = irccrypt.Blowfish
+ decrypt_func = irccrypt.blowcrypt_unpack
+ if 3 <= inp.find(' *') <= 4:
+ decrypt_clz = irccrypt.BlowfishCBC
+ decrypt_func = irccrypt.mircryption_cbc_unpack
+ b = decrypt_clz(key.key)
+ return decrypt_func(inp, b)
+
+def encrypt(key, inp):
+ encrypt_clz = irccrypt.Blowfish
+ encrypt_func = irccrypt.blowcrypt_pack
+ if key.cbc_mode:
+ encrypt_clz = irccrypt.BlowfishCBC
+ encrypt_func = irccrypt.mircryption_cbc_pack
+ b = encrypt_clz(key.key)
+ return encrypt_func(inp, b)
+
+def decrypt_print(word, word_eol, userdata):
+ if is_processing():
+ return xchat.EAT_NONE
+ ctx = xchat.get_context()
+ id = get_id(ctx)
+ if id not in KEY_MAP:
+ return xchat.EAT_NONE
+ speaker, message = word[0], word_eol[1]
+ # if there is mode char, remove it from the message
+ if len(word_eol) >= 3:
+ message = message[ : -(len(word_eol[2]) + 1)]
+ if message.startswith('+OK ') or message.startswith('mcps '):
+ message = decrypt(KEY_MAP[id], message)
+ set_processing()
+ ctx.emit_print(userdata, speaker, message)
+ unset_processing()
+ return xchat.EAT_XCHAT
+ else:
+ return xchat.EAT_NONE
+
+def encrypt_privmsg(word, word_eol, userdata):
+ message = word_eol[0]
+ ctx = xchat.get_context()
+ id = get_id(ctx)
+ if id not in KEY_MAP:
+ return xchat.EAT_NONE
+ key = KEY_MAP[id]
+ if not key.key:
+ return xchat.EAT_NONE
+ cipher = encrypt(key, message)
+ xchat.command('PRIVMSG %s :%s' % (id[1], cipher))
+ xchat.emit_print('Your Message', xchat.get_info('nick'), message)
+ return xchat.EAT_ALL
+
+def key(word, word_eol, userdata):
+ ctx = xchat.get_context()
+ target = ctx.get_info('channel')
+ if len(word) >= 2:
+ target = word[1]
+ id = (ctx.get_info('server'), target)
+ try:
+ key = KEY_MAP[id]
+ except KeyError:
+ key = SecretKey(None)
+ if len(word) >= 3:
+ key.key = word_eol[2]
+ KEY_MAP[id] = key
+ print 'Key for', target, 'set to', key.key
+ return xchat.EAT_ALL
+
+def key_exchange(word, word_eol, userdata):
+ ctx = xchat.get_context()
+ target = ctx.get_info('channel')
+ if len(word) >= 2:
+ target = word[1]
+ id = (ctx.get_info('server'), target)
+ dh = irccrypt.DH1080Ctx()
+ KEY_MAP[id] = SecretKey(dh)
+ ctx.command('NOTICE %s %s' % (target, irccrypt.dh1080_pack(dh)))
+ return xchat.EAT_ALL
+
+def dh1080_finish(word, word_eol, userdata):
+ ctx = xchat.get_context()
+ speaker, command, target, message = word[0], word[1], word[2], word_eol[3]
+ id = (ctx.get_info('server'), get_nick(speaker))
+ if id not in KEY_MAP:
+ return xchat.EAT_NONE
+ key = KEY_MAP[id]
+ irccrypt.dh1080_unpack(message[1 : ], key.dh)
+ key.key = irccrypt.dh1080_secret(key.dh)
+ print 'Key for', id[1], 'set to', key.key
+ return xchat.EAT_ALL
+
+def dh1080_init(word, word_eol, userdata):
+ ctx = xchat.get_context()
+ speaker, command, target, message = word[0], word[1], word[2], word_eol[3]
+ id = (ctx.get_info('server'), get_nick(speaker))
+ key = SecretKey(None)
+ dh = irccrypt.DH1080Ctx()
+ irccrypt.dh1080_unpack(message[1 : ], dh)
+ key.key = irccrypt.dh1080_secret(dh)
+ xchat.command('NOTICE %s %s' % (id[1], irccrypt.dh1080_pack(dh)))
+ KEY_MAP[id] = key
+ print 'Key for', id[1], 'set to', key.key
+ return xchat.EAT_ALL
+
+def dh1080(word, word_eol, userdata):
+ if word_eol[3].startswith(':DH1080_FINISH'):
+ return dh1080_finish(word, word_eol, userdata)
+ elif word_eol[3].startswith(':DH1080_INIT'):
+ return dh1080_init(word, word_eol, userdata)
+ return xchat.EAT_NONE
+
+def load():
+ global KEY_MAP
+ try:
+ with open(os.path.join(xchat.get_info('xchatdir'),
+ 'fish.pickle'), 'rb') as f:
+ KEY_MAP = pickle.load(f)
+ except IOError:
+ pass
+ print 'fish loaded'
+
+def key_list(word, word_eol, userdata):
+ for id, key in KEY_MAP.iteritems():
+ print id, key.key, bool(key.cbc_mode)
+ return xchat.EAT_ALL
+
+def key_remove(word, word_eol, userdata):
+ id = (xchat.get_info('server'), word[1])
+ try:
+ del KEY_MAP[id]
+ except KeyError:
+ print 'Key not found'
+ else:
+ print 'Key removed'
+ return xchat.EAT_ALL
+
+def key_cbc(word, word_eol, userdata):
+ id = (xchat.get_info('server'), word[1])
+ try:
+ KEY_MAP[id].cbc_mode = int(word[2])
+ print 'CBC mode', bool(KEY_MAP[id].cbc_mode)
+ except KeyError:
+ print 'Key not found'
+ return xchat.EAT_ALL
+
+# handle topic line
+def server_332(word, word_eol, userdata):
+ if is_processing():
+ return xchat.EAT_NONE
+ id = get_id(xchat.get_context())
+ if id not in KEY_MAP:
+ return xchat.EAT_NONE
+ key = KEY_MAP[id]
+ server, cmd, nick, channel, topic = word[0], word[1], word[2], word[3], word_eol[4]
+ if topic[0] == ':':
+ topic = topic[1 : ]
+ if not (topic.startswith('+OK ') or topic.startswith('mcps ')):
+ return xchat.EAT_NONE
+ topic = decrypt(key, topic)
+ set_processing()
+ xchat.command('RECV %s %s %s %s :%s' % (server, cmd, nick, channel, topic))
+ unset_processing()
+ return xchat.EAT_ALL
+
+def change_nick(word, word_eol, userdata):
+ old, new = word[0], word[1]
+ ctx = xchat.get_context()
+ old_id = (xchat.get_info('server'), old)
+ new_id = (old_id[0], new)
+ try:
+ KEY_MAP[new_id] = KEY_MAP[old_id]
+ del KEY_MAP[old_id]
+ except KeyError:
+ pass
+ return xchat.EAT_NONE
+
+import xchat
+xchat.hook_command('key', key, help='show information or set key, /key <nick> [new_key]')
+xchat.hook_command('key_exchange', key_exchange, help='exchange a new key, /key_exchange <nick>')
+xchat.hook_command('key_list', key_list, help='list keys, /key_list')
+xchat.hook_command('key_remove', key_remove, help='remove key, /key_remove <nick>')
+xchat.hook_command('key_cbc', key_cbc, help='set cbc mode, /key_cbc <nick> <0|1>')
+xchat.hook_server('notice', dh1080)
+xchat.hook_print('Channel Message', decrypt_print, 'Channel Message')
+xchat.hook_print('Change Nick', change_nick)
+xchat.hook_print('Private Message to Dialog', decrypt_print, 'Private Message to Dialog')
+xchat.hook_server('332', server_332)
+xchat.hook_command('', encrypt_privmsg)
+xchat.hook_unload(unload)
+load()
+
diff --git a/python/irccrypt.py b/python/irccrypt.py
new file mode 100644
index 0000000..6516abf
--- /dev/null
+++ b/python/irccrypt.py
@@ -0,0 +1,782 @@
+#!/usr/bin/env python
+##
+## irccrypt.py - various cryptographic methods for IRC + IRCSRP reference
+## implementation.
+##
+## Copyright (c) 2009, Bjorn Edstrom <be@bjrn.se>
+##
+## Permission to use, copy, modify, and distribute this software for any
+## purpose with or without fee is hereby granted, provided that the above
+## copyright notice and this permission notice appear in all copies.
+##
+## THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+## WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+## MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+## ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+## WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+## ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+## OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+##
+
+""" Library for various common cryptographic methods used on IRC.
+Currently supports:
+
+* blowcrypt - as used by for Fish et al.
+* Mircryption-CBC - an improvement of blowcrypt using the CBC mode.
+* DH1080 - A Diffie Hellman key exchange adapted for IRC usage.
+
+Additionally, implements the new IRCSRP method as described at
+http://www.bjrn.se/ircsrp
+
+Sample usage:
+
+blowcrypt, Fish etc
+-------------------
+
+>>> b = Blowfish("password")
+>>> blowcrypt_pack("Hi bob!", b)
+'+OK BRurM1bWPZ1.'
+>>> blowcrypt_unpack(_, b)
+'Hi bob!'
+
+Mircryption-CBC
+---------------
+
+>>> b = BlowfishCBC("keyTest")
+>>> mircryption_cbc_pack("12345678", b)
+'+OK *oXql/CRQbadX+5kl68g1uQ=='
+
+DH1080
+------
+
+>>> alice = DH1080Ctx()
+>>> bob = DH1080Ctx()
+>>> dh1080_pack(alice)
+'DH1080_INIT qStH1LjBpb47s0XY80W9e3efrVSh2Qfq...<snip>
+>>> dh1080_unpack(_, bob)
+True
+>>> dh1080_pack(bob)
+'DH1080_FINISH mjyk//fqPoEwp5JfbJtzDmlfpzmtME...<snip>
+>>> dh1080_unpack(_, alice)
+True
+>>> dh1080_secret(alice)
+'tfu4Qysoy56OYeckat1HpJWzi+tJVx/cm+Svzb6eunQ'
+>>> dh1080_secret(bob)
+'tfu4Qysoy56OYeckat1HpJWzi+tJVx/cm+Svzb6eunQ'
+
+For more information, see the accompanying article at http://www.bjrn.se/
+"""
+
+__author__ = "Bjorn Edstrom <be@bjrn.se>"
+__date__ = "2009-01-25"
+__version__ = "0.1.1"
+
+import base64
+import hashlib
+from math import log
+try:
+ import Crypto.Cipher.Blowfish
+ import Crypto.Cipher.AES
+except ImportError:
+ print "This module requires PyCrypto / The Python Cryptographic Toolkit."
+ print "Get it from http://www.dlitz.net/software/pycrypto/."
+ raise
+from os import urandom
+import struct
+import time
+
+##
+## Preliminaries.
+##
+
+class MalformedError(Exception):
+ pass
+
+
+def sha256(s):
+ """sha256"""
+ return hashlib.sha256(s).digest()
+
+
+def int2bytes(n):
+ """Integer to variable length big endian."""
+ if n == 0:
+ return '\x00'
+ b = ''
+ while n:
+ b = chr(n % 256) + b
+ n /= 256
+ return b
+
+
+def bytes2int(b):
+ """Variable length big endian to integer."""
+ n = 0
+ for p in b:
+ n *= 256
+ n += ord(p)
+ return n
+
+
+# FIXME! Only usable for really small a with b near 16^x.
+def randint(a, b):
+ """Random integer in [a,b]."""
+ bits = int(log(b, 2) + 1) / 8
+ candidate = 0
+ while True:
+ candidate = bytes2int(urandom(bits))
+ if a <= candidate <= b:
+ break
+ assert a <= candidate <= b
+ return candidate
+
+
+def padto(msg, length):
+ """Pads 'msg' with zeroes until it's length is divisible by 'length'.
+ If the length of msg is already a multiple of 'length', does nothing."""
+ L = len(msg)
+ if L % length:
+ msg += '\x00' * (length - L % length)
+ assert len(msg) % length == 0
+ return msg
+
+
+def xorstring(a, b, blocksize): # Slow.
+ """xor string a and b, both of length blocksize."""
+ xored = ''
+ for i in xrange(blocksize):
+ xored += chr(ord(a[i]) ^ ord(b[i]))
+ return xored
+
+
+def cbc_encrypt(func, data, blocksize):
+ """The CBC mode. The randomy generated IV is prefixed to the ciphertext.
+ 'func' is a function that encrypts data in ECB mode. 'data' is the
+ plaintext. 'blocksize' is the block size of the cipher."""
+ assert len(data) % blocksize == 0
+
+ IV = urandom(blocksize)
+ assert len(IV) == blocksize
+
+ ciphertext = IV
+ for block_index in xrange(len(data) / blocksize):
+ xored = xorstring(data, IV, blocksize)
+ enc = func(xored)
+
+ ciphertext += enc
+ IV = enc
+ data = data[blocksize:]
+
+ assert len(ciphertext) % blocksize == 0
+ return ciphertext
+
+
+def cbc_decrypt(func, data, blocksize):
+ """See cbc_encrypt."""
+ assert len(data) % blocksize == 0
+
+ IV = data[0:blocksize]
+ data = data[blocksize:]
+
+ plaintext = ''
+ for block_index in xrange(len(data) / blocksize):
+ temp = func(data[0:blocksize])
+ temp2 = xorstring(temp, IV, blocksize)
+ plaintext += temp2
+ IV = data[0:blocksize]
+ data = data[blocksize:]
+
+ assert len(plaintext) % blocksize == 0
+ return plaintext
+
+
+class Blowfish:
+
+ def __init__(self, key=None):
+ if key:
+ self.blowfish = Crypto.Cipher.Blowfish.new(key)
+
+ def decrypt(self, data):
+ return self.blowfish.decrypt(data)
+
+ def encrypt(self, data):
+ return self.blowfish.encrypt(data)
+
+
+class BlowfishCBC:
+
+ def __init__(self, key=None):
+ if key:
+ self.blowfish = Crypto.Cipher.Blowfish.new(key)
+
+ def decrypt(self, data):
+ return cbc_decrypt(self.blowfish.decrypt, data, 8)
+
+ def encrypt(self, data):
+ return cbc_encrypt(self.blowfish.encrypt, data, 8)
+
+
+class AESCBC:
+
+ def __init__(self, key):
+ self.aes = Crypto.Cipher.AES.new(key)
+
+ def decrypt(self, data):
+ return cbc_decrypt(self.aes.decrypt, data, 16)
+
+ def encrypt(self, data):
+ return cbc_encrypt(self.aes.encrypt, data, 16)
+
+
+##
+## blowcrypt, Fish etc.
+##
+
+# XXX: Unstable.
+def blowcrypt_b64encode(s):
+ """A non-standard base64-encode."""
+ B64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ res = ''
+ while s:
+ left, right = struct.unpack('>LL', s[:8])
+ for i in xrange(6):
+ res += B64[right & 0x3f]
+ right >>= 6
+ for i in xrange(6):
+ res += B64[left & 0x3f]
+ left >>= 6
+ s = s[8:]
+ return res
+
+
+def blowcrypt_b64decode(s):
+ """A non-standard base64-decode."""
+ B64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ res = ''
+ while s:
+ left, right = 0, 0
+ for i, p in enumerate(s[0:6]):
+ right |= B64.index(p) << (i * 6)
+ for i, p in enumerate(s[6:12]):
+ left |= B64.index(p) << (i * 6)
+ res += struct.pack('>LL', left, right)
+ s = s[12:]
+ return res
+
+
+def blowcrypt_pack(msg, cipher):
+ """."""
+ return '+OK ' + blowcrypt_b64encode(cipher.encrypt(padto(msg, 8)))
+
+
+def blowcrypt_unpack(msg, cipher):
+ """."""
+ if not (msg.startswith('+OK ') or msg.startswith('mcps ')):
+ raise ValueError
+ _, rest = msg.split(' ', 1)
+ if len(rest) < 12:
+ raise MalformedError
+
+ try:
+ raw = blowcrypt_b64decode(padto(rest, 12))
+ except TypeError:
+ raise MalformedError
+ if not raw:
+ raise MalformedError
+
+ try:
+ plain = cipher.decrypt(raw)
+ except ValueError:
+ raise MalformedError
+
+ return plain.strip('\x00')
+
+
+##
+## Mircryption-CBC
+##
+
+def mircryption_cbc_pack(msg, cipher):
+ """."""
+ padded = padto(msg, 8)
+ return '+OK *' + base64.b64encode(cipher.encrypt(padded))
+
+
+def mircryption_cbc_unpack(msg, cipher):
+ """."""
+ if not msg.startswith('+OK *') or msg.startswith('mcps *'):
+ raise ValueError
+
+ try:
+ _, coded = msg.split('*', 1)
+ raw = base64.b64decode(coded)
+ except TypeError:
+ raise MalformedError
+ if not raw:
+ raise MalformedError
+
+ try:
+ padded = cipher.decrypt(raw)
+ except ValueError:
+ raise MalformedError
+ if not padded:
+ raise MalformedError
+
+ plain = padded.strip("\x00")
+ return plain
+
+
+##
+## DH1080
+##
+
+g_dh1080 = 2
+p_dh1080 = int('FBE1022E23D213E8ACFA9AE8B9DFAD'
+ 'A3EA6B7AC7A7B7E95AB5EB2DF85892'
+ '1FEADE95E6AC7BE7DE6ADBAB8A783E'
+ '7AF7A7FA6A2B7BEB1E72EAE2B72F9F'
+ 'A2BFB2A2EFBEFAC868BADB3E828FA8'
+ 'BADFADA3E4CC1BE7E8AFE85E9698A7'
+ '83EB68FA07A77AB6AD7BEB618ACF9C'
+ 'A2897EB28A6189EFA07AB99A8A7FA9'
+ 'AE299EFA7BA66DEAFEFBEFBF0B7D8B', 16)
+q_dh1080 = (p_dh1080 - 1) / 2
+
+
+# XXX: It is probably possible to implement dh1080 base64 using Pythons own, by
+# considering padding, lengths etc. The dh1080 implementation is basically the
+# standard one but with the padding character '=' removed. A trailing 'A'
+# is also added sometimes.
+def dh1080_b64encode(s):
+ """A non-standard base64-encode."""
+ b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+ d = [0]*len(s)*2
+
+ L = len(s) * 8
+ m = 0x80
+ i, j, k, t = 0, 0, 0, 0
+ while i < L:
+ if ord(s[i >> 3]) & m:
+ t |= 1
+ j += 1
+ m >>= 1
+ if not m:
+ m = 0x80
+ if not j % 6:
+ d[k] = b64[t]
+ t &= 0
+ k += 1
+ t <<= 1
+ t %= 0x100
+ #
+ i += 1
+ m = 5 - j % 6
+ t <<= m
+ t %= 0x100
+ if m:
+ d[k] = b64[t]
+ k += 1
+ d[k] = 0
+ res = ''
+ for q in d:
+ if q == 0:
+ break
+ res += q
+ return res
+
+
+def dh1080_b64decode(s):
+ """A non-standard base64-encode."""
+ b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+ buf = [0]*256
+ for i in range(64):
+ buf[ord(b64[i])] = i
+
+ L = len(s)
+ if L < 2:
+ raise ValueError
+ for i in reversed(range(L-1)):
+ if buf[ord(s[i])] == 0:
+ L -= 1
+ else:
+ break
+ if L < 2:
+ raise ValueError
+
+ d = [0]*L
+ i, k = 0, 0
+ while True:
+ i += 1
+ if k + 1 < L:
+ d[i-1] = buf[ord(s[k])] << 2
+ d[i-1] %= 0x100
+ else:
+ break
+ k += 1
+ if k < L:
+ d[i-1] |= buf[ord(s[k])] >> 4
+ else:
+ break
+ i += 1
+ if k + 1 < L:
+ d[i-1] = buf[ord(s[k])] << 4
+ d[i-1] %= 0x100
+ else:
+ break
+ k += 1
+ if k < L:
+ d[i-1] |= buf[ord(s[k])] >> 2
+ else:
+ break
+ i += 1
+ if k + 1 < L:
+ d[i-1] = buf[ord(s[k])] << 6
+ d[i-1] %= 0x100
+ else:
+ break
+ k += 1
+ if k < L:
+ d[i-1] |= buf[ord(s[k])] % 0x100
+ else:
+ break
+ k += 1
+ return ''.join(map(chr, d[0:i-1]))
+
+
+def dh_validate_public(public, q, p):
+ """See RFC 2631 section 2.1.5."""
+ return 1 == pow(public, q, p)
+
+
+class DH1080Ctx:
+ """DH1080 context."""
+ def __init__(self):
+ self.public = 0
+ self.private = 0
+ self.secret = 0
+ self.state = 0
+
+ bits = 1080
+ while True:
+ self.private = bytes2int(urandom(bits/8))
+ self.public = pow(g_dh1080, self.private, p_dh1080)
+ if 2 <= self.public <= p_dh1080 - 1 and \
+ dh_validate_public(self.public, q_dh1080, p_dh1080) == 1:
+ break
+
+
+def dh1080_pack(ctx):
+ """."""
+ cmd = None
+ if ctx.state == 0:
+ ctx.state = 1
+ cmd = "DH1080_INIT "
+ else:
+ cmd = "DH1080_FINISH "
+ return cmd + dh1080_b64encode(int2bytes(ctx.public))
+
+
+def dh1080_unpack(msg, ctx):
+ """."""
+ if not msg.startswith("DH1080_"):
+ raise ValueError
+
+ invalidmsg = "Key does not validate per RFC 2631. This check is not " \
+ "performed by any DH1080 implementation, so we use the key " \
+ "anyway. See RFC 2785 for more details."
+
+ if ctx.state == 0:
+ if not msg.startswith("DH1080_INIT "):
+ raise MalformedError
+ ctx.state = 1
+ try:
+ cmd, public_raw = msg.split(' ', 1)
+ public = bytes2int(dh1080_b64decode(public_raw))
+
+ if not 1 < public < p_dh1080:
+ raise MalformedError
+
+ if not dh_validate_public(public, q_dh1080, p_dh1080):
+ print invalidmsg
+
+ ctx.secret = pow(public, ctx.private, p_dh1080)
+ except:
+ raise MalformedError
+
+ elif ctx.state == 1:
+ if not msg.startswith("DH1080_FINISH "):
+ raise MalformedError
+ ctx.state = 1
+ try:
+ cmd, public_raw = msg.split(' ', 1)
+ public = bytes2int(dh1080_b64decode(public_raw))
+
+ if not 1 < public < p_dh1080:
+ raise MalformedError
+
+ if not dh_validate_public(public, q_dh1080, p_dh1080):
+ print invalidmsg
+
+ ctx.secret = pow(public, ctx.private, p_dh1080)
+ except:
+ raise MalformedError
+
+ return True
+
+
+def dh1080_secret(ctx):
+ """."""
+ if ctx.secret == 0:
+ raise ValueError
+ return dh1080_b64encode(sha256(int2bytes(ctx.secret)))
+
+
+##
+## IRCSRP version 1
+##
+
+modp14 = """
+ FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
+ 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
+ EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
+ E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
+ EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
+ C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
+ 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
+ 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B
+ E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9
+ DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510
+ 15728E5A 8AACAA68 FFFFFFFF FFFFFFFF
+"""
+g = 2
+N = int(modp14.replace(' ', '').replace('\n', ''), 16)
+H = sha256
+
+class IRCSRPExchange:
+ def __init__(self):
+ self.status = 0
+ self.I = 0
+ self.x = 0
+ self.a = 0
+ self.A = 0
+ self.b = 0
+ self.B = 0
+ self.S = 0
+ self.u = 0
+ self.K = 0
+ self.M1 = 0
+ self.M2 = 0
+
+class IRCSRPUsers:
+ def __init__(self):
+ # Store info about friends here, such as
+ # self.db["alice"] = alice_s, alice_v
+ self.db = {}
+
+ # Temporary storage for exchange. The dict key is derived from the
+ # IRC message, not the username.
+ self.others = {}
+
+ def get_details(self, username):
+ s, v = self.db[username]
+ return s, v
+
+
+class IRCSRPCtx:
+ """Everyone has one of these."""
+ def __init__(self, dave=False):
+ self.cipher = None
+ self.status = 0
+ self.username = ''
+ self.password = ''
+ self.sessionkey = ''
+ self.ex = IRCSRPExchange()
+ self.isdave = dave
+ if dave:
+ self.users = IRCSRPUsers()
+
+ def set_key(self, key):
+ assert len(key) == 32
+ self.sessionkey = key
+ self.cipher = AESCBC(key)
+ if self.isdave:
+ padded = padto('\xffKEY' + key, 16)
+ return '+aes ' + base64.b64encode(self.cipher.encrypt(padded))
+ return None
+
+
+def ircsrp_generate(username, password):
+ """Alice runs this and gives the result to Dave."""
+ s = urandom(32)
+ x = bytes2int(H(s + username + password))
+ v = pow(g, x, N)
+ return s, v
+
+
+def ircsrp_pack(ctx, msg):
+ """Encrypt message for channel."""
+ times = struct.pack(">L", int(time.time()))
+ infos = chr(len(ctx.username)) + ctx.username + times
+ padded = padto('M' + infos + msg, 16)
+ return '+aes ' + base64.b64encode(ctx.cipher.encrypt(padded))
+
+
+def ircsrp_unpack(ctx, msg):
+ """Decrypt message for channel."""
+ if not msg.startswith('+aes '):
+ raise ValueError
+
+ try:
+ _, coded = msg.split(' ', 1)
+ raw = base64.b64decode(coded)
+ except TypeError:
+ raise MalformedError
+ if not raw:
+ raise MalformedError
+
+ try:
+ padded = ctx.cipher.decrypt(raw)
+ except ValueError:
+ raise MalformedError
+ if not padded:
+ raise MalformedError
+
+ plain = padded.strip("\x00")
+
+ # New key?
+ if plain.startswith('\xffKEY'):
+ new = plain[4:]
+ if not len(new) == 32:
+ raise MalformedError
+ ctx.sessionkey = new
+ ctx.cipher = AESCBC(new)
+ print "*** Session key changed to:", repr(new)
+ return None
+
+ if not plain[0] == 'M':
+ raise ValueError
+
+ usernamelen = ord(plain[1])
+ username = plain[2:2+usernamelen]
+ timestampstr = plain[2+usernamelen:4+2+usernamelen]
+ timestamp = struct.unpack(">L", timestampstr)[0]
+
+ print "*** Sent by username:", username
+ print "*** Sent at time:", time.ctime(timestamp)
+
+ return plain[4+2+usernamelen:]
+
+
+def ircsrp_exchange(ctx, msg=None, sender=None):
+ """The key exchange, for NOTICE handler. Parameters are:
+
+ :<sender>!user@host.com NOTICE :<msg>\r\n
+ """
+ b64 = lambda s: base64.b64encode(s)
+ b64i = lambda i: b64(int2bytes(i))
+ unb64 = lambda s: base64.b64decode(s)
+ unb64i = lambda s: bytes2int(unb64(s))
+
+ cmd, arg = '', ''
+ if msg:
+ cmd, arg = msg.split(' ', 1)
+ if not cmd.startswith('+srp'):
+ raise ValueError
+ cmd = cmd[5:].strip(' ')
+
+ # Alice initiates the exchange.
+ if msg == None and sender == None and ctx.ex.status == 0:
+
+ ctx.ex.status = 1
+
+ return "+srpa0 " + ctx.username
+
+ # Dave
+ if cmd == '0':
+
+ ex = ctx.users.others[sender] = IRCSRPExchange()
+
+ I = ex.I = arg
+ s, v = ex.s, ex.v = ctx.users.get_details(I)
+ b = ex.b = randint(2, N-1)
+ B = ex.B = (3*v + pow(g, b, N)) % N
+
+ return "+srpa1 " + b64(s + int2bytes(B))
+
+ # Alice
+ if cmd == '1' and ctx.ex.status == 1:
+
+ args = unb64(arg)
+ s = ctx.ex.s = args[:32]
+ B = ctx.ex.B = bytes2int(args[32:])
+ if B % N == 0:
+ raise ValueError
+
+ a = ctx.ex.a = randint(2, N-1)
+ A = ctx.ex.A = pow(g, a, N)
+ x = ctx.ex.x = bytes2int(H(s + ctx.username + ctx.password))
+
+ u = ctx.ex.u = bytes2int(H(int2bytes(A) + int2bytes(B)))
+ S = ctx.ex.S = pow(B - 3*pow(g, x, N), (a + u*x) % N, N)
+ K = ctx.ex.K = H(int2bytes(S))
+ M1 = ctx.ex.M1 = H(int2bytes(A) + int2bytes(B) + int2bytes(S))
+
+ ctx.ex.status = 2
+
+ return "+srpa2 " + b64(M1 + int2bytes(A))
+
+ # Dave
+ if cmd == '2':
+
+ if not sender in ctx.users.others:
+ raise ValueError
+ ex = ctx.users.others[sender]
+
+ args = unb64(arg)
+ M1 = args[:32]
+ A = bytes2int(args[32:])
+ if A % N == 0:
+ raise ValueError
+
+ u = bytes2int(H(int2bytes(A) + int2bytes(ex.B)))
+ S = pow(A * pow(ex.v, u, N), ex.b, N)
+ K = H(int2bytes(S))
+ M2 = H(int2bytes(A) + M1 + int2bytes(S))
+
+ M1ver = H(int2bytes(A) + int2bytes(ex.B) + int2bytes(S))
+ if M1 != M1ver:
+ raise ValueError
+
+ aes = AESCBC(K)
+
+ del ctx.users.others[sender]
+
+ return "+srpa3 " + b64(aes.encrypt(ctx.sessionkey + M2))
+
+ # Alice
+ if cmd == '3' and ctx.ex.status == 2:
+
+ cipher = unb64(arg)
+ aes = AESCBC(ctx.ex.K)
+ plain = aes.decrypt(cipher)
+
+ sessionkey = plain[:32]
+ M2 = plain[32:]
+
+ M2ver = H(int2bytes(ctx.ex.A) + ctx.ex.M1 + int2bytes(ctx.ex.S))
+ if M2 != M2ver:
+ raise ValueError
+
+ ctx.sessionkey = sessionkey
+ ctx.cipher = AESCBC(sessionkey)
+
+ print "*** Session key is:", repr(sessionkey)
+
+ ctx.ex.status = 0
+
+ return True
+
+ raise ValueError
+
+
+# 2*37*22312747