osTicket is (according to them) the "world's most popular open source customer support ticket system." They use one of the worst-designed encryption functions I've ever seen.
First of all, they're trying to use symmetric encryption to secure passwords. They should be using a slow hash (pbkdf2, bcrypt, scrypt) with salt. Edit: Actually, they have to be able to decrypt passwords, because they are used in POP3/IMAP connections. They have to be able to provide the plaintext passwords to a third party, not just verify them. Users' passwords are hashed.
Second, the encryption function fails open. If the mcrypt library is missing, encryption does nothing, and passwords are stored in the database in plain text. It does log a warning message, but who's going to see that?
I can almost feel the programmer's frustration, trying all possible combinations of trim(), IVs, and cipher modes to get the damn thing to decrypt properly. They
tried to generate a random IV at encryption time, but did not realize they need to encode it with the ciphertext so it can be given to mcrypt_decrypt(). Instead, they use ECB mode, which ignores the IV. They even generate a random IV in the
decryption function!
function encrypt($text, $salt) {
//if mcrypt extension is not installed--simply return unencryted text and log a warning.
if(!function_exists('mcrypt_encrypt') || !function_exists('mcrypt_decrypt')) {
$msg='Cryptography extension mcrypt is not enabled or installed. IMAP/POP passwords are being stored as plain text in database.';
Sys::log(LOG_WARN,'mcrypt missing',$msg);
return $text;
}
return trim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256,$salt, $text, MCRYPT_MODE_ECB,
mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND))));
}
function decrypt($text, $salt) {
if(!function_exists('mcrypt_encrypt') || !function_exists('mcrypt_decrypt'))
return $text;
return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $salt, base64_decode($text), MCRYPT_MODE_ECB,
mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)));
}
The "salt" (which is really the encryption key) is "randomly" generated at install time and stored in a source code file. If that happens to be missing, they use the md5 hash of the admin's email address.
# This is to support old installations. with no secret salt.
if(!defined('SECRET_SALT')) define('SECRET_SALT',md5(TABLE_PREFIX.ADMIN_EMAIL));
Here's how they generate the "salt." I shouldn't need to explain why this is wrong.
function randCode($len=8) {
return substr(strtoupper(base_convert(microtime(),10,16)),0,$len);
}
$configFile= str_replace('%CONFIG-SIRI',Misc::randcode(32),$configFile);
Lessons Learned:
- Fail safe. If your encryption library isn't available, fail hard, don't just return the plaintext.
- Use a cryptographically secure random number generator to generate secrets.
- Protect passwords by hashing them.