(Un)Roast a password sent via AIM/ICQ

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""(Un)Roast a password sent via AIM/ICQ.

The OSCAR protocol (used by AIM and ICQ) uses two alternative methods to pass
authentification credentials.  While the second uses an MD5 hash, the first
only XORs the password with a static character set.

This is meant to recover a forgotten password that is still stored by an ICQ
client.

Anyway, use Jabber/XMPP.

:Copyright: 2006 Jochen Kupperschmidt
:Date: 22-Mar-2006
:License: MIT
"""

from itertools import cycle


CHARS = '\xF3\x26\x81\xC4\x39\x86\xDB\x92\x71\xA3\xB9\xE6\x53\x7A\x95\x7C'

def roast(password):
    """(Un)Roast a password.

    Its characters are `XOR`ed with those in the roast array.

    When this function is applied on the output of itself, the result equals
    the original input string.
    """
    chars = cycle(CHARS)
    return ''.join(chr(ord(char) ^ ord(chars.next())) for char in password)

if __name__ == '__main__':
    # tests
    TEST_STRING = '12345secret'
    TEST_ROASTED = '\xc2\x14\xb2\xf0\x0c\xf5\xbe\xf1\x03\xc6\xcd'
    assert roast(TEST_STRING) == TEST_ROASTED
    assert roast(roast(TEST_STRING)) == TEST_STRING

    # example usage
    print roast('\xba\x65\xd0\xb7\x4c\xe5\xb0\xe1\x50')