1: <?php
2: /**
3: * Provides an API for encrypting and decrypting small pieces of data with the
4: * use of a shared key stored in a cookie.
5: *
6: * Copyright 1999-2012 Horde LLC (http://www.horde.org/)
7: *
8: * See the enclosed file COPYING for license information (LGPL). If you
9: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
10: *
11: * @author Chuck Hagenbuch <chuck@horde.org>
12: * @author Michael Slusarz <slusarz@horde.org>
13: * @category Horde
14: * @license http://www.horde.org/licenses/lgpl21 LGPL
15: * @package Secret
16: */
17: class Horde_Secret
18: {
19: /** Generic, default keyname. */
20: const DEFAULT_KEY = 'generic';
21:
22: /**
23: * Configuration parameters.
24: *
25: * @var array
26: */
27: protected $_params = array(
28: 'cookie_domain' => '',
29: 'cookie_path' => '',
30: 'cookie_ssl' => false,
31: 'session_name' => 'horde_secret'
32: );
33:
34: /**
35: * Cipher cache.
36: *
37: * @var array
38: */
39: protected $_cipherCache = array();
40:
41: /**
42: * Key cache.
43: *
44: * @var array
45: */
46: protected $_keyCache = array();
47:
48: /**
49: * Constructor.
50: *
51: * @param array $params Configuration parameters:
52: * - cookie_domain: (string) The cookie domain.
53: * - cookie_path: (string) The cookie path.
54: * - cookie_ssl: (boolean) Only transmit cookie securely?
55: * - session_name: (string) The cookie session name.
56: */
57: public function __construct($params = array())
58: {
59: $this->_params = array_merge($this->_params, $params);
60: }
61:
62: /**
63: * Take a small piece of data and encrypt it with a key.
64: *
65: * @param string $key The key to use for encryption.
66: * @param string $message The plaintext message.
67: *
68: * @return string The ciphertext message.
69: * @throws Horde_Secret_Exception
70: */
71: public function write($key, $message)
72: {
73: $message = (string)$message;
74: if (strlen($key) && strlen($message)) {
75: return $this->_getCipherOb($key)->encrypt($message);
76: } else {
77: return '';
78: }
79: }
80:
81: /**
82: * Decrypt a message encrypted with write().
83: *
84: * @param string $key The key to use for decryption.
85: * @param string $message The ciphertext message.
86: *
87: * @return string The plaintext message.
88: * @throws Horde_Secret_Exception
89: */
90: public function read($key, $ciphertext)
91: {
92: $ciphertext = (string)$ciphertext;
93: if (strlen($key) && strlen($ciphertext)) {
94: return rtrim($this->_getCipherOb($key)->decrypt($ciphertext), "\0");
95: } else {
96: return '';
97: }
98: }
99:
100: /**
101: * Returns the cached crypt object.
102: *
103: * @param string $key The key to use for [de|en]cryption. Only the first
104: * 56 bytes of this string is used.
105: *
106: * @return Crypt_Blowfish The crypt object.
107: * @throws Horde_Secret_Exception
108: */
109: protected function _getCipherOb($key)
110: {
111: if (!is_string($key)) {
112: throw new Horde_Secret_Exception('Key must be a string', 2);
113: }
114:
115: if (!strlen($key)) {
116: throw new Horde_Secret_Exception('Key must be non-zero.', 3);
117: }
118:
119: $key = substr($key, 0, 56);
120:
121: $idx = hash('md5', $key);
122:
123: if (!isset($this->_cipherCache[$idx])) {
124: if (!class_exists('Crypt_Blowfish')) {
125: throw new Horde_Secret_Exception('Crypt_Blowfish library not found.');
126: }
127: $this->_cipherCache[$idx] = new Crypt_Blowfish($key);
128: }
129:
130: return $this->_cipherCache[$idx];
131: }
132:
133: /**
134: * Generate a secret key (for encryption), either using a random
135: * md5 string and storing it in a cookie if the user has cookies
136: * enabled, or munging some known values if they don't.
137: *
138: * @param string $keyname The name of the key to set.
139: *
140: * @return string The secret key that has been generated.
141: */
142: public function setKey($keyname = self::DEFAULT_KEY)
143: {
144: $set = true;
145:
146: if (isset($_COOKIE[$this->_params['session_name']])) {
147: if (isset($_COOKIE[$keyname . '_key'])) {
148: $key = $_COOKIE[$keyname . '_key'];
149: $set = false;
150: } else {
151: $key = $_COOKIE[$keyname . '_key'] = uniqid(mt_rand());
152: }
153: } else {
154: $key = session_id();
155: }
156:
157: if ($set) {
158: $this->_setCookie($keyname, $key);
159: }
160:
161: return $key;
162: }
163:
164: /**
165: * Return a secret key, either from a cookie, or if the cookie
166: * isn't there, assume we are using a munged version of a known
167: * base value.
168: *
169: * @param string $keyname The name of the key to get.
170: *
171: * @return string The secret key.
172: */
173: public function getKey($keyname = self::DEFAULT_KEY)
174: {
175: if (!isset($this->_keyCache[$keyname])) {
176: if (isset($_COOKIE[$keyname . '_key'])) {
177: $key = $_COOKIE[$keyname . '_key'];
178: } else {
179: $key = session_id();
180: $this->_setCookie($keyname, $key);
181: }
182:
183: $this->_keyCache[$keyname] = $key;
184: }
185:
186: return $this->_keyCache[$keyname];
187: }
188:
189: /**
190: * Clears a secret key entry from the current cookie.
191: *
192: * @param string $keyname The name of the key to clear.
193: *
194: * @return boolean True if key existed, false if not.
195: */
196: public function clearKey($keyname = self::DEFAULT_KEY)
197: {
198: if (isset($_COOKIE[$this->_params['session_name']]) &&
199: isset($_COOKIE[$keyname . '_key'])) {
200: $this->_setCookie($keyname, false);
201: return true;
202: }
203:
204: return false;
205: }
206:
207: /**
208: * Sets the cookie with the given keyname/key.
209: *
210: * @param string $keyname The name of the key to set.
211: * @param string $key The key to use for encryption.
212: */
213: protected function _setCookie($keyname, $key)
214: {
215: @setcookie(
216: $keyname . '_key',
217: $key,
218: 0,
219: $this->_params['cookie_path'],
220: $this->_params['cookie_domain'],
221: $this->_params['cookie_ssl'],
222: true
223: );
224:
225: if ($key === false) {
226: unset($_COOKIE[$keyname], $this->_keyCache[$keyname]);
227: } else {
228: $_COOKIE[$keyname] = $this->_keyCache[$keyname] = $key;
229: }
230: }
231:
232: }
233: