PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/core/src/main/php/security/SecureString.class.php

http://github.com/xp-framework/xp-framework
PHP | 191 lines | 94 code | 19 blank | 78 comment | 8 complexity | 58f708a253299892f36071802e7aca8b 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('lang.Runtime', 'security.SecurityException');
  7. /**
  8. * SecureString provides a reasonable secure storage for security-sensistive
  9. * lists of characters, such as passwords.
  10. *
  11. * It prevents accidentially revealing them in output, by var_dump()ing,
  12. * echo()ing, or casting the object to array. All these cases will not
  13. * show the password, nor the crypt of it.
  14. *
  15. * However, it is not safe to consider this implementation secure in a crypto-
  16. * graphically sense, because it does not care for a very strong encryption,
  17. * and it does share the encryption key with all instances of it in a single
  18. * PHP instance.
  19. *
  20. * Hint: when using this class, you must make sure not to extract the secured string
  21. * and pass it to a place where an exception might occur, as it might be exposed as
  22. * method argument.
  23. *
  24. * As a rule of thumb: extract it from the container at the last possible location.
  25. *
  26. * @test xp://net.xp_framework.unittest.security.SecureStringTest
  27. * @test xp://net.xp_framework.unittest.security.McryptSecureStringTest
  28. * @test xp://net.xp_framework.unittest.security.OpenSSLSecureStringTest
  29. * @test xp://net.xp_framework.unittest.security.PlainTextSecureStringTest
  30. */
  31. final class SecureString extends Object {
  32. const BACKING_MCRYPT = 0x01;
  33. const BACKING_OPENSSL = 0x02;
  34. const BACKING_PLAINTEXT = 0x03;
  35. private static $store = array();
  36. private static $encrypt = NULL;
  37. private static $decrypt = NULL;
  38. static function __static() {
  39. if (Runtime::getInstance()->extensionAvailable('mcrypt')) {
  40. self::useBacking(self::BACKING_MCRYPT);
  41. } else if (Runtime::getInstance()->extensionAvailable('openssl')) {
  42. self::useBacking(self::BACKING_OPENSSL);
  43. } else {
  44. self::useBacking(self::BACKING_PLAINTEXT);
  45. }
  46. }
  47. /**
  48. * Switch storage algorithm backing
  49. *
  50. * @param int $type one of BACKING_MCRYPT, BACKING_OPENSSL, BACKING_PLAINTEXT
  51. * @throws lang.IllegalArgumentException If illegal backing type was given
  52. * @throws lang.IllegalStateException If chosen backing missed a extension dependency
  53. */
  54. public static function useBacking($type) {
  55. switch ($type) {
  56. case self::BACKING_MCRYPT: {
  57. if (!Runtime::getInstance()->extensionAvailable('mcrypt')) {
  58. throw new IllegalStateException('Backing "mcrypt" required but extension not available.');
  59. }
  60. $engine= mcrypt_module_open(MCRYPT_DES, '', 'ecb', '');
  61. $engineiv= mcrypt_create_iv(mcrypt_enc_get_iv_size($engine), MCRYPT_RAND);
  62. $key= substr(md5(uniqid()), 0, mcrypt_enc_get_key_size($engine));
  63. mcrypt_generic_init($engine, $key, $engineiv);
  64. return self::setBacking(
  65. function($value) use($engine) { return mcrypt_generic($engine, $value); },
  66. function($value) use($engine) { return rtrim(mdecrypt_generic($engine, $value), "\0"); }
  67. );
  68. }
  69. case self::BACKING_OPENSSL: {
  70. if (!Runtime::getInstance()->extensionAvailable('openssl')) {
  71. throw new IllegalStateException('Backing "openssl" required but extension not available.');
  72. }
  73. $key= md5(uniqid());
  74. $iv= substr(md5(uniqid()), 0, openssl_cipher_iv_length('des'));
  75. return self::setBacking(
  76. function($value) use ($key, $iv) { return openssl_encrypt($value, 'DES', $key, 0, $iv); },
  77. function($value) use ($key, $iv) { return openssl_decrypt($value, 'DES', $key, 0, $iv); }
  78. );
  79. }
  80. case self::BACKING_PLAINTEXT: {
  81. return self::setBacking(
  82. function($value) { return base64_encode($value); },
  83. function($value) { return base64_decode($value); }
  84. );
  85. }
  86. default: {
  87. throw new IllegalArgumentException('Invalid backing given: '.xp::stringOf($type));
  88. }
  89. }
  90. }
  91. /**
  92. * Store encryption and decryption routines (unittest method only)
  93. *
  94. * @param callable $encrypt
  95. * @param callable $decrypt
  96. */
  97. public static function setBacking($encrypt, $decrypt) {
  98. self::$encrypt= $encrypt;
  99. self::$decrypt= $decrypt;
  100. }
  101. /**
  102. * Constructor
  103. *
  104. * @param string $c Characters to secure
  105. */
  106. public function __construct($c) {
  107. $this->setCharacters($c);
  108. }
  109. /**
  110. * Prevent serialization of object
  111. *
  112. * @return array
  113. */
  114. public function __sleep() {
  115. throw new IllegalStateException('Cannot serialize SecureString instances.');
  116. }
  117. /**
  118. * Set characters to secure
  119. *
  120. * @param string $c
  121. */
  122. public function setCharacters(&$c) {
  123. try {
  124. $m= self::$encrypt;
  125. self::$store[$this->hashCode()]= $m($c);
  126. } catch (Exception $e) {
  127. // This intentionally catches *ALL* exceptions, in order not to fail
  128. // and produce a stacktrace (containing arguments on the stack that were)
  129. // supposed to be protected.
  130. // Also, cleanup XP error stack
  131. unset(self::$store[$this->hashCode()]);
  132. xp::gc();
  133. }
  134. $c= str_repeat('*', strlen($c));
  135. $c= NULL;
  136. }
  137. /**
  138. * Retrieve secured characters
  139. *
  140. * @return string
  141. */
  142. public function getCharacters() {
  143. if (!isset(self::$store[$this->hashCode()])) {
  144. throw new SecurityException('An error occurred during storing the encrypted password.');
  145. }
  146. $m= self::$decrypt;
  147. return $m(self::$store[$this->hashCode()]);
  148. }
  149. /**
  150. * Override regular __toString() output
  151. *
  152. * @return string
  153. */
  154. public function __toString() {
  155. return $this->toString();
  156. }
  157. /**
  158. * Provide string representation
  159. *
  160. * @return string
  161. */
  162. public function toString() {
  163. return $this->getClassName().'('.$this->hashCode().') {}';
  164. }
  165. /**
  166. * Destructor; removes references from crypted storage for this instance.
  167. */
  168. public function __destruct() {
  169. unset(self::$store[$this->hashCode()]);
  170. }
  171. }
  172. ?>