PageRenderTime 49ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/core/src/main/php/security/crypto/UnixCrypt.class.php

http://github.com/xp-framework/xp-framework
PHP | 246 lines | 129 code | 16 blank | 101 comment | 25 complexity | 52db4c7bd107c268a5cb7eb90c0cdd6a MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /* This class is part of the XP framework
  3. *
  4. * $Id$
  5. */
  6. uses(
  7. 'security.crypto.CryptoException',
  8. 'security.crypto.NativeCryptImpl',
  9. 'security.crypto.CryptNotImplemented'
  10. );
  11. /**
  12. * Unix crypt algorithm implementation. Note: There is no decrypt
  13. * function, since crypt() uses a one-way algorithm.
  14. *
  15. * Usage: Generating a crypted password
  16. * <code>
  17. * // Use system default, generate a salt
  18. * $default= UnixCrypt::crypt('plain');
  19. *
  20. * // Use traditional
  21. * $traditional= UnixCrypt::crypt('plain', 'ab');
  22. *
  23. * // Use MD5 encryption with 12 character salt
  24. * $md5= UnixCrypt::crypt('plain', '$1$0123456789AB');
  25. *
  26. * // Use blowfish encryption with 16 character salt
  27. * $blowfish= UnixCrypt::crypt('plain', '$2$0123456789ABCDEF');
  28. *
  29. * // Use extended DES-based encryption with a nine character salt
  30. * $extdes= UnixCrypt::crypt('plain', '_012345678');
  31. * </code>
  32. *
  33. * Usage: Verifying an entered password
  34. * <code>
  35. * $verified= UnixCrypt::matches($crypted, $entered);
  36. * </code>
  37. *
  38. * @test xp://net.xp_framework.unittest.security.UnixCryptTest
  39. * @see php://crypt
  40. * @purpose One-way string encryption (hashing)
  41. */
  42. class UnixCrypt extends Object {
  43. public static $DEFAULT;
  44. public static $STANDARD;
  45. public static $EXTENDED;
  46. public static $BLOWFISH;
  47. public static $MD5;
  48. static function __static() {
  49. $builtin= version_compare(PHP_VERSION, '5.3.0', 'ge');
  50. if (!CRYPT_STD_DES) {
  51. self::$STANDARD= new CryptNotImplemented('STD_DES');
  52. } else {
  53. self::$STANDARD= new NativeCryptImpl();
  54. // Before 5.3.2, PHP's crypt() function returned incorrect values
  55. // when given salt characters outside of the alphabet "./0-9A-Za-z".
  56. // No real workaround, so throw an exception - this is inconsistent
  57. // with XP on newer PHP versions which yields the correct results.
  58. // On systems >= 5.3.2, check for usage of libc crypt() which also
  59. // allows salts which are too short and unsafe characters \n and :
  60. if (version_compare(PHP_VERSION, '5.3.2', 'lt')) {
  61. self::$STANDARD= newinstance('security.crypto.NativeCryptImpl', array(), '{
  62. public function crypt($plain, $salt) {
  63. if (!preg_match("#^[./0-9A-Za-z]{2}#", $salt)) {
  64. throw new CryptoException("Malformed salt");
  65. }
  66. return parent::crypt($plain, $salt);
  67. }
  68. public function toString() {
  69. return "security.crypto.NativeCryptImpl+std:salt-alphabet-constraint";
  70. }
  71. }');
  72. } else if (':' === substr(crypt('', ':'), 0, 1)) {
  73. self::$STANDARD= newinstance('security.crypto.NativeCryptImpl', array(), '{
  74. public function crypt($plain, $salt) {
  75. if (strlen($salt) < 1 || strcspn($salt, "\n:") < 2) {
  76. throw new CryptoException("Malformed salt");
  77. }
  78. return parent::crypt($plain, $salt);
  79. }
  80. public function toString() {
  81. return "security.crypto.NativeCryptImpl+std:salt-unsafe-check";
  82. }
  83. }');
  84. }
  85. }
  86. if (!CRYPT_BLOWFISH) {
  87. self::$BLOWFISH= new CryptNotImplemented('BLOWFISH');
  88. } else {
  89. self::$BLOWFISH= new NativeCryptImpl();
  90. // The blowfish method has a bug between PHP 5.3.0 and 5.3.2 which
  91. // returns a bogus result instead of failing when the cost parameter
  92. // is incorrect. For *any* builtin implementation, recognition is
  93. // broken as the "__" in "$2a$__$" for example makes PHP not jump
  94. // into the blowfish branch but fall back to the else branch, and thus
  95. // to standard DES. See line 247 and following in ext/standard/crypt.c
  96. if ($builtin) {
  97. self::$BLOWFISH= newinstance('security.crypto.NativeCryptImpl', array(), '{
  98. public function crypt($plain, $salt) {
  99. if (1 !== sscanf($salt, "$2a$%02d$", $cost)) {
  100. throw new CryptoException("Malformed cost parameter");
  101. }
  102. if ($cost < 4 || $cost > 31) {
  103. throw new CryptoException("Cost parameter must be between 04 and 31");
  104. }
  105. return parent::crypt($plain, $salt);
  106. }
  107. public function toString() {
  108. return "security.crypto.NativeCryptImpl+blowfish:cost-param-check";
  109. }
  110. }');
  111. }
  112. }
  113. if (!CRYPT_EXT_DES) {
  114. self::$EXTENDED= new CryptNotImplemented('EXT_DES');
  115. } else {
  116. // PHP's crypt() function crashes if the salt is too short due to PHP
  117. // using its own DES implementations as 5.3 - these don't check the
  118. // return value correctly. See Bug #51059, which was fixed in PHP 5.3.2
  119. // Debian only recognizes EXT_DES if the salt is 9 characters long, see
  120. // notes in PHP Bug #51254. As there is no reliable way to detect whether
  121. // the patch (referenced in this bug) is applied, always use the check
  122. // and strip off rest of crypted when matching.
  123. self::$EXTENDED= newinstance('security.crypto.NativeCryptImpl', array(), '{
  124. public function crypt($plain, $salt) {
  125. if (strlen($salt) < 9) {
  126. throw new CryptoException("Extended DES: Salt too short");
  127. }
  128. return parent::crypt($plain, $salt);
  129. }
  130. public function matches($encrypted, $entered) {
  131. return ($encrypted === $this->crypt($entered, substr($encrypted, 0, 9)));
  132. }
  133. public function toString() {
  134. return "security.crypto.NativeCryptImpl+ext:recognition-fix";
  135. }
  136. }');
  137. }
  138. if (!CRYPT_MD5) {
  139. self::$MD5= XPClass::forName('security.crypto.MD5CryptImpl')->newInstance();
  140. } else {
  141. self::$MD5= new NativeCryptImpl();
  142. // In PHP version between 5.3.0 and 5.3.5, this fails for situations when
  143. // the salt is too short, too long or does not end with "$". 5.3.6 only
  144. // breaks when the salt is too short. 5.3.7 is the first version to get it
  145. // right, except: In PHP Bug #55439, crypt() returns just the salt for MD5
  146. // on Un*x systems. This bug first occurred in PHP 5.3.7 RC6 and was shipped
  147. // with PHP 5.3.7, and fixed in the release thereafter.
  148. if ($builtin && version_compare(PHP_VERSION, '5.3.7', 'lt')) {
  149. self::$MD5= XPClass::forName('security.crypto.MD5CryptImpl')->newInstance();
  150. } else if (0 === strpos(PHP_VERSION, '5.3.7')) {
  151. if ('$1$' === crypt('', '$1$')) {
  152. self::$MD5= XPClass::forName('security.crypto.MD5CryptImpl')->newInstance();
  153. }
  154. }
  155. }
  156. self::$DEFAULT= self::$MD5;
  157. }
  158. /**
  159. * Encrypt a string
  160. *
  161. * The salt may be in one of three forms (from man 3 crypt):
  162. *
  163. * <pre>
  164. * Extended
  165. * --------
  166. * If it begins with an underscore (``_'') then the DES Extended
  167. * Format is used in interpreting both the key and the salt, as
  168. * outlined below.
  169. *
  170. * Modular
  171. * -------
  172. * If it begins with the string ``$digit$'' then the Modular Crypt
  173. * Format is used, as outlined below.
  174. *
  175. * Traditional
  176. * -----------
  177. * If neither of the above is true, it assumes the Traditional
  178. * Format, using the entire string as the salt (or the first portion).
  179. * </pre>
  180. *
  181. * If ommitted, the salt is generated and the system default is used.
  182. *
  183. * @param string original
  184. * @param string salt default NULL
  185. * @return string crypted
  186. */
  187. public static function crypt($original, $salt= NULL) {
  188. if (NULL === $salt) {
  189. $impl= self::$DEFAULT;
  190. } else if ('_' === $salt{0}) {
  191. $impl= self::$EXTENDED;
  192. } else if (0 === strpos($salt, '$1$')) {
  193. $impl= self::$MD5;
  194. } else if (0 === strpos($salt, '$2a$')) {
  195. $impl= self::$BLOWFISH;
  196. } else {
  197. $impl= self::$STANDARD;
  198. }
  199. return $impl->crypt($original, $salt);
  200. }
  201. /**
  202. * Check if an entered string matches the crypt
  203. *
  204. * @param string encrypted
  205. * @param string entered
  206. * @return bool
  207. */
  208. public static function matches($encrypted, $entered) {
  209. return ($encrypted === self::crypt($entered, $encrypted));
  210. }
  211. /**
  212. * Returns crypt implementations
  213. *
  214. * @return [:security.crypto.CryptImpl]
  215. */
  216. public static function implementations() {
  217. return array(
  218. 'std_des' => self::$STANDARD,
  219. 'ext_des' => self::$EXTENDED,
  220. 'blowfish' => self::$BLOWFISH,
  221. 'md5' => self::$MD5
  222. );
  223. }
  224. }
  225. ?>