File php-5.3.5-CVE-2011-2483-suhosin.patch of Package php5.512

http://news.php.net/php.internals/54000
http://news.php.net/php.internals/54098
Index: ext/suhosin/crypt_blowfish.c
===================================================================
--- ext/suhosin/crypt_blowfish.c.orig
+++ ext/suhosin/crypt_blowfish.c
@@ -40,12 +40,8 @@
 #define __set_errno(val) errno = (val)
 #endif
 
-#undef __CONST
-#ifdef __GNUC__
-#define __CONST __const
-#else
-#define __CONST
-#endif
+/* Just to make sure the prototypes match the actual definitions */
+#include "crypt_blowfish.h"
 
 #ifdef __i386__
 #define BF_ASM				0
@@ -59,6 +55,7 @@
 #endif
 
 typedef unsigned int BF_word;
+typedef signed int BF_word_signed;
 
 /* Number of Blowfish rounds, this is also hardcoded into a few places */
 #define BF_N				16
@@ -366,35 +363,21 @@ static unsigned char BF_atoi64[0x60] = {
 	43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64
 };
 
-/*
- * This may be optimized out if built with function inlining and no BF_ASM.
- */
-static void clean(void *data, int size)
-{
-#if BF_ASM
-	extern void _BF_clean(void *data);
-#endif
-	memset(data, 0, size);
-#if BF_ASM
-	_BF_clean(data);
-#endif
-}
-
 #define BF_safe_atoi64(dst, src) \
 { \
 	tmp = (unsigned char)(src); \
-	if (tmp == '$') break; \
+	if (tmp == '$') break; /* PHP hack */ \
 	if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \
 	tmp = BF_atoi64[tmp]; \
 	if (tmp > 63) return -1; \
 	(dst) = tmp; \
 }
 
-static int BF_decode(BF_word *dst, __CONST char *src, int size)
+static int BF_decode(BF_word *dst, const char *src, int size)
 {
 	unsigned char *dptr = (unsigned char *)dst;
 	unsigned char *end = dptr + size;
-	unsigned char *sptr = (unsigned char *)src;
+	const unsigned char *sptr = (const unsigned char *)src;
 	unsigned int tmp, c1, c2, c3, c4;
 
 	do {
@@ -411,16 +394,16 @@ static int BF_decode(BF_word *dst, __CON
 		*dptr++ = ((c3 & 0x03) << 6) | c4;
 	} while (dptr < end);
 
-	while (dptr < end)
+	while (dptr < end) /* PHP hack */
 		*dptr++ = 0;
 
 	return 0;
 }
 
-static void BF_encode(char *dst, __CONST BF_word *src, int size)
+static void BF_encode(char *dst, const BF_word *src, int size)
 {
-	unsigned char *sptr = (unsigned char *)src;
-	unsigned char *end = sptr + size;
+	const unsigned char *sptr = (const unsigned char *)src;
+	const unsigned char *end = sptr + size;
 	unsigned char *dptr = (unsigned char *)dst;
 	unsigned int c1, c2;
 
@@ -551,32 +534,118 @@ static void BF_swap(BF_word *x, int coun
 	} while (ptr < &data.ctx.S[3][0xFF]);
 #endif
 
-static void BF_set_key(__CONST char *key, BF_key expanded, BF_key initial)
+static void BF_set_key(const char *key, BF_key expanded, BF_key initial,
+    unsigned char flags)
 {
-	__CONST char *ptr = key;
-	int i, j;
-	BF_word tmp;
+        const char *ptr = key;
+        unsigned int bug, i, j;
+        BF_word safety, sign, diff, tmp[2];
+
+/*
+ * There was a sign extension bug in older revisions of this function. While
+ * we would have liked to simply fix the bug and move on, we have to provide
+ * a backwards compatibility feature (essentially the bug) for some systems an
+ * a safety measure for some others. The latter is needed because for certain
+ * multiple inputs to the buggy algorithm there exist easily found inputs to
+ * the correct algorithm that produce the same hash. Thus, we optionally
+ * deviate from the correct algorithm just enough to avoid such collisions.
+ * While the bug itself affected the majority of passwords containing
+ * characters with the 8th bit set (although only a percentage of those in a
+ * collision-producing way), the anti-collision safety measure affects
+ * only a subset of passwords containing the '\xff' character (not even all of
+ * those passwords, just some of them). This character is not found in valid
+ * UTF-8 sequences and is rarely used in popular 8-bit character encodings.
+ * Thus, the safety measure is unlikely to cause much annoyance, and is a
+ * reasonable tradeoff to use when authenticating against existing hashes that
+ * are not reliably known to have been computed with the correct algorithm.
+ *
+ * We use an approach that tries to minimize side-channel leaks of password
+ * information - that is, we mostly use fixed-cost bitwise operations instead
+ * of branches or table lookups. (One conditional branch based on password
+ * length remains. It is not part of the bug aftermath, though, and is
+ * difficult and possibly unreasonable to avoid given the use of C strings by
+ * the caller, which results in similar timing leaks anyway.)
+ *
+ * For actual implementation, we set an array index in the variable "bug"
+ * (0 means no bug, 1 means sign extension bug emulation) and a flag in the
+ * variable "safety" (bit 16 is set when the safety measure is requested).
+ * Valid combinations of settings are:
+ *
+ * Prefix "$2a$": bug = 0, safety = 0x10000
+ * Prefix "$2x$": bug = 1, safety = 0
+ * Prefix "$2y$": bug = 0, safety = 0
+ */
+        bug = (unsigned int)flags & 1;
+        safety = ((BF_word)flags & 2) << 15;
+ 
+        sign = diff = 0;
 
 	for (i = 0; i < BF_N + 2; i++) {
-		tmp = 0;
+		tmp[0] = tmp[1] = 0;
 		for (j = 0; j < 4; j++) {
-			tmp <<= 8;
-			tmp |= *ptr;
+                        tmp[0] <<= 8;
+                        tmp[0] |= (unsigned char)*ptr; /* correct */
+                        tmp[1] <<= 8;
+                        tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */
+/*
+ * Sign extension in the first char has no effect - nothing to overwrite yet,
+ * and those extra 24 bits will be fully shifted out of the 32-bit word. For
+ * chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign
+ * extension in tmp[1] occurs. Once this flag is set, it remains set.
+ */
+                        if (j)
+                                sign |= tmp[1] & 0x80;
+                        if (!*ptr)
+                                ptr = key;
+                        else
+                                ptr++;
 
-			if (!*ptr) ptr = key; else ptr++;
 		}
+                diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */
 
-		expanded[i] = tmp;
-		initial[i] = BF_init_state.P[i] ^ tmp;
+                expanded[i] = tmp[bug];
+                initial[i] = BF_init_state.P[i] ^ tmp[bug];
 	}
+
+/*
+ * At this point, "diff" is zero iff the correct and buggy algorithms produced
+ * exactly the same result. If so and if "sign" is non-zero, which indicates
+ * that there was a non-benign sign extension, this means that we have a
+ * collision between the correctly computed hash for this password and a set of
+ * passwords that could be supplied to the buggy algorithm. Our safety measure
+ * is meant to protect from such many-buggy to one-correct collisions, by
+ * deviating from the correct algorithm in such cases. Let's check for this.
+ */
+       diff |= diff >> 16; /* still zero iff exact match */
+       diff &= 0xffff; /* ditto */
+       diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */
+       sign <<= 9; /* move the non-benign sign extension flag to bit 16 */
+       sign &= ~diff & safety; /* action needed? */
+
+/*
+ * If we have determined that we need to deviate from the correct algorithm,
+ * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but
+ * let's stick to it now. It came out of the approach we used above, and it's
+ * not any worse than any other choice we could make.)
+ *
+ * It is crucial that we don't do the same to the expanded key used in the main
+ * Eksblowfish loop. By doing it to only one of these two, we deviate from a
+ * state that could be directly specified by a password to the buggy algorithm
+ * (and to the fully correct one as well, but that's a side-effect).
+ */
+       initial[0] ^= sign;
 }
 
-char *suhosin_crypt_blowfish_rn(__CONST char *key, __CONST char *setting,
-	char *output, int size)
+char *BF_crypt(const char *key, const char *setting,
+	char *output, int size,
+        BF_word min)
 {
 #if BF_ASM
 	extern void _BF_body_r(BF_ctx *ctx);
 #endif
+       static const unsigned char flags_by_subtype[26] =
+               {2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0};
 	struct {
 		BF_ctx ctx;
 		BF_key expanded_key;
@@ -598,7 +667,8 @@ char *suhosin_crypt_blowfish_rn(__CONST
 
 	if (setting[0] != '$' ||
 	    setting[1] != '2' ||
-	    setting[2] != 'a' ||
+	    setting[2] < 'a' || setting[2] > 'z' ||
+	    !flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] ||
 	    setting[3] != '$' ||
 	    setting[4] < '0' || setting[4] > '3' ||
 	    setting[5] < '0' || setting[5] > '9' ||
@@ -608,15 +678,14 @@ char *suhosin_crypt_blowfish_rn(__CONST
 	}
 
 	count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0'));
-	if (count < 16 || BF_decode(data.binary.salt, &setting[7], 16)) {
-		clean(data.binary.salt, sizeof(data.binary.salt));
+	if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) {
 		__set_errno(EINVAL);
 		return NULL;
 	}
-
 	BF_swap(data.binary.salt, 4);
 
-	BF_set_key(key, data.expanded_key, data.ctx.P);
+	BF_set_key(key, data.expanded_key, data.ctx.P,
+	    flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']);
 
 	memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S));
 
@@ -646,51 +715,33 @@ char *suhosin_crypt_blowfish_rn(__CONST
 	} while (ptr < &data.ctx.S[3][0xFF]);
 
 	do {
-		data.ctx.P[0] ^= data.expanded_key[0];
-		data.ctx.P[1] ^= data.expanded_key[1];
-		data.ctx.P[2] ^= data.expanded_key[2];
-		data.ctx.P[3] ^= data.expanded_key[3];
-		data.ctx.P[4] ^= data.expanded_key[4];
-		data.ctx.P[5] ^= data.expanded_key[5];
-		data.ctx.P[6] ^= data.expanded_key[6];
-		data.ctx.P[7] ^= data.expanded_key[7];
-		data.ctx.P[8] ^= data.expanded_key[8];
-		data.ctx.P[9] ^= data.expanded_key[9];
-		data.ctx.P[10] ^= data.expanded_key[10];
-		data.ctx.P[11] ^= data.expanded_key[11];
-		data.ctx.P[12] ^= data.expanded_key[12];
-		data.ctx.P[13] ^= data.expanded_key[13];
-		data.ctx.P[14] ^= data.expanded_key[14];
-		data.ctx.P[15] ^= data.expanded_key[15];
-		data.ctx.P[16] ^= data.expanded_key[16];
-		data.ctx.P[17] ^= data.expanded_key[17];
-
-		BF_body();
-
-		tmp1 = data.binary.salt[0];
-		tmp2 = data.binary.salt[1];
-		tmp3 = data.binary.salt[2];
-		tmp4 = data.binary.salt[3];
-		data.ctx.P[0] ^= tmp1;
-		data.ctx.P[1] ^= tmp2;
-		data.ctx.P[2] ^= tmp3;
-		data.ctx.P[3] ^= tmp4;
-		data.ctx.P[4] ^= tmp1;
-		data.ctx.P[5] ^= tmp2;
-		data.ctx.P[6] ^= tmp3;
-		data.ctx.P[7] ^= tmp4;
-		data.ctx.P[8] ^= tmp1;
-		data.ctx.P[9] ^= tmp2;
-		data.ctx.P[10] ^= tmp3;
-		data.ctx.P[11] ^= tmp4;
-		data.ctx.P[12] ^= tmp1;
-		data.ctx.P[13] ^= tmp2;
-		data.ctx.P[14] ^= tmp3;
-		data.ctx.P[15] ^= tmp4;
-		data.ctx.P[16] ^= tmp1;
-		data.ctx.P[17] ^= tmp2;
+		int done;
+
+		for (i = 0; i < BF_N + 2; i += 2) {
+			data.ctx.P[i] ^= data.expanded_key[i];
+			data.ctx.P[i + 1] ^= data.expanded_key[i + 1];
+		}
 
-		BF_body();
+		done = 0;
+		do {
+			BF_body();
+			if (done)
+				break;
+			done = 1;
+
+			tmp1 = data.binary.salt[0];
+			tmp2 = data.binary.salt[1];
+			tmp3 = data.binary.salt[2];
+			tmp4 = data.binary.salt[3];
+			for (i = 0; i < BF_N; i += 4) {
+				data.ctx.P[i] ^= tmp1;
+				data.ctx.P[i + 1] ^= tmp2;
+				data.ctx.P[i + 2] ^= tmp3;
+				data.ctx.P[i + 3] ^= tmp4;
+			}
+			data.ctx.P[16] ^= tmp1;
+			data.ctx.P[17] ^= tmp2;
+		} while (1);
 	} while (--count);
 
 	for (i = 0; i < 6; i += 2) {
@@ -716,19 +767,117 @@ char *suhosin_crypt_blowfish_rn(__CONST
 	BF_encode(&output[7 + 22], data.binary.output, 23);
 	output[7 + 22 + 31] = '\0';
 
-/* Overwrite the most obvious sensitive data we have on the stack. Note
- * that this does not guarantee there's no sensitive data left on the
- * stack and/or in registers; I'm not aware of portable code that does. */
 	clean(&data, sizeof(data));
 
 	return output;
 }
 
-char *suhosin_crypt_gensalt_blowfish_rn(unsigned long count,
-	__CONST char *input, int size, char *output, int output_size)
+static int _crypt_output_magic(const char *setting, char *output, int size)
+{
+       if (size < 3)
+               return -1;
+
+       output[0] = '*';
+       output[1] = '0';
+       output[2] = '\0';
+
+       if (setting[0] == '*' && setting[1] == '0')
+               output[1] = '1';
+
+       return 0;
+}
+
+/*
+ * Please preserve the runtime self-test. It serves two purposes at once:
+ *
+ * 1. We really can't afford the risk of producing incompatible hashes e.g.
+ * when there's something like gcc bug 26587 again, whereas an application or
+ * library integrating this code might not also integrate our external tests or
+ * it might not run them after every build. Even if it does, the miscompile
+ * might only occur on the production build, but not on a testing build (such
+ * as because of different optimization settings). It is painful to recover
+ * from incorrectly-computed hashes - merely fixing whatever broke is not
+ * enough. Thus, a proactive measure like this self-test is needed.
+ *
+ * 2. We don't want to leave sensitive data from our actual password hash
+ * computation on the stack or in registers. Previous revisions of the code
+ * would do explicit cleanups, but simply running the self-test after hash
+ * computation is more reliable.
+ *
+ * The performance cost of this quick self-test is around 0.6% at the "$2a$08"
+ * setting.
+ */
+
+char *suhosin_crypt_blowfish_rn(const char *key, const char *setting,
+       char *output, int size)
+{
+       const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8";
+       const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu";
+       static const char * const test_hash[2] =
+               {"VUrPmXD6q/nVSSp7pNDhCR9071IfIRe\0\x55", /* $2x$ */
+               "i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55"}; /* $2a$, $2y$ */
+       char *retval;
+       const char *p;
+       int save_errno, ok;
+       struct {
+               char s[7 + 22 + 1];
+               char o[7 + 22 + 31 + 1 + 1 + 1];
+       } buf;
+
+/* Hash the supplied password */
+       _crypt_output_magic(setting, output, size);
+       retval = BF_crypt(key, setting, output, size, 16);
+       save_errno = errno;
+
+/*
+ * Do a quick self-test. It is important that we make both calls to BF_crypt()
+ * from the same scope such that they likely use the same stack locations,
+ * which makes the second call overwrite the first call's sensitive data on the
+ * stack and makes it more likely that any alignment related issues would be
+ * detected by the self-test.
+ */
+       memcpy(buf.s, test_setting, sizeof(buf.s));
+       if (retval)
+               buf.s[2] = setting[2];
+       memset(buf.o, 0x55, sizeof(buf.o));
+       buf.o[sizeof(buf.o) - 1] = 0;
+       p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1);
+
+       ok = (p == buf.o &&
+           !memcmp(p, buf.s, 7 + 22) &&
+           !memcmp(p + (7 + 22),
+           test_hash[(unsigned int)(unsigned char)buf.s[2] & 1],
+           31 + 1 + 1 + 1));
+
+       {
+               const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345";
+               BF_key ae, ai, ye, yi;
+               BF_set_key(k, ae, ai, 2); /* $2a$ */
+               BF_set_key(k, ye, yi, 4); /* $2y$ */
+               ai[0] ^= 0x10000; /* undo the safety (for comparison) */
+               ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 &&
+                   !memcmp(ae, ye, sizeof(ae)) &&
+                   !memcmp(ai, yi, sizeof(ai));
+       }
+
+       __set_errno(save_errno);
+       if (ok)
+               return retval;
+
+/* Should not happen */
+       _crypt_output_magic(setting, output, size);
+       __set_errno(EINVAL); /* pretend we don't support this hash type */
+       return NULL;
+}
+
+#if 0
+char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count,
+       const char *input, int size, char *output, int output_size)
 {
 	if (size < 16 || output_size < 7 + 22 + 1 ||
-	    (count && (count < 4 || count > 31))) {
+           (count && (count < 4 || count > 31)) ||
+           prefix[0] != '$' || prefix[1] != '2' ||
+           (prefix[2] != 'a' && prefix[2] != 'y')) {
 		if (output_size > 0) output[0] = '\0';
 		__set_errno((output_size < 7 + 22 + 1) ? ERANGE : EINVAL);
 		return NULL;
@@ -738,14 +887,15 @@ char *suhosin_crypt_gensalt_blowfish_rn(
 
 	output[0] = '$';
 	output[1] = '2';
-	output[2] = 'a';
+	output[2] = prefix[2];
 	output[3] = '$';
 	output[4] = '0' + count / 10;
 	output[5] = '0' + count % 10;
 	output[6] = '$';
 
-	BF_encode(&output[7], (BF_word *)input, 16);
+	BF_encode(&output[7], (const BF_word *)input, 16);
 	output[7 + 22] = '\0';
 
 	return output;
 }
+#endif
Index: ext/suhosin/crypt_blowfish.h
===================================================================
--- /dev/null
+++ ext/suhosin/crypt_blowfish.h
@@ -0,0 +1,32 @@
+/* $Id$ */
+/*
+ * Written by Solar Designer <solar at openwall.com> in 2000-2011.
+ * No copyright is claimed, and the software is hereby placed in the public
+ * domain. In case this attempt to disclaim copyright and place the software
+ * in the public domain is deemed null and void, then the software is
+ * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the
+ * general public under the following terms:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted.
+ *
+ * There's ABSOLUTELY NO WARRANTY, express or implied.
+ *
+ * See crypt_blowfish.c for more information.
+ */
+
+#ifndef _CRYPT_BLOWFISH_H
+#define _CRYPT_BLOWFISH_H
+
+#if 0
+extern int _crypt_output_magic(const char *setting, char *output, int size);
+#endif
+extern char *suhosin_crypt_blowfish_rn(const char *key, const char *setting,
+	char *output, int size);
+#if 0
+extern char *_crypt_gensalt_blowfish_rn(const char *prefix,
+	unsigned long count,
+	const char *input, int size, char *output, int output_size);
+#endif
+
+#endif
Index: ext/suhosin/crypt.c
===================================================================
--- ext/suhosin/crypt.c.orig
+++ ext/suhosin/crypt.c
@@ -66,8 +66,7 @@ static MUTEX_T suhosin_crypt_mutex;
 
 static int CRYPT_MD5 = 0;
 
-char *suhosin_crypt_blowfish_rn(char *key, char *setting, char *output, int size);
-char *suhosin_crypt_gensalt_blowfish_rn(unsigned long count, char *input, int size, char *output, int output_size);
+#include "crypt_blowfish.h"
 
 static unsigned char itoa64[] = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
 
@@ -116,7 +115,7 @@ static PHP_FUNCTION(suhosin_crypt)
 
 	if (salt[0] == '$' &&
 	    salt[1] == '2' &&
-	    salt[2] == 'a' &&
+	    salt[2] >= 'a' && salt[2] <= 'z' &&
 	    salt[3] == '$' &&
 	    salt[4] >= '0' && salt[4] <= '3' &&
 	    salt[5] >= '0' && salt[5] <= '9' &&
Index: ext/suhosin/tests/strings/crypt_blowfish.phpt
===================================================================
--- /dev/null
+++ ext/suhosin/tests/strings/crypt_blowfish.phpt
@@ -0,0 +1,78 @@
+--TEST--
+Official blowfish tests (http://cvsweb.openwall.com/cgi/cvsweb.cgi/Owl/packages/glibc/crypt_blowfish/wrapper.c)
+--SKIPIF--
+<?php
+if (!function_exists('crypt') || !defined("CRYPT_BLOWFISH")) {
+    die("SKIP crypt()-blowfish is not available");
+}
+?>
+--FILE--
+<?php
+
+$tests =array(
+	array('$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW', 'U*U'),
+	array('$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK', 'U*U*'),
+	array('$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a', 'U*U*U'),
+	array('$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui', '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789chars after 72 are ignored'),
+	array('$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e', "\xa3"),
+	array('$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e', "\xff\xff\xa3"),
+	array('$2y$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e', "\xff\xff\xa3"),
+	array('$2a$05$/OK.fbVrR/bpIqNJ5ianF.nqd1wy.pTMdcvrRWxyiGL2eMz.2a85.', "\xff\xff\xa3"),
+	array('$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq', "\xa3"),
+	array('$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq', "\xa3"),
+	array('$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi', "1\xa3345"),
+	array('$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi', "\xff\xa3345"),
+	array('$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi', "\xff\xa334\xff\xff\xff\xa3345"),
+	array('$2y$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi', "\xff\xa334\xff\xff\xff\xa3345"),
+	array('$2a$05$/OK.fbVrR/bpIqNJ5ianF.ZC1JEJ8Z4gPfpe1JOr/oyPXTWl9EFd.', "\xff\xa334\xff\xff\xff\xa3345"),
+	array('$2y$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e', "\xff\xa3345"),
+	array('$2a$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e', "\xff\xa3345"),
+	array('$2a$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS', "\xa3ab"),
+	array('$2x$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS', "\xa3ab"),
+	array('$2y$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS', "\xa3ab"),
+	array('$2x$05$6bNw2HLQYeqHYyBfLMsv/OiwqTymGIGzFsA4hOTWebfehXHNprcAS', "\xd1\x91"),
+	array('$2x$05$6bNw2HLQYeqHYyBfLMsv/O9LIGgn8OMzuDoHfof8AQimSGfcSWxnS', "\xd0\xc1\xd2\xcf\xcc\xd8"),
+	array('$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6', "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaachars after 72 are ignored as usual"),
+	array('$2a$05$/OK.fbVrR/bpIqNJ5ianF.R9xrDjiycxMbQE2bp.vgqlYpW5wx2yy', "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"),
+	array('$2a$05$/OK.fbVrR/bpIqNJ5ianF.9tQZzcJfm3uj2NvJ/n5xkhpqLrMpWCe', "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"),
+	array('$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy', ''),
+
+);
+$i=0;
+foreach($tests as $test) {
+  if(crypt($test[1], $test[0]) == $test[0]) {
+    echo "$i. OK\n";
+  } else {
+    echo "$i. Not OK: $test[0] ".crypt($test[1], $test[0])."\n";
+  }
+  $i++;
+}
+
+?>
+--EXPECT--
+0. OK
+1. OK
+2. OK
+3. OK
+4. OK
+5. OK
+6. OK
+7. OK
+8. OK
+9. OK
+10. OK
+11. OK
+12. OK
+13. OK
+14. OK
+15. OK
+16. OK
+17. OK
+18. OK
+19. OK
+20. OK
+21. OK
+22. OK
+23. OK
+24. OK
+25. OK