1<?php
2/**
3 * Copyright 2005-2008 Matthew Fonda <mfonda@php.net>
4 * Copyright 2012-2017 Horde LLC (http://www.horde.org/)
5 *
6 * See the enclosed file LICENSE for license information (LGPL). If you
7 * did not receive this file, see http://www.horde.org/licenses/lgpl21.
8 *
9 * @author   Matthew Fonda <mfonda@php.net>
10 * @author   Michael Slusarz <slusarz@horde.org>
11 * @category Horde
12 * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
13 * @package  Crypt_Blowfish
14 */
15
16/**
17 * Provides blowfish encryption/decryption, with or without a secret key,
18 * for PHP strings.
19 *
20 * @author    Matthew Fonda <mfonda@php.net>
21 * @author    Michael Slusarz <slusarz@horde.org>
22 * @category  Horde
23 * @copyright 2005-2008 Matthew Fonda
24 * @copyright 2012-2017 Horde LLC
25 * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
26 * @package   Crypt_Blowfish
27 *
28 * @property string $cipher  The cipher block mode ('ecb' or 'cbc').
29 * @property string $key  The encryption key in use.
30 * @property mixed $iv  The initialization vector (false if using 'ecb').
31 */
32class Horde_Crypt_Blowfish
33{
34    // Constants for 'ignore' parameter of constructor.
35    const IGNORE_OPENSSL = 1;
36    const IGNORE_MCRYPT = 2;
37
38    // Block size for Blowfish
39    const BLOCKSIZE = 8;
40
41    // Maximum key size for Blowfish
42    const MAXKEYSIZE = 56;
43
44    // IV Length for CBC
45    const IV_LENGTH = 8;
46
47    /**
48     * Blowfish crypt driver.
49     *
50     * @var Horde_Crypt_Blowfish_Base
51     */
52    protected $_crypt;
53
54    /**
55     * Constructor.
56     *
57     * @param string $key  Encryption key.
58     * @param array $opts  Additional options:
59     *   - cipher: (string) Either 'ecb' or 'cbc'.
60     *   - ignore: (integer) A mask of drivers to ignore (IGNORE_* constants).
61     *   - iv: (string) IV to use.
62     */
63    public function __construct($key, array $opts = array())
64    {
65        $opts = array_merge(array(
66            'cipher' => 'ecb',
67            'ignore' => 0,
68            'iv' => null
69        ), $opts);
70
71        if (!($opts['ignore'] & self::IGNORE_OPENSSL) &&
72            Horde_Crypt_Blowfish_Openssl::supported()) {
73            $this->_crypt = new Horde_Crypt_Blowfish_Openssl($opts['cipher']);
74        } elseif (!($opts['ignore'] & self::IGNORE_MCRYPT) &&
75                  Horde_Crypt_Blowfish_Mcrypt::supported()) {
76            $this->_crypt = new Horde_Crypt_Blowfish_Mcrypt($opts['cipher']);
77        } else {
78            $this->_crypt = new Horde_Crypt_Blowfish_Php($opts['cipher']);
79        }
80
81        $this->setKey($key, $opts['iv']);
82    }
83
84    /**
85     */
86    public function __get($name)
87    {
88        switch ($name) {
89        case 'cipher':
90        case 'key':
91        case 'iv':
92            return $this->_crypt->$name;
93        }
94    }
95
96    /**
97     * Encrypts a string.
98     *
99     * @param string $text  The string to encrypt.
100     *
101     * @return string  The ciphertext.
102     * @throws Horde_Crypt_Blowfish_Exception
103     */
104    public function encrypt($text)
105    {
106        if (!is_string($text)) {
107            throw new Horde_Crypt_Blowfish_Exception('Data to encrypt must be a string.');
108        }
109
110        return $this->_crypt->encrypt($text);
111    }
112
113    /**
114     * Decrypts a string.
115     *
116     * @param string $text  The string to decrypt.
117     *
118     * @return string  The plaintext.
119     * @throws Horde_Crypt_Blowfish_Exception
120     */
121    public function decrypt($text)
122    {
123        if (!is_string($text)) {
124            throw new Horde_Crypt_Blowfish_Exception('Data to decrypt must be a string.');
125        }
126
127        return $this->_crypt->decrypt($text);
128    }
129
130    /**
131     * Sets the secret key.
132     *
133     * The key must be non-zero, and less than or equal to MAXKEYSIZE
134     * characters (bytes) in length.
135     *
136     * @param string $key  Key must be non-empty and less than MAXKEYSIZE
137     *                     bytes in length.
138     * @param string $iv   The initialization vector to use. Only needed for
139     *                     'cbc' cipher. If null, an IV is automatically
140     *                     generated.
141     *
142     * @throws Horde_Crypt_Blowfish_Exception
143     */
144    public function setKey($key, $iv = null)
145    {
146        if (!is_string($key)) {
147            throw new Horde_Crypt_Blowfish_Exception('Encryption key must be a string.');
148        }
149
150        $len = strlen($key);
151        if (($len > self::MAXKEYSIZE) || ($len == 0)) {
152            throw new Horde_Crypt_Blowfish_Exception(sprintf('Encryption key must be less than %d characters (bytes) and non-zero. Supplied key length: %d', self::MAXKEYSIZE, $len));
153        }
154
155        $this->_crypt->key = $key;
156
157        switch ($this->_crypt->cipher) {
158        case 'cbc':
159            if (is_null($iv)) {
160                if (is_null($this->iv)) {
161                    $this->_crypt->setIv();
162                }
163            } else {
164                $iv = substr($iv, 0, self::IV_LENGTH);
165                if (($len = strlen($iv)) < self::IV_LENGTH) {
166                    $iv .= str_repeat(chr(0), self::IV_LENGTH - $len);
167                }
168                $this->_crypt->setIv($iv);
169            }
170            break;
171
172        case 'ecb':
173            $this->iv = false;
174            break;
175        }
176    }
177
178}
179