view libpurple/cipher.c @ 18372:47ff5e98b371

Make the MSN logger set flags and ues <br> instead of \n. This fixes the problem where these logs would show up as one line in the history plugin. Thank Michael Shkutkov for tracking this down and providing the fixes.
author Richard Laager <rlaager@wiktel.com>
date Sat, 30 Jun 2007 22:04:32 +0000
parents 103a37dd9398
children 9a1c7a566d7c 941965d6fd88
line wrap: on
line source

/*
 * purple
 *
 * Purple is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * Original md5
 * Copyright (C) 2001-2003  Christophe Devine <c.devine@cr0.net>
 *
 * Original md4 taken from linux kernel
 * MD4 Message Digest Algorithm (RFC1320).
 *
 * Implementation derived from Andrew Tridgell and Steve French's
 * CIFS MD4 implementation, and the cryptoapi implementation
 * originally based on the public domain implementation written
 * by Colin Plumb in 1993.
 *
 * Copyright (c) Andrew Tridgell 1997-1998.
 * Modified by Steve French (sfrench@us.ibm.com) 2002
 * Copyright (c) Cryptoapi developers.
 * Copyright (c) 2002 David S. Miller (davem@redhat.com)
 * Copyright (c) 2002 James Morris <jmorris@intercode.com.au>
 *
 * Original des taken from gpg
 *
 * des.c - DES and Triple-DES encryption/decryption Algorithm
 *	Copyright (C) 1998 Free Software Foundation, Inc.
 *
 *	Please see below for more legal information!
 *
 *	 According to the definition of DES in FIPS PUB 46-2 from December 1993.
 *	 For a description of triple encryption, see:
 *	   Bruce Schneier: Applied Cryptography. Second Edition.
 *	   John Wiley & Sons, 1996. ISBN 0-471-12845-7. Pages 358 ff.
 *
 *	 This file is part of GnuPG.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#include <glib.h>
#include <string.h>
#include <stdio.h>

#include "internal.h"
#include "cipher.h"
#include "dbus-maybe.h"
#include "debug.h"
#include "signals.h"
#include "value.h"

/*******************************************************************************
 * MD5
 ******************************************************************************/
struct MD5Context {
	guint32 total[2];
	guint32 state[4];
	guchar buffer[64];
};

#define MD5_GET_GUINT32(n,b,i) {			\
	(n) = ((guint32)(b) [(i)    ]      )	\
		| ((guint32)(b) [(i) + 1] <<  8)	\
		| ((guint32)(b) [(i) + 2] << 16)	\
		| ((guint32)(b) [(i) + 3] << 24);	\
}
#define MD5_PUT_GUINT32(n,b,i) {			\
	(b)[(i)    ] = (guchar)((n)      );		\
	(b)[(i) + 1] = (guchar)((n) >>  8);		\
	(b)[(i) + 2] = (guchar)((n) >> 16);		\
	(b)[(i) + 3] = (guchar)((n) >> 24);		\
}

static void
md5_init(PurpleCipherContext *context, gpointer extra) {
	struct MD5Context *md5_context;

	md5_context = g_new0(struct MD5Context, 1);

	purple_cipher_context_set_data(context, md5_context);

	purple_cipher_context_reset(context, extra);
}

static void
md5_reset(PurpleCipherContext *context, gpointer extra) {
	struct MD5Context *md5_context;

	md5_context = purple_cipher_context_get_data(context);

	md5_context->total[0] = 0;
	md5_context->total[1] = 0;

	md5_context->state[0] = 0x67452301;
	md5_context->state[1] = 0xEFCDAB89;
	md5_context->state[2] = 0x98BADCFE;
	md5_context->state[3] = 0x10325476;

	memset(md5_context->buffer, 0, sizeof(md5_context->buffer));
}

static void
md5_uninit(PurpleCipherContext *context) {
	struct MD5Context *md5_context;

	purple_cipher_context_reset(context, NULL);

	md5_context = purple_cipher_context_get_data(context);
	memset(md5_context, 0, sizeof(md5_context));

	g_free(md5_context);
	md5_context = NULL;
}

static void
md5_process(struct MD5Context *md5_context, const guchar data[64]) {
	guint32 X[16], A, B, C, D;

	A = md5_context->state[0];
	B = md5_context->state[1];
	C = md5_context->state[2];
	D = md5_context->state[3];

	MD5_GET_GUINT32(X[ 0], data,  0);
	MD5_GET_GUINT32(X[ 1], data,  4);
	MD5_GET_GUINT32(X[ 2], data,  8);
	MD5_GET_GUINT32(X[ 3], data, 12);
	MD5_GET_GUINT32(X[ 4], data, 16);
	MD5_GET_GUINT32(X[ 5], data, 20);
	MD5_GET_GUINT32(X[ 6], data, 24);
	MD5_GET_GUINT32(X[ 7], data, 28);
	MD5_GET_GUINT32(X[ 8], data, 32);
	MD5_GET_GUINT32(X[ 9], data, 36);
	MD5_GET_GUINT32(X[10], data, 40);
	MD5_GET_GUINT32(X[11], data, 44);
	MD5_GET_GUINT32(X[12], data, 48);
	MD5_GET_GUINT32(X[13], data, 52);
	MD5_GET_GUINT32(X[14], data, 56);
	MD5_GET_GUINT32(X[15], data, 60);

	#define S(x,n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n)))
	#define P(a,b,c,d,k,s,t) {		\
		a += F(b,c,d) + X[k] + t;	\
		a = S(a,s) + b;				\
	}

	/* first pass */
	#define F(x,y,z) (z ^ (x & (y ^ z)))
	P(A, B, C, D,  0,  7, 0xD76AA478);
	P(D, A, B, C,  1, 12, 0xE8C7B756);
	P(C, D, A, B,  2, 17, 0x242070DB);
	P(B, C, D, A,  3, 22, 0xC1BDCEEE);
	P(A, B, C, D,  4,  7, 0xF57C0FAF);
	P(D, A, B, C,  5, 12, 0x4787C62A);
	P(C, D, A, B,  6, 17, 0xA8304613);
	P(B, C, D, A,  7, 22, 0xFD469501);
	P(A, B, C, D,  8,  7, 0x698098D8);
	P(D, A, B, C,  9, 12, 0x8B44F7AF);
	P(C, D, A, B, 10, 17, 0xFFFF5BB1);
	P(B, C, D, A, 11, 22, 0x895CD7BE);
	P(A, B, C, D, 12,  7, 0x6B901122);
	P(D, A, B, C, 13, 12, 0xFD987193);
	P(C, D, A, B, 14, 17, 0xA679438E);
	P(B, C, D, A, 15, 22, 0x49B40821);
	#undef F

	/* second pass */
	#define F(x,y,z) (y ^ (z & (x ^ y)))
	P(A, B, C, D,  1,  5, 0xF61E2562);
	P(D, A, B, C,  6,  9, 0xC040B340);
	P(C, D, A, B, 11, 14, 0x265E5A51);
	P(B, C, D, A,  0, 20, 0xE9B6C7AA);
	P(A, B, C, D,  5,  5, 0xD62F105D);
	P(D, A, B, C, 10,  9, 0x02441453);
	P(C, D, A, B, 15, 14, 0xD8A1E681);
	P(B, C, D, A,  4, 20, 0xE7D3FBC8);
	P(A, B, C, D,  9,  5, 0x21E1CDE6);
	P(D, A, B, C, 14,  9, 0xC33707D6);
	P(C, D, A, B,  3, 14, 0xF4D50D87);
	P(B, C, D, A,  8, 20, 0x455A14ED);
	P(A, B, C, D, 13,  5, 0xA9E3E905);
	P(D, A, B, C,  2,  9, 0xFCEFA3F8);
	P(C, D, A, B,  7, 14, 0x676F02D9);
	P(B, C, D, A, 12, 20, 0x8D2A4C8A);
	#undef F

	/* third pass */
	#define F(x,y,z) (x ^ y ^ z)
	P(A, B, C, D,  5,  4, 0xFFFA3942);
	P(D, A, B, C,  8, 11, 0x8771F681);
	P(C, D, A, B, 11, 16, 0x6D9D6122);
	P(B, C, D, A, 14, 23, 0xFDE5380C);
	P(A, B, C, D,  1,  4, 0xA4BEEA44);
	P(D, A, B, C,  4, 11, 0x4BDECFA9);
	P(C, D, A, B,  7, 16, 0xF6BB4B60);
	P(B, C, D, A, 10, 23, 0xBEBFBC70);
	P(A, B, C, D, 13,  4, 0x289B7EC6);
	P(D, A, B, C,  0, 11, 0xEAA127FA);
	P(C, D, A, B,  3, 16, 0xD4EF3085);
	P(B, C, D, A,  6, 23, 0x04881D05);
	P(A, B, C, D,  9,  4, 0xD9D4D039);
	P(D, A, B, C, 12, 11, 0xE6DB99E5);
	P(C, D, A, B, 15, 16, 0x1FA27CF8);
	P(B, C, D, A,  2, 23, 0xC4AC5665);
	#undef F

	/* forth pass */
	#define F(x,y,z) (y ^ (x | ~z))
	P(A, B, C, D,  0,  6, 0xF4292244);
	P(D, A, B, C,  7, 10, 0x432AFF97);
	P(C, D, A, B, 14, 15, 0xAB9423A7);
	P(B, C, D, A,  5, 21, 0xFC93A039);
	P(A, B, C, D, 12,  6, 0x655B59C3);
	P(D, A, B, C,  3, 10, 0x8F0CCC92);
	P(C, D, A, B, 10, 15, 0xFFEFF47D);
	P(B, C, D, A,  1, 21, 0x85845DD1);
	P(A, B, C, D,  8,  6, 0x6FA87E4F);
	P(D, A, B, C, 15, 10, 0xFE2CE6E0);
	P(C, D, A, B,  6, 15, 0xA3014314);
	P(B, C, D, A, 13, 21, 0x4E0811A1);
	P(A, B, C, D,  4,  6, 0xF7537E82);
	P(D, A, B, C, 11, 10, 0xBD3AF235);
	P(C, D, A, B,  2, 15, 0x2AD7D2BB);
	P(B, C, D, A,  9, 21, 0xEB86D391);
	#undef F
	#undef P
	#undef S

	md5_context->state[0] += A;
	md5_context->state[1] += B;
	md5_context->state[2] += C;
	md5_context->state[3] += D;
}

static void
md5_append(PurpleCipherContext *context, const guchar *data, size_t len) {
	struct MD5Context *md5_context = NULL;
	guint32 left = 0, fill = 0;

	g_return_if_fail(context != NULL);

	md5_context = purple_cipher_context_get_data(context);
	g_return_if_fail(md5_context != NULL);

	left = md5_context->total[0] & 0x3F;
	fill = 64 - left;

	md5_context->total[0] += len;
	md5_context->total[0] &= 0xFFFFFFFF;

	if(md5_context->total[0] < len)
		md5_context->total[1]++;

	if(left && len >= fill) {
		memcpy((md5_context->buffer + left), data, fill);
		md5_process(md5_context, md5_context->buffer);
		len -= fill;
		data += fill;
		left = 0;
	}

	while(len >= 64) {
		md5_process(md5_context, data);
		len -= 64;
		data += 64;
	}

	if(len) {
		memcpy((md5_context->buffer + left), data, len);
	}
}

static gboolean
md5_digest(PurpleCipherContext *context, size_t in_len, guchar digest[16],
		   size_t *out_len)
{
	struct MD5Context *md5_context = NULL;
	guint32 last, pad;
	guint32 high, low;
	guchar message[8];
	guchar padding[64] = {
		0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
	};

	g_return_val_if_fail(in_len >= 16, FALSE);

	md5_context = purple_cipher_context_get_data(context);

	high = (md5_context->total[0] >> 29)
		 | (md5_context->total[1] << 3);
	low = (md5_context->total[0] << 3);

	MD5_PUT_GUINT32(low, message, 0);
	MD5_PUT_GUINT32(high, message, 4);

	last = md5_context->total[0] & 0x3F;
	pad = (last < 56) ? (56 - last) : (120 - last);

	md5_append(context, padding, pad);
	md5_append(context, message, 8);

	MD5_PUT_GUINT32(md5_context->state[0], digest, 0);
	MD5_PUT_GUINT32(md5_context->state[1], digest, 4);
	MD5_PUT_GUINT32(md5_context->state[2], digest, 8);
	MD5_PUT_GUINT32(md5_context->state[3], digest, 12);

	if(out_len)
		*out_len = 16;

	return TRUE;
}

static PurpleCipherOps MD5Ops = {
	NULL,			/* Set option */
	NULL,			/* Get option */
	md5_init,		/* init */
	md5_reset,		/* reset */
	md5_uninit,		/* uninit */
	NULL,			/* set iv */
	md5_append,		/* append */
	md5_digest,		/* digest */
	NULL,			/* encrypt */
	NULL,			/* decrypt */
	NULL,			/* set salt */
	NULL,			/* get salt size */
	NULL,			/* set key */
	NULL,			/* get key size */

	/* padding */
	NULL,
	NULL,
	NULL,
	NULL
};

/*******************************************************************************
 * MD4
 ******************************************************************************/
#define MD4_DIGEST_SIZE		16
#define MD4_HMAC_BLOCK_SIZE	64
#define MD4_BLOCK_WORDS		16
#define MD4_HASH_WORDS		4



struct MD4_Context {
	guint32 hash[MD4_HASH_WORDS];
	guint32 block[MD4_BLOCK_WORDS];
	guint64 byte_count;
};

static inline guint32 lshift(guint32 x, unsigned int s)
{
	x &= 0xFFFFFFFF;
	return ((x << s) & 0xFFFFFFFF) | (x >> (32 - s));
}

static inline guint32 F(guint32 x, guint32 y, guint32 z)
{
	return (x & y) | ((~x) & z);
}

static inline guint32 G(guint32 x, guint32 y, guint32 z)
{
	return (x & y) | (x & z) | (y & z);
}

static inline guint32 H(guint32 x, guint32 y, guint32 z)
{
	return x ^ y ^ z;
}

#define ROUND1(a,b,c,d,k,s) (a = lshift(a + F(b,c,d) + k, s))
#define ROUND2(a,b,c,d,k,s) (a = lshift(a + G(b,c,d) + k + (guint32)0x5A827999,s))
#define ROUND3(a,b,c,d,k,s) (a = lshift(a + H(b,c,d) + k + (guint32)0x6ED9EBA1,s))

static inline void le32_to_cpu_array(guint32 *buf, unsigned int words)
{
	while (words--) {
		*buf=GUINT_FROM_LE(*buf);
		buf++;
	}
}

static inline void cpu_to_le32_array(guint32 *buf, unsigned int words)
{
	while (words--) {
		*buf=GUINT_TO_LE(*buf);
		buf++;
	}
}

static void md4_transform(guint32 *hash, guint32 const *in)
{
	guint32 a, b, c, d;

	a = hash[0];
	b = hash[1];
	c = hash[2];
	d = hash[3];

	ROUND1(a, b, c, d, in[0], 3);
	ROUND1(d, a, b, c, in[1], 7);
	ROUND1(c, d, a, b, in[2], 11);
	ROUND1(b, c, d, a, in[3], 19);
	ROUND1(a, b, c, d, in[4], 3);
	ROUND1(d, a, b, c, in[5], 7);
	ROUND1(c, d, a, b, in[6], 11);
	ROUND1(b, c, d, a, in[7], 19);
	ROUND1(a, b, c, d, in[8], 3);
	ROUND1(d, a, b, c, in[9], 7);
	ROUND1(c, d, a, b, in[10], 11);
	ROUND1(b, c, d, a, in[11], 19);
	ROUND1(a, b, c, d, in[12], 3);
	ROUND1(d, a, b, c, in[13], 7);
	ROUND1(c, d, a, b, in[14], 11);
	ROUND1(b, c, d, a, in[15], 19);

	ROUND2(a, b, c, d,in[ 0], 3);
	ROUND2(d, a, b, c, in[4], 5);
	ROUND2(c, d, a, b, in[8], 9);
	ROUND2(b, c, d, a, in[12], 13);
	ROUND2(a, b, c, d, in[1], 3);
	ROUND2(d, a, b, c, in[5], 5);
	ROUND2(c, d, a, b, in[9], 9);
	ROUND2(b, c, d, a, in[13], 13);
	ROUND2(a, b, c, d, in[2], 3);
	ROUND2(d, a, b, c, in[6], 5);
	ROUND2(c, d, a, b, in[10], 9);
	ROUND2(b, c, d, a, in[14], 13);
	ROUND2(a, b, c, d, in[3], 3);
	ROUND2(d, a, b, c, in[7], 5);
	ROUND2(c, d, a, b, in[11], 9);
	ROUND2(b, c, d, a, in[15], 13);

	ROUND3(a, b, c, d,in[ 0], 3);
	ROUND3(d, a, b, c, in[8], 9);
	ROUND3(c, d, a, b, in[4], 11);
	ROUND3(b, c, d, a, in[12], 15);
	ROUND3(a, b, c, d, in[2], 3);
	ROUND3(d, a, b, c, in[10], 9);
	ROUND3(c, d, a, b, in[6], 11);
	ROUND3(b, c, d, a, in[14], 15);
	ROUND3(a, b, c, d, in[1], 3);
	ROUND3(d, a, b, c, in[9], 9);
	ROUND3(c, d, a, b, in[5], 11);
	ROUND3(b, c, d, a, in[13], 15);
	ROUND3(a, b, c, d, in[3], 3);
	ROUND3(d, a, b, c, in[11], 9);
	ROUND3(c, d, a, b, in[7], 11);
	ROUND3(b, c, d, a, in[15], 15);

	hash[0] += a;
	hash[1] += b;
	hash[2] += c;
	hash[3] += d;
}

static inline void md4_transform_helper(struct MD4_Context *ctx)
{
	le32_to_cpu_array(ctx->block, sizeof(ctx->block) / sizeof(guint32));
	md4_transform(ctx->hash, ctx->block);
}

static void
md4_init(PurpleCipherContext *context, gpointer extra) {
	struct MD4_Context *mctx;
	mctx = g_new0(struct MD4_Context, 1);
	purple_cipher_context_set_data(context, mctx);
	purple_cipher_context_reset(context, extra);

	mctx->hash[0] = 0x67452301;
	mctx->hash[1] = 0xefcdab89;
	mctx->hash[2] = 0x98badcfe;
	mctx->hash[3] = 0x10325476;
	mctx->byte_count = 0;
}

static void
md4_reset(PurpleCipherContext *context, gpointer extra) {
	struct MD4_Context *mctx;

	mctx = purple_cipher_context_get_data(context);

	mctx->hash[0] = 0x67452301;
	mctx->hash[1] = 0xefcdab89;
	mctx->hash[2] = 0x98badcfe;
	mctx->hash[3] = 0x10325476;
	mctx->byte_count = 0;
}

static void
md4_append(PurpleCipherContext *context, const guchar *data, size_t len) 
{
	struct MD4_Context *mctx = purple_cipher_context_get_data(context);
	const guint32 avail = sizeof(mctx->block) - (mctx->byte_count & 0x3f);

	mctx->byte_count += len;

	if (avail > len) {
		memcpy((char *)mctx->block + (sizeof(mctx->block) - avail),
				data, len);
		return;
	}

	memcpy((char *)mctx->block + (sizeof(mctx->block) - avail),
			data, avail);

	md4_transform_helper(mctx);
	data += avail;
	len -= avail;

	while (len >= sizeof(mctx->block)) {
		memcpy(mctx->block, data, sizeof(mctx->block));
		md4_transform_helper(mctx);
		data += sizeof(mctx->block);
		len -= sizeof(mctx->block);
	}

	memcpy(mctx->block, data, len);
}

static gboolean
md4_digest(PurpleCipherContext *context, size_t in_len, guchar *out,
		                   size_t *out_len)
{
	struct MD4_Context *mctx = purple_cipher_context_get_data(context);
	const unsigned int offset = mctx->byte_count & 0x3f;
	char *p = (char *)mctx->block + offset;
	int padding = 56 - (offset + 1);

	
	if(in_len<16) return FALSE;
	if(out_len) *out_len = 16;
	*p++ = 0x80;
	if (padding < 0) {
		memset(p, 0x00, padding + sizeof (guint64));
		md4_transform_helper(mctx);
		p = (char *)mctx->block;
		padding = 56;
	}

	memset(p, 0, padding);
	mctx->block[14] = mctx->byte_count << 3;
	mctx->block[15] = mctx->byte_count >> 29;
	le32_to_cpu_array(mctx->block, (sizeof(mctx->block) -
				sizeof(guint64)) / sizeof(guint32));
	md4_transform(mctx->hash, mctx->block);
	cpu_to_le32_array(mctx->hash, sizeof(mctx->hash) / sizeof(guint32));
	memcpy(out, mctx->hash, sizeof(mctx->hash));
	memset(mctx, 0, sizeof(*mctx));
	return TRUE;
}

static void
md4_uninit(PurpleCipherContext *context) {
	struct MD4_Context *md4_context;

	purple_cipher_context_reset(context, NULL);

	md4_context = purple_cipher_context_get_data(context);
	memset(md4_context, 0, sizeof(md4_context));

	g_free(md4_context);
	md4_context = NULL;
}

static PurpleCipherOps MD4Ops = {
	NULL,                   /* Set option */
	NULL,                   /* Get option */
	md4_init,               /* init */
	md4_reset,              /* reset */
	md4_uninit,             /* uninit */
	NULL,                   /* set iv */
	md4_append,             /* append */
	md4_digest,             /* digest */
	NULL,                   /* encrypt */
	NULL,                   /* decrypt */
	NULL,                   /* set salt */
	NULL,                   /* get salt size */
	NULL,                   /* set key */
	NULL,                   /* get key size */

	/* padding */
	NULL,
	NULL,
	NULL,
	NULL
};

/******************************************************************************
 * DES
 *****************************************************************************/

typedef struct _des_ctx
{
	guint32 encrypt_subkeys[32];
	guint32 decrypt_subkeys[32];
} des_ctx[1];

/*
 *  The s-box values are permuted according to the 'primitive function P'
 */
static guint32 sbox1[64] =
{
	0x00808200, 0x00000000, 0x00008000, 0x00808202, 0x00808002, 0x00008202, 0x00000002, 0x00008000,
	0x00000200, 0x00808200, 0x00808202, 0x00000200, 0x00800202, 0x00808002, 0x00800000, 0x00000002,
	0x00000202, 0x00800200, 0x00800200, 0x00008200, 0x00008200, 0x00808000, 0x00808000, 0x00800202,
	0x00008002, 0x00800002, 0x00800002, 0x00008002, 0x00000000, 0x00000202, 0x00008202, 0x00800000,
	0x00008000, 0x00808202, 0x00000002, 0x00808000, 0x00808200, 0x00800000, 0x00800000, 0x00000200,
	0x00808002, 0x00008000, 0x00008200, 0x00800002, 0x00000200, 0x00000002, 0x00800202, 0x00008202,
	0x00808202, 0x00008002, 0x00808000, 0x00800202, 0x00800002, 0x00000202, 0x00008202, 0x00808200,
	0x00000202, 0x00800200, 0x00800200, 0x00000000, 0x00008002, 0x00008200, 0x00000000, 0x00808002
};

static guint32 sbox2[64] =
{
	0x40084010, 0x40004000, 0x00004000, 0x00084010, 0x00080000, 0x00000010, 0x40080010, 0x40004010,
	0x40000010, 0x40084010, 0x40084000, 0x40000000, 0x40004000, 0x00080000, 0x00000010, 0x40080010,
	0x00084000, 0x00080010, 0x40004010, 0x00000000, 0x40000000, 0x00004000, 0x00084010, 0x40080000,
	0x00080010, 0x40000010, 0x00000000, 0x00084000, 0x00004010, 0x40084000, 0x40080000, 0x00004010,
	0x00000000, 0x00084010, 0x40080010, 0x00080000, 0x40004010, 0x40080000, 0x40084000, 0x00004000,
	0x40080000, 0x40004000, 0x00000010, 0x40084010, 0x00084010, 0x00000010, 0x00004000, 0x40000000,
	0x00004010, 0x40084000, 0x00080000, 0x40000010, 0x00080010, 0x40004010, 0x40000010, 0x00080010,
	0x00084000, 0x00000000, 0x40004000, 0x00004010, 0x40000000, 0x40080010, 0x40084010, 0x00084000
};

static guint32 sbox3[64] =
{
	0x00000104, 0x04010100, 0x00000000, 0x04010004, 0x04000100, 0x00000000, 0x00010104, 0x04000100,
	0x00010004, 0x04000004, 0x04000004, 0x00010000, 0x04010104, 0x00010004, 0x04010000, 0x00000104,
	0x04000000, 0x00000004, 0x04010100, 0x00000100, 0x00010100, 0x04010000, 0x04010004, 0x00010104,
	0x04000104, 0x00010100, 0x00010000, 0x04000104, 0x00000004, 0x04010104, 0x00000100, 0x04000000,
	0x04010100, 0x04000000, 0x00010004, 0x00000104, 0x00010000, 0x04010100, 0x04000100, 0x00000000,
	0x00000100, 0x00010004, 0x04010104, 0x04000100, 0x04000004, 0x00000100, 0x00000000, 0x04010004,
	0x04000104, 0x00010000, 0x04000000, 0x04010104, 0x00000004, 0x00010104, 0x00010100, 0x04000004,
	0x04010000, 0x04000104, 0x00000104, 0x04010000, 0x00010104, 0x00000004, 0x04010004, 0x00010100
};

static guint32 sbox4[64] =
{
	0x80401000, 0x80001040, 0x80001040, 0x00000040, 0x00401040, 0x80400040, 0x80400000, 0x80001000,
	0x00000000, 0x00401000, 0x00401000, 0x80401040, 0x80000040, 0x00000000, 0x00400040, 0x80400000,
	0x80000000, 0x00001000, 0x00400000, 0x80401000, 0x00000040, 0x00400000, 0x80001000, 0x00001040,
	0x80400040, 0x80000000, 0x00001040, 0x00400040, 0x00001000, 0x00401040, 0x80401040, 0x80000040,
	0x00400040, 0x80400000, 0x00401000, 0x80401040, 0x80000040, 0x00000000, 0x00000000, 0x00401000,
	0x00001040, 0x00400040, 0x80400040, 0x80000000, 0x80401000, 0x80001040, 0x80001040, 0x00000040,
	0x80401040, 0x80000040, 0x80000000, 0x00001000, 0x80400000, 0x80001000, 0x00401040, 0x80400040,
	0x80001000, 0x00001040, 0x00400000, 0x80401000, 0x00000040, 0x00400000, 0x00001000, 0x00401040
};

static guint32 sbox5[64] =
{
	0x00000080, 0x01040080, 0x01040000, 0x21000080, 0x00040000, 0x00000080, 0x20000000, 0x01040000,
	0x20040080, 0x00040000, 0x01000080, 0x20040080, 0x21000080, 0x21040000, 0x00040080, 0x20000000,
	0x01000000, 0x20040000, 0x20040000, 0x00000000, 0x20000080, 0x21040080, 0x21040080, 0x01000080,
	0x21040000, 0x20000080, 0x00000000, 0x21000000, 0x01040080, 0x01000000, 0x21000000, 0x00040080,
	0x00040000, 0x21000080, 0x00000080, 0x01000000, 0x20000000, 0x01040000, 0x21000080, 0x20040080,
	0x01000080, 0x20000000, 0x21040000, 0x01040080, 0x20040080, 0x00000080, 0x01000000, 0x21040000,
	0x21040080, 0x00040080, 0x21000000, 0x21040080, 0x01040000, 0x00000000, 0x20040000, 0x21000000,
	0x00040080, 0x01000080, 0x20000080, 0x00040000, 0x00000000, 0x20040000, 0x01040080, 0x20000080
};

static guint32 sbox6[64] =
{
	0x10000008, 0x10200000, 0x00002000, 0x10202008, 0x10200000, 0x00000008, 0x10202008, 0x00200000,
	0x10002000, 0x00202008, 0x00200000, 0x10000008, 0x00200008, 0x10002000, 0x10000000, 0x00002008,
	0x00000000, 0x00200008, 0x10002008, 0x00002000, 0x00202000, 0x10002008, 0x00000008, 0x10200008,
	0x10200008, 0x00000000, 0x00202008, 0x10202000, 0x00002008, 0x00202000, 0x10202000, 0x10000000,
	0x10002000, 0x00000008, 0x10200008, 0x00202000, 0x10202008, 0x00200000, 0x00002008, 0x10000008,
	0x00200000, 0x10002000, 0x10000000, 0x00002008, 0x10000008, 0x10202008, 0x00202000, 0x10200000,
	0x00202008, 0x10202000, 0x00000000, 0x10200008, 0x00000008, 0x00002000, 0x10200000, 0x00202008,
	0x00002000, 0x00200008, 0x10002008, 0x00000000, 0x10202000, 0x10000000, 0x00200008, 0x10002008
};

static guint32 sbox7[64] =
{
	0x00100000, 0x02100001, 0x02000401, 0x00000000, 0x00000400, 0x02000401, 0x00100401, 0x02100400,
	0x02100401, 0x00100000, 0x00000000, 0x02000001, 0x00000001, 0x02000000, 0x02100001, 0x00000401,
	0x02000400, 0x00100401, 0x00100001, 0x02000400, 0x02000001, 0x02100000, 0x02100400, 0x00100001,
	0x02100000, 0x00000400, 0x00000401, 0x02100401, 0x00100400, 0x00000001, 0x02000000, 0x00100400,
	0x02000000, 0x00100400, 0x00100000, 0x02000401, 0x02000401, 0x02100001, 0x02100001, 0x00000001,
	0x00100001, 0x02000000, 0x02000400, 0x00100000, 0x02100400, 0x00000401, 0x00100401, 0x02100400,
	0x00000401, 0x02000001, 0x02100401, 0x02100000, 0x00100400, 0x00000000, 0x00000001, 0x02100401,
	0x00000000, 0x00100401, 0x02100000, 0x00000400, 0x02000001, 0x02000400, 0x00000400, 0x00100001
};

static guint32 sbox8[64] =
{
	0x08000820, 0x00000800, 0x00020000, 0x08020820, 0x08000000, 0x08000820, 0x00000020, 0x08000000,
	0x00020020, 0x08020000, 0x08020820, 0x00020800, 0x08020800, 0x00020820, 0x00000800, 0x00000020,
	0x08020000, 0x08000020, 0x08000800, 0x00000820, 0x00020800, 0x00020020, 0x08020020, 0x08020800,
	0x00000820, 0x00000000, 0x00000000, 0x08020020, 0x08000020, 0x08000800, 0x00020820, 0x00020000,
	0x00020820, 0x00020000, 0x08020800, 0x00000800, 0x00000020, 0x08020020, 0x00000800, 0x00020820,
	0x08000800, 0x00000020, 0x08000020, 0x08020000, 0x08020020, 0x08000000, 0x00020000, 0x08000820,
	0x00000000, 0x08020820, 0x00020020, 0x08000020, 0x08020000, 0x08000800, 0x08000820, 0x00000000,
	0x08020820, 0x00020800, 0x00020800, 0x00000820, 0x00000820, 0x00020020, 0x08000000, 0x08020800
};



/*
 *  * These two tables are part of the 'permuted choice 1' function.
 *   * In this implementation several speed improvements are done.
 *    */
static guint32 leftkey_swap[16] =
{
	0x00000000, 0x00000001, 0x00000100, 0x00000101,
	0x00010000, 0x00010001, 0x00010100, 0x00010101,
	0x01000000, 0x01000001, 0x01000100, 0x01000101,
	0x01010000, 0x01010001, 0x01010100, 0x01010101
};

static guint32 rightkey_swap[16] =
{
	0x00000000, 0x01000000, 0x00010000, 0x01010000,
	0x00000100, 0x01000100, 0x00010100, 0x01010100,
	0x00000001, 0x01000001, 0x00010001, 0x01010001,
	0x00000101, 0x01000101, 0x00010101, 0x01010101,
};



/*
 *  Numbers of left shifts per round for encryption subkey schedule
 *  To calculate the decryption key scheduling we just reverse the
 *  ordering of the subkeys so we can omit the table for decryption
 *  subkey schedule.
 */
static guint8 encrypt_rotate_tab[16] =
{
	1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
};

/*
 *  Macro to swap bits across two words
 **/
#define DO_PERMUTATION(a, temp, b, offset, mask)	\
	temp = ((a>>offset) ^ b) & mask;			\
b ^= temp;						\
a ^= temp<<offset;


/*
 *  This performs the 'initial permutation' for the data to be encrypted or decrypted
 **/
#define INITIAL_PERMUTATION(left, temp, right)		\
	DO_PERMUTATION(left, temp, right, 4, 0x0f0f0f0f)	\
DO_PERMUTATION(left, temp, right, 16, 0x0000ffff)	\
DO_PERMUTATION(right, temp, left, 2, 0x33333333)	\
DO_PERMUTATION(right, temp, left, 8, 0x00ff00ff)	\
DO_PERMUTATION(left, temp, right, 1, 0x55555555)


/*
 * The 'inverse initial permutation'
 **/
#define FINAL_PERMUTATION(left, temp, right)		\
	DO_PERMUTATION(left, temp, right, 1, 0x55555555)	\
DO_PERMUTATION(right, temp, left, 8, 0x00ff00ff)	\
DO_PERMUTATION(right, temp, left, 2, 0x33333333)	\
DO_PERMUTATION(left, temp, right, 16, 0x0000ffff)	\
DO_PERMUTATION(left, temp, right, 4, 0x0f0f0f0f)


/*
 * A full DES round including 'expansion function', 'sbox substitution'
 * and 'primitive function P' but without swapping the left and right word.
 **/
#define DES_ROUND(from, to, work, subkey)		\
	work = ((from<<1) | (from>>31)) ^ *subkey++;	\
to ^= sbox8[  work	    & 0x3f ];			\
to ^= sbox6[ (work>>8)  & 0x3f ];			\
to ^= sbox4[ (work>>16) & 0x3f ];			\
to ^= sbox2[ (work>>24) & 0x3f ];			\
work = ((from>>3) | (from<<29)) ^ *subkey++;	\
to ^= sbox7[  work	    & 0x3f ];			\
to ^= sbox5[ (work>>8)  & 0x3f ];			\
to ^= sbox3[ (work>>16) & 0x3f ];			\
to ^= sbox1[ (work>>24) & 0x3f ];


/*
 * Macros to convert 8 bytes from/to 32bit words
 **/
#define READ_64BIT_DATA(data, left, right)					\
	left  = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];	\
right = (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7];

#define WRITE_64BIT_DATA(data, left, right)					\
	data[0] = (left >> 24) &0xff; data[1] = (left >> 16) &0xff; 		\
data[2] = (left >> 8) &0xff; data[3] = left &0xff;				\
data[4] = (right >> 24) &0xff; data[5] = (right >> 16) &0xff;		\
data[6] = (right >> 8) &0xff; data[7] = right &0xff;






/*
 * des_key_schedule():	  Calculate 16 subkeys pairs (even/odd) for
 *			  16 encryption rounds.
 *			  To calculate subkeys for decryption the caller
 *    			  have to reorder the generated subkeys.
 *     
 *        rawkey:	    8 Bytes of key data
 *        subkey:	    Array of at least 32 guint32s. Will be filled
 *    		    with calculated subkeys.
 *     
 **/
static void
des_key_schedule (const guint8 * rawkey, guint32 * subkey)
{
	guint32 left, right, work;
	int round;

	READ_64BIT_DATA (rawkey, left, right)

		DO_PERMUTATION (right, work, left, 4, 0x0f0f0f0f)
		DO_PERMUTATION (right, work, left, 0, 0x10101010)

		left = (leftkey_swap[(left >> 0) & 0xf] << 3) | (leftkey_swap[(left >> 8) & 0xf] << 2)
		| (leftkey_swap[(left >> 16) & 0xf] << 1) | (leftkey_swap[(left >> 24) & 0xf])
		| (leftkey_swap[(left >> 5) & 0xf] << 7) | (leftkey_swap[(left >> 13) & 0xf] << 6)
		| (leftkey_swap[(left >> 21) & 0xf] << 5) | (leftkey_swap[(left >> 29) & 0xf] << 4);

	left &= 0x0fffffff;

	right = (rightkey_swap[(right >> 1) & 0xf] << 3) | (rightkey_swap[(right >> 9) & 0xf] << 2)
		| (rightkey_swap[(right >> 17) & 0xf] << 1) | (rightkey_swap[(right >> 25) & 0xf])
		| (rightkey_swap[(right >> 4) & 0xf] << 7) | (rightkey_swap[(right >> 12) & 0xf] << 6)
		| (rightkey_swap[(right >> 20) & 0xf] << 5) | (rightkey_swap[(right >> 28) & 0xf] << 4);

	right &= 0x0fffffff;

	for (round = 0; round < 16; ++round)
	{
		left = ((left << encrypt_rotate_tab[round]) | (left >> (28 - encrypt_rotate_tab[round]))) & 0x0fffffff;
		right = ((right << encrypt_rotate_tab[round]) | (right >> (28 - encrypt_rotate_tab[round]))) & 0x0fffffff;

		*subkey++ = ((left << 4) & 0x24000000)
			| ((left << 28) & 0x10000000)
			| ((left << 14) & 0x08000000)
			| ((left << 18) & 0x02080000)
			| ((left << 6) & 0x01000000)
			| ((left << 9) & 0x00200000)
			| ((left >> 1) & 0x00100000)
			| ((left << 10) & 0x00040000)
			| ((left << 2) & 0x00020000)
			| ((left >> 10) & 0x00010000)
			| ((right >> 13) & 0x00002000)
			| ((right >> 4) & 0x00001000)
			| ((right << 6) & 0x00000800)
			| ((right >> 1) & 0x00000400)
			| ((right >> 14) & 0x00000200)
			| (right & 0x00000100)
			| ((right >> 5) & 0x00000020)
			| ((right >> 10) & 0x00000010)
			| ((right >> 3) & 0x00000008)
			| ((right >> 18) & 0x00000004)
			| ((right >> 26) & 0x00000002)
			| ((right >> 24) & 0x00000001);

		*subkey++ = ((left << 15) & 0x20000000)
			| ((left << 17) & 0x10000000)
			| ((left << 10) & 0x08000000)
			| ((left << 22) & 0x04000000)
			| ((left >> 2) & 0x02000000)
			| ((left << 1) & 0x01000000)
			| ((left << 16) & 0x00200000)
			| ((left << 11) & 0x00100000)
			| ((left << 3) & 0x00080000)
			| ((left >> 6) & 0x00040000)
			| ((left << 15) & 0x00020000)
			| ((left >> 4) & 0x00010000)
			| ((right >> 2) & 0x00002000)
			| ((right << 8) & 0x00001000)
			| ((right >> 14) & 0x00000808)
			| ((right >> 9) & 0x00000400)
			| ((right) & 0x00000200)
			| ((right << 7) & 0x00000100)
			| ((right >> 7) & 0x00000020)
			| ((right >> 3) & 0x00000011)
			| ((right << 2) & 0x00000004)
			| ((right >> 21) & 0x00000002);
	}
}



/*
 *  Fill a DES context with subkeys calculated from a 64bit key.
 *  Does not check parity bits, but simply ignore them.
 *  Does not check for weak keys.
 **/
static void
des_set_key (PurpleCipherContext *context, const guchar * key)
{
	struct _des_ctx *ctx = purple_cipher_context_get_data(context);
	int i;

	des_key_schedule (key, ctx->encrypt_subkeys);

	for(i=0; i<32; i+=2)
	{
		ctx->decrypt_subkeys[i]	= ctx->encrypt_subkeys[30-i];
		ctx->decrypt_subkeys[i+1] = ctx->encrypt_subkeys[31-i];
	}
}



/*
 *  Electronic Codebook Mode DES encryption/decryption of data according
 *  to 'mode'.
 **/
static int
des_ecb_crypt (struct _des_ctx *ctx, const guint8 * from, guint8 * to, int mode)
{
	guint32 left, right, work;
	guint32 *keys;

	keys = mode ? ctx->decrypt_subkeys : ctx->encrypt_subkeys;

	READ_64BIT_DATA (from, left, right)
		INITIAL_PERMUTATION (left, work, right)

		DES_ROUND (right, left, work, keys) DES_ROUND (left, right, work, keys)
		DES_ROUND (right, left, work, keys) DES_ROUND (left, right, work, keys)
		DES_ROUND (right, left, work, keys) DES_ROUND (left, right, work, keys)
		DES_ROUND (right, left, work, keys) DES_ROUND (left, right, work, keys)
		DES_ROUND (right, left, work, keys) DES_ROUND (left, right, work, keys)
		DES_ROUND (right, left, work, keys) DES_ROUND (left, right, work, keys)
		DES_ROUND (right, left, work, keys) DES_ROUND (left, right, work, keys)
		DES_ROUND (right, left, work, keys) DES_ROUND (left, right, work, keys)

		FINAL_PERMUTATION (right, work, left)
		WRITE_64BIT_DATA (to, right, left)

		return 0;
}

static gint
des_encrypt(PurpleCipherContext *context, const guchar data[],
	    size_t len, guchar output[], size_t *outlen) {
	int offset = 0;
	int i = 0;
	int tmp;
	guint8 buf[8] = {0,0,0,0,0,0,0,0};
	while(offset+8<=len) {
		des_ecb_crypt(purple_cipher_context_get_data(context),
				data+offset,
				output+offset,
				0);
		offset+=8;
	}
	*outlen = len;
	if(offset<len) {
		*outlen += len - offset;
		tmp = offset;
		while(tmp<len) {
			buf[i++] = data[tmp];
			tmp++;
		}
		des_ecb_crypt(purple_cipher_context_get_data(context),
				buf,
				output+offset,
				0);
	}	
	return 0;
}

static void
des_init(PurpleCipherContext *context, gpointer extra) {
	struct _des_ctx *mctx;
	mctx = g_new0(struct _des_ctx, 1);
	purple_cipher_context_set_data(context, mctx);
}

static void
des_uninit(PurpleCipherContext *context) {
	struct _des_ctx *des_context;

	des_context = purple_cipher_context_get_data(context);
	memset(des_context, 0, sizeof(des_context));

	g_free(des_context);
	des_context = NULL;
}

static PurpleCipherOps DESOps = {
	NULL,                   /* Set option */
	NULL,                   /* Get option */
	des_init,               /* init */
	NULL,              /* reset */
	des_uninit,             /* uninit */
	NULL,                   /* set iv */
	NULL,             /* append */
	NULL,             /* digest */
	des_encrypt,                   /* encrypt */
	NULL,                   /* decrypt */
	NULL,                   /* set salt */
	NULL,                   /* get salt size */
	des_set_key,		/* set key */
	NULL,                   /* get key size */

	/* padding */
	NULL,
	NULL,
	NULL,
	NULL
};


/*******************************************************************************
 * SHA-1
 ******************************************************************************/
#define SHA1_ROTL(X,n) ((((X) << (n)) | ((X) >> (32-(n)))) & 0xFFFFFFFF)

struct SHA1Context {
	guint32 H[5];
	guint32 W[80];

	gint lenW;

	guint32 sizeHi;
	guint32 sizeLo;
};

static void
sha1_hash_block(struct SHA1Context *sha1_ctx) {
	gint i;
	guint32 A, B, C, D, E, T;

	for(i = 16; i < 80; i++) {
		sha1_ctx->W[i] = SHA1_ROTL(sha1_ctx->W[i -  3] ^
								   sha1_ctx->W[i -  8] ^
								   sha1_ctx->W[i - 14] ^
								   sha1_ctx->W[i - 16], 1);
	}

	A = sha1_ctx->H[0];
	B = sha1_ctx->H[1];
	C = sha1_ctx->H[2];
	D = sha1_ctx->H[3];
	E = sha1_ctx->H[4];

	for(i = 0; i < 20; i++) {
		T = (SHA1_ROTL(A, 5) + (((C ^ D) & B) ^ D) + E + sha1_ctx->W[i] + 0x5A827999) & 0xFFFFFFFF;
		E = D;
		D = C;
		C = SHA1_ROTL(B, 30);
		B = A;
		A = T;
	}

	for(i = 20; i < 40; i++) {
		T = (SHA1_ROTL(A, 5) + (B ^ C ^ D) + E + sha1_ctx->W[i] + 0x6ED9EBA1) & 0xFFFFFFFF;
		E = D;
		D = C;
		C = SHA1_ROTL(B, 30);
		B = A;
		A = T;
	}

	for(i = 40; i < 60; i++) {
		T = (SHA1_ROTL(A, 5) + ((B & C) | (D & (B | C))) + E + sha1_ctx->W[i] + 0x8F1BBCDC) & 0xFFFFFFFF;
		E = D;
		D = C;
		C = SHA1_ROTL(B, 30);
		B = A;
		A = T;
	}

	for(i = 60; i < 80; i++) {
		T = (SHA1_ROTL(A, 5) + (B ^ C ^ D) + E + sha1_ctx->W[i] + 0xCA62C1D6) & 0xFFFFFFFF;
		E = D;
		D = C;
		C = SHA1_ROTL(B, 30);
		B = A;
		A = T;
	}

	sha1_ctx->H[0] += A;
	sha1_ctx->H[1] += B;
	sha1_ctx->H[2] += C;
	sha1_ctx->H[3] += D;
	sha1_ctx->H[4] += E;
}

static void
sha1_set_opt(PurpleCipherContext *context, const gchar *name, void *value) {
	struct SHA1Context *ctx;

	ctx = purple_cipher_context_get_data(context);

	if(!strcmp(name, "sizeHi")) {
		ctx->sizeHi = GPOINTER_TO_INT(value);
	} else if(!strcmp(name, "sizeLo")) {
		ctx->sizeLo = GPOINTER_TO_INT(value);
	} else if(!strcmp(name, "lenW")) {
		ctx->lenW = GPOINTER_TO_INT(value);
	}
}

static void *
sha1_get_opt(PurpleCipherContext *context, const gchar *name) {
	struct SHA1Context *ctx;

	ctx = purple_cipher_context_get_data(context);

	if(!strcmp(name, "sizeHi")) {
		return GINT_TO_POINTER(ctx->sizeHi);
	} else if(!strcmp(name, "sizeLo")) {
		return GINT_TO_POINTER(ctx->sizeLo);
	} else if(!strcmp(name, "lenW")) {
		return GINT_TO_POINTER(ctx->lenW);
	}

	return NULL;
}

static void
sha1_init(PurpleCipherContext *context, void *extra) {
	struct SHA1Context *sha1_ctx;

	sha1_ctx = g_new0(struct SHA1Context, 1);

	purple_cipher_context_set_data(context, sha1_ctx);

	purple_cipher_context_reset(context, extra);
}

static void
sha1_reset(PurpleCipherContext *context, void *extra) {
	struct SHA1Context *sha1_ctx;
	gint i;

	sha1_ctx = purple_cipher_context_get_data(context);

	g_return_if_fail(sha1_ctx);

	sha1_ctx->lenW = 0;
	sha1_ctx->sizeHi = 0;
	sha1_ctx->sizeLo = 0;

	sha1_ctx->H[0] = 0x67452301;
	sha1_ctx->H[1] = 0xEFCDAB89;
	sha1_ctx->H[2] = 0x98BADCFE;
	sha1_ctx->H[3] = 0x10325476;
	sha1_ctx->H[4] = 0xC3D2E1F0;

	for(i = 0; i < 80; i++)
		sha1_ctx->W[i] = 0;
}

static void
sha1_uninit(PurpleCipherContext *context) {
	struct SHA1Context *sha1_ctx;

	purple_cipher_context_reset(context, NULL);

	sha1_ctx = purple_cipher_context_get_data(context);

	memset(sha1_ctx, 0, sizeof(struct SHA1Context));

	g_free(sha1_ctx);
	sha1_ctx = NULL;
}


static void
sha1_append(PurpleCipherContext *context, const guchar *data, size_t len) {
	struct SHA1Context *sha1_ctx;
	gint i;

	sha1_ctx = purple_cipher_context_get_data(context);

	g_return_if_fail(sha1_ctx);

	for(i = 0; i < len; i++) {
		sha1_ctx->W[sha1_ctx->lenW / 4] <<= 8;
		sha1_ctx->W[sha1_ctx->lenW / 4] |= data[i];

		if((++sha1_ctx->lenW) % 64 == 0) {
			sha1_hash_block(sha1_ctx);
			sha1_ctx->lenW = 0;
		}

		sha1_ctx->sizeLo += 8;
		sha1_ctx->sizeHi += (sha1_ctx->sizeLo < 8);
	}
}

static gboolean
sha1_digest(PurpleCipherContext *context, size_t in_len, guchar digest[20],
			size_t *out_len)
{
	struct SHA1Context *sha1_ctx;
	guchar pad0x80 = 0x80, pad0x00 = 0x00;
	guchar padlen[8];
	gint i;

	g_return_val_if_fail(in_len >= 20, FALSE);

	sha1_ctx = purple_cipher_context_get_data(context);

	g_return_val_if_fail(sha1_ctx, FALSE);

	padlen[0] = (guchar)((sha1_ctx->sizeHi >> 24) & 255);
	padlen[1] = (guchar)((sha1_ctx->sizeHi >> 16) & 255);
	padlen[2] = (guchar)((sha1_ctx->sizeHi >> 8) & 255);
	padlen[3] = (guchar)((sha1_ctx->sizeHi >> 0) & 255);
	padlen[4] = (guchar)((sha1_ctx->sizeLo >> 24) & 255);
	padlen[5] = (guchar)((sha1_ctx->sizeLo >> 16) & 255);
	padlen[6] = (guchar)((sha1_ctx->sizeLo >> 8) & 255);
	padlen[7] = (guchar)((sha1_ctx->sizeLo >> 0) & 255);

	/* pad with a 1, then zeroes, then length */
	purple_cipher_context_append(context, &pad0x80, 1);
	while(sha1_ctx->lenW != 56)
		purple_cipher_context_append(context, &pad0x00, 1);
	purple_cipher_context_append(context, padlen, 8);

	for(i = 0; i < 20; i++) {
		digest[i] = (guchar)(sha1_ctx->H[i / 4] >> 24);
		sha1_ctx->H[i / 4] <<= 8;
	}

	purple_cipher_context_reset(context, NULL);

	if(out_len)
		*out_len = 20;

	return TRUE;
}

static PurpleCipherOps SHA1Ops = {
	sha1_set_opt,	/* Set Option		*/
	sha1_get_opt,	/* Get Option		*/
	sha1_init,		/* init				*/
	sha1_reset,		/* reset			*/
	sha1_uninit,	/* uninit			*/
	NULL,			/* set iv			*/
	sha1_append,	/* append			*/
	sha1_digest,	/* digest			*/
	NULL,			/* encrypt			*/
	NULL,			/* decrypt			*/
	NULL,			/* set salt			*/
	NULL,			/* get salt size	*/
	NULL,			/* set key			*/
	NULL,			/* get key size		*/

	/* padding */
	NULL,
	NULL,
	NULL,
	NULL
};

/*******************************************************************************
 * RC4
 ******************************************************************************/

struct RC4Context {
  guchar state[256];
  guchar x;
  guchar y;
  gint key_len;
};

static void
rc4_init(PurpleCipherContext *context, void *extra) {
	struct RC4Context *rc4_ctx;
	rc4_ctx = g_new0(struct RC4Context, 1);
	purple_cipher_context_set_data(context, rc4_ctx);
	purple_cipher_context_reset(context, extra);
}


static void
rc4_reset(PurpleCipherContext *context, void *extra) {
	struct RC4Context *rc4_ctx;
	guint i;

	rc4_ctx = purple_cipher_context_get_data(context);

	g_return_if_fail(rc4_ctx);

	for(i = 0; i < 256; i++)
		rc4_ctx->state[i] = i;
	rc4_ctx->x = 0;
	rc4_ctx->y = 0;

	/* default is 5 bytes (40bit key) */
	rc4_ctx->key_len = 5;

}

static void
rc4_uninit(PurpleCipherContext *context) {
	struct RC4Context *rc4_ctx;

	rc4_ctx = purple_cipher_context_get_data(context);
	memset(rc4_ctx, 0, sizeof(rc4_ctx));

	g_free(rc4_ctx);
	rc4_ctx = NULL;
}



static void
rc4_set_key (PurpleCipherContext *context, const guchar * key) {
	struct RC4Context *ctx;
	guchar *state;
	guchar temp_swap;
	guchar x, y;
	guint i;

	ctx = purple_cipher_context_get_data(context);

	x = 0;
	y = 0;
	state = &ctx->state[0];
	for(i = 0; i < 256; i++)
	{
		y = (key[x] + state[i] + y) % 256;
		temp_swap = state[i];
		state[i] = state[y];
		state[y] = temp_swap;
		x = (x + 1) % ctx->key_len;
	}
}

static void
rc4_set_opt(PurpleCipherContext *context, const gchar *name, void *value) {
	struct RC4Context *ctx;

	ctx = purple_cipher_context_get_data(context);

	if(!strcmp(name, "key_len")) {
		ctx->key_len = GPOINTER_TO_INT(value);
	}
}

static size_t 
rc4_get_key_size (PurpleCipherContext *context)
{
	struct RC4Context *ctx;

	g_return_val_if_fail(context, -1);

	ctx = purple_cipher_context_get_data(context);

	g_return_val_if_fail(ctx, -1);

	return ctx->key_len;
}

static void *
rc4_get_opt(PurpleCipherContext *context, const gchar *name) {
	struct RC4Context *ctx;

	ctx = purple_cipher_context_get_data(context);

	if(!strcmp(name, "key_len")) {
		return GINT_TO_POINTER(ctx->key_len);
	}

	return NULL;
}

static gint
rc4_encrypt(PurpleCipherContext *context, const guchar data[],
	    size_t len, guchar output[], size_t *outlen) {
	struct RC4Context *ctx;
	guchar temp_swap;
	guchar x, y, z;
	guchar *state;
	guint i;

	ctx = purple_cipher_context_get_data(context);

	x = ctx->x;
	y = ctx->y;
	state = &ctx->state[0];

	for(i = 0; i < len; i++)
	{
		x = (x + 1) % 256;
		y = (state[x] + y) % 256;
		temp_swap = state[x];
		state[x] = state[y];
		state[y] = temp_swap;
		z = state[x] + (state[y]) % 256;
		output[i] = data[i] ^ state[z];
	}
	ctx->x = x;
	ctx->y = y;
	if(outlen)
		*outlen = len;

	return 0;
}

static PurpleCipherOps RC4Ops = {
	rc4_set_opt,   /* Set Option    */
	rc4_get_opt,   /* Get Option    */
	rc4_init,      /* init          */
	rc4_reset,     /* reset         */
	rc4_uninit,    /* uninit        */
	NULL,          /* set iv        */
	NULL,          /* append        */
	NULL,          /* digest        */
	rc4_encrypt,   /* encrypt       */
	NULL,          /* decrypt       */
	NULL,          /* set salt      */
	NULL,          /* get salt size */
	rc4_set_key,   /* set key       */
	rc4_get_key_size, /* get key size  */

	/* padding */
	NULL,
	NULL,
	NULL,
	NULL
};

/*******************************************************************************
 * Structs
 ******************************************************************************/
struct _PurpleCipher {
	gchar *name;          /**< Internal name - used for searching */
	PurpleCipherOps *ops; /**< Operations supported by this cipher */
	guint ref;            /**< Reference count */
};

struct _PurpleCipherContext {
	PurpleCipher *cipher; /**< Cipher this context is under */
	gpointer data;        /**< Internal cipher state data */
};

/******************************************************************************
 * Globals
 *****************************************************************************/
static GList *ciphers = NULL;

/******************************************************************************
 * PurpleCipher API
 *****************************************************************************/
const gchar *
purple_cipher_get_name(PurpleCipher *cipher) {
	g_return_val_if_fail(cipher, NULL);

	return cipher->name;
}

guint
purple_cipher_get_capabilities(PurpleCipher *cipher) {
	PurpleCipherOps *ops = NULL;
	guint caps = 0;

	g_return_val_if_fail(cipher, 0);

	ops = cipher->ops;
	g_return_val_if_fail(ops, 0);

	if(ops->set_option)
		caps |= PURPLE_CIPHER_CAPS_SET_OPT;
	if(ops->get_option)
		caps |= PURPLE_CIPHER_CAPS_GET_OPT;
	if(ops->init)
		caps |= PURPLE_CIPHER_CAPS_INIT;
	if(ops->reset)
		caps |= PURPLE_CIPHER_CAPS_RESET;
	if(ops->uninit)
		caps |= PURPLE_CIPHER_CAPS_UNINIT;
	if(ops->set_iv)
		caps |= PURPLE_CIPHER_CAPS_SET_IV;
	if(ops->append)
		caps |= PURPLE_CIPHER_CAPS_APPEND;
	if(ops->digest)
		caps |= PURPLE_CIPHER_CAPS_DIGEST;
	if(ops->encrypt)
		caps |= PURPLE_CIPHER_CAPS_ENCRYPT;
	if(ops->decrypt)
		caps |= PURPLE_CIPHER_CAPS_DECRYPT;
	if(ops->set_salt)
		caps |= PURPLE_CIPHER_CAPS_SET_SALT;
	if(ops->get_salt_size)
		caps |= PURPLE_CIPHER_CAPS_GET_SALT_SIZE;
	if(ops->set_key)
		caps |= PURPLE_CIPHER_CAPS_SET_KEY;
	if(ops->get_key_size)
		caps |= PURPLE_CIPHER_CAPS_GET_KEY_SIZE;

	return caps;
}

gboolean
purple_cipher_digest_region(const gchar *name, const guchar *data,
						  size_t data_len, size_t in_len,
						  guchar digest[], size_t *out_len)
{
	PurpleCipher *cipher;
	PurpleCipherContext *context;
	gboolean ret = FALSE;

	g_return_val_if_fail(name, FALSE);
	g_return_val_if_fail(data, FALSE);

	cipher = purple_ciphers_find_cipher(name);

	g_return_val_if_fail(cipher, FALSE);

	if(!cipher->ops->append || !cipher->ops->digest) {
		purple_debug_info("cipher", "purple_cipher_region failed: "
						"the %s cipher does not support appending and or "
						"digesting.", cipher->name);
		return FALSE;
	}

	context = purple_cipher_context_new(cipher, NULL);
	purple_cipher_context_append(context, data, data_len);
	ret = purple_cipher_context_digest(context, in_len, digest, out_len);
	purple_cipher_context_destroy(context);

	return ret;
}

/******************************************************************************
 * PurpleCiphers API
 *****************************************************************************/
PurpleCipher *
purple_ciphers_find_cipher(const gchar *name) {
	PurpleCipher *cipher;
	GList *l;

	g_return_val_if_fail(name, NULL);

	for(l = ciphers; l; l = l->next) {
		cipher = PURPLE_CIPHER(l->data);

		if(!g_ascii_strcasecmp(cipher->name, name))
			return cipher;
	}

	return NULL;
}

PurpleCipher *
purple_ciphers_register_cipher(const gchar *name, PurpleCipherOps *ops) {
	PurpleCipher *cipher = NULL;

	g_return_val_if_fail(name, NULL);
	g_return_val_if_fail(ops, NULL);
	g_return_val_if_fail(!purple_ciphers_find_cipher(name), NULL);

	cipher = g_new0(PurpleCipher, 1);
	PURPLE_DBUS_REGISTER_POINTER(cipher, PurpleCipher);

	cipher->name = g_strdup(name);
	cipher->ops = ops;

	ciphers = g_list_append(ciphers, cipher);

	purple_signal_emit(purple_ciphers_get_handle(), "cipher-added", cipher);

	return cipher;
}

gboolean
purple_ciphers_unregister_cipher(PurpleCipher *cipher) {
	g_return_val_if_fail(cipher, FALSE);
	g_return_val_if_fail(cipher->ref == 0, FALSE);

	purple_signal_emit(purple_ciphers_get_handle(), "cipher-removed", cipher);

	ciphers = g_list_remove(ciphers, cipher);

	g_free(cipher->name);

	PURPLE_DBUS_UNREGISTER_POINTER(cipher);
	g_free(cipher);

	return TRUE;
}

GList *
purple_ciphers_get_ciphers() {
	return ciphers;
}

/******************************************************************************
 * PurpleCipher Subsystem API
 *****************************************************************************/
gpointer
purple_ciphers_get_handle() {
	static gint handle;

	return &handle;
}

void
purple_ciphers_init() {
	gpointer handle;

	handle = purple_ciphers_get_handle();

	purple_signal_register(handle, "cipher-added",
						 purple_marshal_VOID__POINTER, NULL, 1,
						 purple_value_new(PURPLE_TYPE_SUBTYPE,
										PURPLE_SUBTYPE_CIPHER));
	purple_signal_register(handle, "cipher-removed",
						 purple_marshal_VOID__POINTER, NULL, 1,
						 purple_value_new(PURPLE_TYPE_SUBTYPE,
										PURPLE_SUBTYPE_CIPHER));

	purple_ciphers_register_cipher("md5", &MD5Ops);
	purple_ciphers_register_cipher("sha1", &SHA1Ops);
	purple_ciphers_register_cipher("md4", &MD4Ops);
	purple_ciphers_register_cipher("des", &DESOps);
	purple_ciphers_register_cipher("rc4", &RC4Ops);
}

void
purple_ciphers_uninit() {
	PurpleCipher *cipher;
	GList *l, *ll;

	for(l = ciphers; l; l = ll) {
		ll = l->next;

		cipher = PURPLE_CIPHER(l->data);
		purple_ciphers_unregister_cipher(cipher);

		ciphers = g_list_remove(ciphers, cipher);
	}

	g_list_free(ciphers);

	purple_signals_unregister_by_instance(purple_ciphers_get_handle());
}
/******************************************************************************
 * PurpleCipherContext API
 *****************************************************************************/
void
purple_cipher_context_set_option(PurpleCipherContext *context, const gchar *name,
							   gpointer value)
{
	PurpleCipher *cipher = NULL;

	g_return_if_fail(context);
	g_return_if_fail(name);

	cipher = context->cipher;
	g_return_if_fail(cipher);

	if(cipher->ops && cipher->ops->set_option)
		cipher->ops->set_option(context, name, value);
	else
		purple_debug_info("cipher", "the %s cipher does not support the "
						"set_option operation\n", cipher->name);
}

gpointer
purple_cipher_context_get_option(PurpleCipherContext *context, const gchar *name) {
	PurpleCipher *cipher = NULL;

	g_return_val_if_fail(context, NULL);
	g_return_val_if_fail(name, NULL);

	cipher = context->cipher;
	g_return_val_if_fail(cipher, NULL);

	if(cipher->ops && cipher->ops->get_option)
		return cipher->ops->get_option(context, name);
	else {
		purple_debug_info("cipher", "the %s cipher does not support the "
						"get_option operation\n", cipher->name);

		return NULL;
	}
}

PurpleCipherContext *
purple_cipher_context_new(PurpleCipher *cipher, void *extra) {
	PurpleCipherContext *context = NULL;

	g_return_val_if_fail(cipher, NULL);

	cipher->ref++;

	context = g_new0(PurpleCipherContext, 1);
	context->cipher = cipher;

	if(cipher->ops->init)
		cipher->ops->init(context, extra);

	return context;
}

PurpleCipherContext *
purple_cipher_context_new_by_name(const gchar *name, void *extra) {
	PurpleCipher *cipher;

	g_return_val_if_fail(name, NULL);

	cipher = purple_ciphers_find_cipher(name);

	g_return_val_if_fail(cipher, NULL);

	return purple_cipher_context_new(cipher, extra);
}

void
purple_cipher_context_reset(PurpleCipherContext *context, void *extra) {
	PurpleCipher *cipher = NULL;

	g_return_if_fail(context);

	cipher = context->cipher;
	g_return_if_fail(cipher);

	if(cipher->ops && cipher->ops->reset)
		context->cipher->ops->reset(context, extra);
}

void
purple_cipher_context_destroy(PurpleCipherContext *context) {
	PurpleCipher *cipher = NULL;

	g_return_if_fail(context);

	cipher = context->cipher;
	g_return_if_fail(cipher);

	cipher->ref--;

	if(cipher->ops && cipher->ops->uninit)
		cipher->ops->uninit(context);

	memset(context, 0, sizeof(context));
	g_free(context);
	context = NULL;
}

void
purple_cipher_context_set_iv(PurpleCipherContext *context, guchar *iv, size_t len)
{
	PurpleCipher *cipher = NULL;

	g_return_if_fail(context);
	g_return_if_fail(iv);

	cipher = context->cipher;
	g_return_if_fail(cipher);

	if(cipher->ops && cipher->ops->set_iv)
		cipher->ops->set_iv(context, iv, len);
	else
		purple_debug_info("cipher", "the %s cipher does not support the set"
						"initialization vector operation\n", cipher->name);
}

void
purple_cipher_context_append(PurpleCipherContext *context, const guchar *data,
								size_t len)
{
	PurpleCipher *cipher = NULL;

	g_return_if_fail(context);

	cipher = context->cipher;
	g_return_if_fail(cipher);

	if(cipher->ops && cipher->ops->append)
		cipher->ops->append(context, data, len);
	else
		purple_debug_info("cipher", "the %s cipher does not support the append "
						"operation\n", cipher->name);
}

gboolean
purple_cipher_context_digest(PurpleCipherContext *context, size_t in_len,
						   guchar digest[], size_t *out_len)
{
	PurpleCipher *cipher = NULL;

	g_return_val_if_fail(context, FALSE);

	cipher = context->cipher;
	g_return_val_if_fail(context, FALSE);

	if(cipher->ops && cipher->ops->digest)
		return cipher->ops->digest(context, in_len, digest, out_len);
	else {
		purple_debug_info("cipher", "the %s cipher does not support the digest "
						"operation\n", cipher->name);
		return FALSE;
	}
}

gboolean
purple_cipher_context_digest_to_str(PurpleCipherContext *context, size_t in_len,
								   gchar digest_s[], size_t *out_len)
{
	/* 8k is a bit excessive, will tweak later. */
	guchar digest[BUF_LEN * 4];
	gint n = 0;
	size_t dlen = 0;

	g_return_val_if_fail(context, FALSE);
	g_return_val_if_fail(digest_s, FALSE);

	if(!purple_cipher_context_digest(context, sizeof(digest), digest, &dlen))
		return FALSE;

	/* in_len must be greater than dlen * 2 so we have room for the NUL. */
	if(in_len <= dlen * 2)
		return FALSE;

	for(n = 0; n < dlen; n++)
		sprintf(digest_s + (n * 2), "%02x", digest[n]);

	digest_s[n * 2] = '\0';

	if(out_len)
		*out_len = dlen * 2;

	return TRUE;
}

gint
purple_cipher_context_encrypt(PurpleCipherContext *context, const guchar data[],
							size_t len, guchar output[], size_t *outlen)
{
	PurpleCipher *cipher = NULL;

	g_return_val_if_fail(context, -1);

	cipher = context->cipher;
	g_return_val_if_fail(cipher, -1);

	if(cipher->ops && cipher->ops->encrypt)
		return cipher->ops->encrypt(context, data, len, output, outlen);
	else {
		purple_debug_info("cipher", "the %s cipher does not support the encrypt"
						"operation\n", cipher->name);

		if(outlen)
			*outlen = -1;

		return -1;
	}
}

gint
purple_cipher_context_decrypt(PurpleCipherContext *context, const guchar data[],
							size_t len, guchar output[], size_t *outlen)
{
	PurpleCipher *cipher = NULL;

	g_return_val_if_fail(context, -1);

	cipher = context->cipher;
	g_return_val_if_fail(cipher, -1);

	if(cipher->ops && cipher->ops->decrypt)
		return cipher->ops->decrypt(context, data, len, output, outlen);
	else {
		purple_debug_info("cipher", "the %s cipher does not support the decrypt"
						"operation\n", cipher->name);

		if(outlen)
			*outlen = -1;

		return -1;
	}
}

void
purple_cipher_context_set_salt(PurpleCipherContext *context, guchar *salt) {
	PurpleCipher *cipher = NULL;

	g_return_if_fail(context);

	cipher = context->cipher;
	g_return_if_fail(cipher);

	if(cipher->ops && cipher->ops->set_salt)
		cipher->ops->set_salt(context, salt);
	else
		purple_debug_info("cipher", "the %s cipher does not support the "
						"set_salt operation\n", cipher->name);
}

size_t
purple_cipher_context_get_salt_size(PurpleCipherContext *context) {
	PurpleCipher *cipher = NULL;

	g_return_val_if_fail(context, -1);

	cipher = context->cipher;
	g_return_val_if_fail(cipher, -1);

	if(cipher->ops && cipher->ops->get_salt_size)
		return cipher->ops->get_salt_size(context);
	else {
		purple_debug_info("cipher", "the %s cipher does not support the "
						"get_salt_size operation\n", cipher->name);

		return -1;
	}
}

void
purple_cipher_context_set_key(PurpleCipherContext *context, const guchar *key) {
	PurpleCipher *cipher = NULL;

	g_return_if_fail(context);

	cipher = context->cipher;
	g_return_if_fail(cipher);

	if(cipher->ops && cipher->ops->set_key)
		cipher->ops->set_key(context, key);
	else
		purple_debug_info("cipher", "the %s cipher does not support the "
						"set_key operation\n", cipher->name);
}

size_t
purple_cipher_context_get_key_size(PurpleCipherContext *context) {
	PurpleCipher *cipher = NULL;

	g_return_val_if_fail(context, -1);

	cipher = context->cipher;
	g_return_val_if_fail(cipher, -1);

	if(cipher->ops && cipher->ops->get_key_size)
		return cipher->ops->get_key_size(context);
	else {
		purple_debug_info("cipher", "the %s cipher does not support the "
						"get_key_size operation\n", cipher->name);

		return -1;
	}
}

void
purple_cipher_context_set_data(PurpleCipherContext *context, gpointer data) {
	g_return_if_fail(context);

	context->data = data;
}

gpointer
purple_cipher_context_get_data(PurpleCipherContext *context) {
	g_return_val_if_fail(context, NULL);

	return context->data;
}

gchar *purple_cipher_http_digest_calculate_session_key(
		const gchar *algorithm,
		const gchar *username,
		const gchar *realm,
		const gchar *password,
		const gchar *nonce,
		const gchar *client_nonce)
{
	PurpleCipher *cipher;
	PurpleCipherContext *context;
	gchar hash[33]; /* We only support MD5. */

	g_return_val_if_fail(username != NULL, NULL);
	g_return_val_if_fail(realm    != NULL, NULL);
	g_return_val_if_fail(password != NULL, NULL);
	g_return_val_if_fail(nonce    != NULL, NULL);

	/* Check for a supported algorithm. */
	g_return_val_if_fail(algorithm == NULL ||
						 *algorithm == '\0' ||
						 g_ascii_strcasecmp(algorithm, "MD5") ||
						 g_ascii_strcasecmp(algorithm, "MD5-sess"), NULL);

	cipher = purple_ciphers_find_cipher("md5");
	g_return_val_if_fail(cipher != NULL, NULL);

	context = purple_cipher_context_new(cipher, NULL);

	purple_cipher_context_append(context, (guchar *)username, strlen(username));
	purple_cipher_context_append(context, (guchar *)":", 1);
	purple_cipher_context_append(context, (guchar *)realm, strlen(realm));
	purple_cipher_context_append(context, (guchar *)":", 1);
	purple_cipher_context_append(context, (guchar *)password, strlen(password));

	if (algorithm != NULL && !g_ascii_strcasecmp(algorithm, "MD5-sess"))
	{
		guchar digest[16];

		if (client_nonce == NULL)
		{
			purple_cipher_context_destroy(context);
			purple_debug_error("cipher", "Required client_nonce missing for MD5-sess digest calculation.");
			return NULL;
		}

		purple_cipher_context_digest(context, sizeof(digest), digest, NULL);
		purple_cipher_context_destroy(context);

		context = purple_cipher_context_new(cipher, NULL);
		purple_cipher_context_append(context, digest, sizeof(digest));
		purple_cipher_context_append(context, (guchar *)":", 1);
		purple_cipher_context_append(context, (guchar *)nonce, strlen(nonce));
		purple_cipher_context_append(context, (guchar *)":", 1);
		purple_cipher_context_append(context, (guchar *)client_nonce, strlen(client_nonce));
	}

	purple_cipher_context_digest_to_str(context, sizeof(hash), hash, NULL);
	purple_cipher_context_destroy(context);

	return g_strdup(hash);
}

gchar *purple_cipher_http_digest_calculate_response(
		const gchar *algorithm,
		const gchar *method,
		const gchar *digest_uri,
		const gchar *qop,
		const gchar *entity,
		const gchar *nonce,
		const gchar *nonce_count,
		const gchar *client_nonce,
		const gchar *session_key)
{
	PurpleCipher *cipher;
	PurpleCipherContext *context;
	static gchar hash2[33]; /* We only support MD5. */

	g_return_val_if_fail(method      != NULL, NULL);
	g_return_val_if_fail(digest_uri  != NULL, NULL);
	g_return_val_if_fail(nonce       != NULL, NULL);
	g_return_val_if_fail(session_key != NULL, NULL);

	/* Check for a supported algorithm. */
	g_return_val_if_fail(algorithm == NULL ||
						 *algorithm == '\0' ||
						 g_ascii_strcasecmp(algorithm, "MD5") ||
						 g_ascii_strcasecmp(algorithm, "MD5-sess"), NULL);

	/* Check for a supported "quality of protection". */
	g_return_val_if_fail(qop == NULL ||
						 *qop == '\0' ||
						 g_ascii_strcasecmp(qop, "auth") ||
						 g_ascii_strcasecmp(qop, "auth-int"), NULL);

	cipher = purple_ciphers_find_cipher("md5");
	g_return_val_if_fail(cipher != NULL, NULL);

	context = purple_cipher_context_new(cipher, NULL);

	purple_cipher_context_append(context, (guchar *)method, strlen(method));
	purple_cipher_context_append(context, (guchar *)":", 1);
	purple_cipher_context_append(context, (guchar *)digest_uri, strlen(digest_uri));

	if (qop != NULL && !g_ascii_strcasecmp(qop, "auth-int"))
	{
		PurpleCipherContext *context2;
		gchar entity_hash[33];

		if (entity == NULL)
		{
			purple_cipher_context_destroy(context);
			purple_debug_error("cipher", "Required entity missing for auth-int digest calculation.");
			return NULL;
		}

		context2 = purple_cipher_context_new(cipher, NULL);
		purple_cipher_context_append(context2, (guchar *)entity, strlen(entity));
		purple_cipher_context_digest_to_str(context2, sizeof(entity_hash), entity_hash, NULL);
		purple_cipher_context_destroy(context2);

		purple_cipher_context_append(context, (guchar *)":", 1);
		purple_cipher_context_append(context, (guchar *)entity_hash, strlen(entity_hash));
	}

	purple_cipher_context_digest_to_str(context, sizeof(hash2), hash2, NULL);
	purple_cipher_context_destroy(context);

	context = purple_cipher_context_new(cipher, NULL);
	purple_cipher_context_append(context, (guchar *)session_key, strlen(session_key));
	purple_cipher_context_append(context, (guchar *)":", 1);
	purple_cipher_context_append(context, (guchar *)nonce, strlen(nonce));
	purple_cipher_context_append(context, (guchar *)":", 1);

	if (qop != NULL && *qop != '\0')
	{
		if (nonce_count == NULL)
		{
			purple_cipher_context_destroy(context);
			purple_debug_error("cipher", "Required nonce_count missing for digest calculation.");
			return NULL;
		}

		if (client_nonce == NULL)
		{
			purple_cipher_context_destroy(context);
			purple_debug_error("cipher", "Required client_nonce missing for digest calculation.");
			return NULL;
		}

		purple_cipher_context_append(context, (guchar *)nonce_count, strlen(nonce_count));
		purple_cipher_context_append(context, (guchar *)":", 1);
		purple_cipher_context_append(context, (guchar *)client_nonce, strlen(client_nonce));
		purple_cipher_context_append(context, (guchar *)":", 1);

		purple_cipher_context_append(context, (guchar *)qop, strlen(qop));

		purple_cipher_context_append(context, (guchar *)":", 1);
	}

	purple_cipher_context_append(context, (guchar *)hash2, strlen(hash2));
	purple_cipher_context_digest_to_str(context, sizeof(hash2), hash2, NULL);
	purple_cipher_context_destroy(context);

	return g_strdup(hash2);
}