├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json ├── keepassphp-cli.php └── keepassphp ├── data └── .htaccess ├── keepassphp.php ├── lib ├── database.php ├── entry.php ├── filter.php ├── group.php ├── kdbxfile.php ├── kdbxheader.php ├── key.php ├── kphpdb.php └── protectedxmlreader.php ├── util ├── cipher.php ├── filemanager.php ├── gzdecode2.php ├── protectedstring.php ├── reader.php ├── salsa20stream.php └── uploadmanager.php └── vendor └── random_compat-2.0.10 └── lib ├── byte_safe_strings.php ├── cast_to_int.php ├── error_polyfill.php ├── random.php ├── random_bytes_com_dotnet.php ├── random_bytes_dev_urandom.php ├── random_bytes_libsodium.php ├── random_bytes_libsodium_legacy.php ├── random_bytes_mcrypt.php └── random_int.php /.gitignore: -------------------------------------------------------------------------------- 1 | /keepassphp/data/db 2 | /keepassphp/data/key 3 | /keepassphp/data/kphpdb 4 | sftp-config.json 5 | test.kdbx 6 | test.key -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 3 | 4 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | KeePassPHP 2 | ========== 3 | 4 | KeePassPHP is a pure-PHP library that can read password databases generated by the password manager KeePass 2.x (.kdbx files). It reads groups, entries and passwords, and makes them easily accessible through a simple PHP API. Its aim is not (yet) to completely replace KeePass: it cannot modify kdbx files, it can just read them. 5 | 6 | More generally, KeePassPHP can also encrypt and decrypt files in the same secure way as KeePass does (with integrity checks, brute-force-guessing protection, etc). 7 | 8 | Examples 9 | --------------------- 10 | 11 | See [KeePassPHP-UI](//github.com/shkdee/KeePassPHP-UI), a web user interface directly using KeePassPHP to list all entries of a kdbx file, and then extract selected specific passwords on demand. Otherwise, the file keepassphp-cli.php is a command-line interface for KeePassPHP that also shows how to use almost everything provided by this library. 12 | 13 | Library usages 14 | --------------------- 15 | KeePassPHP can be used at two levels. 16 | 17 | At low-level, KeePassPHP exposes an API to decrypt and encrypt content with the same secure format as KeePass. This content can be an XML-formatted KeePass password database, but also any other kind of data. So KeePassPHP can be used as a simple and strong file encryption/decryption library. It also exposes an API to read kdbx password database content as a tree of PHP objects, providing easy and natural access to every entry stored inside the kdbx file. 18 | 19 | At high-level, KeePassPHP can store kdbx files associated with a unique ID. This way, many users can store and access their password database easily, just with their ID and password. When used like this, KeePassPHP stores data internally in "kphpdb" files to associate each ID with a kdbx file and a possible key file, along with possibly more data to make it faster for users to access the list of entries of their kdbx files (see below). 20 | 21 | Internal kphpdb files 22 | --------------------- 23 | 24 | Decrypting a kdbx file is computationally expensive. Indeed, to prevent an attacker from guessing your main password through brute-force attack, KeePass repeatedly encrypts that password a great number of rounds, to make the whole decryption process artificially hard. Thus, finding the password by brute-force would take a very long time. 25 | 26 | But since PHP can be quite slow, decrypting a kdbx file only once can actually take a long time. Moreover, you may have to decrypt a single database twice just to find one password: a first time to get the list of all entries and display them to the user, and a second time to actually get the chosen password (that is typically what happens with KeePassPHP-UI). 27 | 28 | To try and fix this, KeePassPHP can extract from kdbx files a subset of the entries data, without passwords. This extracted, less-sensitive information is then encrypted again and stored alongside the actual kdbx file in what I called a kphpdb file. A kphpdb file is encrypted exactly like a kdbx file, but with a low number of encryption rounds, so that it is faster to decrypt. With this sytem, accessing the list of entries without passwords is fast; but getting a password is still as hard as before. kphpdb files are also used to store key files associated with kdbx files, when there are some. 29 | 30 | Security concerns 31 | --------------------- 32 | 33 | First of all, if you use KeePassPHP on a server that you access through the Internet, you obviously need to secure the communication pipe to that server, since your passwords will be sent back and forth inside that pipe. So you always need to use https to access KeePassPHP. 34 | 35 | Then, since your password database will be stored on that server, it must be hard to break into this server and get the kdbx file itself. Ideally, as hard as it is to do so for your computer, or wherever your kdbx file is also stored (maybe your phone, your dropbox account, etc). Now, if your server is correctly configured, this should actually be okay. 36 | 37 | Another security problem to be aware of is that when you access KeePassPHP on a device which is not yours (which is typically the kind of usage you may have of KeePassPHP), you never know how much you can trust this device. It can have a keylogger that could sniff your main password, it can register the passwords you will probably copy in the clipboard, etc. Never use KeePassPHP - or any other password manager for that matter - on a computer you cannot trust! 38 | 39 | Finally, the idea of kphpdb files that are faster to decrypt than the real kdbx files can actually destroy the protection against brute-force password guessing. Indeed, if the *same* password is used to encrypt a kdbx file and its corresponding kphpdb file, it will be as easy to brute-force-guess that password as it is for the kphpdb file, which is by design easier than for the kdbx file. If different passwords are used, there is no risk, but then the user has to remember another password and that's not the spirit of a password manager. With KeePassPHP, if you use kphpdb files, you can choose to either use a completely different password, or to use only half of the kdbx file password to encrypt the kphpdb file. In the latter case, the other half of the password would still be hard to brute-force-guess. 40 | 41 | Note that this last problem only regards main password guessing; an attacker can still try to decrypt the content of the kdbx file without guessing the text password, and the complexity of this task is unchanged by the kphpdb file. 42 | 43 | API 44 | --------------------- 45 | 46 | [to be documented; see examples] 47 | 48 | Requirements 49 | --------------------- 50 | The recommended environment to run KeePassPHP is PHP 5.4 and higher, with the rather common OpenSSL extension. 51 | 52 | KeePassPHP should actually work with PHP 5.3 and higher, and can use the common crypto library mcrypt when OpenSSL is not available (or when the PHP version is lower than 5.4). But OpenSSL is much faster and more reliable than mcrypt, so it is strongly suggested to load it when using KeePassPHP. Moreover, with PHP 7.2 and higher, mcrypt is no longer included in PHP by default, so OpenSSL is actually required in this case. 53 | 54 | Note that using very high-level crypto libraries like libsodium or NaCl is not possible for KeePassPHP (as I understand those libraries), because some very specific ciphers (AES 256, CBC and ECB) are required to decrypt the very specific format of kdbx files, and these ciphers are not exposed by those libraries. 55 | 56 | License 57 | --------------------- 58 | This work is MIT licensed. 59 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "keepassphp/keepassphp", 3 | "type": "library", 4 | "description": "KeePass password manager in PHP", 5 | "keywords": ["keepass","password manager"], 6 | "homepage": "https://github.com/shkdee/KeePassPHP", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Louis Traynard", 11 | "email": "louis.traynard@m4x.org", 12 | "homepage": "https://github.com/shkdee" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=5.3.0", 17 | "ext-hash": "*" 18 | }, 19 | "suggest": { 20 | "ext-openssl": "A better and faster alternative to mcrypt" 21 | }, 22 | "autoload": { 23 | "files": ["keepassphp/keepassphp.php"] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /keepassphp-cli.php: -------------------------------------------------------------------------------- 1 | [args...]", 6 | "\n\nPossible commands are:", 7 | "\n add [key file] [kphpdb password] Adds a database", 8 | "\n get [kphpdb password] Shows the content of a database", 9 | "\n pwd [kphpdb password] Gets a password", 10 | "\n rem Removes a database", 11 | "\n kdbx [key file] Decrypts a database file", 12 | "\n kdbx-pwd [key file] Gets a password from a database file", 13 | "\n encrypt [key file] Encrypts a file with the kdbx format", 14 | "\n decrypt [key file] Decrypts a file encrypted with encrypt"; 15 | die(); 16 | } 17 | 18 | function KPHPDebugAndDie($msg) 19 | { 20 | echo "\nError: $msg\n", "Debug data:\n", 21 | \KeePassPHP\KeePassPHP::$debugData, "\n"; 22 | die(); 23 | } 24 | 25 | function errorAndDie($msg) 26 | { 27 | echo "\nError: $msg\n"; 28 | die(); 29 | } 30 | 31 | function visitDatabase(\KeePassPHP\Database $db) 32 | { 33 | echo "Database '", $db->getName(), "'\n"; 34 | $groups = $db->getGroups(); 35 | if($groups == null) 36 | echo " (no groups)"; 37 | else 38 | { 39 | foreach($groups as &$g) 40 | visitGroup($g, 4); 41 | } 42 | } 43 | 44 | function visitGroup(\KeePassPHP\Group $group, $indent) 45 | { 46 | echo str_pad("", $indent, " "), "Group '", $group->name, "'\n"; 47 | if($group->groups != null) 48 | { 49 | foreach($group->groups as &$g) 50 | visitGroup($g, $indent + 4); 51 | } 52 | if($group->entries == null) 53 | echo str_pad("", $indent + 4, " "), "(no entries)\n"; 54 | else 55 | { 56 | foreach($group->entries as &$e) 57 | visitEntry($e, $indent + 4); 58 | } 59 | } 60 | 61 | function visitEntry(\KeePassPHP\Entry $entry, $indent) 62 | { 63 | echo str_pad("", $indent, " "), 64 | $entry->uuid, "\t => ", $entry->getStringField(\KeePassPHP\Database::KEY_TITLE), 65 | "\t", $entry->getStringField(\KeePassPHP\Database::KEY_USERNAME), 66 | "\t", $entry->getStringField(\KeePassPHP\Database::KEY_URL), "\n"; 67 | } 68 | 69 | $count = isset($argc) ? intval($argc) : 0; 70 | if($count < 2) 71 | usageAndDie(); 72 | 73 | // load classes 74 | require_once "keepassphp/keepassphp.php"; 75 | use \KeePassPHP\KeePassPHP as KeePassPHP; 76 | 77 | // configuration 78 | $debugMode = true; 79 | 80 | // execute command 81 | $command = $argv[1]; 82 | 83 | if($command == "add") 84 | { 85 | if($count < 5) 86 | usageAndDie(); 87 | 88 | // initialize KeePassPHP 89 | if(!KeePassPHP::init(null, $debugMode)) 90 | KPHPDebugAndDie("Initialization failed."); 91 | 92 | $dbid = $argv[2]; 93 | $file = $argv[3]; 94 | $pwd = $argv[4]; 95 | $keyfile = $count >= 6 ? $argv[5] : null; 96 | $kphpdbPwd = $count >= 7 ? $argv[6] : null; 97 | if(empty($kphpdbPwd)) 98 | $kphpdbPwd = KeePassPHP::extractHalfPassword($pwd); 99 | 100 | if(KeePassPHP::existsKphpDB($dbid)) 101 | { 102 | if(!KeePassPHP::removeDatabase($dbid, $kphpdbPwd)) 103 | KPHPDebugAndDie("Database '" . $dbid . 104 | "' already exists and cannot be deleted."); 105 | } 106 | 107 | if(!KeePassPHP::addDatabaseFromFiles($dbid, $file, $pwd, $keyfile, 108 | $kphpdbPwd, true)) 109 | KPHPDebugAndDie("Cannot add database '" . $dbid . "'."); 110 | echo "Database '", $dbid, "' added successfully."; 111 | } 112 | 113 | else if($command == "get" || $command == "pwd") 114 | { 115 | $offset = $command == "pwd" ? 1 : 0; 116 | if($count < 4 + $offset) 117 | usageAndDie(); 118 | 119 | // initialize KeePassPHP 120 | if(!KeePassPHP::init(null, $debugMode)) 121 | KPHPDebugAndDie("Initialization failed."); 122 | 123 | $dbid = $argv[2]; 124 | $pwd = $argv[3 + $offset]; 125 | $kphpdbPwd = $count >= 5 + $offset ? $argv[4 + $offset] : null; 126 | if(empty($kphpdbPwd)) 127 | $kphpdbPwd = KeePassPHP::extractHalfPassword($pwd); 128 | 129 | $db = KeePassPHP::getDatabase($dbid, $kphpdbPwd, $pwd, $command == "pwd"); 130 | if($db == null) 131 | KPHPDebugAndDie("Cannot get database '" . $dbid . "'."); 132 | 133 | if($command == "pwd") 134 | { 135 | $r = $db->getPassword($argv[3]); 136 | if($r == null) 137 | echo "entry '", $argv[3], "' not found!"; 138 | else 139 | echo $r; 140 | } 141 | else 142 | visitDatabase($db); 143 | } 144 | 145 | else if($command == "rem") 146 | { 147 | if($count < 4) 148 | usageAndDie(); 149 | 150 | // initialize KeePassPHP 151 | if(!KeePassPHP::init(null, $debugMode)) 152 | KPHPDebugAndDie("Initialization failed."); 153 | 154 | $dbid = $argv[2]; 155 | $pwd = $argv[3]; 156 | 157 | if(KeePassPHP::removeDatabase($dbid, $pwd)) 158 | echo "Database '", $dbid, "' successfully removed."; 159 | else 160 | KPHPDebugAndDie("Cannot remove database '" . $dbid . "'."); 161 | } 162 | 163 | else if($command == "kdbx" || $command == "kdbx-pwd") 164 | { 165 | // no need to initialize KeePassPHP here, since 166 | // we're only using low-level API. 167 | 168 | $offset = $command == "kdbx-pwd" ? 1 : 0; 169 | if($count < 4 + $offset) 170 | usageAndDie(); 171 | 172 | $file = $argv[2 + $offset]; 173 | $pwd = $argv[3 + $offset]; 174 | $keyfile = $count >= 5 + $offset ? $argv[4 + $offset] : null; 175 | 176 | $ckey = KeePassPHP::masterKey(); 177 | KeePassPHP::addPassword($ckey, $pwd); 178 | if(!KeePassPHP::addKeyFile($ckey, $keyfile)) 179 | errorAndDie("file key parsing error."); 180 | 181 | // The loading may take some time if your database is hard to decrypt. 182 | $error = null; 183 | $db = KeePassPHP::openDatabaseFile($file, $ckey, $error); 184 | if($db == null) 185 | errorAndDie($error); 186 | 187 | if($command == "kdbx-pwd") 188 | { 189 | $r = $db->getPassword($argv[2]); 190 | if($r == null) 191 | echo "entry '", $argv[2], "' not found!"; 192 | else 193 | echo $r; 194 | } 195 | else 196 | visitDatabase($db); 197 | } 198 | 199 | else if($command == "encrypt" || $command == "decrypt") 200 | { 201 | if($count < 4) 202 | usageAndDie(); 203 | 204 | $fileContent = file_get_contents($argv[2]); 205 | if(!$fileContent) 206 | errrorAndDie("Cannot open file '" . $argv[2] . "'"); 207 | $pwd = $argv[3]; 208 | $keyfile = $count >= 5 ? $argv[4] : null; 209 | 210 | $ckey = KeePassPHP::masterKey(); 211 | KeePassPHP::addPassword($ckey, $pwd); 212 | if(!KeePassPHP::addKeyFile($ckey, $keyfile)) 213 | errorAndDie("file key parsing error."); 214 | 215 | $error = null; 216 | $result = $command == "encrypt" 217 | ? KeePassPHP::encryptInKdbx($fileContent, $ckey, 6000, $error) 218 | : KeePassPHP::decryptFromKdbx($fileContent, $ckey, true, $error); 219 | if($result === null) 220 | KPHPDebugAndDie($error); 221 | echo $result; 222 | } 223 | 224 | else 225 | { 226 | echo "\nUnkown command '", $command, "'.\n"; 227 | usageAndDie(); 228 | } 229 | ?> -------------------------------------------------------------------------------- /keepassphp/data/.htaccess: -------------------------------------------------------------------------------- 1 | Order deny,allow 2 | Deny from all -------------------------------------------------------------------------------- /keepassphp/keepassphp.php: -------------------------------------------------------------------------------- 1 | 24 | * @copyright Louis Traynard 25 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 26 | * @link https://github.com/shkdee/KeePassPHP 27 | */ 28 | 29 | namespace KeePassPHP; 30 | 31 | class KeePassPHPException extends \Exception 32 | { 33 | public function __construct($message) 34 | { 35 | parent::__construct($message); 36 | } 37 | } 38 | 39 | /* 40 | * Here we include everything, we could use an autoload in the future but the 41 | * fact is that from the moment we want to decrypt a KeePass database, we do 42 | * need almost everything, so there is not much difference. The important point 43 | * is to include keepassphp.php in client applications only when we are sure we 44 | * will try to add or get a database file. 45 | */ 46 | 47 | require_once "vendor/random_compat-2.0.10/lib/random.php"; 48 | 49 | require_once "util/cipher.php"; 50 | require_once "util/reader.php"; 51 | require_once "util/gzdecode2.php"; 52 | require_once "util/salsa20stream.php"; 53 | require_once "util/filemanager.php"; 54 | require_once "util/protectedstring.php"; 55 | 56 | require_once "lib/protectedxmlreader.php"; 57 | require_once "lib/key.php"; 58 | require_once "lib/filter.php"; 59 | require_once "lib/kdbxheader.php"; 60 | require_once "lib/kdbxfile.php"; 61 | require_once "lib/database.php"; 62 | require_once "lib/group.php"; 63 | require_once "lib/entry.php"; 64 | require_once "lib/kphpdb.php"; 65 | 66 | /** 67 | * Main entry point of the KeePassPHP application. 68 | * Exposes the high-level API of KeePassPHP. 69 | */ 70 | abstract class KeePassPHP 71 | { 72 | static public $debugData = ""; 73 | static public $debug = false; 74 | 75 | static private $_started = false; 76 | static private $_kphpdbManager = null; 77 | static private $_databaseManager = null; 78 | static private $_keyManager = null; 79 | 80 | const PREFIX_KPHPDB = "kphpdb"; 81 | const PREFIX_DATABASE = "db"; 82 | const EXT_KDBX = "kdbx"; 83 | const PREFEXT_KEY = "key"; 84 | 85 | const DEFAULT_DATA_DIR = "data/"; 86 | 87 | const DIR_DATABASE = "db/"; 88 | const DIR_KPHPDB = "kphpdb/"; 89 | const DIR_KEY = "key/"; 90 | 91 | const IV_SIZE = 32; 92 | const DBTYPE_KDBX = 1; 93 | const KEY_PWD = 1; 94 | const KEY_FILE = 2; 95 | 96 | const IDX_DBTYPE = 0; 97 | const IDX_HASHNAME = 1; 98 | const IDX_KEYS = 2; 99 | const IDX_WRITEABLE = 3; 100 | const IDX_ENTRIES = 4; 101 | const IDX_COUNT = 5; 102 | 103 | /** 104 | * The version of the API exposed by this version of KeePassPHP. 105 | */ 106 | const API_VERSION = 1; 107 | 108 | /** 109 | * Starts the KeePassPHP application. This method must be called before all 110 | * high-level methods of this class. If $debug is true, debug data will be 111 | * added to the static variable self::$debugData (especially when an error 112 | * occurs). 113 | * @param $dataDir Relative path to the KeePassPHP data directory. If null, 114 | * the default directory ./data/ is used. 115 | * @param $debug True to enable debug mode, false otherwise. 116 | */ 117 | public static function init($dataDir = null, $debug = false) 118 | { 119 | if(self::$_started) 120 | return true; 121 | 122 | self::$debug = $debug; 123 | 124 | if(!defined("PHP_VERSION_ID") || PHP_VERSION_ID < 50300) 125 | { 126 | self::addDebug("PHP version must be >= 5.3 to run KeePassPHP."); 127 | return false; 128 | } 129 | if(!extension_loaded("hash")) 130 | { 131 | self::addDebug("hash must be loaded to use KeePassPHP."); 132 | return false; 133 | } 134 | if(PHP_VERSION_ID >= 50400 && extension_loaded("openssl")) 135 | { 136 | self::addDebug("KeePassPHP will use the OpenSSL extension."); 137 | } 138 | else if(!extension_loaded("mcrypt")) 139 | { 140 | self::addDebug("No suitable cryptography extension found."); 141 | return false; 142 | } 143 | else if(!defined("MCRYPT_RIJNDAEL_128")) 144 | { 145 | self::addDebug("Rijndael 128 is not supported by your libmcrypt (it is probably too old)."); 146 | return false; 147 | } 148 | else 149 | { 150 | self::addDebug("KeePassPHP will use the Mcrypt extension."); 151 | if(PHP_VERSION_ID >= 70100) 152 | { 153 | self::addDebug("The Mcrypt extension is deprecated since PHP 7.1. KeePassPHP will use it anyway, but consider installing OpenSSL."); 154 | } 155 | } 156 | 157 | if($dataDir === null) 158 | $dataDir = dirname(__FILE__) . '/' . self::DEFAULT_DATA_DIR; 159 | else 160 | $dataDir = rtrim($dataDir, '/') . '/'; 161 | 162 | self::$_kphpdbManager = new FileManager( 163 | $dataDir . self::DIR_KPHPDB, self::PREFIX_KPHPDB, true, false); 164 | self::$_databaseManager = new FileManager( 165 | $dataDir . self::DIR_DATABASE, self::PREFIX_DATABASE, true, false); 166 | self::$_keyManager = new FileManager( 167 | $dataDir . self::DIR_KEY, self::PREFEXT_KEY, true, false); 168 | 169 | self::$_started = true; 170 | self::addDebug("KeePassPHP application started."); 171 | return true; 172 | } 173 | 174 | /**************************** 175 | * Debug and error handling * 176 | ****************************/ 177 | 178 | /** 179 | * Adds $e to the debug data if debug mode is on. 180 | * @param $e An exception. 181 | */ 182 | public static function raiseError(\Exception $e) 183 | { 184 | if(self::$debug) 185 | self::$debugData .= "Exception at " . basename($e->getFile()) . ":" 186 | . $e->getLine() . ": " . self::makePrintable($e->getMessage()) 187 | . "\n"; 188 | } 189 | 190 | /** 191 | * Adds $msg to the debug data if debug mode is on. 192 | * @param $msg A printable string. 193 | */ 194 | public static function addDebug($msg) 195 | { 196 | if(self::$debug) 197 | self::$debugData .= self::makePrintable($msg) . "\n"; 198 | } 199 | 200 | /** 201 | * Adds $msg, then $bin in hexadecimal, to the debug data. 202 | * @param $msg A printable string. 203 | * @param $bin A binary string. 204 | */ 205 | public static function addDebugHexa($msg, $bin) 206 | { 207 | if(self::$debug) 208 | self::$debugData .= self::makePrintable($msg) . ": " . 209 | self::strToHex($bin) . "\n"; 210 | } 211 | 212 | /** 213 | * Adds $msg, then $var (with print_r), to the debug data. 214 | * @param $msg A printable stirng. 215 | * @param $var An object or a value. 216 | */ 217 | public static function addVar($msg, $var) 218 | { 219 | if(self::$debug) 220 | { 221 | self::$debugData .= self::makePrintable($msg) . ": " . 222 | self::makePrintable(print_r($var, true)) . "\n"; 223 | } 224 | } 225 | 226 | /************************** 227 | * Util string operations * 228 | **************************/ 229 | 230 | /** 231 | * Computes the hexadecimal form of the bytes of $str. 232 | * @param $str A string. 233 | * @return A string of all hexadecimal codes of bytes of $str. 234 | */ 235 | public static function strToHex($str) 236 | { 237 | $r = ""; 238 | $l = strlen($str); 239 | for($i = 0; $i < $l; $i++) 240 | $r .= str_pad(strtoupper(dechex(ord($str[$i]))), 2, " ", 241 | STR_PAD_LEFT) . " "; 242 | return $r; 243 | } 244 | 245 | /** 246 | * Makes the string $s HTML-printable. 247 | * @param $s An UTF-8 string. 248 | * @return A sanitized string. 249 | */ 250 | public static function makePrintable($s) 251 | { 252 | return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); 253 | } 254 | 255 | /** 256 | * Extracts a subpart of the input string if it long enough. 257 | * @param $pwd A password, that should be longer than 4 characters. 258 | * @return A subpart of the input string. 259 | */ 260 | public static function extractHalfPassword($pwd) 261 | { 262 | $l = strlen($pwd); 263 | if($l < 4) 264 | return $pwd; 265 | else 266 | return substr($pwd, 0, intval(floor($l/2))); 267 | } 268 | 269 | /********************************** 270 | * High-level database management * 271 | **********************************/ 272 | 273 | /** 274 | * Tries to decrypts the database of id $dbid, and extracts the password 275 | * of the entry whose uuid is $uuid from it. 276 | * @param $dbid A database ID. 277 | * @param $kphpdbPwd The password of the KphpDB file. 278 | * @param $dbPwd The password of the database file. 279 | * @param $uuid An entry uuid in base64. 280 | * @return The password as a string, or null in case of error. 281 | */ 282 | public static function getPassword($dbid, $kphpdbPwd, $dbPwd, $uuid) 283 | { 284 | $db = self::getDatabase($dbid, $kphpdbPwd, $dbPwd, true); 285 | return $db == null ? null : $db->getPassword($uuid); 286 | } 287 | 288 | /** 289 | * Gets the database of id $dbid from the internal KeePassPHP database. 290 | * It may be a cached version with less data (e.g no passwords), but 291 | * cheaper to decrypt. 292 | * @param $dbid A database ID. 293 | * @param $kphpdbPwd The password of the KphpDB file. 294 | * @param $dbPwd The password of the database file (may be unused if a 295 | * cached version exists and if $full is false). 296 | * @param $full Whether the real, full database file must be returned. 297 | * Otherwise, the cached version will be returned if it exists. 298 | * @return A Database instance, or null if an error occured. 299 | */ 300 | public static function getDatabase($dbid, $kphpdbPwd, $dbPwd, $full) 301 | { 302 | if(!self::$_started) 303 | { 304 | self::addDebug("KeepassPHP is not started!"); 305 | return null; 306 | } 307 | 308 | try 309 | { 310 | $kphpdb = self::openKphpDB($dbid, $kphpdbPwd); 311 | 312 | $db = null; 313 | if(!$full && $kphpdb->getDBType() == KphpDB::DBTYPE_KDBX) 314 | { 315 | $db = $kphpdb->getDB(); 316 | if($db != null) 317 | return $db; 318 | } 319 | 320 | $dbContent = self::$_databaseManager->getContent( 321 | $kphpdb->getDBFileHash()); 322 | if(empty($dbContent)) 323 | throw new KeePassPHPException("Database file not found (hash = " 324 | . $kphpdb->getDBFileHash() . ")"); 325 | 326 | $rawKey = null; 327 | $keyFileHash = $kphpdb->getKeyFileHash(); 328 | if(!empty($keyFileHash)) 329 | { 330 | $rawKey = self::$_keyManager->getContent($keyFileHash); 331 | if($rawKey == null) 332 | throw new KeePassPHPException("Key file not found (ID = " 333 | . $dbid . ")"); 334 | } 335 | return self::openDatabase($dbContent, $dbPwd, $rawKey); 336 | } 337 | catch(KeePassPHPException $exception) 338 | { 339 | self::raiseError($exception); 340 | return null; 341 | } 342 | } 343 | 344 | /** 345 | * Adds a database to the internal KeePassPHP database, with the id $dbid. 346 | * $dbid must not exist already. A cached version with less data (e.g no 347 | * passwords), but cheaper to decrypt, may be stored as well. 348 | * @param $dbid A database ID. 349 | * @param $dbFile The path of the KeePass database file. 350 | * @param $dbPwd The password of the database file. 351 | * @param $dbKeyFile The path of a key file for the database file, if 352 | * applicable (use null otherwise). 353 | * @param $kphpdbPwd A password to use to create the KphpDB file. 354 | * @param $cache Whether to also create a cached version cheaper to decrypt 355 | * (using $filter to select the data stored in it). 356 | * @param $filter A filter to select what is stored in the cached database 357 | * (if null, it will store everything except from passwords). 358 | * @return true in case of success, false otherwise. 359 | */ 360 | public static function addDatabaseFromFiles($dbid, $dbFile, $dbPwd, 361 | $dbKeyFile, $kphpdbPwd, $cache, iFilter $filter = null) 362 | { 363 | if(empty($dbFile)) 364 | { 365 | self::raiseError(new KeePassPHPException('$dbFile is empty')); 366 | return false; 367 | } 368 | return self::addDatabase($dbid, file_get_contents($dbFile), $dbPwd, 369 | empty($dbKeyFile) ? null : file_get_contents($dbKeyFile), 370 | $kphpdbPwd, $cache, $filter); 371 | } 372 | 373 | /** 374 | * Adds a database to the internal KeePassPHP database, with the id $dbid. 375 | * $dbid must not exist already. A cached version with less data (e.g no 376 | * passwords), but cheaper to decrypt, may be stored as well. 377 | * @param $dbid A database ID. 378 | * @param $dbContent The content of the KeePass database file, as a string. 379 | * @param $dbPwd The password of the database file. 380 | * @param $dbKeyContent The content of a key file for the database file, as 381 | * a string (if applicable; use null otherwise). 382 | * @param $kphpdbPwd A password to use to create the KphpDB file. 383 | * @param $cache Whether to also create a cached version cheaper to decrypt 384 | * (using $filter to select the data stored in it). 385 | * @param $filter A filter to select what is stored in the cached database 386 | * (if null, it will store everything except from passwords). 387 | * @return true in case of success, false otherwise. 388 | */ 389 | public static function addDatabase($dbid, $dbContent, $dbPwd, 390 | $dbKeyContent, $kphpdbPwd, $cache, iFilter $filter = null) 391 | { 392 | if(!self::$_started) 393 | { 394 | self::addDebug("KeepassPHP is not started!"); 395 | return false; 396 | } 397 | 398 | $dbHash = null; 399 | $keyFileHash = null; 400 | try 401 | { 402 | if(self::$_kphpdbManager->existsKey($dbid)) 403 | throw new KeePassPHPException("ID already exists."); 404 | 405 | // check that the database being added can be opened 406 | $db = self::openDatabase($dbContent, $dbPwd, $dbKeyContent); 407 | // add it to the db manager 408 | $dbHash = self::$_databaseManager->addWithKey(random_bytes(32), 409 | $dbContent, self::EXT_KDBX, true, true); 410 | if($dbHash == null) 411 | throw new KeePassPHPException("Database file writing failed."); 412 | 413 | // try to add the key file if it exists 414 | if(!empty($dbKeyContent)) 415 | { 416 | $keyFileHash = self::$_keyManager->addWithKey(random_bytes(32), 417 | $dbKeyContent, self::PREFEXT_KEY, true, true); 418 | if($keyFileHash == null) 419 | throw new KeePassPHPException("Key file writing failed."); 420 | } 421 | 422 | // build the KphpDB instance 423 | $kphpdb = $cache 424 | ? KphpDB::createFromDatabase($db, $dbHash, $keyFileHash) 425 | : KphpDB::createEmpty($dbHash, $keyFileHash); 426 | $error = null; 427 | $kphpdbContent = $kphpdb->toKdbx( 428 | new KeyFromPassword($kphpdbPwd, KdbxFile::HASH), 429 | $filter, $error); 430 | if($kphpdbContent == null) 431 | throw new KeePassPHPException($error); 432 | 433 | // add the KphpDB instance 434 | $dbidHash = self::$_kphpdbManager->addWithKey($dbid, 435 | $kphpdbContent, self::EXT_KDBX, true, true); 436 | if($dbidHash == null) 437 | throw new KeePassPHPException("KphpDB file writing failed."); 438 | return true; 439 | } 440 | catch(KeePassPHPException $exception) 441 | { 442 | if($dbHash != null) 443 | { 444 | if(!self::$_databaseManager->remove($dbHash)) 445 | self::addDebug("Cannot delete database '" . $dbHash ."'."); 446 | } 447 | if($keyFileHash != null) 448 | { 449 | if(!self::$_keyManager->remove($keyFileHash)) 450 | self::addDebug("Cannot delete key file '" . 451 | $keyFileHash . "'."); 452 | } 453 | self::raiseError($exception); 454 | return false; 455 | } 456 | } 457 | 458 | /** 459 | * Removes the database of id $dbid from the internal KeePassPHP database. 460 | * @param $dbid A database ID. 461 | * @param $kphpdbPwd The password of the KphpDB file. 462 | * @return true if the database $dbid existed and could be removed, 463 | * false otherwise. 464 | */ 465 | public static function removeDatabase($dbid, $kphpdbPwd) 466 | { 467 | if(!self::$_started) 468 | { 469 | self::addDebug("KeepassPHP is not started!"); 470 | return false; 471 | } 472 | 473 | try 474 | { 475 | $kphpdb = self::openKphpDB($dbid, $kphpdbPwd); 476 | $hash = $kphpdb->getDBFileHash(); 477 | if($hash !== null) 478 | { 479 | if(!self::$_databaseManager->remove($hash)) 480 | self::addDebug("Cannot delete database '" . $hash . "'."); 481 | } 482 | $hash = $kphpdb->getKeyFileHash(); 483 | if($hash !== null) 484 | { 485 | if(!self::$_keyManager->remove($hash)) 486 | self::addDebug("Cannot delete key file '" . $hash . "'."); 487 | } 488 | return self::$_kphpdbManager->removeFromKey($dbid); 489 | } 490 | catch(KeePassPHPException $exception) 491 | { 492 | self::raiseError($exception); 493 | return false; 494 | } 495 | } 496 | 497 | /** 498 | * Checks whether a database of id $dbid exists in the internal KeePassPHP 499 | * database. 500 | * @param $dbid A database ID. 501 | * @return true if $dbid exists, false otherwise. 502 | */ 503 | public static function existsKphpDB($dbid) 504 | { 505 | if(!self::$_started) 506 | { 507 | self::addDebug("KeepassPHP is not started!"); 508 | return false; 509 | } 510 | return self::$_kphpdbManager->existsKey($dbid); 511 | } 512 | 513 | /** 514 | * Checks whether the internal KeePassPHP password for the id $dbid is 515 | * $kphpdbPwd. 516 | * @param $dbid A database ID. 517 | * @param $kphpdbPwd The password of the KphpDB file. 518 | * @return true $kphpdbPwd is the password for the id $dbid. 519 | */ 520 | public static function checkKphpDBPassword($dbid, $kphpdbPwd) 521 | { 522 | if(!self::$_started) 523 | { 524 | self::addDebug("KeepassPHP is not started!"); 525 | return false; 526 | } 527 | try 528 | { 529 | $kphpdb = self::openKphpDB($dbid, $kphpdbPwd); 530 | return true; 531 | } 532 | catch(KeePassPHPException $e) 533 | { 534 | self::raiseError($e); 535 | return false; 536 | } 537 | } 538 | 539 | 540 | /*************************** 541 | * Low-level API shortcuts * 542 | ***************************/ 543 | 544 | /** 545 | * Creates a KeePass master key. 546 | * @return A new CompositeKey instance. 547 | */ 548 | public static function masterKey() 549 | { 550 | return new CompositeKey(KdbxFile::HASH); 551 | } 552 | 553 | /** 554 | * Creates a key from a password. 555 | * @param $pwd A text password. 556 | * @return A new KeyFromPassword instance. 557 | */ 558 | public static function keyFromPassword($pwd) 559 | { 560 | return new KeyFromPassword($pwd, KdbxFile::HASH); 561 | } 562 | 563 | /** 564 | * Adds a password to a master key. 565 | * @param $mkey A master key. 566 | * @param $pwd A text password. 567 | * @return true if the operation succeeded, false otherwise. 568 | */ 569 | public static function addPassword(CompositeKey $mkey, $pwd) 570 | { 571 | $mkey->addKey(self::keyFromPassword($pwd)); 572 | return true; 573 | } 574 | 575 | /** 576 | * Adds a file key to a master key. 577 | * @param $mkey A master key. 578 | * @param $file The path of a file key. 579 | * @return true if the operation succeeded, false otherwise. 580 | */ 581 | public static function addKeyFile(CompositeKey $mkey, $file) 582 | { 583 | if(empty($file)) 584 | return true; 585 | $k = new KeyFromFile(file_get_contents($file)); 586 | if(!$k->isParsed) 587 | return false; 588 | $mkey->addKey($k); 589 | return true; 590 | } 591 | 592 | /** 593 | * Opens a KeePass password database (.kdbx) file with the key $mkey. 594 | * @param $file The path of a KeePass password database file. 595 | * @param $mkey A master key. 596 | * @param &$error A string that will receive a message in case of error. 597 | * @return A new Database instance, or null in case of error. 598 | */ 599 | public static function openDatabaseFile($file, iKey $mkey, &$error) 600 | { 601 | $reader = ResourceReader::openFile($file); 602 | if($reader == null) 603 | { 604 | $error = "file '" . $file . '" does not exist.'; 605 | return null; 606 | } 607 | $db = Database::loadFromKdbx($reader, $mkey, $error); 608 | $reader->close(); 609 | return $db; 610 | } 611 | 612 | /** 613 | * Embedds a string into a new kdbx file with the key $key, using $rounds 614 | * rounds of encryption. Use the method decryptFromKdbx() on the result 615 | * with the same key to get back $content from the kdbx file. 616 | * @param $content A string that will be embedded. 617 | * @param $key An iKey instance. 618 | * @param $rounds An integer. 619 | * @param &$error A string that will receive a message in case of error. 620 | * @return The content of the new kdbx file, or null in case of error. 621 | */ 622 | public static function encryptInKdbx($content, iKey $key, $rounds, &$error) 623 | { 624 | $kdbx = KdbxFile::createForEncrypting($rounds, $error); 625 | if($kdbx == null) 626 | return null; 627 | return $kdbx->encrypt($kdbx->getHeaderHash() . $content, $key, $error); 628 | } 629 | 630 | /** 631 | * Extracts a string embedded in a kdbx file, decrypting it with the key 632 | * $key. 633 | * @param $content The content of the kdbx file, as a string. 634 | * @param $key An iKey instance. 635 | * @param $headerHash true if the header hash is prepended to the decrypted 636 | * content (use true if the kdbx file was created with 637 | * the metod encryptInKdbx()). 638 | * @param &$error A string that will receive a message in case of error. 639 | * @return The decrypted embedded string, or null in case of error. 640 | */ 641 | public static function decryptFromKdbx($content, iKey $key, $headerHash, 642 | &$error) 643 | { 644 | $reader = new StringReader($content); 645 | $result = KdbxFile::decrypt($reader, $key, $error); 646 | $reader->close(); 647 | if($result === null) 648 | return null; 649 | $content = $result->getContent(); 650 | if($headerHash) 651 | { 652 | $hash = $result->getHeaderHash(); 653 | $hashLen = strlen($hash); 654 | if(strlen($content) < $hashLen || 655 | substr($content, 0, $hashLen) != $hash) 656 | { 657 | $error = "Kdbx file decrypt: header hash is not correct."; 658 | return null; 659 | } 660 | $content = substr($content, $hashLen); 661 | } 662 | return $content; 663 | } 664 | 665 | /********************* 666 | * private functions * 667 | *********************/ 668 | 669 | /** 670 | * Opens the database $dbContent with the keys $dbPwd and $dbKeyContent. 671 | * @param $dbContent The content of a KeePass database file. 672 | * @param $dbPwd A text password for the database file. 673 | * @param $dbKeyContent A possible key file for the database file. 674 | * @return A non-null Database instance. 675 | * @throws KeePassPHPException 676 | */ 677 | private static function openDatabase($dbContent, $dbPwd, $dbKeyContent) 678 | { 679 | $ckey = new CompositeKey(KdbxFile::HASH); 680 | $ckey->addKey(new KeyFromPassword($dbPwd, KdbxFile::HASH)); 681 | if(!empty($dbKeyContent)) 682 | { 683 | $fileKey = new KeyFromFile($dbKeyContent); 684 | if(!$fileKey->isParsed) 685 | throw new KeePassPHPException("key file parsing failure"); 686 | $ckey->addKey($fileKey); 687 | } 688 | 689 | $error = null; 690 | $reader = new StringReader($dbContent); 691 | $db = Database::loadFromKdbx($reader, $ckey, $error); 692 | $reader->close(); 693 | if($db == null) 694 | throw new KeePassPHPException($error); 695 | return $db; 696 | } 697 | 698 | /** 699 | * Opens the KphpDB file of id $dbid with the password $kphpdbPwd. 700 | * @param $dbid A database ID. 701 | * @param $kphpdbPwd The password of the KphpDB file. 702 | * @return A non-null KpbpDB instance. 703 | * @throws KeePassPHPException 704 | */ 705 | private static function openKphpDB($dbid, $kphpdbPwd) 706 | { 707 | $kphpdbFile = self::$_kphpdbManager->getContentFromKey($dbid); 708 | if($kphpdbFile == null) 709 | throw new KeePassPHPException( 710 | "KphpDB file not found or void (ID = " . $dbid . ")."); 711 | $error = null; 712 | $reader = new StringReader($kphpdbFile); 713 | $kphpdb = KphpDB::loadFromKdbx($reader, 714 | new KeyFromPassword($kphpdbPwd, KdbxFile::HASH), $error); 715 | $reader->close(); 716 | if($kphpdb == null) 717 | throw new KeePassPHPException($error); 718 | return $kphpdb; 719 | } 720 | } 721 | 722 | ?> -------------------------------------------------------------------------------- /keepassphp/lib/database.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Louis Traynard 11 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 12 | * @link https://github.com/shkdee/KeePassPHP 13 | */ 14 | class Database 15 | { 16 | const XML_KEEPASSFILE = "KeePassFile"; 17 | const XML_META = "Meta"; 18 | const XML_HEADERHASH = "HeaderHash"; 19 | const XML_DATABASENAME = "DatabaseName"; 20 | const XML_CUSTOMICONS = "CustomIcons"; 21 | const XML_ICON = "Icon"; 22 | const XML_UUID = "UUID"; 23 | const XML_DATA = "Data"; 24 | const XML_ROOT = "Root"; 25 | const XML_GROUP = "Group"; 26 | const XML_ENTRY = "Entry"; 27 | const XML_NAME = "Name"; 28 | const XML_ICONID = "IconID"; 29 | const XML_CUSTOMICONUUID = "CustomIconUUID"; 30 | const XML_STRING = "String"; 31 | const XML_STRING_KEY = "Key"; 32 | const XML_STRING_VALUE = "Value"; 33 | const XML_HISTORY = "History"; 34 | const XML_TAGS = "Tags"; 35 | 36 | const KEY_PASSWORD = "Password"; 37 | const KEY_STRINGFIELDS = "StringFields"; 38 | const KEY_TITLE = "Title"; 39 | const KEY_USERNAME = "UserName"; 40 | const KEY_URL = "URL"; 41 | 42 | const GROUPS = "Groups"; 43 | const ENTRIES = "Entries"; 44 | 45 | private $_name; 46 | private $_groups; 47 | /** Associative array (icon uuid in base64 => icon data in base64) keeping 48 | * the data of all custom icons. */ 49 | private $_customIcons; 50 | /** Header hash registered in this database. */ 51 | private $_headerHash; 52 | 53 | private function __construct() 54 | { 55 | $this->_name = null; 56 | $this->_groups = null; 57 | $this->_customIcons = null; 58 | $this->_headerHash = null; 59 | } 60 | 61 | /** 62 | * Gets the name of this database. 63 | * @return This database name. 64 | */ 65 | public function getName() 66 | { 67 | return $this->_name; 68 | } 69 | 70 | /** 71 | * Gets the groups of this database. 72 | * @return An array of Group instances. 73 | */ 74 | public function getGroups() 75 | { 76 | return $this->_groups; 77 | } 78 | 79 | /** 80 | * Gets the data of the custom icon whose uuid is $uuid. 81 | * @param $uuid A custom icon uuid in base64. 82 | * @return A custom icon data in base64 if it exists, null otherwise. 83 | */ 84 | public function getCustomIcon($uuid) 85 | { 86 | return $this->_customIcons == null ? null 87 | : "data:image/png;base64," . $this->_customIcons[$uuid]; 88 | } 89 | 90 | /** 91 | * Gets the password of the entry whose uuid is $uuid. 92 | * @param $uuid An entry uuid in base64. 93 | * @return The decrypted password if the entry exists, null otherwise. 94 | */ 95 | public function getPassword($uuid) 96 | { 97 | if($this->_groups != null) 98 | { 99 | foreach($this->_groups as &$group) 100 | { 101 | $value = $group->getPassword($uuid); 102 | if($value != null) 103 | return $value->getPlainString(); 104 | } 105 | } 106 | return null; 107 | } 108 | 109 | /** 110 | * Parses a custom icon XML element node, and adds the result to the 111 | * $customIcons array. 112 | * @param $reader A ProtectedXMLReader instance located at a custom icon 113 | * element node. 114 | */ 115 | private function parseCustomIcon(ProtectedXMLReader $reader) 116 | { 117 | $uuid = null; 118 | $data = null; 119 | $d = $reader->depth(); 120 | while($reader->read($d)) 121 | { 122 | if($reader->isElement(self::XML_UUID)) 123 | $uuid = $reader->readTextInside(); 124 | elseif($reader->isElement(self::XML_DATA)) 125 | $data = $reader->readTextInside(); 126 | } 127 | if(!empty($uuid) && !empty($data)) 128 | { 129 | if($this->_customIcons == null) 130 | $this->_customIcons = array(); 131 | $this->_customIcons[$uuid] = $data; 132 | } 133 | } 134 | 135 | /** 136 | * Adds a Group instance to this Database. 137 | * @param $entry A Group instance, possibly null (it is then ignored). 138 | */ 139 | private function addGroup($group) 140 | { 141 | if($group != null) 142 | { 143 | if($this->_groups == null) 144 | $this->_groups = array(); 145 | $this->_groups[] = $group; 146 | } 147 | } 148 | 149 | /** 150 | * Loads the content of a Database from a ProtectedXMLReader instance 151 | * reading a KeePass 2.x database and located at a KeePass file element 152 | * node. 153 | * @param $reader A XML reader. 154 | */ 155 | private function parseXML(ProtectedXMLReader $reader) 156 | { 157 | $d = $reader->depth(); 158 | while($reader->read($d)) 159 | { 160 | if($reader->isElement(self::XML_META)) 161 | { 162 | $metaD = $reader->depth(); 163 | while($reader->read($metaD)) 164 | { 165 | if($reader->isElement(self::XML_HEADERHASH)) 166 | $this->_headerHash = base64_decode($reader->readTextInside()); 167 | elseif($reader->isElement(self::XML_DATABASENAME)) 168 | $this->_name = $reader->readTextInside(); 169 | elseif($reader->isElement(self::XML_CUSTOMICONS)) 170 | { 171 | $iconsD = $reader->depth(); 172 | while($reader->read($iconsD)) 173 | { 174 | if($reader->isElement(self::XML_ICON)) 175 | $this->parseCustomIcon($reader); 176 | } 177 | } 178 | } 179 | } 180 | elseif($reader->isElement(self::XML_ROOT)) 181 | { 182 | $rootD = $reader->depth(); 183 | while($reader->read($rootD)) 184 | { 185 | if($reader->isElement(self::XML_GROUP)) 186 | $this->addGroup(Group::loadFromXML($reader)); 187 | } 188 | } 189 | } 190 | } 191 | 192 | /** 193 | * Creates an array describing this database (with respect to the filter). 194 | * This array can be safely serialized to json after. 195 | * @param $filter A filter to select the data that is actually copied to 196 | * the array (if null, it will serialize everything except 197 | * from passowrds). 198 | * @return An array containing this database (except passwords). 199 | */ 200 | public function toArray(iFilter $filter = null) 201 | { 202 | if($filter == null) 203 | $filter = new AllExceptFromPasswordsFilter(); 204 | $result = array(); 205 | if($this->_name != null) 206 | $result[self::XML_DATABASENAME] = $this->_name; 207 | if($this->_customIcons != null && $filter->acceptIcons()) 208 | $result[self::XML_CUSTOMICONS] = $this->_customIcons; 209 | if($this->_groups != null) 210 | { 211 | $groups = array(); 212 | foreach($this->_groups as &$group) 213 | { 214 | if($filter->acceptGroup($group)) 215 | $groups[] = $group->toArray($filter); 216 | } 217 | if(!empty($groups)) 218 | $result[self::GROUPS] = $groups; 219 | } 220 | return $result; 221 | } 222 | 223 | /** 224 | * Creates a new Database instance from an array created by the method 225 | * toArray() of another Database instance. 226 | * @param $array An array created by the method toArray(). 227 | * @param $version The version of the array format. 228 | * @param &$error A string that will receive a message in case of error. 229 | * @return A Database instance if the parsing went okay, null otherwise. 230 | */ 231 | public static function loadFromArray(array $array, $version, &$error) 232 | { 233 | if($array == null) 234 | { 235 | $error = "Database array load: array is empty."; 236 | return null; 237 | } 238 | $db = new Database(); 239 | $db->_name = self::getIfSet($array, self::XML_DATABASENAME); 240 | $db->_customIcons = self::getIfSet($array, self::XML_CUSTOMICONS); 241 | $groups = self::getIfSet($array, self::GROUPS); 242 | if(!empty($groups)) 243 | { 244 | foreach($groups as &$group) 245 | $db->addGroup(Group::loadFromArray($group, $version)); 246 | } 247 | if($db->_name == null && $db->_groups == null) 248 | { 249 | $error = "Database array load: empty database."; 250 | return null; 251 | } 252 | $error = null; 253 | return $db; 254 | } 255 | 256 | /** 257 | * Creates a new Database instance from an XML string with the format of 258 | * a KeePass 2.x database. 259 | * @param $xml An XML string. 260 | * @param $randomStream A iRandomStream instance to decrypt protected data. 261 | * @param &$error A string that will receive a message in case of error. 262 | * @return A Database instance if the parsing went okay, null otherwise. 263 | */ 264 | public static function loadFromXML($xml, iRandomStream $randomStream, 265 | &$error) 266 | { 267 | $reader = new ProtectedXMLReader($randomStream); 268 | if(!$reader->XML($xml) || !$reader->read(-1)) 269 | { 270 | $error = "Database XML load: cannot parse the XML string."; 271 | $reader->close(); 272 | return null; 273 | } 274 | if(!$reader->isElement(self::XML_KEEPASSFILE)) 275 | { 276 | $error = "Database XML load: the root element is not '" . self::XML_KEEPASSFILE . "'."; 277 | $reader->close(); 278 | return null; 279 | } 280 | $db = new Database(); 281 | $db->parseXML($reader); 282 | $reader->close(); 283 | if($db->_name == null && $db->_groups == null) 284 | { 285 | $error = "Database XML load: empty database."; 286 | return null; 287 | } 288 | $error = null; 289 | return $db; 290 | } 291 | 292 | /** 293 | * Creates a new Database instance from a .kdbx (KeePass 2.x) file. 294 | * @param $reader A Reader instance that reads a .kdbx file. 295 | * @param $key A iKey instance to use to decrypt the .kdbx file. 296 | * @param &$error A string that will receive a message in case of error. 297 | * @return A Database instance if the parsing went okay, null otherwise. 298 | */ 299 | public static function loadFromKdbx(Reader $reader, iKey $key, &$error) 300 | { 301 | $kdbx = KdbxFile::decrypt($reader, $key, $error); 302 | if($kdbx == null) 303 | return null; 304 | $db = self::loadFromXML($kdbx->getContent(), $kdbx->getRandomStream(), 305 | $error); 306 | if($db == null) 307 | return null; 308 | if($db->_headerHash !== $kdbx->getHeaderHash()) 309 | { 310 | $error = "Database Kdbx load: header hash is not correct."; 311 | return null; 312 | } 313 | return $db; 314 | } 315 | 316 | /** 317 | * Returns $array[$key] if it exists, null otherwise. 318 | * @param $array An array. 319 | * @param $key An array key. 320 | * @return $array[$key] if it exists, null otherwise. 321 | */ 322 | public static function getIfSet(array $array, $key) 323 | { 324 | return isset($array[$key]) ? $array[$key] : null; 325 | } 326 | } 327 | 328 | ?> -------------------------------------------------------------------------------- /keepassphp/lib/entry.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Louis Traynard 11 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 12 | * @link https://github.com/shkdee/KeePassPHP 13 | */ 14 | class Entry 15 | { 16 | /** 17 | * uuid of this entry in base64. 18 | */ 19 | public $uuid; 20 | /** 21 | * ID of the KeePass icon of this entry (if non null). 22 | */ 23 | public $icon; 24 | /** 25 | * uuid in base64 of the custom icon of this entry (if non null). 26 | */ 27 | public $customIcon; 28 | /** 29 | * tags of this entry (if non null). 30 | */ 31 | public $tags; 32 | /** 33 | * iBoxedString instance containing the password of this entry (if non null). 34 | */ 35 | public $password; 36 | /** 37 | * string fields of this entry as a map (key => value as an iBoxedString 38 | * instance). It contains all the standard string fields of KeePass except 39 | * password (notes, title, url, username), and all custom user-defined 40 | * string fields. 41 | */ 42 | public $stringFields; 43 | /** 44 | * Array of entries of this entry's history (if non null). 45 | */ 46 | public $history; 47 | 48 | public function __construct() 49 | { 50 | $this->uuid = null; 51 | $this->icon = null; 52 | $this->customIcon = null; 53 | $this->tags = null; 54 | $this->password = null; 55 | $this->stringFields = array(); 56 | $this->history = null; 57 | } 58 | 59 | /** 60 | * Gets the string value of this Entry string field corresponding to the 61 | * given key, or an empty string of no field with that key exists. 62 | * @param $key A key. 63 | * @return A non-null string, containing the value of the field. 64 | */ 65 | function getStringField($key) 66 | { 67 | return isset($this->stringFields[$key]) 68 | ? $this->stringFields[$key]->getPlainString() 69 | : ""; 70 | } 71 | 72 | /** 73 | * Adds an Entry instance to the history of this entry. 74 | * @param $entry An Entry instance, possibly null (it is then ignored). 75 | */ 76 | private function addHistoryEntry($entry) 77 | { 78 | if($entry != null) 79 | { 80 | if($this->history == null) 81 | $this->history = array(); 82 | $this->history[] = $entry; 83 | } 84 | } 85 | 86 | /** 87 | * Parses a string XML element node. 88 | * @param $reader A XML reader located at a string element node. 89 | */ 90 | private function readString(ProtectedXMLReader $reader) 91 | { 92 | $d = $reader->depth(); 93 | $key = null; 94 | $value = null; 95 | while($reader->read($d)) 96 | { 97 | if($reader->isElement(Database::XML_STRING_KEY)) 98 | $key = $reader->readTextInside(); 99 | elseif($reader->isElement(Database::XML_STRING_VALUE)) 100 | $value = $reader->readTextInside(true); 101 | } 102 | if(empty($key) || $value == null) 103 | return; 104 | if(\strcasecmp($key, Database::KEY_PASSWORD) == 0) 105 | $this->password = $value; 106 | else 107 | $this->stringFields[$key] = $value; 108 | } 109 | 110 | /** 111 | * Creates an array describing this entry (with respect to the filter). 112 | * This array can be safely serialized to json after. 113 | * @param $filter A filter to select the data that is actually copied to 114 | * the array. 115 | * @return An array containing this entry. 116 | */ 117 | public function toArray(iFilter $filter) 118 | { 119 | $result = array(); 120 | if($this->uuid != null) 121 | $result[Database::XML_UUID] = $this->uuid; 122 | if($this->icon != null && $filter->acceptIcons()) 123 | $result[Database::XML_ICONID] = $this->icon; 124 | if($this->customIcon != null && $filter->acceptIcons()) 125 | $result[Database::XML_CUSTOMICONUUID] = $this->customIcon; 126 | if($this->tags != null && $filter->acceptTags()) 127 | $result[Database::XML_TAGS] = $this->tags; 128 | $stringFields = array(); 129 | if($this->password != null && $filter->acceptPasswords()) 130 | $stringFields[Database::KEY_PASSWORD] = $this->password->getPlainString(); 131 | if(!empty($this->stringFields)) 132 | { 133 | foreach($this->stringFields as $key => &$value) 134 | { 135 | if($filter->acceptStrings($key)) 136 | $stringFields[$key] = $value->getPlainString(); 137 | } 138 | } 139 | if(!empty($stringFields)) 140 | $result[Database::KEY_STRINGFIELDS] = $stringFields; 141 | if($this->history != null) 142 | { 143 | $history = array(); 144 | foreach($this->history as &$entry) 145 | { 146 | if($filter->acceptHistoryEntry($entry)) 147 | $history[] = $entry->toArray($filter); 148 | } 149 | if(!empty($history)) 150 | $result[Database::XML_HISTORY] = $history; 151 | } 152 | return $result; 153 | } 154 | 155 | /** 156 | * Creates a new Entry instance from an array created by the method 157 | * toArray() of another Entry instance. 158 | * @param $array An array created by the method toArray(). 159 | * @param $version The version of the array format. 160 | * @return A Entry instance if the parsing went okay, null otherwise. 161 | */ 162 | public static function loadFromArray(array $array, $version) 163 | { 164 | if($array == null) 165 | return null; 166 | $entry = new Entry(); 167 | $entry->uuid = Database::getIfSet($array, Database::XML_UUID); 168 | $entry->icon = Database::getIfSet($array, Database::XML_ICONID); 169 | $entry->customIcon = Database::getIfSet($array, Database::XML_CUSTOMICONUUID); 170 | $entry->tags = Database::getIfSet($array, Database::XML_TAGS); 171 | if($version <= KphpDB::VERSION_0) 172 | { 173 | $keys = array(Database::KEY_TITLE, Database::KEY_USERNAME, 174 | Database::KEY_URL); 175 | foreach($keys as $key) 176 | { 177 | $value = Database::getIfSet($array, $key); 178 | if($value != null) 179 | $entry->stringFields[$key] = new UnprotectedString($value); 180 | } 181 | } 182 | else 183 | { 184 | $stringFields = Database::getIfSet($array, Database::KEY_STRINGFIELDS); 185 | if(!empty($stringFields)) 186 | { 187 | foreach($stringFields as $key => $value) 188 | $entry->stringFields[$key] = new UnprotectedString($value); 189 | } 190 | } 191 | $history = Database::getIfSet($array, Database::XML_HISTORY); 192 | if(!empty($history)) 193 | { 194 | foreach($history as &$e) 195 | $entry->addHistoryEntry(self::loadFromArray($e, $version)); 196 | } 197 | return $entry; 198 | } 199 | 200 | /** 201 | * Creates a new Entry instance from a ProtectedXMLReader instance reading 202 | * a KeePass 2.x database and located at an Entry element node. 203 | * @param $reader A XML reader. 204 | * @return A Entry instance if the parsing went okay, null otherwise. 205 | */ 206 | public static function loadFromXML(ProtectedXMLReader $reader) 207 | { 208 | if($reader == null) 209 | return null; 210 | $entry = new Entry(); 211 | $d = $reader->depth(); 212 | while($reader->read($d)) 213 | { 214 | if($reader->isElement(Database::XML_UUID)) 215 | $entry->uuid = $reader->readTextInside(); 216 | elseif($reader->isElement(Database::XML_ICONID)) 217 | $entry->icon = $reader->readTextInside(); 218 | elseif($reader->isElement(Database::XML_CUSTOMICONUUID)) 219 | $entry->customIcon = $reader->readTextInside(); 220 | else if($reader->isElement(Database::XML_TAGS)) 221 | $entry->tags = $reader->readTextInside(); 222 | elseif($reader->isElement(Database::XML_STRING)) 223 | $entry->readString($reader); 224 | elseif($reader->isElement(Database::XML_HISTORY)) 225 | { 226 | $historyD = $reader->depth(); 227 | while($reader->read($historyD)) 228 | { 229 | if($reader->isElement(Database::XML_ENTRY)) 230 | $entry->addHistoryEntry(self::loadFromXML($reader)); 231 | } 232 | } 233 | } 234 | return $entry; 235 | } 236 | } 237 | 238 | 239 | ?> -------------------------------------------------------------------------------- /keepassphp/lib/filter.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Louis Traynard 11 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 12 | * @link https://github.com/shkdee/KeePassPHP 13 | */ 14 | 15 | 16 | /** 17 | * A set of rules to determine which data to write when serializing a database. 18 | * Implementing this interface makes it possible to write only specific data. 19 | */ 20 | interface iFilter 21 | { 22 | /** 23 | * Returns true if the given entry must be serialized (otherwise it will be 24 | * discarded). 25 | * @param $entry An entry. 26 | */ 27 | public function acceptEntry(Entry $entry); 28 | /** 29 | * Returns true if the given group must be serialized (otherwise it will be 30 | * discarded). 31 | * @param $entry A group. 32 | */ 33 | public function acceptGroup(Group $group); 34 | /** 35 | * Returns true if the given history entry must be serialized (otherwise it 36 | * will be discarded). 37 | * @param $entry A history entry. 38 | */ 39 | public function acceptHistoryEntry(Entry $entry); 40 | /** 41 | * Returns true if tags must be serialized. 42 | */ 43 | public function acceptTags(); 44 | /** 45 | * Returns true if icons must be serialized. 46 | */ 47 | public function acceptIcons(); 48 | /** 49 | * Returns true if passwords must be serialized. 50 | * WARNING: it is NOT recommanded to return true in implementations of this 51 | * method, because passwords should not be copied in most cases. 52 | */ 53 | public function acceptPasswords(); 54 | /** 55 | * Returns true if string fields with the given key must be serialized. 56 | * @param $key A string field key. 57 | */ 58 | public function acceptStrings($key); 59 | } 60 | 61 | /** 62 | * A default filter that writes everything except from passwords. 63 | */ 64 | class AllExceptFromPasswordsFilter implements iFilter 65 | { 66 | public function acceptEntry(Entry $entry) 67 | { 68 | return true; 69 | } 70 | 71 | public function acceptGroup(Group $group) 72 | { 73 | return true; 74 | } 75 | 76 | public function acceptHistoryEntry(Entry $historyEntry) 77 | { 78 | return true; 79 | } 80 | 81 | public function acceptTags() 82 | { 83 | return true; 84 | } 85 | 86 | public function acceptIcons() 87 | { 88 | return true; 89 | } 90 | 91 | public function acceptPasswords() 92 | { 93 | return false; 94 | } 95 | 96 | public function acceptStrings($key) 97 | { 98 | return true; 99 | } 100 | } 101 | 102 | ?> -------------------------------------------------------------------------------- /keepassphp/lib/group.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Louis Traynard 11 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 12 | * @link https://github.com/shkdee/KeePassPHP 13 | */ 14 | class Group 15 | { 16 | /** 17 | * uuid of this group in base64. 18 | */ 19 | public $uuid; 20 | /** 21 | * Name of the group (if non null). 22 | */ 23 | public $name; 24 | /** 25 | * ID of the KeePass icon of this group (if non null). 26 | */ 27 | public $icon; 28 | /** 29 | * uuid in base64 of the custom icon of this group (if non null). 30 | */ 31 | public $customIcon; 32 | /** 33 | * Array of sub-groups of this group (if non null). 34 | */ 35 | public $groups; 36 | /** 37 | * Array of entries of this group (if non null). 38 | */ 39 | public $entries; 40 | 41 | private function __construct() 42 | { 43 | $this->uuid = null; 44 | $this->name = null; 45 | $this->icon = null; 46 | $this->customIcon = null; 47 | $this->groups = null; 48 | $this->entries = null; 49 | } 50 | 51 | /** 52 | * Gets the password of the entry of this group or of a sub-group whose 53 | * uuid is $uuid. 54 | * @param $uuid An entry uuid in base64. 55 | * @return The decrypted password if the entry exists inside this group or 56 | * a sub-group, null otherwise. 57 | */ 58 | public function getPassword($uuid) 59 | { 60 | if($this->entries != null) 61 | { 62 | foreach($this->entries as &$entry) 63 | { 64 | if($entry->uuid === $uuid) 65 | return $entry->password; 66 | } 67 | } 68 | if($this->groups != null) 69 | { 70 | foreach($this->groups as &$group) 71 | { 72 | $value = $group->getPassword($uuid); 73 | if($value != null) 74 | return $value; 75 | } 76 | } 77 | return null; 78 | } 79 | 80 | /** 81 | * Adds a Group instance as a sub-group of this group. 82 | * @param $entry A Group instance, possibly null (it is then ignored). 83 | */ 84 | private function addGroup($group) 85 | { 86 | if($group != null) 87 | { 88 | if($this->groups == null) 89 | $this->groups = array(); 90 | $this->groups[] = $group; 91 | } 92 | } 93 | 94 | /** 95 | * Adds an Entry instance to this group. 96 | * @param $entry An Entry instance, possibly null (it is then ignored). 97 | */ 98 | private function addEntry($entry) 99 | { 100 | if($entry != null) 101 | { 102 | if($this->entries == null) 103 | $this->entries = array(); 104 | $this->entries[] = $entry; 105 | } 106 | } 107 | 108 | /** 109 | * Creates an array describing this group (with respect to the filter). 110 | * This array can be safely serialized to json after. 111 | * @param $filter A filter to select the data that is actually copied to 112 | * the array. 113 | * @return An array containing this group. 114 | */ 115 | public function toArray(iFilter $filter) 116 | { 117 | $result = array(); 118 | if($this->uuid != null) 119 | $result[Database::XML_UUID] = $this->uuid; 120 | if($this->name != null) 121 | $result[Database::XML_NAME] = $this->name; 122 | if($this->icon != null && $filter->acceptIcons()) 123 | $result[Database::XML_ICONID] = $this->icon; 124 | if($this->customIcon != null && $filter->acceptIcons()) 125 | $result[Database::XML_CUSTOMICONUUID] = $this->customIcon; 126 | if($this->groups != null) 127 | { 128 | $groups = array(); 129 | foreach($this->groups as &$group) 130 | { 131 | if($filter->acceptGroup($group)) 132 | $groups[] = $group->toArray($filter); 133 | } 134 | if(!empty($groups)) 135 | $result[Database::GROUPS] = $groups; 136 | } 137 | if($this->entries != null) 138 | { 139 | $entries = array(); 140 | foreach($this->entries as &$entry) 141 | { 142 | if($filter->acceptEntry($entry)) 143 | $entries[] = $entry->toArray($filter); 144 | } 145 | if(!empty($entries)) 146 | $result[Database::ENTRIES] = $entries; 147 | } 148 | return $result; 149 | } 150 | 151 | /** 152 | * Creates a new Group instance from an array created by the method 153 | * toArray() of another Group instance. 154 | * @param $array An array created by the method toArray(). 155 | * @param $version The version of the array format. 156 | * @return A Group instance if the parsing went okay, null otherwise. 157 | */ 158 | public static function loadFromArray(array $array, $version) 159 | { 160 | if($array == null) 161 | return null; 162 | $group = new Group(); 163 | $group->uuid = Database::getIfSet($array, Database::XML_UUID); 164 | $group->name = Database::getIfSet($array, Database::XML_NAME); 165 | $group->icon = Database::getIfSet($array, Database::XML_ICONID); 166 | $group->customIcon = Database::getIfSet($array, Database::XML_CUSTOMICONUUID); 167 | $groups = Database::getIfSet($array, Database::GROUPS); 168 | if(!empty($groups)) 169 | { 170 | foreach($groups as &$subgroup) 171 | $group->addGroup(self::loadFromArray($subgroup, $version)); 172 | } 173 | $entries = Database::getIfSet($array, Database::ENTRIES); 174 | if(!empty($entries)) 175 | { 176 | foreach($entries as &$entry) 177 | $group->addEntry(Entry::loadFromArray($entry, $version)); 178 | } 179 | return $group; 180 | } 181 | 182 | /** 183 | * Creates a new Group instance from a ProtectedXMLReader instance reading 184 | * a KeePass 2.x database and located at a Group element node. 185 | * @param $reader A XML reader. 186 | * @return A Group instance if the parsing went okay, null otherwise. 187 | */ 188 | public static function loadFromXML(ProtectedXMLReader $reader) 189 | { 190 | if($reader == null) 191 | return null; 192 | $group = new Group(); 193 | $d = $reader->depth(); 194 | while($reader->read($d)) 195 | { 196 | if($reader->isElement(Database::XML_GROUP)) 197 | $group->addGroup(Group::loadFromXML($reader)); 198 | elseif($reader->isElement(Database::XML_ENTRY)) 199 | $group->addEntry(Entry::loadFromXML($reader)); 200 | elseif($reader->isElement(Database::XML_UUID)) 201 | $group->uuid = $reader->readTextInside(); 202 | elseif($reader->isElement(Database::XML_NAME)) 203 | $group->name = $reader->readTextInside(); 204 | elseif($reader->isElement(Database::XML_ICONID)) 205 | $group->icon = $reader->readTextInside(); 206 | elseif($reader->isElement(Database::XML_CUSTOMICONUUID)) 207 | $group->customIcon = $reader->readTextInside(); 208 | } 209 | return $group; 210 | } 211 | } 212 | 213 | ?> -------------------------------------------------------------------------------- /keepassphp/lib/kdbxfile.php: -------------------------------------------------------------------------------- 1 | 12 | * @copyright Louis Traynard 13 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 14 | * @link https://github.com/shkdee/KeePassPHP 15 | */ 16 | class KdbxFile 17 | { 18 | private $_header; 19 | private $_headerBinary; 20 | private $_content; 21 | private $_randomStream; 22 | 23 | const SALSA20_IV = "\xE8\x30\x09\x4B\x97\x20\x5D\x2A"; 24 | const CIPHER_LEN = 16; 25 | const SEED_LEN = 32; 26 | const STARTBYTES_LEN = 32; 27 | const ROUNDS_LEN = 8; 28 | 29 | const HASH = 'SHA256'; 30 | 31 | /** 32 | * Creates a new KdbxFile instance with the given KdbxHeader instance, 33 | * and completely empty otherwise. 34 | */ 35 | public function __construct(KdbxHeader $header) 36 | { 37 | $this->_header = $header; 38 | $this->_randomStream = null; 39 | $this->_content = null; 40 | $this->_headerBinary = null; 41 | } 42 | 43 | /** 44 | * Gets the header instance. 45 | * @return A KdbxHeader instance. 46 | */ 47 | public function getHeader() 48 | { 49 | return $this->_header; 50 | } 51 | 52 | /** 53 | * Gets the header hash. 54 | * @return A 32-byte-long hash. 55 | */ 56 | public function getHeaderHash() 57 | { 58 | return $this->_header->headerHash; 59 | } 60 | 61 | /** 62 | * Gets the content of this instance, if already set (if encrypt or decrypt 63 | * has been called). 64 | * @return A string. 65 | */ 66 | public function getContent() 67 | { 68 | return $this->_content; 69 | } 70 | 71 | /** 72 | * Gets the random stream defined by this instance header. 73 | * @return An iRandomStream instance. 74 | */ 75 | public function getRandomStream() 76 | { 77 | return $this->_randomStream; 78 | } 79 | 80 | /** 81 | * Prepares the encryption of this instance by generating new random 82 | * strings for the header, serializing the header and computing its hash. 83 | * Non-random header fields must be set before calling this method. 84 | * @param &$error A string that will receive a message in case of error. 85 | * @return true if everything went well and the encrypt method can be 86 | * called, false otherwise. 87 | */ 88 | public function prepareEncrypting(&$error) 89 | { 90 | $header = $this->getHeader(); 91 | $header->masterSeed = random_bytes(self::SEED_LEN); 92 | $header->transformSeed = random_bytes(self::SEED_LEN); 93 | $header->encryptionIV = random_bytes(16); 94 | $header->randomStreamKey = random_bytes(self::SEED_LEN); 95 | $header->startBytes = random_bytes(self::STARTBYTES_LEN); 96 | 97 | if($header->randomStream == KdbxHeader::RANDOMSTREAM_SALSA20) 98 | { 99 | $this->_randomStream = Salsa20Stream::create( 100 | hash(self::HASH, $header->randomStreamKey, true), 101 | self::SALSA20_IV); 102 | if($this->_randomStream == null) 103 | { 104 | $error = "Kdbx file encrypt: random stream parameters error."; 105 | return false; 106 | } 107 | } 108 | 109 | $result = $header->toBinary(self::HASH); 110 | 111 | if(!self::headerCheck($header)) 112 | { 113 | $error = "Kdbx file encrypt: header check failed."; 114 | return false; 115 | } 116 | 117 | $this->_headerBinary = $result; 118 | return true; 119 | } 120 | 121 | /** 122 | * Encrypts $content with $key and the header of this KdbxFile instance. 123 | * @param $content A string to encrypt. 124 | * @param $key A iKey instance to use as encryption key. 125 | * @param &$error A string that will receive a message in case of error. 126 | * @return A binary string containing the encrypted Kdbx file, or null in 127 | * case of error. 128 | */ 129 | public function encrypt($content, iKey $key, &$error) 130 | { 131 | if(empty($content)) 132 | { 133 | $error = "Kdbx file encrypt: empty content."; 134 | return null; 135 | } 136 | 137 | if(empty($this->_headerBinary)) 138 | { 139 | $error = "Kdbx file encrypt: encryption not prepared."; 140 | return null; 141 | } 142 | 143 | $header = $this->getHeader(); 144 | if($header->compression == KdbxHeader::COMPRESSION_GZIP) 145 | { 146 | $error = "Kdbx file encrypt: gzip compression not yet supported."; 147 | return null; 148 | } 149 | 150 | $cipherMethod = $header->cipher === KdbxHeader::CIPHER_AES 151 | ? 'aes-256-cbc' : null; 152 | if($cipherMethod === null) 153 | { 154 | $error = "Kdbx file encrypt: unkown cipher."; 155 | return null; 156 | } 157 | 158 | $hashedContent = HashedBlockReader::hashString($content, self::HASH); 159 | $transformedKey = self::transformKey($key, $header); 160 | $cipher = Cipher::Create($cipherMethod, $transformedKey, 161 | $header->encryptionIV); 162 | if($cipher == null || $transformedKey == null) 163 | { 164 | $error = "Kdbx file encrypt: cannot create cipher (no suitable cryptography extension found)."; 165 | return null; 166 | } 167 | $encrypted = $cipher->encrypt($header->startBytes . $hashedContent); 168 | if(empty($encrypted)) 169 | { 170 | $error = "Kdbx file encrypt: encryption failed."; 171 | return null; 172 | } 173 | 174 | $this->_content = $content; 175 | $r = $this->_headerBinary; 176 | $this->_headerBinary = null; 177 | return $r . $encrypted; 178 | } 179 | 180 | /** 181 | * Creates a new KdbxFile instance, sets its header with $rounds rounds of 182 | * AES encryption, no compression and no random stream, and prepares the 183 | * encryption. 184 | * @param &$error A string that will receive a message in case of error. 185 | * @return A new KdbxFile instance ready to be encrypted. 186 | */ 187 | public static function createForEncrypting($rounds, &$error) 188 | { 189 | $rounds = intval($rounds); 190 | if($rounds <= 0) 191 | { 192 | $error = "Kdbx file encrypt: rounds must be strictly positive."; 193 | return null; 194 | } 195 | 196 | $header = new KdbxHeader(); 197 | $header->cipher = KdbxHeader::CIPHER_AES; 198 | $header->compression = KdbxHeader::COMPRESSION_NONE; 199 | $header->randomStream = KdbxHeader::RANDOMSTREAM_NONE; 200 | $header->rounds = pack('V', $rounds) . "\x00\x00\x00\x00"; 201 | $file = new KdbxFile($header); 202 | return $file->prepareEncrypting($rounds, $error) ? $file : null; 203 | } 204 | 205 | /** 206 | * Decrypts an encrypted Kdbx file with $key. 207 | * @param $reader A Reader instance that reads a Kdbx file. 208 | * @param $key The encryption key of the Kdbx file. 209 | * @param &$error A string that will receive a message in case of error. 210 | * @return A new KdbxFile instance containing the decrypted content, 211 | * header and random stream (if applicable), or null if something 212 | * went wrong. 213 | */ 214 | public static function decrypt(Reader $reader, iKey $key, &$error) 215 | { 216 | if($reader == null) 217 | { 218 | $error = "Kdbx file decrypt: reader is null."; 219 | return null; 220 | } 221 | 222 | $header = KdbxHeader::load($reader, self::HASH, $error); 223 | if($header == null) 224 | return null; 225 | 226 | if(!self::headerCheck($header, true)) 227 | { 228 | $error = "Kdbx file decrypt: header check failed."; 229 | return null; 230 | } 231 | 232 | $randomStream = null; 233 | if($header->randomStream == KdbxHeader::RANDOMSTREAM_SALSA20) 234 | { 235 | $randomStream = Salsa20Stream::create( 236 | hash(self::HASH, $header->randomStreamKey, true), 237 | self::SALSA20_IV); 238 | if($randomStream == null) 239 | { 240 | $error = "Kdbx file decrypt: random stream parameters error."; 241 | return null; 242 | } 243 | } 244 | 245 | $cipherMethod = $header->cipher === KdbxHeader::CIPHER_AES 246 | ? 'aes-256-cbc' : null; 247 | if($cipherMethod === null) 248 | { 249 | $error = "Kdbx file decrypt: unkown cipher."; 250 | return null; 251 | } 252 | 253 | $transformedKey = self::transformKey($key, $header); 254 | $cipher = Cipher::Create($cipherMethod, $transformedKey, 255 | $header->encryptionIV); 256 | if($cipher == null || $transformedKey == null) 257 | { 258 | $error = "Kdbx file decrypt: cannot create cipher (no suitable cryptography extension found)."; 259 | return null; 260 | } 261 | $decrypted = $cipher->decrypt($reader->readToTheEnd()); 262 | if(empty($decrypted) || substr($decrypted, 0, self::STARTBYTES_LEN) 263 | !== $header->startBytes) 264 | { 265 | $error = "Kdbx file decrypt: decryption failed."; 266 | return null; 267 | } 268 | 269 | $hashedReader = new HashedBlockReader( 270 | new StringReader(substr($decrypted, self::STARTBYTES_LEN)), 271 | self::HASH); 272 | $decoded = $hashedReader->readToTheEnd(); 273 | $hashedReader->close(); 274 | if(strlen($decoded) == 0 || $hashedReader->isCorrupted()) 275 | { 276 | $error = "Kdbx file decrypt: integrity check failed."; 277 | return null; 278 | } 279 | 280 | if($header->compression == KdbxHeader::COMPRESSION_GZIP) 281 | { 282 | $filename = null; 283 | $gzerror = null; 284 | $decoded = gzdecode2($decoded, $filename, $gzerror); 285 | if(strlen($decoded) == 0) 286 | { 287 | $error = "Kdbx file decrypt: ungzip error: " . $gzerror . "."; 288 | return null; 289 | } 290 | } 291 | 292 | $file = new KdbxFile($header); 293 | $file->_content = $decoded; 294 | $file->_randomStream = $randomStream; 295 | return $file; 296 | } 297 | 298 | /** 299 | * Computes the AES encryption key of a Kdbx file from its keys and header. 300 | * @param $key The encryption key. 301 | * @param $header The Kdbx file header. 302 | * @return A 32-byte-long string that can be used as an AES key, or null 303 | * if no suitable cryptography extension is laoded. 304 | */ 305 | private static function transformKey(iKey $key, KdbxHeader $header) 306 | { 307 | $keyHash = $key->getHash(); 308 | $cipher = Cipher::Create('aes-256-ecb', $header->transformSeed, 309 | "", Cipher::PADDING_NONE); 310 | if($cipher == null) 311 | return null; 312 | // We have to do $rounds encryptions, where $rounds is a 64 bit 313 | // unsigned integer. Since PHP does not handle 64 bit integers in a 314 | // clear way, nor 32 bit unsigned integers, it is safer to take 315 | // $rounds as an array of 4 short (16 bit) unsigned integers. 316 | // Remember that $rounds is encoded in little-endian. 317 | $rounds = array_values(unpack("v4", $header->rounds)); 318 | // To go even faster, represent $rounds in base 2**30 by only three 319 | // signed integers, that PHP should handle correctly. $o, $t and $h 320 | // will respectively be ones, tens and hundrers. $o and $t each take 30 321 | // bits, $h takes the remaining 4. 322 | $o = $rounds[0] | (($rounds[1] & 0x3fff) << 16); 323 | $t = (($rounds[1] & 0xc000) >> 14) | ($rounds[2] << 2) | 324 | (($rounds[3] & 0x0fff) << 18); 325 | $h = ($rounds[3] & 0xf000) >> 12; 326 | // So virtually, the number of rounds is $o + ($t << 30) + ($h << 60). 327 | $loop = false; 328 | do 329 | { 330 | // Let's do a direct, very fast loop on the ones $o 331 | if($o > 0) 332 | $keyHash = $cipher->encryptManyTimes($keyHash, $o); 333 | // whether there is still some rounds to perform 334 | $loop = false; 335 | // then, remove 1 from the number of rounds (that's just a 336 | // substraction in base 2**30 of a 3-digit number), knowing that 337 | // $o equals 0. 338 | if($t > 0) 339 | { 340 | $t--; 341 | // We set $o to (2**30 - 1) + 1 = 2**30 because we still 342 | // have to do the encryption round that we're currently 343 | // substracting. So we don't really do a substraction, we 344 | // just write the number differently. That's also why we 345 | // chose to represent $rounds in base 2**30 rather than 2**31. 346 | $o = 0x40000000; 347 | $loop = true; 348 | } 349 | else if($h > 0) 350 | { 351 | $h--; 352 | $t = 0x3fffffff; 353 | // same as above 354 | $o = 0x40000000; 355 | $loop = true; 356 | } 357 | } while($loop); 358 | 359 | $finalKey = hash(self::HASH, $keyHash, true); 360 | return hash(self::HASH, $header->masterSeed . $finalKey, true); 361 | } 362 | 363 | /** 364 | * Checks that the $header is valid for a Kdbx file. 365 | * @param $header A KdbxHeader instance. 366 | * @return true if the header is valid, false otherwise. 367 | */ 368 | private static function headerCheck(KdbxHeader $header) 369 | { 370 | return strlen($header->cipher) == self::CIPHER_LEN 371 | && $header->compression !== 0 372 | && strlen($header->masterSeed) == self::SEED_LEN 373 | && strlen($header->transformSeed) == self::SEED_LEN 374 | && strlen($header->rounds) == self::ROUNDS_LEN 375 | && $header->encryptionIV !== null 376 | && strlen($header->startBytes) == self::STARTBYTES_LEN 377 | && $header->headerHash !== null 378 | && $header->randomStreamKey !== null 379 | && $header->randomStream !== 0; 380 | } 381 | } 382 | 383 | ?> 384 | -------------------------------------------------------------------------------- /keepassphp/lib/kdbxheader.php: -------------------------------------------------------------------------------- 1 | 16 | * @copyright Louis Traynard 17 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 18 | * @link https://github.com/shkdee/KeePassPHP 19 | */ 20 | class KdbxHeader 21 | { 22 | /** A binary string identifying the cipher used to encrypt the file. */ 23 | public $cipher; 24 | /** An integer identifying the algorithm used to compress the file. */ 25 | public $compression; 26 | /** Master seed for the file encryption key. */ 27 | public $masterSeed; 28 | /** Specific seed to compute the file encryption key. */ 29 | public $transformSeed; 30 | /** Number of cipher rounds to perform to compute the file encryption key. */ 31 | public $rounds; 32 | /** The IV to use to decrypt the file. */ 33 | public $encryptionIV; 34 | /** The specific initializing key for possible random stream. */ 35 | public $randomStreamKey; 36 | /** The first bytes of the decrypted file (before un-compressing). */ 37 | public $startBytes; 38 | /** An integer identifying the random stream generator to use. */ 39 | public $randomStream; 40 | /** The hash of the binary format of the header. */ 41 | public $headerHash; 42 | 43 | const SIGNATURE1 = "\x03\xD9\xA2\x9A"; 44 | const SIGNATURE2 = "\x67\xFB\x4B\xB5"; 45 | const VERSION = "\x01\x00\x03\x00"; 46 | const MAXIMAL_VERSION = 3; 47 | 48 | const CIPHER_AES = "\x31\xC1\xF2\xE6\xBF\x71\x43\x50\xBE\x58\x05\x21\x6A\xFC\x5A\xFF"; 49 | 50 | const COMPRESSION_NONE = 1; 51 | const COMPRESSION_GZIP = 2; 52 | const RANDOMSTREAM_NONE = 1; 53 | //const RANDOMSTREAM_ARC4 = 2; 54 | const RANDOMSTREAM_SALSA20 = 3; 55 | 56 | const INT_0 = "\x00\x00\x00\x00"; 57 | const INT_1 = "\x01\x00\x00\x00"; 58 | const INT_2 = "\x02\x00\x00\x00"; 59 | 60 | public function __construct() 61 | { 62 | $this->cipher = null; 63 | $this->compression = 0; 64 | $this->masterSeed = null; 65 | $this->transformSeed = null; 66 | $this->rounds = null; 67 | $this->encryptionIV = null; 68 | $this->randomStreamKey = null; 69 | $this->startBytes = null; 70 | $this->headerHash = null; 71 | $this->randomStream = 0; 72 | } 73 | 74 | /** 75 | * Gets the binary format of this Header instance, and computes its hash. 76 | * @param $hashAlgo The hash algorithm to use to compute the header hash. 77 | * @return A binary string representing this Header instance. 78 | */ 79 | public function toBinary($hashAlgo) 80 | { 81 | $s = self::SIGNATURE1 . self::SIGNATURE2 . self::VERSION 82 | . self::fieldToString(2, $this->cipher) 83 | . self::fieldToString(3, 84 | $this->compression == self::COMPRESSION_GZIP 85 | ? self::INT_1 : self::INT_0) 86 | . self::fieldToString(4, $this->masterSeed) 87 | . self::fieldToString(5, $this->transformSeed) 88 | . self::fieldToString(6, $this->rounds) 89 | . self::fieldToString(7, $this->encryptionIV) 90 | . self::fieldToString(8, $this->randomStreamKey) 91 | . self::fieldToString(9, $this->startBytes) 92 | . self::fieldToString(10, 93 | $this->randomStream == self::RANDOMSTREAM_SALSA20 94 | ? self::INT_2 : self::INT_0) 95 | . self::fieldToString(0, null); 96 | $this->headerHash = hash($hashAlgo, $s, true); 97 | return $s; 98 | } 99 | 100 | /** 101 | * Gets the binary format of the given header field. 102 | * @param $id The field id. 103 | * @param $value The field value. 104 | * @return A binary string representing the header field. 105 | */ 106 | private static function fieldToString($id, $value) 107 | { 108 | $l = strlen($value); 109 | return chr($id) . ($l == 0 ? "\x00\x00" : (pack("v", $l) . $value)); 110 | } 111 | 112 | /** 113 | * Checks whether all fields are set in this instance. 114 | * @return true if all fields are set, false otherwise. 115 | */ 116 | public function check() 117 | { 118 | if($this->cipher === null) 119 | return false; 120 | if($this->compression === 0) 121 | return false; 122 | if($this->masterSeed === null) 123 | return false; 124 | if($this->transformSeed === null) 125 | return false; 126 | if($this->rounds === null) 127 | return false; 128 | if($this->encryptionIV === null) 129 | return false; 130 | if($this->startBytes === null) 131 | return false; 132 | if($this->headerHash === null) 133 | return false; 134 | if($this->randomStreamKey === null) 135 | return false; 136 | if($this->randomStream === 0) 137 | return false; 138 | return true; 139 | } 140 | 141 | /** 142 | * Parses the content of a Reader as a KdbxHeader in binary format. 143 | * @param $reader A Reader that reads the header. 144 | * @param $hashAlgo The hash algorithm to use to compute the header hash. 145 | * @param &$error A string that will receive a message in case of error. 146 | * @return A new KdbxHeader instance if it could be correctly parsed from 147 | * the reader, and null otherwise. 148 | */ 149 | public static function load(Reader $reader, $hashAlgo, &$error) 150 | { 151 | $dreader = new DigestReader($reader, $hashAlgo); 152 | 153 | $sig1 = $dreader->read(4); 154 | $sig2 = $dreader->read(4); 155 | if($sig1 != self::SIGNATURE1 || $sig2 != self::SIGNATURE2) 156 | { 157 | $error = "Kdbx header: signature not correct."; 158 | return null; 159 | } 160 | 161 | $lowerversion = $dreader->readNumber(2); 162 | $upperversion = $dreader->readNumber(2); 163 | if($upperversion > self::MAXIMAL_VERSION) 164 | { 165 | $error = "Kdbx header: version not supported."; 166 | return null; 167 | } 168 | 169 | $header = new KdbxHeader(); 170 | $ended = false; 171 | while(!$ended) 172 | { 173 | $fieldId = $dreader->readByte(); 174 | $fieldLen = $dreader->readNumber(2); 175 | $field = null; 176 | if($fieldLen > 0) 177 | { 178 | $field = $dreader->read($fieldLen); 179 | if($field == null || strlen($field) != $fieldLen) 180 | { 181 | $error = "Kdbx header: uncomplete header field."; 182 | return null; 183 | } 184 | } 185 | 186 | /* 187 | * end of header 188 | */ 189 | if($fieldId == 0) 190 | $ended = true; 191 | 192 | /* 193 | * comment (let's ignore) 194 | */ 195 | //elseif($fieldId == 1) 196 | // ; 197 | 198 | /* 199 | * Cipher type 200 | */ 201 | elseif($fieldId == 2) 202 | $header->cipher = $field; 203 | 204 | /* 205 | * Compression method 206 | */ 207 | elseif($fieldId == 3) 208 | { 209 | if($field == self::INT_0) 210 | $header->compression = self::COMPRESSION_NONE; 211 | elseif($field == self::INT_1) 212 | $header->compression = self::COMPRESSION_GZIP; 213 | } 214 | 215 | /* 216 | * MasterSeed 217 | */ 218 | elseif($fieldId == 4) 219 | $header->masterSeed = $field; 220 | 221 | /* 222 | * TransformSeed 223 | */ 224 | elseif($fieldId == 5) 225 | $header->transformSeed = $field; 226 | 227 | /* 228 | * Number of rounds 229 | */ 230 | elseif($fieldId == 6) 231 | $header->rounds = $field; 232 | 233 | /* 234 | * EncryptionIV 235 | */ 236 | elseif($fieldId == 7) 237 | $header->encryptionIV = $field; 238 | 239 | /* 240 | * Random stream key 241 | */ 242 | elseif($fieldId == 8) 243 | $header->randomStreamKey = $field; 244 | 245 | /* 246 | * First bytes of result 247 | */ 248 | elseif($fieldId == 9) 249 | $header->startBytes = $field; 250 | 251 | /* 252 | * Random stream type 253 | */ 254 | elseif($fieldId == 10) 255 | { 256 | if($field == self::INT_0) 257 | $header->randomStream = self::RANDOMSTREAM_NONE; 258 | elseif($field == self::INT_2) 259 | $header->randomStream = self::RANDOMSTREAM_SALSA20; 260 | /*elseif($field == self::INT_1) // unsuported 261 | $header->randomStream= self::RANDOMSTREAM_ARC4;*/ 262 | } 263 | } 264 | $header->headerHash = $dreader->GetDigest(); 265 | $error = null; 266 | return $header; 267 | } 268 | } 269 | 270 | ?> -------------------------------------------------------------------------------- /keepassphp/lib/key.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright Louis Traynard 11 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 12 | * @link https://github.com/shkdee/KeePassPHP 13 | */ 14 | 15 | /** 16 | * An object that contains a secret in the form of a hash. 17 | */ 18 | interface iKey 19 | { 20 | /** 21 | * Gets this instance hash. 22 | * @return A raw hash string. 23 | */ 24 | public function getHash(); 25 | } 26 | 27 | /** 28 | * A KeePass composite key, used in the decryption of a kdbx file. It takes 29 | * several iKeys and hashes all of them toghether to build the composite key. 30 | */ 31 | class CompositeKey implements iKey 32 | { 33 | private $_keys; 34 | private $_hashAlgo; 35 | 36 | /** 37 | * Constructs a new CompositeKey instance using $hashAlgo to hash all 38 | * keys all together. 39 | * @param $hashAlgo A hash algorithm name. 40 | */ 41 | public function __construct($hashAlgo) 42 | { 43 | $this->_keys = array(); 44 | $this->_hashAlgo = $hashAlgo; 45 | } 46 | 47 | /** 48 | * Adds the given key $key to this CompositeKey. 49 | * @param $key An iKey instance to add. 50 | */ 51 | public function addKey(iKey $key) 52 | { 53 | array_push($this->_keys, $key->getHash()); 54 | } 55 | 56 | /** 57 | * Computes the hash of all the keys of this CompositeKey. 58 | * @return A raw hash string. 59 | */ 60 | public function getHash() 61 | { 62 | $h = hash_init($this->_hashAlgo); 63 | foreach($this->_keys as &$v) 64 | hash_update($h, $v); 65 | $r = hash_final($h, true); 66 | unset($h); 67 | return $r; 68 | } 69 | } 70 | 71 | /** 72 | * An iKey built from something already hashed. 73 | */ 74 | class KeyFromHash implements iKey 75 | { 76 | protected $hash; 77 | 78 | /** 79 | * Stores the given hash string. 80 | * @param $h A raw hash string. 81 | */ 82 | public function __construct($h) 83 | { 84 | $this->hash = $h; 85 | } 86 | 87 | /** 88 | * Retrieves the stored hash. 89 | * @return A raw hash string. 90 | */ 91 | public function getHash() 92 | { 93 | return $this->hash; 94 | } 95 | } 96 | 97 | /** 98 | * An iKey built from a string password. 99 | */ 100 | class KeyFromPassword extends KeyFromHash 101 | { 102 | /** 103 | * Constructs a KeyFromPassword instance from the password $pwd. 104 | * @param $pwd A string. 105 | * @param $hashAlgo A hash algorithm name. 106 | */ 107 | public function __construct($pwd, $hashAlgo) 108 | { 109 | parent::__construct(hash($hashAlgo, $pwd, true)); 110 | } 111 | } 112 | 113 | /** 114 | * An iKey built from a KeePass key file. Supports XML, binary and hex files. 115 | * If the parsing of the file is successful, the property $isParsed is set to 116 | * true, and false otherwise; its value must then be checked when a new 117 | * KeyFromFile object is created, to see whether something went wrong or not: 118 | * if it false, the hash that this object may return will probably mean 119 | * nothing. 120 | */ 121 | class KeyFromFile extends KeyFromHash 122 | { 123 | const XML_ROOT = "KeyFile"; 124 | const XML_KEY = "Key"; 125 | const XML_META = "Meta"; 126 | const XML_VERSION = "Version"; 127 | const XML_DATA = "Data"; 128 | 129 | public $isParsed = false; 130 | 131 | /** 132 | * Tries to parse $content to find the hash inside. If the parsing is 133 | * successfully, the property $this->isParsed is set to true. 134 | * @param $content A key file content. 135 | */ 136 | public function __construct($content) 137 | { 138 | $this->isParsed = $this->tryParseXML($content) || 139 | $this->tryParse($content); 140 | } 141 | 142 | /** 143 | * Tries to parse $content as a binary or a hex key file. 144 | * @param $content A key file content. 145 | * @return true in case of success, false otherwise. 146 | */ 147 | private function tryParse($content) 148 | { 149 | if(strlen($content) == 32) 150 | { 151 | $this->hash = $content; 152 | return true; 153 | } 154 | if(strlen($content) == 64) 155 | { 156 | $this->hash = hex2bin($content); 157 | return true; 158 | } 159 | return false; 160 | } 161 | 162 | /** 163 | * Tries to parse $content as a KeePass XML key file. 164 | * @param $content A key file content. 165 | * @return true in case of success, false otherwise. 166 | */ 167 | private function tryParseXML($content) 168 | { 169 | $xml = new ProtectedXMLReader(null); 170 | if(!$xml->XML($content) || !$xml->read(-1)) 171 | return false; 172 | if($xml->isElement(self::XML_ROOT)) 173 | { 174 | $d = $xml->depth(); 175 | // get KeyFile version from 176 | $version = null; 177 | while($xml->read($d)) 178 | { 179 | if($xml->isElement(self::XML_META)) 180 | { 181 | $keyD = $xml->depth(); 182 | while($xml->read($keyD)) 183 | { 184 | if($xml->isElement(self::XML_VERSION)) 185 | { 186 | $version = $xml->readTextInside(); 187 | } 188 | } 189 | } 190 | if($xml->isElement(self::XML_KEY)) 191 | { 192 | $keyD = $xml->depth(); 193 | while($xml->read($keyD)) 194 | { 195 | if($xml->isElement(self::XML_DATA)) 196 | { 197 | /* KeePass 2.47 introduced Enhanced XML key file format (added hash that allows to verify the integrity of the key; values are now encoded using hexadecimal characters in order to improve the readability). */ 198 | $value = $xml->readTextInside(); 199 | if($version == "1.0") 200 | { 201 | $this->hash = base64_decode($value); 202 | $xml->close(); 203 | return true; 204 | } 205 | elseif($version == "2.0") 206 | { 207 | $this->hash = hex2bin(preg_replace('/\s*/m', '', $value)); 208 | $xml->close(); 209 | return true; 210 | } 211 | else 212 | { 213 | $xml->close(); 214 | return false; 215 | } 216 | } 217 | } 218 | } 219 | } 220 | } 221 | $xml->close(); 222 | return false; 223 | } 224 | } 225 | 226 | ?> -------------------------------------------------------------------------------- /keepassphp/lib/kphpdb.php: -------------------------------------------------------------------------------- 1 | 15 | * @copyright Louis Traynard 16 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 17 | * @link https://github.com/shkdee/KeePassPHP 18 | */ 19 | class KphpDB 20 | { 21 | private $_dbType; 22 | private $_db; 23 | private $_headerHash; 24 | private $_dbFileHash; 25 | private $_keyFileHash; 26 | 27 | const KEY_DBTYPE = "type"; 28 | const KEY_DBFILEHASH = "dbfile"; 29 | const KEY_KEYFILEHASH = "keyfile"; 30 | const KEY_HEADERHASH = "headerhash"; 31 | const KEY_DB = "db"; 32 | const KEY_VERSION = "version"; 33 | 34 | const DBTYPE_NONE = 1; 35 | const DBTYPE_KDBX = 2; 36 | const ROUNDS = 128; 37 | 38 | /** Version 0 of the kphpdb serialization format. */ 39 | const VERSION_0 = 0; 40 | /** Version 1 of the kphpdb serialization format. */ 41 | const VERSION_1 = 1; 42 | /** Current version of the kphpdb serialization format. Databases 43 | * serialized with the current code have this format. */ 44 | const VERSION_CURRENT = self::VERSION_1; 45 | 46 | /** 47 | * Constructs a new KphpDB instance. 48 | * @param $dbType The wrapped database type. 49 | * @param $db An object that will be wrapped by this KphpDB. 50 | * @param $dbFileHash The hexadecimal hash of the original db file. 51 | * @param $keyFileHash A possible hexadecimal hash of an additional key 52 | * file if needed to decrypt the original db file. 53 | */ 54 | protected function __construct($dbType, $db, $dbFileHash, $keyFileHash) 55 | { 56 | $this->_dbType = $dbType; 57 | $this->_db = $db; 58 | $this->_dbFileHash = $dbFileHash; 59 | $this->_keyFileHash = $keyFileHash; 60 | $this->_headerHash = null; 61 | } 62 | 63 | /** 64 | * Creates a new KphpDB instance that wraps a Database instance. 65 | * @param $db A Database instance. 66 | * @param $dbFileHash The hexadecimal hash of the original db file. 67 | * @param $keyFileHash A possible hexadecimal hash of an additional key 68 | * file if needed to decrypt the original db file. 69 | * @return A new KphpDB instance. 70 | */ 71 | public static function createFromDatabase(Database $db, $dbFileHash, 72 | $keyFileHash) 73 | { 74 | return new KphpDB(self::DBTYPE_KDBX, $db, $dbFileHash, $keyFileHash); 75 | } 76 | 77 | /** 78 | * Creates a new KphpDB instance that wraps nothing. 79 | * @param $dbFileHash The hexadecimal hash of the original db file. 80 | * @param $keyFileHash A possible hexadecimal hash of an additional key 81 | * file if needed to decrypt the original db file. 82 | * @return A new KphpDB instance. 83 | */ 84 | public static function createEmpty($dbFileHash, $keyFileHash) 85 | { 86 | return new KphpDB(self::DBTYPE_NONE, null, $dbFileHash, $keyFileHash); 87 | } 88 | 89 | /** 90 | * Gets the type of the database wrapped by this KphpDB file. 91 | * @return One of the KphpDB::DBTYPE_* constants. 92 | */ 93 | public function getDBType() 94 | { 95 | return $this->_dbType; 96 | } 97 | 98 | /** 99 | * Gets the database wrapped by this KphpDB file, whose class depends on 100 | * this instance DB type. It does not contain passwords. 101 | * @return For now, either null or a Database instance. 102 | */ 103 | public function getDB() 104 | { 105 | return $this->_db; 106 | } 107 | 108 | /** 109 | * Gets the original DB file hash. 110 | * @return A hexadecimal hash. 111 | */ 112 | public function getDBFileHash() 113 | { 114 | return $this->_dbFileHash; 115 | } 116 | 117 | /** 118 | * Gets the hexadecimal hash of the additional key file associated with the 119 | * Database instance, if there is one. 120 | * @return A hexadecimal hash, or null if there is none. 121 | */ 122 | public function getKeyFileHash() 123 | { 124 | return $this->_keyFileHash; 125 | } 126 | 127 | /** 128 | * Serializes this instance to a JSon string. 129 | * @param $filter An iFilter instance to select the data of the database 130 | * that must actually be serialized (if null, it will 131 | * serialize everything except from passowrds). 132 | * @param &$error A string that will receive a message in case of error. 133 | * @return A JSon string in case of success, or null in case of error. 134 | */ 135 | public function toJSon($filter, &$error) 136 | { 137 | $array = array( 138 | self::KEY_VERSION => self::VERSION_CURRENT, 139 | self::KEY_DBTYPE => $this->_dbType, 140 | self::KEY_DBFILEHASH => $this->_dbFileHash, 141 | self::KEY_KEYFILEHASH => $this->_keyFileHash, 142 | self::KEY_HEADERHASH => base64_encode($this->_headerHash), 143 | self::KEY_DB => $this->_db->toArray($filter)); 144 | $r = json_encode($array); 145 | if($r === false) 146 | { 147 | $error = "KphpDB JSon save: " . json_last_error(); 148 | return null; 149 | } 150 | $error = null; 151 | return $r; 152 | } 153 | 154 | /** 155 | * Serializes this instance to a JSon string and encrypts it in a kdbx 156 | * file. 157 | * @param $key A iKey instance to use to encrypt the kdbx file. 158 | * @param $filter An iFilter instance to select the data of the database 159 | * that must actually be serialized (if null, it will 160 | * serialize everything except from passowrds). 161 | * @param &$error A string that will receive a message in case of error. 162 | * @return A string containing a kdbx file embbeding this serialized 163 | * instance, or null in case of error. 164 | */ 165 | public function toKdbx(iKey $key, $filter, &$error) 166 | { 167 | $kdbx = KdbxFile::createForEncrypting(self::ROUNDS, $error); 168 | if($kdbx == null) 169 | return null; 170 | 171 | $this->_headerHash = $kdbx->getHeaderHash(); 172 | 173 | $json = $this->toJSon($filter, $error); 174 | if(empty($json)) 175 | return null; 176 | 177 | return $kdbx->encrypt($json, $key, $error); 178 | } 179 | 180 | /** 181 | * Creates a new KphpDB instance from a JSon string, that should have been 182 | * created by the method toJSon() of another KphpDB instance. 183 | * @param json A JSon string. 184 | * @param &$error A string that will receive a message in case of error. 185 | * @return A new KphpDB instance, or null in case of error. 186 | */ 187 | public static function loadFromJSon($json, &$error) 188 | { 189 | $array = json_decode($json, true); 190 | if($array === null) 191 | { 192 | $error = "KphpDB JSon load: cannot parse JSon string: " 193 | . json_last_error(); 194 | return null; 195 | } 196 | 197 | if(!array_key_exists(self::KEY_DBTYPE, $array) || 198 | !array_key_exists(self::KEY_DBFILEHASH, $array) || 199 | !array_key_exists(self::KEY_KEYFILEHASH, $array) || 200 | !array_key_exists(self::KEY_HEADERHASH, $array) || 201 | !array_key_exists(self::KEY_DB, $array)) 202 | { 203 | $error = "KphpDB JSon load: incomplete file."; 204 | return null; 205 | } 206 | $version = array_key_exists(self::KEY_VERSION, $array) 207 | ? intval($array[self::KEY_VERSION]) 208 | : self::VERSION_0; 209 | 210 | $dbType = $array[self::KEY_DBTYPE]; 211 | $db = null; 212 | if($dbType == self::DBTYPE_KDBX) 213 | { 214 | $db = Database::loadFromArray($array[self::KEY_DB], $version, 215 | $error); 216 | if($db === null) 217 | return null; 218 | } 219 | else if($dbType != self::DBTYPE_NONE) 220 | { 221 | $error = "KphpDB JSon load: unkown db type '" + $dbType + '".'; 222 | return null; 223 | } 224 | $kdbx = new KphpDB($dbType, $db, 225 | $array[self::KEY_DBFILEHASH], $array[self::KEY_KEYFILEHASH]); 226 | $kdbx->_headerHash = base64_decode($array[self::KEY_HEADERHASH]); 227 | return $kdbx; 228 | } 229 | 230 | /** 231 | * Creates a new KphpDB instance from a kdbx file, that should have been 232 | * created by the method toKdbx() of another KphpDB instance. 233 | * @param $reader A Reader instance reading a kdbx file. 234 | * @param $key A iKey instance to use to decrypt the kdbx file. 235 | * @param &$error A string that will receive a message in case of error. 236 | * @return A new KphpDB instance, or null in case of error. 237 | */ 238 | public static function loadFromKdbx(Reader $reader, iKey $key, &$error) 239 | { 240 | $kdbx = KdbxFile::decrypt($reader, $key, $error); 241 | if($kdbx == null) 242 | return null; 243 | $kphpdb = self::loadFromJSon($kdbx->getContent(), $error); 244 | if($kphpdb == null) 245 | return null; 246 | if($kphpdb->_headerHash !== $kdbx->getHeaderHash()) 247 | { 248 | $error = "KphpDB Kdbx load: header hash is not correct."; 249 | return null; 250 | } 251 | return $kphpdb; 252 | } 253 | } 254 | 255 | ?> -------------------------------------------------------------------------------- /keepassphp/lib/protectedxmlreader.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright Louis Traynard 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/shkdee/KeePassPHP 14 | */ 15 | class ProtectedXMLReader 16 | { 17 | const STOP = 0; 18 | const GO_ON = 1; 19 | const DO_NOT_READ = 2; 20 | 21 | const XML_ATTR_PROTECTED = "Protected"; 22 | const XML_ATTR_TRUE = "True"; 23 | 24 | public $r; 25 | private $_state; 26 | private $_randomStream; 27 | 28 | public function __construct(iRandomStream $randomStream = null) 29 | { 30 | $this->r = new \XMLReader(); 31 | $this->_state = self::GO_ON; 32 | $this->_randomStream = $randomStream; 33 | } 34 | 35 | /** 36 | * Opens the UTF-8-encoded file $file with the internal XMLReader. 37 | * @param $file A path to an UTF-8-encoded XML file. 38 | * @return true in case of success, false otherwise. 39 | */ 40 | public function open($file) 41 | { 42 | return file_exists($file) && $this->r->open($file, 'UTF-8'); 43 | } 44 | 45 | /** 46 | * Sets the UTF-8-encoded $src string as the XML source. 47 | * @param $src An UTF-8-encoded XML string. 48 | * @return true in case of success, false otherwise. 49 | */ 50 | public function XML($src) 51 | { 52 | return $this->r->XML($src, 'UTF-8'); 53 | } 54 | 55 | /** 56 | * Closes the XMLReader input. 57 | * @return Returns true on success or false on failure. 58 | */ 59 | public function close() 60 | { 61 | return $this->r->close(); 62 | } 63 | 64 | /** 65 | * Gets the depth of the current node in the XML tree. 66 | * @return The depth of the current node. 67 | */ 68 | public function depth() 69 | { 70 | return $this->r->depth; 71 | } 72 | 73 | /** 74 | * Checks whether the current element has the given tagname. 75 | * @return true if the tag name is $name, false otherwise. 76 | */ 77 | public function isElement($name) 78 | { 79 | return \strcasecmp($name, $this->r->name) == 0; 80 | } 81 | 82 | /** 83 | * Reads the next Element node of the XML stream if its depth is strictly 84 | * higher than $depth. 85 | * @param $depth The minimum depth of the Element to read. 86 | * @return false if the XML source ended, or if the depth of the next node 87 | * is lower than or equal to $depth. 88 | */ 89 | public function read($depth) 90 | { 91 | if($this->_state == self::STOP) 92 | return false; 93 | if($this->_state == self::GO_ON) 94 | { 95 | do 96 | { 97 | if(!@$this->r->read()) 98 | { 99 | $this->_state = self::STOP; 100 | return false; 101 | } 102 | } 103 | while($this->r->nodeType != \XMLReader::ELEMENT); 104 | } 105 | if($this->r->depth > $depth) 106 | { 107 | $this->_state = self::GO_ON; 108 | return true; 109 | } 110 | else 111 | { 112 | $this->_state = self::DO_NOT_READ; 113 | return false; 114 | } 115 | } 116 | 117 | /** 118 | * Reads the text content of the current Element node. 119 | * @param $asProtectedString Whether to return an iBoxedString instance 120 | * rather than a plain string. 121 | * @return The text content if it exists, or null. 122 | */ 123 | public function readTextInside($asProtectedString = false) 124 | { 125 | if($this->_state != self::GO_ON || $this->r->isEmptyElement) 126 | return null; 127 | $isProtected = $this->r->hasAttributes && 128 | $this->r->getAttribute(self::XML_ATTR_PROTECTED) == self::XML_ATTR_TRUE; 129 | 130 | if(!@$this->r->read()) 131 | { 132 | $this->_state = self::STOP; 133 | return null; 134 | } 135 | 136 | if($this->r->nodeType == \XMLReader::TEXT) 137 | { 138 | $value = $this->r->value; 139 | if(!$isProtected || empty($value) || $this->_randomStream == null) 140 | return $asProtectedString 141 | ? new UnprotectedString($value) 142 | : $value; 143 | $value = base64_decode($value); 144 | $random = $this->_randomStream->getNextBytes(strlen($value)); 145 | return $asProtectedString 146 | ? new ProtectedString($value, $random) 147 | : $value ^ $random; 148 | } 149 | elseif($this->r->nodeType == \XMLReader::ELEMENT) 150 | { 151 | $this->_state = self::DO_NOT_READ; 152 | return null; 153 | } 154 | } 155 | } 156 | 157 | ?> -------------------------------------------------------------------------------- /keepassphp/util/cipher.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright Louis Traynard 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/shkdee/KeePassPHP 14 | */ 15 | abstract class Cipher 16 | { 17 | protected $_method; 18 | protected $_key; 19 | protected $_iv; 20 | protected $_padding; 21 | 22 | /** Add no padding (the data must be of correct length). */ 23 | const PADDING_NONE = 0; 24 | 25 | /** Add PKCS7 padding. */ 26 | const PADDING_PKCS7 = 1; 27 | 28 | /** 29 | * Constructs a new Cipher instance. 30 | * @param $method One of the OpenSSL ciphers constants. 31 | * @param $key A binary string used as key (must be of correct length). 32 | * @param $iv A binary string used as initialization vector (must be of 33 | * correct length), or "" if none are needed. 34 | * @param $padding The type of padding to use. Must be one of the constants 35 | * self::PADDING_*. 36 | */ 37 | protected function __construct($method, $key, $iv, $padding) 38 | { 39 | $this->setKey($key); 40 | $this->setIV($iv); 41 | $this->setPadding($padding); 42 | $this->setMethod($method); 43 | } 44 | 45 | /** 46 | * Sets the cipher method to use. 47 | * @param $method One of the OpenSSL ciphers constants. 48 | */ 49 | public function setMethod($method) 50 | { 51 | $this->_method = $method; 52 | } 53 | 54 | /** 55 | * Sets the encryption or decryption key to use. 56 | * @param $key A binary string (must be of correct length). 57 | */ 58 | public function setKey($k) 59 | { 60 | $this->_key = $k; 61 | } 62 | 63 | /** 64 | * Sets the initialization vector to use. 65 | * @param $iv A binary string (must be of correct length), or "" if none 66 | * are needed. 67 | */ 68 | public function setIV($iv) 69 | { 70 | $this->_iv = $iv; 71 | } 72 | 73 | /** 74 | * Sets the padding mode to use. 75 | * @param $padding A padding type. Must be one of the constants 76 | * self::PADDING_*. 77 | */ 78 | public function setPadding($padding) 79 | { 80 | $this->_padding = $padding; 81 | } 82 | 83 | /** 84 | * Encrypts $s with this cipher instance method and key. 85 | * @param $s A raw string to encrypt. 86 | * @return The result as a raw string, or null in case of error. 87 | */ 88 | abstract public function encrypt($s); 89 | 90 | /** 91 | * Performs $r rounds of encryption on $s with this cipher instance. 92 | * @param $s A raw string, that must have a correct length to be encrypted 93 | * with no padding. 94 | * @param $r The number of encryption rounds to perform. 95 | * @return The result as a raw string, or null in case of error. 96 | */ 97 | abstract public function encryptManyTimes($s, $r); 98 | 99 | /** 100 | * Decrypts $s with this cipher instance method and key. 101 | * @param $s A raw string to decrypt. 102 | * @return The result as a raw string, or null in case of error. 103 | */ 104 | abstract public function decrypt($s); 105 | 106 | /** 107 | * Creates a new Cipher instance of one of the implementing classes, 108 | * depending on the available extensions, or returns null if no extension 109 | * is available. 110 | * If $method and $key are null and are not set in some way before 111 | * encrypting or decrypting, the operation will fail miserably. 112 | * @param $method The OpenSSL method to use. 113 | * @param $key The key, used for decryption as well as encryption. 114 | * @param $iv The initialization vector, or "" if none are needed. 115 | * @param $padding The type of padding to use. Must be one of the constants 116 | * self::PADDING_*. 117 | * @return A Cipher instance, or null if no suitable crypto library is 118 | * loaded. 119 | */ 120 | public static function Create($method, $key = null, $iv = "", 121 | $padding = self::PADDING_PKCS7) 122 | { 123 | return (PHP_VERSION_ID >= 50400 && extension_loaded("openssl")) 124 | ? new CipherOpenSSL($method, $key, $iv, $padding) 125 | : (extension_loaded("mcrypt") && defined("MCRYPT_RIJNDAEL_128") 126 | ? new CipherMcrypt($method, $key, $iv, $padding) 127 | : null); 128 | } 129 | } 130 | 131 | /** 132 | * A Cipher implementation based on the OpenSSL PHP extension. This class 133 | * should be preferred over CipherMcrypt if the OpenSSL extension is available, 134 | * as OpenSSL is faster and more reliable than libmcrypt. 135 | */ 136 | class CipherOpenSSL extends Cipher 137 | { 138 | /** 139 | * Constructs a new CipherOpenSSL instance. Calling code should check 140 | * before creating this instance that the OpenSSL extension is loaded. 141 | * @param $method The OpenSSL method to use. 142 | * @param $key The key, used for decryption as well as encryption. 143 | * @param $iv The initialization vector, or "" if none are needed. 144 | * @param $padding The type of padding to use. Must be one of the constants 145 | * parent::PADDING_*. 146 | */ 147 | public function __construct($method, $key = null, $iv = "", 148 | $padding = self::PADDING_PKCS7) 149 | { 150 | parent::__construct($method, $key, $iv, $padding); 151 | } 152 | 153 | /** 154 | * Encrypts $s with this cipher instance method and key. 155 | * @param $s A raw string to encrypt. 156 | * @return The result as a raw string, or null in case of error. 157 | */ 158 | public function encrypt($s) 159 | { 160 | if(strlen($this->_method) == 0 || strlen($this->_key) == 0) 161 | return null; 162 | $options = OPENSSL_RAW_DATA; 163 | if($this->_padding == parent::PADDING_NONE) 164 | $options = $options | OPENSSL_NO_PADDING; 165 | return openssl_encrypt($s, $this->_method, $this->_key, $options, 166 | $this->_iv); 167 | } 168 | 169 | /** 170 | * Performs $r rounds of encryption on $s with this cipher instance. 171 | * @param $s A raw string, that must have a correct length to be encrypted 172 | * with no padding. 173 | * @param $r The number of encryption rounds to perform. 174 | * @return The result as a raw string, or null in case of error. 175 | */ 176 | public function encryptManyTimes($s, $r) 177 | { 178 | if(strlen($this->_method) == 0 || strlen($this->_key) == 0) 179 | return null; 180 | $options = OPENSSL_RAW_DATA | OPENSSL_NO_PADDING; 181 | for($i = 0; $i < $r; $i++) 182 | $s = openssl_encrypt($s, $this->_method, $this->_key, $options, 183 | $this->_iv); 184 | return $s; 185 | } 186 | 187 | /** 188 | * Decrypts $s with this cipher instance method and key. 189 | * @param $s A raw string to decrypt. 190 | * @return The result as a raw string, or null in case of error. 191 | */ 192 | public function decrypt($s) 193 | { 194 | if(strlen($this->_method) == 0 || strlen($this->_key) == 0) 195 | return null; 196 | $options = OPENSSL_RAW_DATA; 197 | if($this->_padding == parent::PADDING_NONE) 198 | $options = $options | OPENSSL_NO_PADDING; 199 | return openssl_decrypt($s, $this->_method, $this->_key, $options, 200 | $this->_iv); 201 | } 202 | } 203 | 204 | /** 205 | * A Cipher implementation based on the mcrypt PHP extension. 206 | */ 207 | class CipherMcrypt extends Cipher 208 | { 209 | private $_type; 210 | private $_mode; 211 | 212 | // 0 = unloaded, 1 = loaded, 213 | // 2 = encrypting, 3 = decrypting 214 | // private $_state = 0; 215 | 216 | /** 217 | * Constructs a new CipherMcrypt instance. 218 | * @param $method The OpenSSL method to use (will be translated to mcrypt 219 | * corresponding cipher type and mode). 220 | * @param $key The key, used for decryption as well as encryption. 221 | * @param $iv The initialization vector, or "" if none are needed. 222 | * @param $padding The type of padding to use. Must be one of the constants 223 | * parent::PADDING_*. 224 | */ 225 | public function __construct($method, $key = null, $iv = "", 226 | $padding = self::PADDING_PKCS7) 227 | { 228 | $this->_type = null; 229 | $this->_mode = null; 230 | parent::__construct($method, $key, $iv, $padding); 231 | } 232 | 233 | /** 234 | * Sets the cipher method to use. 235 | * @param $method One of the OpenSSL ciphers constants. 236 | */ 237 | public function setMethod($method) 238 | { 239 | parent::setMethod($method); 240 | $method = strtolower($method); 241 | if($method == "aes-256-ecb") 242 | { 243 | $this->_type = MCRYPT_RIJNDAEL_128; 244 | $this->_mode = "ecb"; 245 | } 246 | elseif($method == "aes-256-cbc") 247 | { 248 | $this->_type = MCRYPT_RIJNDAEL_128; 249 | $this->_mode = "cbc"; 250 | } 251 | } 252 | 253 | /** 254 | * Encrypts $s with this cipher instance method and key. 255 | * @param $s A raw string to encrypt. 256 | * @return The result as a raw string, or null in case of error. 257 | */ 258 | public function encrypt($s) 259 | { 260 | $m = $this->load(); 261 | if($m === null) 262 | return null; 263 | $r = mcrypt_generic($m, $this->_padding == parent::PADDING_PKCS7 264 | ? self::addPKCS7Padding($s, 265 | mcrypt_enc_get_block_size($m)) 266 | : $s); 267 | $this->unload($m); 268 | return $r; 269 | } 270 | 271 | /** 272 | * Performs $r rounds of encryption on $s with this cipher instance. 273 | * @param $s A raw string, that must have a correct length to be encrypted 274 | * with no padding. 275 | * @param $r The number of encryption rounds to perform. 276 | * @return The result as a raw string, or null in case of error. 277 | */ 278 | public function encryptManyTimes($s, $r) 279 | { 280 | $m = $this->load(); 281 | if($m === null) 282 | return null; 283 | for($i = 0; $i < $r; $i++) 284 | $s = mcrypt_generic($m, $s); 285 | $this->unload($m); 286 | return $s; 287 | } 288 | 289 | /** 290 | * Decrypts $s with this cipher instance method and key. 291 | * @param $s A raw string to decrypt. 292 | * @return The result as a raw string, or null in case of error. 293 | */ 294 | public function decrypt($s) 295 | { 296 | $m = $this->load(); 297 | if($m === null) 298 | return null; 299 | $padded = mdecrypt_generic($m, $s); 300 | $r = $this->_padding == parent::PADDING_PKCS7 301 | ? self::removePKCS7Padding($padded, 302 | mcrypt_enc_get_block_size($m)) 303 | : $padded; 304 | $this->unload($m); 305 | return $r; 306 | } 307 | 308 | /******************* 309 | * Private methods * 310 | *******************/ 311 | 312 | /** 313 | * Opens a mcrypt module. 314 | * @return A mcrypt module resource, or null if an error occurred. 315 | */ 316 | private function load() 317 | { 318 | if(strlen($this->_method) == 0 || strlen($this->_key) == 0) 319 | return null; 320 | $m = mcrypt_module_open($this->_type, '', $this->_mode, ''); 321 | if($m === false) 322 | return null; 323 | // This check is performed by mcrypt_generic_init, but it's better 324 | // to do it now, because mcrypt_generic_init does not return a 325 | // negative or false value if it fails. 326 | $ivsize = mcrypt_enc_get_iv_size($m); 327 | if(strlen($this->_iv) != $ivsize) 328 | { 329 | // In ECB (and some other modes), the IV is not used but still 330 | // required to have the this size by mcrypt_generic_init, so 331 | // let's make a fake one. 332 | if(strtolower($this->_mode) == "ecb") 333 | $ivsize = str_repeat("\0", $ivsize); 334 | else 335 | return null; 336 | } 337 | $r = @mcrypt_generic_init($m, $this->_key, $this->_iv); 338 | if($r < 0 || $r === false) 339 | return null; 340 | return $m; 341 | } 342 | 343 | /** 344 | * Closes a mcrypt module. 345 | * @param $m A mcrypt module. 346 | */ 347 | private function unload($m) 348 | { 349 | mcrypt_generic_deinit($m); 350 | mcrypt_module_close($m); 351 | } 352 | 353 | /** 354 | * Pads the given string $str with the PKCS7 padding scheme, so that its 355 | * length shall be a multiple of $blocksize. 356 | * @param $str A string to pad. 357 | * @param $blocksize The block size. 358 | * @return The resulting padded string. 359 | */ 360 | private static function addPKCS7Padding($str, $blocksize) 361 | { 362 | $len = strlen($str); 363 | $pad = $blocksize - ($len % $blocksize); 364 | return $str . str_repeat(chr($pad), $pad); 365 | } 366 | 367 | /** 368 | * Tries to unpad the PKCS7-padded string $string. 369 | * @param $string The string to unpad. 370 | * @return The unpadded string, or null in case of error. 371 | */ 372 | private static function removePKCS7Padding($string, $blocksize) 373 | { 374 | $len = strlen($string); 375 | $padlen = ord($string[$len - 1]); 376 | $padding = substr($string, -$padlen); 377 | if($padlen > $blocksize || $padlen == 0 || 378 | substr_count($padding, chr($padlen)) != $padlen) 379 | return null; 380 | return substr($string, 0, $len - $padlen); 381 | } 382 | } 383 | 384 | ?> 385 | -------------------------------------------------------------------------------- /keepassphp/util/filemanager.php: -------------------------------------------------------------------------------- 1 | 16 | * @copyright Louis Traynard 17 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 18 | * @link https://github.com/shkdee/KeePassPHP 19 | */ 20 | class FileManager 21 | { 22 | protected $prefix; 23 | protected $elements; 24 | protected $dir; 25 | protected $acceptFiles; 26 | protected $acceptMem; 27 | protected $load; 28 | 29 | const DEFAULT_EXT = "bin"; 30 | const TYPE_FILE = 1; 31 | const TYPE_MEM = 2; 32 | 33 | /** 34 | * Creates a new FileManager, storing files (if possible) in the directory 35 | * $dir, and using the prefix $prefix for filenames. If $filesonly is true, 36 | * all the data will be stored with files (and thus be persistent), and if 37 | * $memonly is true, no files will be used (the data will thus not be 38 | * persistent). If both are true, $memonly takes precedence. If both are 39 | * false, the data will be stored in a file if possible, and in memory 40 | * otherwise. 41 | * 42 | * @param $dir The directory where to store files. 43 | * @param $prefix The prefix for filenames. 44 | * @param $filesonly Whether only files must be used. 45 | * @param $memonly Whether only memory must be used. 46 | */ 47 | public function __construct($dir, $prefix, $filesonly, $memonly = false) 48 | { 49 | $this->elements = array(); 50 | $this->acceptMem = !$filesonly; 51 | $this->acceptFiles = !$memonly; 52 | $this->loaded = false; 53 | 54 | $this->dir = rtrim($dir, '/') . '/'; 55 | $this->prefix = $prefix; 56 | } 57 | 58 | /****************** 59 | * Public methods * 60 | ******************/ 61 | 62 | /** 63 | * Adds $value to the collection, associating it with they key $key. Gives 64 | * the extension $ext to the storing file (if any). If $writeable is false, 65 | * the string will not be stored in a file, only kept in memory. If 66 | * $override is true, and if the $key already exists in the collection, the 67 | * value will be overriden. 68 | * @param $key A string key. 69 | * @param $value A string to store. 70 | * @param $ext The storing file extension. 71 | * @param $writeable Whether this value can be written on disk. 72 | * @param $override Whether to override an already existing file. 73 | * @return The hash of $key, or null in case of error. 74 | * @throws Exception If this FileManager directory is not accessible. 75 | */ 76 | public function addWithKey($key, $value, $ext = self::DEFAULT_EXT, 77 | $writeable = true, $override = false) 78 | { 79 | $h = $this->hash($key); 80 | return $this->addElement($h, $value, $ext, $writeable, $override) ? 81 | $h : null; 82 | } 83 | 84 | /** 85 | * Adds $value to the collection, using $value itself as key. Gives the 86 | * extension $ext to the storing file (if any). If $writeable is false, the 87 | * string will not be stored in a file, only kept in memory. If $override 88 | * is true, and if the $key already exists in the collection, the value 89 | * will be overriden. 90 | * @param $value A string to store using itself as key. 91 | * @param $ext The storing file extension. 92 | * @param $writeable Whether this value can be written on disk. 93 | * @param $override Whether to override an already existing file. 94 | * @return The hash of $value, or null in case of error. 95 | * @throws Exception If this FileManager directory is not accessible. 96 | */ 97 | public function add($value, $ext = self::DEFAULT_EXT, $writeable = true, 98 | $override = false) 99 | { 100 | $h = $this->hash($value); 101 | return $this->addElement($h, $value, $ext, $writeable, $override) ? 102 | $h : null; 103 | } 104 | 105 | /** 106 | * Checks whether the key $key exists in this collection. 107 | * @param $key A string key. 108 | * @return true if $key already exists, false otherwise. 109 | * @throws Exception If this FileManager directory is not accessible. 110 | */ 111 | public function existsKey($key) 112 | { 113 | return $this->exists($this->hash($key)); 114 | } 115 | 116 | /** 117 | * Checks whether the hash $h exists in this collection. 118 | * @param $h A string key hash in hexadecimal. 119 | * @return true if $h already exists, false otherwise. 120 | * @throws Exception If this FileManager directory is not accessible. 121 | */ 122 | public function exists($h) 123 | { 124 | $this->load(); 125 | return array_key_exists($h, $this->elements); 126 | } 127 | 128 | /** 129 | * Gets the string value indexed by the key $h. 130 | * @param $key A string key. 131 | * @return A string value, or null if $key does not exist. 132 | * @throws Exception If this FileManager directory is not accessible. 133 | */ 134 | public function getContentFromKey($key) 135 | { 136 | return $this->getContent($this->hash($key)); 137 | } 138 | 139 | /** 140 | * Gets the string value indexed by the hash $h. 141 | * @param $h A string key hash in hexadecimal. 142 | * @return A string value, or null if $h does not exist. 143 | * @throws Exception If this FileManager directory is not accessible. 144 | */ 145 | public function getContent($h) 146 | { 147 | $e = $this->getRawElement($h); 148 | if($e != null) 149 | { 150 | if($e[0] == self::TYPE_FILE) 151 | return file_get_contents($this->dir . $e[1]); 152 | elseif($e[0] == self::TYPE_MEM) 153 | return $e[1]; 154 | } 155 | return null; 156 | } 157 | 158 | /** 159 | * Gets the filename of the file containing the string indexed by the key 160 | * $key. 161 | * @param $key A string key. 162 | * @return A filename, or null if the value does not exist or is not stored 163 | * in a file. 164 | * @throws Exception If this FileManager directory is not accessible. 165 | */ 166 | public function getFileFromKey($key) 167 | { 168 | return $this->getFile($this->hash($key)); 169 | } 170 | 171 | /** 172 | * Gets the filename of the file containing the string indexed by the hash 173 | * $h. 174 | * @param $h A string key hash in hexadecimal. 175 | * @return A filename, or null if the value does not exist or is not stored 176 | * in a file. 177 | * @throws Exception If this FileManager directory is not accessible. 178 | */ 179 | public function getFile($h) 180 | { 181 | $e = $this->getRawElement($h); 182 | if($e != null && $e[0] == self::TYPE_FILE) 183 | return $this->dir . $e[1]; 184 | return null; 185 | } 186 | 187 | /** 188 | * Removes the element indexed by the key $key. 189 | * @param $key A string key. 190 | * @return true if the element existed and could be removed. 191 | * @throws Exception If this FileManager directory is not accessible. 192 | */ 193 | public function removeFromKey($key) 194 | { 195 | return $this->remove($this->hash($key)); 196 | } 197 | 198 | /** 199 | * Removes the element indexed by the hash $h. 200 | * @param $h A string key hash in hexadecimal. 201 | * @return true if the element existed and could be removed. 202 | * @throws Exception If this FileManager directory is not accessible. 203 | */ 204 | public function remove($h) 205 | { 206 | $e = $this->getRawElement($h); 207 | if($e == null) 208 | return false; 209 | if($e[0] == self::TYPE_FILE) 210 | $this->removeFile($e[1]); 211 | unset($this->elements[$h]); 212 | return true; 213 | } 214 | 215 | /** 216 | * Prepends the directory of that FileManager to the filename $f, so that 217 | * this file may be accessed and used by another application. 218 | * @param $f A filename. 219 | * @return The filename prepended by this FileManager directory. 220 | */ 221 | public function prependDir($f) 222 | { 223 | return $this->dir . $f; 224 | } 225 | 226 | /********************* 227 | * Protected methods * 228 | *********************/ 229 | 230 | /** 231 | * Loads already-existing files in this FileManager, by scanning its 232 | * directory for matching file names (only if this FileManager accepts 233 | * files). If this directory does not exist, it will be created, and if 234 | * this creation fails or if the directory cannot be read and open, an 235 | * exception is thrown. 236 | * @throws Exception If this FileManager directory is not accessible. 237 | */ 238 | protected function load() 239 | { 240 | if($this->loaded) 241 | return; 242 | if($this->acceptFiles) 243 | { 244 | if((is_dir($this->dir) || mkdir($this->dir, 0700, true)) && 245 | $this->prefix != null && is_writable($this->dir) && 246 | $dh = opendir($this->dir)) 247 | { 248 | $pattern = "/^".$this->prefix."_([a-f0-9]+)\.\w+$/i"; 249 | $matches = array(); 250 | while(($file = readdir($dh)) !== false) 251 | if(preg_match($pattern, $file, $matches)) 252 | $this->elements[strtolower($matches[1])] = array( 253 | self::TYPE_FILE, $file); 254 | } 255 | elseif(!$this->acceptMem) 256 | throw new \Exception("The directory " . $this->dir . 257 | " does not exist and cannot be created."); 258 | else 259 | $this->acceptFiles = false; 260 | } 261 | $this->loaded = true; 262 | } 263 | 264 | /** 265 | * Gets the internal raw element indexed by the key $key. 266 | * @see $this->getRawElement($h) 267 | * @param $key A string key. 268 | * @return An array, or null if the key $key does not exist. 269 | * @throws Exception If this FileManager directory is not accessible. 270 | */ 271 | protected function getRawElementFromKey($key) 272 | { 273 | return $this->getRawElement($this->hash($key)); 274 | } 275 | 276 | /** 277 | * Gets the internal raw element indexed by the hash $h. It is an array 278 | * of 3 items: 279 | * * The 0th contains self::TYPE_FILE if the element is stored in a file, 280 | * self::TYPE_MEM otherwise; 281 | * * The 1st contains the extension of the file (even for a 282 | * memory-stored element); 283 | * * The 2nd contains the name of the file storing the string, or the 284 | * string itself if the element is memory-stored. 285 | * @param $h A string key hash in hexadecimal. 286 | * @return An array, or null if the hash $h does not exist. 287 | * @throws Exception If this FileManager directory is not accessible. 288 | */ 289 | protected function getRawElement($h) 290 | { 291 | $this->load(); 292 | if(array_key_exists($h, $this->elements)) 293 | return $this->elements[$h]; 294 | return null; 295 | } 296 | 297 | /** 298 | * If overriden in an extended class, performs an operation on $value 299 | * before it is saved to a file. 300 | * @param $value A string value that will be stored to a file. 301 | * @return The transformed value (by default, same as $value). 302 | */ 303 | protected function processForFile($value) 304 | { 305 | return $value; 306 | } 307 | 308 | /** 309 | * If overriden in an extended class, performs an operation on $value 310 | * before it is saved to memory. 311 | * @param $value A string value that will be stored in memory. 312 | * @return The transformed value (by default, same as $value). 313 | */ 314 | protected function processForMem($value) 315 | { 316 | return $value; 317 | } 318 | 319 | /** 320 | * Computes the name of the file whose key hash is $h and extension $ext. 321 | * @param $h The value key hash. 322 | * @param $ext The value extension. 323 | * @return The value filename. 324 | */ 325 | protected function filename($h, $ext) 326 | { 327 | return $this->prefix . "_" . $h . "." . $ext; 328 | } 329 | 330 | /** 331 | * Computes the hash of $k (by default, SHA-1) in hexadecimal. 332 | * @param $k A string. 333 | * @return A hash as an hexadecimal string. 334 | */ 335 | protected function hash($k) 336 | { 337 | return sha1($k, false); 338 | } 339 | 340 | /** 341 | * Adds the string $v, indexed by the hash $h, to the collection. Tries to 342 | * save it to a file (if possible, and if $writeable is true) with the 343 | * extension $ext, or keeps it in memory if it fails and if the collection 344 | * is not file-only. If $h already exists in the collection, the associated 345 | * value will be replaced if $override is true. 346 | * @param $h A string key hash in hexadecimal. 347 | * @param $v A string value. 348 | * @param $ext A file extension. 349 | * @param $writeable Whether the file can be written on disk. 350 | * @param $override Whether to override an already existing file. 351 | * @return true if the element was correctly added or already exists, and 352 | * false otherwise. 353 | * @throws Exception If this FileManager directory is not accessible. 354 | */ 355 | protected function addElement($h, $v, $ext, $writeable, $override) 356 | { 357 | $this->load(); 358 | $fileexists = array_key_exists($h, $this->elements); 359 | if(!$override && $fileexists) 360 | return true; 361 | 362 | if($writeable && $this->acceptFiles) 363 | { 364 | $filename = $this->filename($h, $ext); 365 | if($this->saveFile($filename, $v)) 366 | { 367 | if($fileexists && $this->elements[$h][1] != $filename) 368 | $this->removeFile($this->elements[$h][1]); 369 | $this->elements[$h] = array(self::TYPE_FILE, $filename); 370 | return true; 371 | } 372 | } 373 | if($this->acceptMem) 374 | { 375 | $this->elements[$h] = array(self::TYPE_MEM, 376 | $this->processForMem($v)); 377 | return true; 378 | } 379 | return false; 380 | } 381 | 382 | /** 383 | * Performs the actual writing of the string $content to the file $f. 384 | * @param $filename An element filename. 385 | * @param $content A string to write. 386 | * @return true in case of success, or false otherwise. 387 | */ 388 | protected function saveFile($filename, $content) 389 | { 390 | $filename = $this->dir . $filename; 391 | $f = fopen($filename, "wb"); 392 | if($f) 393 | { 394 | if(fwrite($f, $this->processForFile($content))) 395 | { 396 | fclose($f); 397 | chmod($filename, 0600); 398 | return true; 399 | } 400 | fclose($f); 401 | } 402 | return false; 403 | } 404 | 405 | /** 406 | * Deletes (if possible) the file $f of an element. 407 | * @param $filename An element filename. 408 | * @return true in case of success, false otherwise. 409 | */ 410 | protected function removeFile($filename) 411 | { 412 | return @unlink($this->dir . $filename); 413 | } 414 | } 415 | 416 | ?> -------------------------------------------------------------------------------- /keepassphp/util/gzdecode2.php: -------------------------------------------------------------------------------- 1 | 0) { 99 | switch ($method) { 100 | case 8: 101 | // Currently the only supported compression method: 102 | $data = gzinflate($body, $maxlength); 103 | break; 104 | default: 105 | $error = "Unknown compression method."; 106 | return false; 107 | } 108 | } // zero-byte body content is allowed 109 | // Verifiy CRC32 110 | $crc = sprintf("%u", crc32($data)); 111 | $crcOK = $crc == $datacrc; 112 | $lenOK = $isize == strlen($data); 113 | if (!$lenOK || !$crcOK) { 114 | $error = ( $lenOK ? '' : 'Length check FAILED. ') . 115 | ( $crcOK ? '' : 'Checksum FAILED.'); 116 | return false; 117 | } 118 | return $data; 119 | } 120 | 121 | ?> -------------------------------------------------------------------------------- /keepassphp/util/protectedstring.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright Louis Traynard 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/shkdee/KeePassPHP 14 | */ 15 | 16 | /** 17 | * An object that can yield a string. 18 | */ 19 | interface iBoxedString 20 | { 21 | /** 22 | * Gets the boxed string. 23 | * @return a string. 24 | */ 25 | public function getPlainString(); 26 | } 27 | 28 | /** 29 | * A boxed plain string. 30 | */ 31 | class UnprotectedString implements iBoxedString 32 | { 33 | private $_string; 34 | 35 | public function __construct($string) 36 | { 37 | $this->_string = $string; 38 | } 39 | 40 | /** 41 | * Gets the boxed string. 42 | * @return a string. 43 | */ 44 | public function getPlainString() 45 | { 46 | return $this->_string; 47 | } 48 | } 49 | 50 | /** 51 | * A string protected by a mask to xor. 52 | */ 53 | class ProtectedString implements iBoxedString 54 | { 55 | private $_string; 56 | private $_random; 57 | 58 | public function __construct($string, $random) 59 | { 60 | $this->_string = $string; 61 | $this->_random = $random; 62 | } 63 | 64 | /** 65 | * Gets the real content of the protected string. 66 | * @return a string. 67 | */ 68 | public function getPlainString() 69 | { 70 | return $this->_string ^ $this->_random; 71 | } 72 | } 73 | 74 | 75 | ?> -------------------------------------------------------------------------------- /keepassphp/util/reader.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright Louis Traynard 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 13 | * @link https://github.com/shkdee/KeePassPHP 14 | */ 15 | 16 | /** 17 | * Base class for all readers. Instances of this class can read bytes from a 18 | * source, which produces them one by one until no more can be produced. The 19 | * end of the stream may not be known in advance. The backing data source can 20 | * be a string, a file, a network resource, a transformation of another reader, 21 | * etc. 22 | */ 23 | abstract class Reader 24 | { 25 | /** 26 | * Checks whether more bytes can be read from this instance. 27 | * @return true if more bytes can be read, false otherwise. 28 | */ 29 | abstract public function canRead(); 30 | 31 | /** 32 | * Tries to read $n bytes from this instance. This method will read as many 33 | * bytes as required, except if the underlying byte stream ends. It means 34 | * that if the result does not contain $n bytes, there is no need to call 35 | * this method again; this instance can no longer read new bytes. 36 | * @return a string containing at most $n bytes, or null if no more bytes 37 | * can be read. 38 | */ 39 | abstract public function read($n); 40 | 41 | /** 42 | * Reads all remaining bytes from this instance. 43 | * @return a string containing all remaining bytes that this Read can read, 44 | * or null if no more bytes can be read. 45 | */ 46 | abstract public function readToTheEnd(); 47 | 48 | /** 49 | * Closes this instance, possibly dismissing the resources it was using. 50 | */ 51 | abstract public function close(); 52 | 53 | /** 54 | * Tries to read $n bytes from this instance, and return them as an 55 | * integer (in little-endian). 56 | * Note that depending on the platform, PHP may not be able to correctly 57 | * handle integers greater than 2**31. 58 | * @return at most $n bytes encoded into one integer, or 0 if no more 59 | * bytes can be read. 60 | */ 61 | public function readNumber($n) 62 | { 63 | $s = $this->read($n); 64 | $l = strlen($s); 65 | $r = 0; 66 | for($i = 0; $i < $l; $i++) 67 | $r += ord($s[$i]) << (8*$i); 68 | return $r; 69 | } 70 | 71 | /** 72 | * Tries to read one (1) byte from this instance, and return it as 73 | * an integer. 74 | * @return one read byte as an integer, or 0 if no more bytes can be read. 75 | */ 76 | public function readByte() 77 | { 78 | $s = $this->read(1); 79 | return empty($s) ? 0 : ord($s[0]); 80 | } 81 | } 82 | 83 | /** 84 | * An implementation of the Reader class, using a string as source. 85 | */ 86 | class StringReader extends Reader 87 | { 88 | private $_str; 89 | private $_n; 90 | private $_pt; 91 | 92 | /** 93 | * Constructs a new StringReader instance that reads the string $s. 94 | * @param $s A non-null string. 95 | */ 96 | public function __construct($s) 97 | { 98 | $this->_str = $s; 99 | $this->_pt = 0; 100 | $this->_n = strlen($s); 101 | } 102 | 103 | public function read($n) 104 | { 105 | if(!$this->canRead()) 106 | return null; 107 | 108 | $t = min($n, $this->_n - $this->_pt); 109 | $res = substr($this->_str, $this->_pt, $t); 110 | $this->_pt += $t; 111 | return $res; 112 | } 113 | 114 | public function canRead() 115 | { 116 | return $this->_pt < $this->_n; 117 | } 118 | 119 | public function readToTheEnd() 120 | { 121 | if(!$this->canRead()) 122 | return null; 123 | 124 | $res = substr($this->_str, $this->_pt); 125 | $this->_pt = $this->_n; 126 | return $res; 127 | } 128 | 129 | public function close() 130 | { 131 | $this->_str = null; 132 | $this->_n = 0; 133 | $this->_pt = 0; 134 | } 135 | } 136 | 137 | /** 138 | * A Reader implementation reading from a PHP resource pointer (such as a 139 | * pointer obtained through the function fopen). 140 | */ 141 | class ResourceReader extends Reader 142 | { 143 | private $_res; 144 | 145 | /** 146 | * Constructs a new ResourceReader instance that reads the PHP resource 147 | * pointer $f. 148 | * @param $f A PHP resource pointer. 149 | */ 150 | public function __construct($f) 151 | { 152 | $this->_res = $f; 153 | } 154 | 155 | /** 156 | * Creates a new ResourceReader instance reading the file $path. 157 | * @param $path A file path. 158 | * @return A new ResourceReader instance if $path could be opened and is 159 | * readable, false otherwise. 160 | */ 161 | static public function openFile($path) 162 | { 163 | if(is_readable($path)) 164 | { 165 | $f = fopen($path, 'rb'); 166 | if($f !== false) 167 | return new ResourceReader($f); 168 | } 169 | return null; 170 | } 171 | 172 | public function read($n) 173 | { 174 | if($this->canRead()) 175 | { 176 | $s = fread($this->_res, $n); 177 | if($s !== false) 178 | return $s; 179 | } 180 | return null; 181 | } 182 | 183 | public function readToTheEnd() 184 | { 185 | if(!$this->canRead()) 186 | return null; 187 | 188 | ob_start(); 189 | fpassthru($this->_res); 190 | $r = ob_get_contents(); 191 | ob_end_clean(); 192 | return $r; 193 | } 194 | 195 | public function canRead() 196 | { 197 | return !feof($this->_res); 198 | } 199 | 200 | public function close() 201 | { 202 | fclose($this->_res); 203 | } 204 | } 205 | 206 | /** 207 | * A Reader implementation, backed by another reader, which can compute the 208 | * hash of all the read data. 209 | */ 210 | class DigestReader extends Reader 211 | { 212 | private $_base; 213 | private $_resource; 214 | 215 | /** 216 | * Constructs a new DigestReader implementation, reading from the Reader 217 | * $reader and hashing all data with the algorithm $hashAlgo. 218 | * @param $reader A Reader instance. 219 | * @param $hashAlgo A hash algorithm name. 220 | */ 221 | public function __construct(Reader $reader, $hashAlgo) 222 | { 223 | $this->_base = $reader; 224 | $this->_resource = hash_init($hashAlgo); 225 | } 226 | 227 | public function read($n) 228 | { 229 | $s = $this->_base->read($n); 230 | if($s !== null) 231 | { 232 | hash_update($this->_resource, $s); 233 | return $s; 234 | } 235 | return null; 236 | } 237 | 238 | public function readToTheEnd() 239 | { 240 | $s = $this->_base->readToTheEnd(); 241 | if($s !== null) 242 | { 243 | hash_update($$this->_resource, $s); 244 | return $s; 245 | } 246 | return null; 247 | } 248 | 249 | public function canRead() 250 | { 251 | return $this->_base->canRead(); 252 | } 253 | 254 | public function close() 255 | { 256 | $this->_base->close(); 257 | } 258 | 259 | /** 260 | * Gets the hash of all read data so far. 261 | * @return A raw hash string. 262 | */ 263 | public function GetDigest() 264 | { 265 | return hash_final($this->_resource, true); 266 | } 267 | } 268 | 269 | /** 270 | * A Reader implementation, backed by another reader, decoding a stream made 271 | * of hashed blocks (used by KeePass). More precisely, it is a sequence of 272 | * blocks, each block containing some data and a hash of this data, in order to 273 | * control its integrity. The format of a block is the following: 274 | * - 4 bytes (little-endian integer): block index (starting from 0) 275 | * - 32 bytes: hash of the block data 276 | * - 4 bytes (little-endian integer): length (in bytes) of the block data 277 | * - n bytes: block data (where n is the number found previously) 278 | */ 279 | class HashedBlockReader extends Reader 280 | { 281 | private $_base; 282 | private $_hashAlgo; 283 | private $_hasError; 284 | private $_stopOnError; 285 | private $_currentIndex; 286 | private $_currentBlock; 287 | private $_currentSize; 288 | private $_currentPos; 289 | 290 | /** 291 | * Default block size used by KeePass. 292 | */ 293 | const DEFAULT_BLOCK_SIZE = 1048576; // 1024*1024 294 | 295 | /** 296 | * Constructs a new HashedBlockReader instance, reading from the reader 297 | * $reader and using the algorithm $hashAlgo to compute block hashs. 298 | * @param $reader A Reader instance. 299 | * @param $hashAlgo A hash algorithm name. 300 | * @param $stopOnError Whether to stop reading immediatly when an integrity 301 | * check fails. If set to false, reading will continue after an 302 | * error but it may well be complete garbage. 303 | */ 304 | public function __construct(Reader $reader, $hashAlgo, $stopOnError = true) 305 | { 306 | $this->_base = $reader; 307 | $this->_hashAlgo = $hashAlgo; 308 | $this->_stopOnError = $stopOnError; 309 | $this->_hasError = false; 310 | $this->_currentIndex = 0; 311 | $this->_currentBlock = null; 312 | $this->_currentSize = 0; 313 | $this->_currentPos = 0; 314 | } 315 | 316 | public function read($n) 317 | { 318 | $s = ""; 319 | $remaining = $n; 320 | while($remaining > 0) 321 | { 322 | if($this->_currentPos >= $this->_currentSize) 323 | if(!$this->readBlock()) 324 | return $s; 325 | $t = min($remaining, $this->_currentSize - $this->_currentPos); 326 | $s .= substr($this->_currentBlock, $this->_currentPos, $t); 327 | $this->_currentPos += $t; 328 | $remaining -= $t; 329 | } 330 | return $s; 331 | } 332 | 333 | public function readToTheEnd() 334 | { 335 | $s = $this->read($this->_currentSize - $this->_currentPos); 336 | while($this->readBlock()) 337 | $s .= $this->_currentBlock; 338 | return $s; 339 | } 340 | 341 | public function canRead() 342 | { 343 | return (!$this->_hasError || !$this->_stopOnError) && 344 | $this->_base->canRead(); 345 | } 346 | 347 | public function close() 348 | { 349 | $this->_base->close(); 350 | } 351 | 352 | /** 353 | * Whether this instance data is corrupted. 354 | * @return true if the data read so far is corrupted, false otherwise. 355 | */ 356 | public function isCorrupted() 357 | { 358 | return $this->_hasError; 359 | } 360 | 361 | private function readBlock() 362 | { 363 | if(!$this->canRead()) 364 | return false; 365 | 366 | $bl = $this->_base->read(4); 367 | if($bl != pack('V', $this->_currentIndex)) 368 | { 369 | $this->_hasError = true; 370 | if($this->_stopOnError) 371 | return false; 372 | } 373 | $this->_currentIndex++; 374 | 375 | $hash = $this->_base->read(32); 376 | if(strlen($hash) != 32) 377 | { 378 | $this->_hasError = true; 379 | return false; 380 | } 381 | 382 | // May not work on 32 bit platforms if $blockSize is greather 383 | // than 2**31, but in KeePass implementation it is set at 2**20. 384 | $blockSize = $this->_base->readNumber(4); 385 | if($blockSize <= 0) 386 | return false; 387 | 388 | $block = $this->_base->read($blockSize); 389 | if(strlen($block) != $blockSize) 390 | { 391 | $this->_hasError = true; 392 | return false; 393 | } 394 | 395 | if($hash !== hash($this->_hashAlgo, $block, true)) 396 | { 397 | $this->_hasError = true; 398 | if($this->_stopOnError) 399 | return false; 400 | } 401 | 402 | $this->_currentBlock = $block; 403 | $this->_currentSize = $blockSize; 404 | $this->_currentPos = 0; 405 | return true; 406 | } 407 | 408 | /** 409 | * Computes the hashed-by-blocks version of the string $source: splits it 410 | * in blocks, computes each block hash, and concats everything together in 411 | * a string that can be read again with a HashedBlockReader instance. 412 | * @param $source The string to hash by blocks. 413 | * @param $hashAlgo A hash algorithm name. 414 | * @return The hashed-by-blocks version of $source. 415 | */ 416 | public static function hashString($source, $hashAlgo) 417 | { 418 | $len = strlen($source); 419 | $blockSize = self::DEFAULT_BLOCK_SIZE; 420 | $binBlockSize = pack("V", $blockSize); 421 | $r = ""; 422 | 423 | $blockIndex = 0; 424 | $i = 0; 425 | while($len >= $i + $blockSize) 426 | { 427 | $block = substr($source, $i, $blockSize); 428 | $r .= pack("V", $blockIndex) 429 | . hash($hashAlgo, $block, true) 430 | . $binBlockSize 431 | . $block; 432 | $i += $blockSize; 433 | $blockIndex++; 434 | } 435 | $rem = $len - $i; 436 | if($rem != 0) { 437 | $block = substr($source, $i); 438 | $r .= pack("V", $blockIndex) 439 | . hash($hashAlgo, $block, true) 440 | . pack("V", strlen($block)) 441 | . $block; 442 | } 443 | return $r; 444 | } 445 | } 446 | 447 | ?> -------------------------------------------------------------------------------- /keepassphp/util/salsa20stream.php: -------------------------------------------------------------------------------- 1 | 25 | * @copyright Louis Traynard 26 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 27 | * @link https://github.com/shkdee/KeePassPHP 28 | */ 29 | class Salsa20Stream implements iRandomStream 30 | { 31 | private $_state; 32 | private $_output; 33 | private $_outputPos; 34 | 35 | const STATE_LEN = 32; 36 | const KEY_LEN = 32; 37 | const OUTPUT_LEN = 64; 38 | const IV_LEN = 8; 39 | 40 | /** 41 | * Creates a new Salsa20Stream instance. 42 | * @param $key The 32-byte-long string to use as key. 43 | * @param $iv The 8-byte-long string to use as initialization vector. 44 | * @return A new Salsa20Stream instance, of null if $key or $iv do not have 45 | * a suitable length. 46 | */ 47 | public static function create($key, $iv) 48 | { 49 | if (strlen($key) != self::KEY_LEN || strlen($iv) != self::IV_LEN) 50 | return null; 51 | return new Salsa20Stream($key, $iv); 52 | } 53 | 54 | private function __construct($key, $iv) 55 | { 56 | $this->_state = array(); 57 | for ($i = 0; $i < self::STATE_LEN; $i++) 58 | $this->_state[$i] = 0; 59 | 60 | $this->_output = array(); 61 | for ($i = 0; $i < self::OUTPUT_LEN; $i++) 62 | $this->_output[$i] = 0; 63 | 64 | $this->_outputPos = self::OUTPUT_LEN; 65 | $this->keySetup(array_values(unpack("v16", $key))); 66 | $this->ivSetup(array_values(unpack("v4", $iv))); 67 | } 68 | 69 | private function keySetup(array $key) 70 | { 71 | for ($i = 0; $i < 4; $i++) 72 | { 73 | $j = 2 * $i; 74 | $this->_state[2 * $i + 2] = $key[$j]; 75 | $this->_state[2 * $i + 3] = $key[$j + 1]; 76 | $this->_state[2 * $i + 22] = $key[$j + 8]; 77 | $this->_state[2 * $i + 23] = $key[$j + 9]; 78 | } 79 | $this->_state[0] = 0x7865; 80 | $this->_state[1] = 0x6170; 81 | $this->_state[10] = 0x646E; 82 | $this->_state[11] = 0x3320; 83 | $this->_state[20] = 0x2D32; 84 | $this->_state[21] = 0x7962; 85 | $this->_state[30] = 0x6574; 86 | $this->_state[31] = 0x6B20; 87 | } 88 | 89 | private function ivSetup(array $iv) 90 | { 91 | $this->_state[12] = $iv[0]; 92 | $this->_state[13] = $iv[1]; 93 | $this->_state[14] = $iv[2]; 94 | $this->_state[15] = $iv[3]; 95 | $this->_state[16] = 0; 96 | $this->_state[17] = 0; 97 | $this->_state[18] = 0; 98 | $this->_state[19] = 0; 99 | } 100 | 101 | private static function addRotXor(&$x, $i, $j, $b, $target) 102 | { 103 | $s = $x[2 * $i] + $x[2 * $j]; 104 | $r = $s >> 16; 105 | $s = $s & 0xFFFF; 106 | $t = ($x[2 * $i + 1] + $x[2 * $j + 1] + $r) & 0xFFFF; 107 | 108 | $m = $b < 16 ? 0 : 1; 109 | $b = $b % 16; 110 | $nt = (($t << $b) & 0xFFFF) | ($s >> (16 - $b)); 111 | $ns = (($s << $b) & 0xFFFF) | ($t >> (16 - $b)); 112 | $x[2*$target + $m] = $x[2*$target + $m] ^ $ns; 113 | $x[2*$target + 1 - $m] = $x[2*$target + 1 - $m] ^ $nt; 114 | } 115 | 116 | private function nextOutput() 117 | { 118 | $x = array(); 119 | for ($i = 0; $i < self::STATE_LEN; $i++) 120 | $x[$i] = $this->_state[$i]; 121 | 122 | for ($i = 0; $i < 10; $i++) 123 | { 124 | $this->addRotXor($x, 0, 12, 7, 4); 125 | $this->addRotXor($x, 4, 0, 9, 8); 126 | $this->addRotXor($x, 8, 4, 13, 12); 127 | $this->addRotXor($x, 12, 8, 18, 0); 128 | $this->addRotXor($x, 5, 1, 7, 9); 129 | $this->addRotXor($x, 9, 5, 9, 13); 130 | $this->addRotXor($x, 13, 9, 13, 1); 131 | $this->addRotXor($x, 1, 13, 18, 5); 132 | $this->addRotXor($x, 10, 6, 7, 14); 133 | $this->addRotXor($x, 14, 10, 9, 2); 134 | $this->addRotXor($x, 2, 14, 13, 6); 135 | $this->addRotXor($x, 6, 2, 18, 10); 136 | $this->addRotXor($x, 15, 11, 7, 3); 137 | $this->addRotXor($x, 3, 15, 9, 7); 138 | $this->addRotXor($x, 7, 3, 13, 11); 139 | $this->addRotXor($x, 11, 7, 18, 15); 140 | $this->addRotXor($x, 0, 3, 7, 1); 141 | $this->addRotXor($x, 1, 0, 9, 2); 142 | $this->addRotXor($x, 2, 1, 13, 3); 143 | $this->addRotXor($x, 3, 2, 18, 0); 144 | $this->addRotXor($x, 5, 4, 7, 6); 145 | $this->addRotXor($x, 6, 5, 9, 7); 146 | $this->addRotXor($x, 7, 6, 13, 4); 147 | $this->addRotXor($x, 4, 7, 18, 5); 148 | $this->addRotXor($x, 10, 9, 7, 11); 149 | $this->addRotXor($x, 11, 10, 9, 8); 150 | $this->addRotXor($x, 8, 11, 13, 9); 151 | $this->addRotXor($x, 9, 8, 18, 10); 152 | $this->addRotXor($x, 15, 14, 7, 12); 153 | $this->addRotXor($x, 12, 15, 9, 13); 154 | $this->addRotXor($x, 13, 12, 13, 14); 155 | $this->addRotXor($x, 14, 13, 18, 15); 156 | } 157 | 158 | for ($i = 0; $i < self::STATE_LEN; $i+= 2) 159 | { 160 | $s = $x[$i] + $this->_state[$i]; 161 | $x[$i] = $s & 0xFFFF; 162 | $x[$i + 1] = ($x[$i + 1] + $this->_state[$i + 1] + ($s >> 16)) & 0xFFFF; 163 | } 164 | 165 | $out = ""; 166 | for ($i = 0; $i < self::STATE_LEN; $i++) 167 | $out .= pack("v", $x[$i]); 168 | 169 | $this->_output = $out; 170 | $this->_outputPos = 0; 171 | $this->_state[16]++; 172 | if ($this->_state[16] == 0xFFFF) 173 | { 174 | $this->_state[16] = 0; 175 | $this->_state[17]++; 176 | if ($this->_state[17] == 0xFFFF) 177 | { 178 | $this->_state[17] = 0; 179 | $this->_state[18]++; 180 | if ($this->_state[18] == 0xFFFF) 181 | { 182 | $this->_state[18] = 0; 183 | $this->_state[19]++; 184 | } 185 | } 186 | } 187 | } 188 | 189 | /** 190 | * Generates $n random bytes and returns them as a string. 191 | * @param $n The number of bytes to generate. 192 | * @return A $n-long string. 193 | */ 194 | public function getNextBytes($n) 195 | { 196 | $s = ""; 197 | $nRem = $n; 198 | while ($nRem > 0) 199 | { 200 | if ($this->_outputPos == 64) 201 | $this->nextOutput(); 202 | $nCopy = min(64 - $this->_outputPos, $nRem); 203 | $s .= substr($this->_output, $this->_outputPos, $nCopy); 204 | 205 | $nRem -= $nCopy; 206 | $this->_outputPos += $nCopy; 207 | } 208 | return $s; 209 | } 210 | } 211 | 212 | ?> -------------------------------------------------------------------------------- /keepassphp/util/uploadmanager.php: -------------------------------------------------------------------------------- 1 | 20 | * @copyright Louis Traynard 21 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 22 | * @link https://github.com/shkdee/KeePassPHP 23 | */ 24 | class UploadManager extends FileManager 25 | { 26 | /** 27 | * Builds a new UploadManager storing files in the directory $dir, and 28 | * using the prefix $prefix for filenames. 29 | * @param $dir A directory path. 30 | * @param $prefix A filename prefix. 31 | */ 32 | public function __construct($dir, $prefix) 33 | { 34 | parent::__construct($dir, $prefix, true, false); 35 | } 36 | 37 | /** 38 | * Adds the uploaded file whose temporary name is $value to the collection, 39 | * using the hash of the file itself as key (so retrieving that file will 40 | * be possible only with that hash). Gives the extension $ext to the new 41 | * file. If $writeable is false, nothing will be done (this is a file-only 42 | * collection), and if $override is true, if the given file already exists 43 | * in the collection, it will be overriden. 44 | * @param $value An uploaded file temporary path. 45 | * @param $ext The final file extension. 46 | * @param $writeable Whether this file can be written on disk. 47 | * @param $override Whether to override an already existing file. 48 | * @return The hash of the file, or null in case of error. 49 | */ 50 | public function add($value, $ext = self::DEFAULT_EXT, $writeable = true, 51 | $override = false) 52 | { 53 | $h = sha1_file($value); 54 | return $this->addElement($h, $value, $ext, $writeable, $override) ? 55 | $h : null; 56 | } 57 | 58 | /** 59 | * Moves the given temporary file $v to its new location, defined by the 60 | * hash $h and the extension $ext. If $writeable is false, nothing is done 61 | * (this is a file-only collection) and if $override is true, the 62 | * corresponding file will be overriden if it already exists. 63 | * @param $h The file hash. 64 | * @param $v The uploaded file temporary path. 65 | * @param $ext The final file extension. 66 | * @param $writeable Whether the file can be written on disk. 67 | * @param $override Whether to override an already existing file. 68 | * @return true if the file was successfully added to the collection or if 69 | * it already exists, and false otherwise (if no file corresponding 70 | * to the hash $h exists). 71 | * @throws Exception If this FileManager directory is not accessible. 72 | */ 73 | protected function addElement($h, $v, $ext, $writeable, $override) 74 | { 75 | $this->load(); 76 | $fileexists = array_key_exists($h, $this->elements); 77 | if((!$override && $fileexists)) 78 | return true; 79 | if(!$writeable || !$this->acceptFiles) 80 | return false; 81 | 82 | $filename = $this->filename($h, $ext); 83 | if(!@move_uploaded_file($v, $this->dir . $filename)) 84 | return false; 85 | 86 | if($fileexists && $this->elements[$h][1] != $filename) 87 | $this->removeFile($this->elements[$h][1]); 88 | $this->elements[$h] = array(self::TYPE_FILE, $filename); 89 | return true; 90 | } 91 | } 92 | 93 | ?> -------------------------------------------------------------------------------- /keepassphp/vendor/random_compat-2.0.10/lib/byte_safe_strings.php: -------------------------------------------------------------------------------- 1 | RandomCompat_strlen($binary_string)) { 133 | return ''; 134 | } 135 | 136 | return (string) mb_substr($binary_string, $start, $length, '8bit'); 137 | } 138 | 139 | } else { 140 | 141 | /** 142 | * substr() implementation that isn't brittle to mbstring.func_overload 143 | * 144 | * This version just uses the default substr() 145 | * 146 | * @param string $binary_string 147 | * @param int $start 148 | * @param int $length (optional) 149 | * 150 | * @throws TypeError 151 | * 152 | * @return string 153 | */ 154 | function RandomCompat_substr($binary_string, $start, $length = null) 155 | { 156 | if (!is_string($binary_string)) { 157 | throw new TypeError( 158 | 'RandomCompat_substr(): First argument should be a string' 159 | ); 160 | } 161 | 162 | if (!is_int($start)) { 163 | throw new TypeError( 164 | 'RandomCompat_substr(): Second argument should be an integer' 165 | ); 166 | } 167 | 168 | if ($length !== null) { 169 | if (!is_int($length)) { 170 | throw new TypeError( 171 | 'RandomCompat_substr(): Third argument should be an integer, or omitted' 172 | ); 173 | } 174 | 175 | return (string) substr($binary_string, $start, $length); 176 | } 177 | 178 | return (string) substr($binary_string, $start); 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /keepassphp/vendor/random_compat-2.0.10/lib/cast_to_int.php: -------------------------------------------------------------------------------- 1 | operators might accidentally let a float 38 | * through. 39 | * 40 | * @param int|float $number The number we want to convert to an int 41 | * @param boolean $fail_open Set to true to not throw an exception 42 | * 43 | * @return float|int 44 | * 45 | * @throws TypeError 46 | */ 47 | function RandomCompat_intval($number, $fail_open = false) 48 | { 49 | if (is_int($number) || is_float($number)) { 50 | $number += 0; 51 | } elseif (is_numeric($number)) { 52 | $number += 0; 53 | } 54 | 55 | if ( 56 | is_float($number) 57 | && 58 | $number > ~PHP_INT_MAX 59 | && 60 | $number < PHP_INT_MAX 61 | ) { 62 | $number = (int) $number; 63 | } 64 | 65 | if (is_int($number)) { 66 | return (int) $number; 67 | } elseif (!$fail_open) { 68 | throw new TypeError( 69 | 'Expected an integer.' 70 | ); 71 | } 72 | return $number; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /keepassphp/vendor/random_compat-2.0.10/lib/error_polyfill.php: -------------------------------------------------------------------------------- 1 | = 70000) { 48 | return; 49 | } 50 | 51 | if (!defined('RANDOM_COMPAT_READ_BUFFER')) { 52 | define('RANDOM_COMPAT_READ_BUFFER', 8); 53 | } 54 | 55 | $RandomCompatDIR = dirname(__FILE__); 56 | 57 | require_once $RandomCompatDIR . '/byte_safe_strings.php'; 58 | require_once $RandomCompatDIR . '/cast_to_int.php'; 59 | require_once $RandomCompatDIR . '/error_polyfill.php'; 60 | 61 | if (!is_callable('random_bytes')) { 62 | /** 63 | * PHP 5.2.0 - 5.6.x way to implement random_bytes() 64 | * 65 | * We use conditional statements here to define the function in accordance 66 | * to the operating environment. It's a micro-optimization. 67 | * 68 | * In order of preference: 69 | * 1. Use libsodium if available. 70 | * 2. fread() /dev/urandom if available (never on Windows) 71 | * 3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM) 72 | * 4. COM('CAPICOM.Utilities.1')->GetRandom() 73 | * 74 | * See RATIONALE.md for our reasoning behind this particular order 75 | */ 76 | if (extension_loaded('libsodium')) { 77 | // See random_bytes_libsodium.php 78 | if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) { 79 | require_once $RandomCompatDIR . '/random_bytes_libsodium.php'; 80 | } elseif (method_exists('Sodium', 'randombytes_buf')) { 81 | require_once $RandomCompatDIR . '/random_bytes_libsodium_legacy.php'; 82 | } 83 | } 84 | 85 | /** 86 | * Reading directly from /dev/urandom: 87 | */ 88 | if (DIRECTORY_SEPARATOR === '/') { 89 | // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast 90 | // way to exclude Windows. 91 | $RandomCompatUrandom = true; 92 | $RandomCompat_basedir = ini_get('open_basedir'); 93 | 94 | if (!empty($RandomCompat_basedir)) { 95 | $RandomCompat_open_basedir = explode( 96 | PATH_SEPARATOR, 97 | strtolower($RandomCompat_basedir) 98 | ); 99 | $RandomCompatUrandom = (array() !== array_intersect( 100 | array('/dev', '/dev/', '/dev/urandom'), 101 | $RandomCompat_open_basedir 102 | )); 103 | $RandomCompat_open_basedir = null; 104 | } 105 | 106 | if ( 107 | !is_callable('random_bytes') 108 | && 109 | $RandomCompatUrandom 110 | && 111 | @is_readable('/dev/urandom') 112 | ) { 113 | // Error suppression on is_readable() in case of an open_basedir 114 | // or safe_mode failure. All we care about is whether or not we 115 | // can read it at this point. If the PHP environment is going to 116 | // panic over trying to see if the file can be read in the first 117 | // place, that is not helpful to us here. 118 | 119 | // See random_bytes_dev_urandom.php 120 | require_once $RandomCompatDIR . '/random_bytes_dev_urandom.php'; 121 | } 122 | // Unset variables after use 123 | $RandomCompat_basedir = null; 124 | } else { 125 | $RandomCompatUrandom = false; 126 | } 127 | 128 | /** 129 | * mcrypt_create_iv() 130 | * 131 | * We only want to use mcypt_create_iv() if: 132 | * 133 | * - random_bytes() hasn't already been defined 134 | * - the mcrypt extensions is loaded 135 | * - One of these two conditions is true: 136 | * - We're on Windows (DIRECTORY_SEPARATOR !== '/') 137 | * - We're not on Windows and /dev/urandom is readabale 138 | * (i.e. we're not in a chroot jail) 139 | * - Special case: 140 | * - If we're not on Windows, but the PHP version is between 141 | * 5.6.10 and 5.6.12, we don't want to use mcrypt. It will 142 | * hang indefinitely. This is bad. 143 | * - If we're on Windows, we want to use PHP >= 5.3.7 or else 144 | * we get insufficient entropy errors. 145 | */ 146 | if ( 147 | !is_callable('random_bytes') 148 | && 149 | // Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be. 150 | (DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307) 151 | && 152 | // Prevent this code from hanging indefinitely on non-Windows; 153 | // see https://bugs.php.net/bug.php?id=69833 154 | ( 155 | DIRECTORY_SEPARATOR !== '/' || 156 | (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613) 157 | ) 158 | && 159 | extension_loaded('mcrypt') 160 | ) { 161 | // See random_bytes_mcrypt.php 162 | require_once $RandomCompatDIR . '/random_bytes_mcrypt.php'; 163 | } 164 | $RandomCompatUrandom = null; 165 | 166 | /** 167 | * This is a Windows-specific fallback, for when the mcrypt extension 168 | * isn't loaded. 169 | */ 170 | if ( 171 | !is_callable('random_bytes') 172 | && 173 | extension_loaded('com_dotnet') 174 | && 175 | class_exists('COM') 176 | ) { 177 | $RandomCompat_disabled_classes = preg_split( 178 | '#\s*,\s*#', 179 | strtolower(ini_get('disable_classes')) 180 | ); 181 | 182 | if (!in_array('com', $RandomCompat_disabled_classes)) { 183 | try { 184 | $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1'); 185 | if (method_exists($RandomCompatCOMtest, 'GetRandom')) { 186 | // See random_bytes_com_dotnet.php 187 | require_once $RandomCompatDIR . '/random_bytes_com_dotnet.php'; 188 | } 189 | } catch (com_exception $e) { 190 | // Don't try to use it. 191 | } 192 | } 193 | $RandomCompat_disabled_classes = null; 194 | $RandomCompatCOMtest = null; 195 | } 196 | 197 | /** 198 | * throw new Exception 199 | */ 200 | if (!is_callable('random_bytes')) { 201 | /** 202 | * We don't have any more options, so let's throw an exception right now 203 | * and hope the developer won't let it fail silently. 204 | * 205 | * @param mixed $length 206 | * @return void 207 | * @throws Exception 208 | */ 209 | function random_bytes($length) 210 | { 211 | unset($length); // Suppress "variable not used" warnings. 212 | throw new Exception( 213 | 'There is no suitable CSPRNG installed on your system' 214 | ); 215 | } 216 | } 217 | } 218 | 219 | if (!is_callable('random_int')) { 220 | require_once $RandomCompatDIR . '/random_int.php'; 221 | } 222 | 223 | $RandomCompatDIR = null; 224 | -------------------------------------------------------------------------------- /keepassphp/vendor/random_compat-2.0.10/lib/random_bytes_com_dotnet.php: -------------------------------------------------------------------------------- 1 | GetRandom($bytes, 0)); 72 | if (RandomCompat_strlen($buf) >= $bytes) { 73 | /** 74 | * Return our random entropy buffer here: 75 | */ 76 | return RandomCompat_substr($buf, 0, $bytes); 77 | } 78 | ++$execCount; 79 | } while ($execCount < $bytes); 80 | 81 | /** 82 | * If we reach here, PHP has failed us. 83 | */ 84 | throw new Exception( 85 | 'Could not gather sufficient random data' 86 | ); 87 | } 88 | } -------------------------------------------------------------------------------- /keepassphp/vendor/random_compat-2.0.10/lib/random_bytes_dev_urandom.php: -------------------------------------------------------------------------------- 1 | 0); 146 | 147 | /** 148 | * Is our result valid? 149 | */ 150 | if (is_string($buf)) { 151 | if (RandomCompat_strlen($buf) === $bytes) { 152 | /** 153 | * Return our random entropy buffer here: 154 | */ 155 | return $buf; 156 | } 157 | } 158 | } 159 | 160 | /** 161 | * If we reach here, PHP has failed us. 162 | */ 163 | throw new Exception( 164 | 'Error reading from source device' 165 | ); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /keepassphp/vendor/random_compat-2.0.10/lib/random_bytes_libsodium.php: -------------------------------------------------------------------------------- 1 | 2147483647) { 64 | $buf = ''; 65 | for ($i = 0; $i < $bytes; $i += 1073741824) { 66 | $n = ($bytes - $i) > 1073741824 67 | ? 1073741824 68 | : $bytes - $i; 69 | $buf .= \Sodium\randombytes_buf($n); 70 | } 71 | } else { 72 | $buf = \Sodium\randombytes_buf($bytes); 73 | } 74 | 75 | if ($buf !== false) { 76 | if (RandomCompat_strlen($buf) === $bytes) { 77 | return $buf; 78 | } 79 | } 80 | 81 | /** 82 | * If we reach here, PHP has failed us. 83 | */ 84 | throw new Exception( 85 | 'Could not gather sufficient random data' 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /keepassphp/vendor/random_compat-2.0.10/lib/random_bytes_libsodium_legacy.php: -------------------------------------------------------------------------------- 1 | 2147483647) { 69 | for ($i = 0; $i < $bytes; $i += 1073741824) { 70 | $n = ($bytes - $i) > 1073741824 71 | ? 1073741824 72 | : $bytes - $i; 73 | $buf .= Sodium::randombytes_buf($n); 74 | } 75 | } else { 76 | $buf .= Sodium::randombytes_buf($bytes); 77 | } 78 | 79 | if (is_string($buf)) { 80 | if (RandomCompat_strlen($buf) === $bytes) { 81 | return $buf; 82 | } 83 | } 84 | 85 | /** 86 | * If we reach here, PHP has failed us. 87 | */ 88 | throw new Exception( 89 | 'Could not gather sufficient random data' 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /keepassphp/vendor/random_compat-2.0.10/lib/random_bytes_mcrypt.php: -------------------------------------------------------------------------------- 1 | operators might accidentally let a float 50 | * through. 51 | */ 52 | 53 | try { 54 | $min = RandomCompat_intval($min); 55 | } catch (TypeError $ex) { 56 | throw new TypeError( 57 | 'random_int(): $min must be an integer' 58 | ); 59 | } 60 | 61 | try { 62 | $max = RandomCompat_intval($max); 63 | } catch (TypeError $ex) { 64 | throw new TypeError( 65 | 'random_int(): $max must be an integer' 66 | ); 67 | } 68 | 69 | /** 70 | * Now that we've verified our weak typing system has given us an integer, 71 | * let's validate the logic then we can move forward with generating random 72 | * integers along a given range. 73 | */ 74 | if ($min > $max) { 75 | throw new Error( 76 | 'Minimum value must be less than or equal to the maximum value' 77 | ); 78 | } 79 | 80 | if ($max === $min) { 81 | return $min; 82 | } 83 | 84 | /** 85 | * Initialize variables to 0 86 | * 87 | * We want to store: 88 | * $bytes => the number of random bytes we need 89 | * $mask => an integer bitmask (for use with the &) operator 90 | * so we can minimize the number of discards 91 | */ 92 | $attempts = $bits = $bytes = $mask = $valueShift = 0; 93 | 94 | /** 95 | * At this point, $range is a positive number greater than 0. It might 96 | * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to 97 | * a float and we will lose some precision. 98 | */ 99 | $range = $max - $min; 100 | 101 | /** 102 | * Test for integer overflow: 103 | */ 104 | if (!is_int($range)) { 105 | 106 | /** 107 | * Still safely calculate wider ranges. 108 | * Provided by @CodesInChaos, @oittaa 109 | * 110 | * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435 111 | * 112 | * We use ~0 as a mask in this case because it generates all 1s 113 | * 114 | * @ref https://eval.in/400356 (32-bit) 115 | * @ref http://3v4l.org/XX9r5 (64-bit) 116 | */ 117 | $bytes = PHP_INT_SIZE; 118 | $mask = ~0; 119 | 120 | } else { 121 | 122 | /** 123 | * $bits is effectively ceil(log($range, 2)) without dealing with 124 | * type juggling 125 | */ 126 | while ($range > 0) { 127 | if ($bits % 8 === 0) { 128 | ++$bytes; 129 | } 130 | ++$bits; 131 | $range >>= 1; 132 | $mask = $mask << 1 | 1; 133 | } 134 | $valueShift = $min; 135 | } 136 | 137 | $val = 0; 138 | /** 139 | * Now that we have our parameters set up, let's begin generating 140 | * random integers until one falls between $min and $max 141 | */ 142 | do { 143 | /** 144 | * The rejection probability is at most 0.5, so this corresponds 145 | * to a failure probability of 2^-128 for a working RNG 146 | */ 147 | if ($attempts > 128) { 148 | throw new Exception( 149 | 'random_int: RNG is broken - too many rejections' 150 | ); 151 | } 152 | 153 | /** 154 | * Let's grab the necessary number of random bytes 155 | */ 156 | $randomByteString = random_bytes($bytes); 157 | 158 | /** 159 | * Let's turn $randomByteString into an integer 160 | * 161 | * This uses bitwise operators (<< and |) to build an integer 162 | * out of the values extracted from ord() 163 | * 164 | * Example: [9F] | [6D] | [32] | [0C] => 165 | * 159 + 27904 + 3276800 + 201326592 => 166 | * 204631455 167 | */ 168 | $val &= 0; 169 | for ($i = 0; $i < $bytes; ++$i) { 170 | $val |= ord($randomByteString[$i]) << ($i * 8); 171 | } 172 | 173 | /** 174 | * Apply mask 175 | */ 176 | $val &= $mask; 177 | $val += $valueShift; 178 | 179 | ++$attempts; 180 | /** 181 | * If $val overflows to a floating point number, 182 | * ... or is larger than $max, 183 | * ... or smaller than $min, 184 | * then try again. 185 | */ 186 | } while (!is_int($val) || $val > $max || $val < $min); 187 | 188 | return (int)$val; 189 | } 190 | } 191 | --------------------------------------------------------------------------------