Commit 8e39ec78 authored by Matthias Klose's avatar Matthias Klose

- Patch #360466: Replace the MD5 implementation from RSA Data Security Inc

  with the implementation from http://sourceforge.net/projects/libmd5-rfc/.
parent db5483be
...@@ -432,26 +432,49 @@ The source for the \module{fpectl} module includes the following notice: ...@@ -432,26 +432,49 @@ The source for the \module{fpectl} module includes the following notice:
The source code for the \module{md5} module contains the following notice: The source code for the \module{md5} module contains the following notice:
\begin{verbatim} \begin{verbatim}
Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved.
rights reserved.
This software is provided 'as-is', without any express or implied
License to copy and use this software is granted provided that it warranty. In no event will the authors be held liable for any damages
is identified as the "RSA Data Security, Inc. MD5 Message-Digest arising from the use of this software.
Algorithm" in all material mentioning or referencing this software
or this function. Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
License is also granted to make and use derivative works provided freely, subject to the following restrictions:
that such works are identified as "derived from the RSA Data
Security, Inc. MD5 Message-Digest Algorithm" in all material 1. The origin of this software must not be misrepresented; you must not
mentioning or referencing the derived work. claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
RSA Data Security, Inc. makes no representations concerning either appreciated but is not required.
the merchantability of this software or the suitability of this 2. Altered source versions must be plainly marked as such, and must not be
software for any particular purpose. It is provided "as is" misrepresented as being the original software.
without express or implied warranty of any kind. 3. This notice may not be removed or altered from any source distribution.
These notices must be retained in any copies of any part of this L. Peter Deutsch
documentation and/or software. ghost@aladdin.com
Independent implementation of MD5 (RFC 1321).
This code implements the MD5 Algorithm defined in RFC 1321, whose
text is available at
http://www.ietf.org/rfc/rfc1321.txt
The code is derived from the text of the RFC, including the test suite
(section A.5) but excluding the rest of Appendix A. It does not include
any code or documentation that is identified in the RFC as being
copyrighted.
The original and principal author of md5.h is L. Peter Deutsch
<ghost@aladdin.com>. Other authors are noted in the change history
that follows (in reverse chronological order):
2002-04-13 lpd Removed support for non-ANSI compilers; removed
references to Ghostscript; clarified derivation from RFC 1321;
now handles byte order either statically or dynamically.
1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
added conditionalization for C++ compilation from Martin
Purschke <purschke@bnl.gov>.
1999-05-03 lpd Original version.
\end{verbatim} \end{verbatim}
......
...@@ -491,6 +491,9 @@ Extension Modules ...@@ -491,6 +491,9 @@ Extension Modules
- datetime.datetime() now has a strptime class method which can be used to - datetime.datetime() now has a strptime class method which can be used to
create datetime object using a string and format. create datetime object using a string and format.
- Patch #360466: Replace the MD5 implementation from RSA Data Security Inc
with the implementation from http://sourceforge.net/projects/libmd5-rfc/.
Library Library
------- -------
......
This diff is collapsed.
/* MD5.H - header file for MD5C.C /*
*/ Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved.
/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All This software is provided 'as-is', without any express or implied
rights reserved. warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
License to copy and use this software is granted provided that it Permission is granted to anyone to use this software for any purpose,
is identified as the "RSA Data Security, Inc. MD5 Message-Digest including commercial applications, and to alter it and redistribute it
Algorithm" in all material mentioning or referencing this software freely, subject to the following restrictions:
or this function.
License is also granted to make and use derivative works provided 1. The origin of this software must not be misrepresented; you must not
that such works are identified as "derived from the RSA Data claim that you wrote the original software. If you use this software
Security, Inc. MD5 Message-Digest Algorithm" in all material in a product, an acknowledgment in the product documentation would be
mentioning or referencing the derived work. appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
RSA Data Security, Inc. makes no representations concerning either L. Peter Deutsch
the merchantability of this software or the suitability of this ghost@aladdin.com
software for any particular purpose. It is provided "as is"
without express or implied warranty of any kind.
These notices must be retained in any copies of any part of this
documentation and/or software.
*/ */
/* $Id$ */
/*
Independent implementation of MD5 (RFC 1321).
This code implements the MD5 Algorithm defined in RFC 1321, whose
text is available at
http://www.ietf.org/rfc/rfc1321.txt
The code is derived from the text of the RFC, including the test suite
(section A.5) but excluding the rest of Appendix A. It does not include
any code or documentation that is identified in the RFC as being
copyrighted.
The original and principal author of md5.h is L. Peter Deutsch
<ghost@aladdin.com>. Other authors are noted in the change history
that follows (in reverse chronological order):
/* ========== include global.h ========== */ 2002-04-13 lpd Removed support for non-ANSI compilers; removed
/* GLOBAL.H - RSAREF types and constants references to Ghostscript; clarified derivation from RFC 1321;
now handles byte order either statically or dynamically.
1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
added conditionalization for C++ compilation from Martin
Purschke <purschke@bnl.gov>.
1999-05-03 lpd Original version.
*/ */
/* POINTER defines a generic pointer type */ #ifndef md5_INCLUDED
typedef unsigned char *POINTER; # define md5_INCLUDED
/* UINT4 defines a four byte word */ /*
#if SIZEOF_LONG == 4 * This package supports both compile-time and run-time determination of CPU
typedef unsigned long int UINT4; * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
#elif SIZEOF_SHORT == 4 * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
typedef unsigned short int UINT4; * defined as non-zero, the code will be compiled to run only on big-endian
#elif INT_MAX == 2147483647 * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
typedef unsigned int UINT4; * run on either big- or little-endian CPUs, but will run slightly less
#else * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
#error "Can't find a 4-byte integral type" */
typedef unsigned char md5_byte_t; /* 8-bit byte */
typedef unsigned int md5_word_t; /* 32-bit word */
/* Define the state of the MD5 Algorithm. */
typedef struct md5_state_s {
md5_word_t count[2]; /* message length in bits, lsw first */
md5_word_t abcd[4]; /* digest buffer */
md5_byte_t buf[64]; /* accumulate block */
} md5_state_t;
#ifdef __cplusplus
extern "C"
{
#endif #endif
/* ========== End global.h; continue md5.h ========== */ /* Initialize the algorithm. */
void md5_init(md5_state_t *pms);
/* MD5 context. */ /* Append a string to the message. */
typedef struct { void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
UINT4 state[4]; /* state (ABCD) */
UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */
unsigned char buffer[64]; /* input buffer */
} MD5_CTX;
/* Rename all exported symbols to avoid conflicts with similarly named /* Finish the message and return the digest. */
symbols in some systems' standard C libraries... */ void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
#define MD5Init _Py_MD5Init #ifdef __cplusplus
#define MD5Update _Py_MD5Update } /* end extern "C" */
#define MD5Final _Py_MD5Final #endif
void MD5Init(MD5_CTX *); #endif /* md5_INCLUDED */
void MD5Update(MD5_CTX *, unsigned char *, unsigned int);
void MD5Final(unsigned char [16], MD5_CTX *);
/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
*/
/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
rights reserved.
License to copy and use this software is granted provided that it
is identified as the "RSA Data Security, Inc. MD5 Message-Digest
Algorithm" in all material mentioning or referencing this software
or this function.
License is also granted to make and use derivative works provided
that such works are identified as "derived from the RSA Data
Security, Inc. MD5 Message-Digest Algorithm" in all material
mentioning or referencing the derived work.
RSA Data Security, Inc. makes no representations concerning either
the merchantability of this software or the suitability of this
software for any particular purpose. It is provided "as is"
without express or implied warranty of any kind.
These notices must be retained in any copies of any part of this
documentation and/or software.
*/
#include "Python.h"
#include "md5.h"
/* Constants for MD5Transform routine. */
#define S11 7
#define S12 12
#define S13 17
#define S14 22
#define S21 5
#define S22 9
#define S23 14
#define S24 20
#define S31 4
#define S32 11
#define S33 16
#define S34 23
#define S41 6
#define S42 10
#define S43 15
#define S44 21
static void MD5Transform(UINT4[4], unsigned char[64]);
/* Encodes input (UINT4) into output (unsigned char). Assumes len is
a multiple of 4.
*/
static void
Encode(unsigned char *output, UINT4 *input, unsigned int len)
{
unsigned int i, j;
for (i = 0, j = 0; j < len; i++, j += 4) {
output[j] = (unsigned char)(input[i] & 0xff);
output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
}
}
/* Decodes input (unsigned char) into output (UINT4). Assumes len is
a multiple of 4.
*/
static void
Decode(UINT4 *output, unsigned char *input, unsigned int len)
{
unsigned int i, j;
for (i = 0, j = 0; j < len; i++, j += 4) {
output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) |
(((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24);
}
}
static unsigned char 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
};
/* F, G, H and I are basic MD5 functions. */
#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define I(x, y, z) ((y) ^ ((x) | (~z)))
/* ROTATE_LEFT rotates x left n bits. */
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
Rotation is separate from addition to prevent recomputation.
*/
#define FF(a, b, c, d, x, s, ac) { \
(a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
#define GG(a, b, c, d, x, s, ac) { \
(a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
#define HH(a, b, c, d, x, s, ac) { \
(a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
#define II(a, b, c, d, x, s, ac) { \
(a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
(a) = ROTATE_LEFT ((a), (s)); \
(a) += (b); \
}
/* MD5 initialization. Begins an MD5 operation, writing a new context. */
void
MD5Init(MD5_CTX *context)
{
context->count[0] = context->count[1] = 0;
/* Load magic initialization constants. */
context->state[0] = 0x67452301;
context->state[1] = 0xefcdab89;
context->state[2] = 0x98badcfe;
context->state[3] = 0x10325476;
}
/* MD5 block update operation. Continues an MD5 message-digest
operation, processing another message block, and updating the
context.
*/
void
MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputLen)
{
unsigned int i, index, partLen;
/* Compute number of bytes mod 64 */
index = (unsigned int)((context->count[0] >> 3) & 0x3F);
/* Update number of bits */
if ((context->count[0] += ((UINT4)inputLen << 3))
< ((UINT4)inputLen << 3))
context->count[1]++;
context->count[1] += ((UINT4)inputLen >> 29);
partLen = 64 - index;
/* Transform as many times as possible. */
if (inputLen >= partLen) {
memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen);
MD5Transform(context->state, context->buffer);
for (i = partLen; i + 63 < inputLen; i += 64)
MD5Transform(context->state, &input[i]);
index = 0;
}
else
i = 0;
/* Buffer remaining input */
memcpy((POINTER)&context->buffer[index],
(POINTER)&input[i], inputLen-i);
}
/* MD5 finalization. Ends an MD5 message-digest operation, writing the
message digest and zeroing the context.
*/
void
MD5Final(unsigned char digest[16], MD5_CTX *context)
{
unsigned char bits[8];
unsigned int index, padLen;
/* Save number of bits */
Encode (bits, context->count, 8);
/* Pad out to 56 mod 64. */
index = (unsigned int)((context->count[0] >> 3) & 0x3f);
padLen = (index < 56) ? (56 - index) : (120 - index);
MD5Update(context, PADDING, padLen);
/* Append length (before padding) */
MD5Update(context, bits, 8);
/* Store state in digest */
Encode(digest, context->state, 16);
/* Zeroize sensitive information. */
memset((POINTER)context, 0, sizeof (*context));
}
/* MD5 basic transformation. Transforms state based on block. */
static void
MD5Transform(UINT4 state[4], unsigned char block[64])
{
UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
Decode (x, block, 64);
/* Round 1 */
FF(a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
FF(d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
FF(c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
FF(b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
FF(a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
FF(d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
FF(c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
FF(b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
FF(a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
FF(d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
/* Round 2 */
GG(a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
GG(d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
GG(b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
GG(a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */
GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
GG(b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
GG(a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
GG(c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
GG(b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
GG(d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
GG(c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
/* Round 3 */
HH(a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
HH(d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
HH(a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
HH(d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
HH(c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
HH(d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
HH(c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
HH(b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */
HH(a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
HH(b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
/* Round 4 */
II(a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
II(d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
II(b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
II(d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
II(b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
II(a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
II(c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
II(a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
II(c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
II(b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
/* Zeroize sensitive information. */
memset((POINTER)x, 0, sizeof (x));
}
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
typedef struct { typedef struct {
PyObject_HEAD PyObject_HEAD
MD5_CTX md5; /* the context holder */ md5_state_t md5; /* the context holder */
} md5object; } md5object;
static PyTypeObject MD5type; static PyTypeObject MD5type;
...@@ -31,7 +31,7 @@ newmd5object(void) ...@@ -31,7 +31,7 @@ newmd5object(void)
if (md5p == NULL) if (md5p == NULL)
return NULL; return NULL;
MD5Init(&md5p->md5); /* actual initialisation */ md5_init(&md5p->md5); /* actual initialisation */
return md5p; return md5p;
} }
...@@ -56,7 +56,7 @@ md5_update(md5object *self, PyObject *args) ...@@ -56,7 +56,7 @@ md5_update(md5object *self, PyObject *args)
if (!PyArg_ParseTuple(args, "s#:update", &cp, &len)) if (!PyArg_ParseTuple(args, "s#:update", &cp, &len))
return NULL; return NULL;
MD5Update(&self->md5, cp, len); md5_append(&self->md5, cp, len);
Py_INCREF(Py_None); Py_INCREF(Py_None);
return Py_None; return Py_None;
...@@ -73,12 +73,12 @@ arguments."); ...@@ -73,12 +73,12 @@ arguments.");
static PyObject * static PyObject *
md5_digest(md5object *self) md5_digest(md5object *self)
{ {
MD5_CTX mdContext; md5_state_t mdContext;
unsigned char aDigest[16]; unsigned char aDigest[16];
/* make a temporary copy, and perform the final */ /* make a temporary copy, and perform the final */
mdContext = self->md5; mdContext = self->md5;
MD5Final(aDigest, &mdContext); md5_finish(&mdContext, aDigest);
return PyString_FromStringAndSize((char *)aDigest, 16); return PyString_FromStringAndSize((char *)aDigest, 16);
} }
...@@ -94,14 +94,14 @@ including null bytes."); ...@@ -94,14 +94,14 @@ including null bytes.");
static PyObject * static PyObject *
md5_hexdigest(md5object *self) md5_hexdigest(md5object *self)
{ {
MD5_CTX mdContext; md5_state_t mdContext;
unsigned char digest[16]; unsigned char digest[16];
unsigned char hexdigest[32]; unsigned char hexdigest[32];
int i, j; int i, j;
/* make a temporary copy, and perform the final */ /* make a temporary copy, and perform the final */
mdContext = self->md5; mdContext = self->md5;
MD5Final(digest, &mdContext); md5_finish(&mdContext, digest);
/* Make hex version of the digest */ /* Make hex version of the digest */
for(i=j=0; i<16; i++) { for(i=j=0; i<16; i++) {
...@@ -272,7 +272,7 @@ MD5_new(PyObject *self, PyObject *args) ...@@ -272,7 +272,7 @@ MD5_new(PyObject *self, PyObject *args)
return NULL; return NULL;
if (cp) if (cp)
MD5Update(&md5p->md5, cp, len); md5_append(&md5p->md5, cp, len);
return (PyObject *)md5p; return (PyObject *)md5p;
} }
......
...@@ -549,8 +549,8 @@ class PyBuildExt(build_ext): ...@@ -549,8 +549,8 @@ class PyBuildExt(build_ext):
exts.append( Extension('_sha', ['shamodule.c']) ) exts.append( Extension('_sha', ['shamodule.c']) )
# The _md5 module implements the RSA Data Security, Inc. MD5 # The _md5 module implements the RSA Data Security, Inc. MD5
# Message-Digest Algorithm, described in RFC 1321. The # Message-Digest Algorithm, described in RFC 1321. The
# necessary files md5c.c and md5.h are included here. # necessary files md5.c and md5.h are included here.
exts.append( Extension('_md5', ['md5module.c', 'md5c.c']) ) exts.append( Extension('_md5', ['md5module.c', 'md5.c']) )
if (openssl_ver < 0x00908000): if (openssl_ver < 0x00908000):
# OpenSSL doesn't do these until 0.9.8 so we'll bring our own hash # OpenSSL doesn't do these until 0.9.8 so we'll bring our own hash
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment