pax_global_header00006660000000000000000000000064133257327740014527gustar00rootroot0000000000000052 comment=0f407c43b953d571421e0020ba92082ed5fb7620 php-encryption-2.2.1/000077500000000000000000000000001332573277400145105ustar00rootroot00000000000000php-encryption-2.2.1/.gitignore000066400000000000000000000002571332573277400165040ustar00rootroot00000000000000*~ /test/unit/File/big-generated-file /composer.lock /vendor defuse-crypto.phar defuse-crypto.phar.sig composer.phar box.phar phpunit.phar phpunit.phar.asc test/unit/File/tmp php-encryption-2.2.1/.php_cs000066400000000000000000000032411332573277400157650ustar00rootroot00000000000000level(Symfony\CS\FixerInterface::PSR2_LEVEL) ->fixers([ 'blankline_after_open_tag', 'empty_return', 'extra_empty_lines', 'function_typehint_space', 'join_function', 'method_argument_default_value', 'multiline_array_trailing_comma', 'no_blank_lines_after_class_opening', 'no_empty_lines_after_phpdocs', 'phpdoc_indent', 'phpdoc_no_access', 'phpdoc_no_empty_return', 'phpdoc_no_package', 'phpdoc_params', 'phpdoc_scalar', 'phpdoc_separation', 'phpdoc_trim', 'phpdoc_type_to_var', 'phpdoc_types', 'phpdoc_var_without_name', 'remove_leading_slash_use', 'remove_lines_between_uses', 'short_bool_cast', 'single_quote', 'spaces_after_semicolon', 'spaces_before_semicolon', 'spaces_cast', 'standardize_not_equal', 'ternary_spaces', 'trim_array_spaces', 'unneeded_control_parentheses', 'unused_use', 'whitespacy_lines', 'align_double_arrow', 'concat_with_spaces', 'logical_not_operators_with_successor_space', 'multiline_spaces_before_semicolon', 'newline_after_open_tag', 'ordered_use', 'php_unit_construct', 'phpdoc_order', 'short_array_syntax', ]); if (null === $input->getArgument('path')) { $config ->finder( Symfony\CS\Finder\DefaultFinder::create() ->in('src') ->in('test') ->exclude('vendor') ); } return $config; php-encryption-2.2.1/LICENSE000066400000000000000000000022101332573277400155100ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016 Taylor Hornby and Paragon Initiative Enterprises . 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. php-encryption-2.2.1/README.md000066400000000000000000000111171332573277400157700ustar00rootroot00000000000000php-encryption =============== [![Build Status](https://travis-ci.org/defuse/php-encryption.svg?branch=master)](https://travis-ci.org/defuse/php-encryption) [![codecov](https://codecov.io/gh/defuse/php-encryption/branch/master/graph/badge.svg)](https://codecov.io/gh/defuse/php-encryption) [![Latest Stable Version](https://poser.pugx.org/defuse/php-encryption/v/stable)](https://packagist.org/packages/defuse/php-encryption) [![Latest Unstable Version](https://poser.pugx.org/defuse/php-encryption/v/unstable)](https://packagist.org/packages/defuse/php-encryption) [![License](https://poser.pugx.org/defuse/php-encryption/license)](https://packagist.org/packages/defuse/php-encryption) [![Downloads](https://img.shields.io/packagist/dt/defuse/php-encryption.svg)](https://packagist.org/packages/defuse/php-encryption) This is a library for encrypting data with a key or password in PHP. **It requires PHP 5.6 or newer and OpenSSL 1.0.1 or newer.** The current version is v2.2.1, which is expected to remain stable and supported by its authors with security and bugfixes until at least January 1st, 2020. The library is a joint effort between [Taylor Hornby](https://defuse.ca/) and [Scott Arciszewski](https://paragonie.com/blog/author/scott-arcizewski) as well as numerous open-source contributors. What separates this library from other PHP encryption libraries is, firstly, that it is secure. The authors used to encounter insecure PHP encryption code on a daily basis, so they created this library to bring more security to the ecosystem. Secondly, this library is "difficult to misuse." Like [libsodium](https://github.com/jedisct1/libsodium), its API is designed to be easy to use in a secure way and hard to use in an insecure way. Dependencies ------------ This library requires no special dependencies except for PHP 5.6 or newer with the OpenSSL extensions (version 1.0.1 or later) enabled (this is the default). It uses [random\_compat](https://github.com/paragonie/random_compat), which is bundled in with this library so that your users will not need to follow any special installation steps. Getting Started ---------------- Start with the [**Tutorial**](docs/Tutorial.md). You can find instructions for obtaining this library's code securely in the [Installing and Verifying](docs/InstallingAndVerifying.md) documentation. After you've read the tutorial and got the code, refer to the formal documentation for each of the classes this library provides: - [Crypto](docs/classes/Crypto.md) - [File](docs/classes/File.md) - [Key](docs/classes/Key.md) - [KeyProtectedByPassword](docs/classes/KeyProtectedByPassword.md) If you encounter difficulties, see the [FAQ](docs/FAQ.md) answers. The fixes to the most commonly-reported problems are explained there. If you're a cryptographer and want to understand the nitty-gritty details of how this library works, look at the [Cryptography Details](docs/CryptoDetails.md) documentation. If you're interested in contributing to this library, see the [Internal Developer Documentation](docs/InternalDeveloperDocs.md). Other Language Support ---------------------- This library is intended for server-side PHP software that needs to encrypt data at rest. If you are building software that needs to encrypt client-side, or building a system that requires cross-platform encryption/decryption support, we strongly recommend using [libsodium](https://download.libsodium.org/doc/bindings_for_other_languages) instead. Examples --------- If the documentation is not enough for you to understand how to use this library, then you can look at an example project that uses this library: - [encutil](https://github.com/defuse/encutil) - [fileencrypt](https://github.com/tsusanka/fileencrypt) Security Audit Status --------------------- This code has not been subjected to a formal, paid, security audit. However, it has received lots of review from members of the PHP security community, and the authors are experienced with cryptography. In all likelihood, you are safer using this library than almost any other encryption library for PHP. If you use this library as a part of your business and would like to help fund a formal audit, please [contact Taylor Hornby](https://defuse.ca/contact.htm). Public Keys ------------ The GnuPG public key used to sign releases is available in [dist/signingkey.asc](https://github.com/defuse/php-encryption/raw/master/dist/signingkey.asc). Its fingerprint is: ``` 2FA6 1D8D 99B9 2658 6BAC 3D53 385E E055 A129 1538 ``` You can verify it against Taylor Hornby's [contact page](https://defuse.ca/contact.htm) and [twitter](https://twitter.com/DefuseSec/status/723741424253059074). php-encryption-2.2.1/bin/000077500000000000000000000000001332573277400152605ustar00rootroot00000000000000php-encryption-2.2.1/bin/generate-defuse-key000077500000000000000000000004521332573277400210400ustar00rootroot00000000000000#!/usr/bin/env php saveToAsciiSafeString(), "\n"; php-encryption-2.2.1/composer.json000066400000000000000000000017211332573277400172330ustar00rootroot00000000000000{ "name": "defuse/php-encryption", "description": "Secure PHP Encryption Library", "license": "MIT", "keywords": ["security", "encryption", "AES", "openssl", "cipher", "cryptography", "symmetric key cryptography", "crypto", "encrypt", "authenticated encryption"], "authors": [ { "name": "Taylor Hornby", "email": "taylor@defuse.ca", "homepage": "https://defuse.ca/" }, { "name": "Scott Arciszewski", "email": "info@paragonie.com", "homepage": "https://paragonie.com" } ], "autoload": { "psr-4": { "Defuse\\Crypto\\": "src" } }, "require": { "paragonie/random_compat": ">= 2", "ext-openssl": "*", "php": ">=5.4.0" }, "require-dev": { "phpunit/phpunit": "^4|^5", "nikic/php-parser": "^2.0|^3.0|^4.0" }, "bin": [ "bin/generate-defuse-key" ] } php-encryption-2.2.1/dist/000077500000000000000000000000001332573277400154535ustar00rootroot00000000000000php-encryption-2.2.1/dist/Makefile000066400000000000000000000017141332573277400171160ustar00rootroot00000000000000# This builds defuse-crypto.phar. To run this Makefile, `box` and `composer` # must be installed and in your $PATH. Run it from inside the dist/ directory. box := $(shell which box) composer := "composer" .PHONY: all all: build-phar .PHONY: sign-phar sign-phar: gpg -u 7B4B2D98 --armor --output defuse-crypto.phar.sig --detach-sig defuse-crypto.phar # ensure we run in clean tree. export git tree and run there. .PHONY: build-phar build-phar: @echo "Creating .phar from revision $(shell git rev-parse HEAD)." rm -rf worktree install -d worktree (cd $(CURDIR)/..; git archive HEAD) | tar -x -C worktree $(MAKE) -f $(CURDIR)/Makefile -C worktree defuse-crypto.phar mv worktree/*.phar . rm -rf worktree .PHONY: clean clean: rm -vf defuse-crypto.phar defuse-crypto.phar.sig # Inside workdir/: defuse-crypto.phar: dist/box.json composer.lock cp dist/box.json . php -d phar.readonly=0 $(box) build -c box.json -v composer.lock: $(composer) install --no-dev php-encryption-2.2.1/dist/box.json000066400000000000000000000007661332573277400171470ustar00rootroot00000000000000{ "chmod": "0755", "finder": [ { "in": "src", "name": "*.php" }, { "in": "vendor/composer", "name": "*.php" }, { "in": "vendor/paragonie", "name": "*.php", "exclude": "other" } ], "compactors": [ "Herrera\\Box\\Compactor\\Php" ], "main": "vendor/autoload.php", "output": "defuse-crypto.phar", "shebang": false, "stub": true } php-encryption-2.2.1/dist/signingkey.asc000066400000000000000000000060401332573277400203120ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2 mQINBFarvO4BEACdQBaLt6SUBx1cB5liUu1qo+YwVLh9bxTregQtmEREMdTVqXYt e5b79uL4pQp2GlKHcEyRURS+6rIIruM0oh9ZYGTJYPAkCDzJxaU2awZeFbfBvpCm iF66/O4ZJI4mlT8dFKmxBJxDhfeOR2UmmhDiEsJK9FxBKUzvo/dWrX2pBzf8Y122 iIaVraSo+tymaf7vriaIf/NnSKhDw8dtQYGM4NMrxxsPTfbCF8XiboDgTkoD2A+6 NpOJYxA4Veedsf2TP9YLhljH4m5yYlfjjqBzbBCPWuE6Hhy5Xze9mncgDr7LKenm Ctf2NxW6y4O3RCI+9eLlBfFWB+KuGV87/b5daetX7NNLbjID8z2rqEa+d6wu5xA5 Ta2uiVkAOEovr3XnkayZ9zth+Za7w7Ai0ln0N/LVMkM+Gu4z/pJv6HjmTGDM2wJb fs+UOM0TFdg+N81Do67XT2M4o0MeHyUqsIiWpYa2Qf1PNmqTQNJnRk8uZZ9I96Nh eCgNuCbhsQiYBMicox+xmuWAlGAfA06y0kCtmqGhiBGArdJlWvUqPqGiZ4Hln9z0 FJmXDOh0Q/FIPxcDg8mKRRbx+lOP389PLsPpj4b2B/4PEgfpCCOwuKpLotATZxC1 9JwFk0Y/cvUUkq4a+nAJBNtBbtRJkEesuuUnRq6XexmnE3uUucDcV0XCSwARAQAB tCBUYXlsb3IgSG9ybmJ5IDx0YXlsb3JAZGVmdXNlLmNhPokCPQQTAQgAJwUCVqu8 7gIbAwUJB4TOAAULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRA4XuBVoSkVOJbx EACG0F9blPMAsK05EWyNnnS4mw25zPfbaqqEvYbquAeM0nBpRDm7sRn2MNR0AL4g 7XrtxE/4qYkdEl6f2wFCQeRhZgxE3w22llredzLme11Hic8hn4i7ysdIw0r9dMMR kjgR5UcWpv8iU847czyK09PkKW2EaLRbX2qbA7rNU5qCFKeD4Sy4bBTteISeVsHo Vr9o1/bRrMhgZ++ts8hYf0LmujIf5cxp+qcdKwCXSnS/gmmXaKRMCPv/Wdlq9bt6 LX9jZB9lXBdGxcBJeFOsTG+QRDiVjg3d6i3o3TAKV87ALBI4v2ADEYtN8lviHo3/ SovVKv6zrUsZHxhoGiLTiksNrYsKMmiMxdJCoOazmtUPnZ4UOtT8NdqMPoKvdoRz f4rhZ+f5jSVD9OuX2PDmfyq21Rdiym7Vcgr+uTIFJ3ShRHjWb/ytCwoB2FeGY6+G AKY58bTQvUIqEJvSov/+TAqZ4BfOuSdTLcHglV1OdUu2SFZvU2gmyVp0l5elGv5t FyUlBJUkQT9MtvsdLOR7vQi8QapV+9LWpqwvaj9hyEJz848DQ2sdYTphUytFHv7H k58DAtVhTrVjHyeefjiYtMl6vSAgTjy5LWAUpo5TfhdGrAi0Tdd/GD7amHoWoDy8 EKXKq2xPLo3JOdkWYQUi5NErzEskfsSzpCOgyDJmGetWK7kCDQRWq7zuARAAu7/i cm8cjgLhHEX/bgfwOT2hLOLSjjve0O8YFSuJO9XqIHXqmfVOrqWtfG0Mh4bwlfqc MAvBfF5NSSPfAE4ftBAQ1e5jEv8hJeqICpq3IHTFX4etBs49NfNkyveQl/amVTu1 +/O5J4CuIcsEf3y0Xuu38n7EB3SfMQCWLcOR1NyZoX3bI+CGRpOVVoFse3ljSWL4 LhLVl0WiEMXULsussEoN+c6x9KCyAi/jFOrxrTrFC//sZesKj6KucoqKGfwMWrrv IeRT9Ga8Wn5MJnQu0aWg+zVVYqTedXZLNLODgQIInFnXO0seBXy15yDok1y5bkx2 sinKg4+mueYaGUpoUti0hM3J3yaC34i6Cwa8MQoLNw1JIS/oNtKjpMxyV10w8aoc PHRK3n7UYp10mJHx7aM+lldSKvVS1NTQmI4vloNLwMp324H5ANDFAlRUz7mysVnu DEEvigPSPxs5ZYENu/i7pCQC5qHfhrlBrQwTjhegr0pQPcumy2fO5SGC9l/5B7ev bqQSZmDeWWoTvh2w2wl5/RWAsgZKx6rDtkCqYx7sSBY17uorrxP24LP4zhq7NxRV nfdsLogbCFNVQ66u7qvq5zFccdFtg9h1HQWdS7wbnKSBGZoo5gl6js7GGtxfGbb0 oQ9kp6eciF4U92r6POhVgbRe4CfPo50nqgZBddkAEQEAAYkCJQQYAQgADwUCVqu8 7gIbDAUJB4TOAAAKCRA4XuBVoSkVOFJ8D/9J8IJ4XWUU3FYIaHJ3XeSoxDmTi7d5 WmNdf1lmwz82MQjG4uw17oCbvQzmj4/a/CM1Ly4v0WwBhUf9aiNErD0ByHASFnuc tlQBLVJdk0vRyD0fZakGg64qCA76hiySjMhlGHkQFyP2mDORc2GNu/OqFGm79pXT ZUplXxd431E603/agM5xJrweutMMpP1nBFTSEMJvbMNzDVN8I1A1CH4zVmAVxOUk sQ5L5rXW+KeXWyiMF24+l2CMnkQ2CxfHpkcpfPJs1Cbt+TIBSSofIqK8QJXrb/2f Zpl/ftqW7Xe86rJFrB/Y/77LDWx10rqWEvfCqrBxrMj7ONAQfbKQF/IjAwDN17Wf 1K74rqKnRu+KHCyNM89s1iDbQC9kzZfzYt4AEOvAH/ZQDMZffzPSbnfkBerExFpa 93XMuiR66jiBsf9IXIQeydpJD4Ogl2sSUSxFEJxJ/bBSxPxC5w7/BVMA7Am1y8Zo 3hrpqnX2PBzxG7L0FZ6fYkfR3p8JS7vI6nByBf2IDv8W32wn43olPf+u6uobHLvt ttapOjwPAhPDalRuxs9U6WSg06QJkT/0F8TFUPWpsFmKTl+G4Ty7PHWsjeeNHJCL 7/5RQboFY3k8Jy3/sIofABO6Un9LJivDuu9PxqA0IgvaS6Mja8JdCCk9Nyk4vHB7 IEgAL/CYqrk38w== =lmD7 -----END PGP PUBLIC KEY BLOCK----- php-encryption-2.2.1/docs/000077500000000000000000000000001332573277400154405ustar00rootroot00000000000000php-encryption-2.2.1/docs/CryptoDetails.md000066400000000000000000000057101332573277400205530ustar00rootroot00000000000000Cryptography Details ===================== Here is a high-level description of how this library works. Any discrepancy between this documentation and the actual implementation will be considered a security bug. Let's start with the following definitions: - HKDF-SHA256(*k*, *n*, *info*, *s*) is the key derivation function specified in RFC 5869 (using the SHA256 hash function). The parameters are: - *k*: The initial keying material. - *n*: The number of output bytes. - *info*: The info string. - *s*: The salt. - AES-256-CTR(*m*, *k*, *iv*) is AES-256 encryption in CTR mode. The parameters are: - *m*: An arbitrary-length (possibly zero-length) message. - *k*: A 32-byte key. - *iv*: A 16-byte initialization vector (nonce). - PBKDF2-SHA256(*p*, *s*, *i*, *n*) is the password-based key derivation function defined in RFC 2898 (using the SHA256 hash function). The parameters are: - *p*: The password string. - *s*: The salt string. - *i*: The iteration count. - *n*: The output length in bytes. - VERSION is the string `"\xDE\xF5\x02\x00"`. - AUTHINFO is the string `"DefusePHP|V2|KeyForAuthentication"`. - ENCRINFO is the string `"DefusePHP|V2|KeyForEncryption"`. To encrypt a message *m* using a 32-byte key *k*, the following steps are taken: 1. Generate a random 32-byte string *salt*. 2. Derive the 32-byte authentication key *akey* = HKDF-SHA256(*k*, 32, AUTHINFO, *salt*). 3. Derive the 32-byte encryption key *ekey* = HKDF-SHA256(*k*, 32, ENCRINFO, *salt*). 4. Generate a random 16-byte initialization vector *iv*. 5. Compute *c* = AES-256-CTR(*m*, *ekey*, *iv*). 6. Combine *ctxt* = VERSION || *salt* || *iv* || *c*. 7. Compute *h* = HMAC-SHA256(*ctxt*, *akey*). 8. Output *ctxt* || *h*. Decryption is roughly the reverse process (see the code for details, since the security of the decryption routine is highly implementation-dependent). For encryption using a password *p*, steps 1-3 above are replaced by: 1. Generate a random 32-byte string *salt*. 2. Compute *k* = PBKDF2-SHA256(SHA256(*p*), *salt*, 100000, 32). 3. Derive the 32-byte authentication key *akey* = HKDF-SHA256(*k*, 32, AUTHINFO, *salt*) 4. Derive the 32-byte encryption key *ekey* = HKDF-SHA256(*k*, 32, ENCRINFO, *salt*) The remainder of the process is the same. Notice the reuse of the same *salt* for PBKDF2-SHA256 and HKDF-SHA256. The prehashing of the password in step 2 is done to prevent a [DoS attack using long passwords](https://github.com/defuse/php-encryption/issues/230). For `KeyProtectedByPassword`, the serialized key is encrypted according to the password encryption defined above. However, the actual password used for encryption is the SHA256 hash of the password the user provided. This is done in order to provide domain separation between the message encryption in the user's application and the internal key encryption done by this library. It fixes a [key replacement chosen-protocol attack](https://github.com/defuse/php-encryption/issues/240). php-encryption-2.2.1/docs/FAQ.md000066400000000000000000000052721332573277400163770ustar00rootroot00000000000000Frequently Asked Questions =========================== How do I use this library to encrypt passwords? ------------------------------------------------ Passwords should not be encrypted, they should be hashed with a *slow* password hashing function that's designed to slow down password guessing attacks. See [How to Safely Store Your Users' Passwords in 2016](https://paragonie.com/blog/2016/02/how-safely-store-password-in-2016). How do I give it the same key every time instead of a new random key? ---------------------------------------------------------------------- A `Key` object can be saved to a string by calling its `saveToAsciiSafeString()` method. You will have to save that string somewhere safe, and then load it back into a `Key` object using `Key`'s `loadFromAsciiSafeString` static method. Where you store the string depends on your application. For example if you are using `KeyProtectedByPassword` to encrypt files with a user's login password, then you should not store the `Key` at all. If you are protecting sensitive data on a server that may be compromised, then you should store it in a hardware security module. When in doubt, consult a security expert. Why is an EnvironmentIsBrokenException getting thrown? ------------------------------------------------------- Either you've encountered a bug in this library, or your system doesn't support the use of this library. For example, if your system does not have a secure random number generator, this library will refuse to run, by throwing that exception, instead of falling back to an insecure random number generator. Why am I getting a BadFormatException when loading a Key from a string? ------------------------------------------------------------------------ If you're getting this exception, then the string you're giving to `loadFromAsciiSafeString()` is *not* the same as the string you got from `saveToAsciiSafeString()`. Perhaps your database column isn't wide enough and it's truncating the string as you insert it? Does encrypting hide the length of the plaintext? -------------------------------------------------- Encryption does not, and is not intended to, hide the length of the data being encrypted. For example, it is not safe to encrypt a field in which only a small number of different-length values are possible (e.g. "male" or "female") since it would be possible to tell what the plaintext is by looking at the length of the ciphertext. In order to do this safely, it is your responsibility to, before encrypting, pad the data out to the length of the longest string that will ever be encrypted. This way, all plaintexts are the same length, and no information about the plaintext can be gleaned from the length of the ciphertext. php-encryption-2.2.1/docs/InstallingAndVerifying.md000066400000000000000000000036131332573277400223770ustar00rootroot00000000000000Getting The Code ================= There are two ways to use this library in your applications. You can either: 1. Use [Composer](https://getcomposer.org/), or 2. `require_once` a single `.phar` file in your application. If you are not using either option (for example, because you're using Git submodules), you may need to write your own autoloader ([example](https://gist.github.com/paragonie-scott/949daee819bb7f19c50e5e103170b400)). Option 1: Using Composer ------------------------- Run this inside the directory of your composer-enabled project: ```sh composer require defuse/php-encryption ``` Unfortunately, composer doesn't provide a way for you to verify that the code you're getting was signed by this library's authors. If you want a more secure option, use the `.phar` method described below. Option 2: Including a PHAR ---------------------------- The `.phar` option lets you include this library into your project simply by calling `require_once` on a single file. Download `defuse-crypto.phar` and `defuse-crypto.phar.sig` from this project's [releases](https://github.com/defuse/php-encryption/releases) page. You should verify the integrity of the `.phar`. The `defuse-crypto.phar.sig` contains the signature of `defuse-crypto.phar`. It is signed with Taylor Hornby's PGP key. You can find Taylor's public key in `dist/signingkey.asc`. You can verify the public key's fingerprint against the Taylor Hornby's [contact page](https://defuse.ca/contact.htm) and [twitter](https://twitter.com/DefuseSec/status/723741424253059074). Once you have verified the signature, it is safe to use the `.phar`. Place it somewhere in your file system, e.g. `/var/www/lib/defuse-crypto.phar`, and then pass that path to `require_once`. ```php Whenever there is a conflict between security and some other property, > security will be favored. For example, the library has runtime tests, > which make it slower, but will hopefully stop it from encrypting stuff > if the platform it's running on is broken. - Rule #2: It should be difficult to misuse the library. > We assume the developers using this library have no experience with > cryptography. We only assume that they know that the "key" is something > you need to encrypt and decrypt the messages, and that it must be kept > secret. Whenever possible, the library should refuse to encrypt or decrypt > messages when it is not being used correctly. - Rule #3: The library aims only to be compatible with itself. > Other PHP encryption libraries try to support every possible type of > encryption, even the insecure ones (e.g. ECB mode). Because there are so > many options, inexperienced developers must decide whether to use "CBC > mode" or "ECB mode" when both are meaningless terms to them. This > inevitably leads to vulnerabilities. > This library will only support one secure mode. A developer using this > library will call "encrypt" and "decrypt" methods without worrying about > how they are implemented. - Rule #4: The library should require no special installation. > Some PHP encryption libraries, like libsodium-php, are not straightforward > to install and cannot packaged with "just download and extract" > applications. This library will always be just a handful of PHP files that > you can copy to your source tree and require(). Publishing Releases -------------------- To make a release, you will need to install [composer](https://getcomposer.org/) and [box](https://github.com/box-project/box2) on your system. They will need to be available in your `$PATH` so that running the commands `composer` and `box` in your terminal run them, respectively. You will also need the private key for signing (ID: 7B4B2D98) available. Once you have those tools installed and the key available follow these steps: **Remember to set the version number in `composer.json`!** Make a fresh clone of the repository: ``` git clone ``` Check out the branch you want to release: ``` git checkout ``` Check that the version number in composer.json is correct: ``` cat composer.json ``` Check that the version number and support lifetime in README.md are correct: ``` cat README.md ``` Run the tests: ``` composer install ./test.sh ``` Generate the `.phar`: ``` cd dist make build-phar ``` Test the `.phar`: ``` cd ../ ./test.sh dist/defuse-crypto.phar ``` Sign the `.phar`: ``` cd dist make sign-phar ``` Tag the release: ``` git -c user.signingkey=7B4B2D98 tag -s "" -m "" ``` `` should be in the format `v2.0.0` and `` should look like "Release of v2.0.0." Push the tag to github, then use the [releases](https://github.com/defuse/php-encryption/releases) page to draft a new release for that tag. Upload the `.phar` and the `.phar.sig` file to be included as part of that release. php-encryption-2.2.1/docs/Tutorial.md000066400000000000000000000316741332573277400176000ustar00rootroot00000000000000Tutorial ========= Hello! If you're reading this file, it's because you want to add encryption to one of your PHP projects. My job, as the person writing this documentation, is to help you make sure you're doing the right thing and then show you how to use this library to do it. To help me help you, please read the documentation *carefully* and *deliberately*. A Word of Caution ------------------ Encryption is not magic dust you can sprinkle on a system to make it more secure. The way encryption is integrated into a system's design needs to be carefully thought out. Sometimes, encryption is the wrong thing to use. Other times, encryption needs to be used in a very specific way in order for it to work as intended. Even if you are sure of what you are doing, we strongly recommend seeking advice from an expert. The first step is to think about your application's threat model. Ask yourself the following questions. Who will want to attack my application, and what will they get out of it? Are they trying to steal some information? Trying to alter or destroy some information? Or just trying to make the system go down so people can't access it? Then ask yourself how encryption can help combat those threats. If you're going to add encryption to your application, you should have a very clear idea of exactly which kinds of attacks it's helping to secure your application against. Once you have your threat model, think about what kinds of attacks it *does not* cover, and whether or not you should improve your threat model to include those attacks. **This isn't for storing user login passwords:** The most common use of cryptography in web applications is to protect the users' login passwords. If you're trying to use this library to "encrypt" your users' passwords, you're in the wrong place. Passwords shouldn't be *encrypted*, they should be *hashed* with a slow computation-heavy function that makes password guessing attacks more expensive. See [How to Safely Store Your Users' Passwords in 2016](https://paragonie.com/blog/2016/02/how-safely-store-password-in-2016). **This isn't for encrypting network communication:** Likewise, if you're trying to encrypt messages sent between two parties over the Internet, you don't want to be using this library. For that, set up a TLS connection between the two points, or, if it's a chat app, use the [Signal Protocol](https://whispersystems.org/blog/advanced-ratcheting/). What this library provides is symmetric encryption for "data at rest." This means it is not suitable for use in building protocols where "data is in motion" (i.e. moving over a network) except in limited set of cases. Please note that **encryption does not, and is not intended to, hide the *length* of the data being encrypted.** For example, it is not safe to encrypt a field in which only a small number of different-length values are possible (e.g. "male" or "female") since it would be possible to tell what the plaintext is by looking at the length of the ciphertext. In order to do this safely, it is your responsibility to, before encrypting, pad the data out to the length of the longest string that will ever be encrypted. This way, all plaintexts are the same length, and no information about the plaintext can be gleaned from the length of the ciphertext. Getting the Code ----------------- There are several different ways to obtain this library's code and to add it to your project. Even if you've already cloned the code from GitHub, you should take steps to verify the cryptographic signatures to make sure the code you got was not intercepted and modified by an attacker. Please head over to the [**Installing and Verifying**](InstallingAndVerifying.md) documentation to get the code, and then come back here to continue the tutorial. Using the Library ------------------ I'm going to assume you know what symmetric encryption is, and the difference between symmetric and asymmetric encryption. If you don't, I recommend taking [Dan Boneh's Cryptography I course](https://www.coursera.org/learn/crypto/) on Coursera. To give you a quick introduction to the library, I'm going to explain how it would be used in two sterotypical scenarios. Hopefully, one of these sterotypes is close enough to what you want to do that you'll be able to figure out what needs to be different on your own. ### Formal Documentation While this tutorial should get you up and running fast, it's important to understand how this library behaves. Please make sure to read the formal documentation of all of the functions you're using, since there are some important security warnings there. The following classes are available for you to use: - [Crypto](classes/Crypto.md): Encrypting and decrypting strings. - [File](classes/File.md): Encrypting and decrypting files. - [Key](classes/Key.md): Represents a secret encryption key. - [KeyProtectedByPassword](classes/KeyProtectedByPassword.md): Represents a secret encryption key that needs to be "unlocked" by a password before it can be used. ### Scenario #1: Keep data secret from the database administrator In this scenario, our threat model is as follows. Alice is a server administrator responsible for managing a trusted web server. Eve is a database administrator responsible for managing a database server. Dave is a web developer working on code that will eventually run on the trusted web server. Let's say Alice and Dave trust each other, and Alice is going to host Dave's application on her server. But both Alice and Dave don't trust Eve. They know Eve is a good database administrator, but she might have incentive to steal the data from the database. They want to keep some of the web application's data secret from Eve. In order to do that, Alice will use the included `generate-defuse-key` script which generates a random encryption key and prints it to standard output: ```sh $ composer require defuse/php-encryption $ vendor/bin/generate-defuse-key ``` Alice will run this script once and save the output to a configuration file, say in `/etc/daveapp-secret-key.txt` and set the file permissions so that only the user that the website PHP scripts run as can access it. Dave will write his code to load the key from the configuration file: ```php saveToAsciiSafeString(); // ... save $protected_key_encoded into the user's account record } ``` **WARNING:** Because of the way `KeyProtectedByPassword` is implemented, knowing `SHA256($password)` is enough to decrypt a `KeyProtectedByPassword`. To be secure, your application MUST NOT EVER compute `SHA256($password)` and use or store it for any reason. You must also make sure that other libraries your application is using don't compute it either. Then, when the user logs in, Dave's code will load the protected key from the user's account record, unlock it to get a `Key` object, and save the `Key` object somewhere safe (like temporary memory-backed session storage). Note that wherever Dave's code saves the key, it must be destroyed once the user logs out, or else the attacker might be able to find users' keys even if they were never logged in during the attack. ```php unlockKey($password); $user_key_encoded = $user_key->saveToAsciiSafeString(); // ... save $user_key_encoded in the session ``` ```php php-encryption-2.2.1/src/000077500000000000000000000000001332573277400152775ustar00rootroot00000000000000php-encryption-2.2.1/src/Core.php000066400000000000000000000360251332573277400167060ustar00rootroot00000000000000 0, 'Trying to increment a nonce by a nonpositive amount' ); Core::ensureTrue( $inc <= PHP_INT_MAX - 255, 'Integer overflow may occur' ); /* * We start at the rightmost byte (big-endian) * So, too, does OpenSSL: http://stackoverflow.com/a/3146214/2224584 */ for ($i = Core::BLOCK_BYTE_SIZE - 1; $i >= 0; --$i) { $sum = \ord($ctr[$i]) + $inc; /* Detect integer overflow and fail. */ Core::ensureTrue(\is_int($sum), 'Integer overflow in CTR mode nonce increment'); $ctr[$i] = \pack('C', $sum & 0xFF); $inc = $sum >> 8; } return $ctr; } /** * Returns a random byte string of the specified length. * * @param int $octets * * @throws Ex\EnvironmentIsBrokenException * * @return string */ public static function secureRandom($octets) { self::ensureFunctionExists('random_bytes'); try { return \random_bytes($octets); } catch (\Exception $ex) { throw new Ex\EnvironmentIsBrokenException( 'Your system does not have a secure random number generator.' ); } } /** * Computes the HKDF key derivation function specified in * http://tools.ietf.org/html/rfc5869. * * @param string $hash Hash Function * @param string $ikm Initial Keying Material * @param int $length How many bytes? * @param string $info What sort of key are we deriving? * @param string $salt * * @throws Ex\EnvironmentIsBrokenException * @psalm-suppress UndefinedFunction - We're checking if the function exists first. * * @return string */ public static function HKDF($hash, $ikm, $length, $info = '', $salt = null) { static $nativeHKDF = null; if ($nativeHKDF === null) { $nativeHKDF = \is_callable('\\hash_hkdf'); } if ($nativeHKDF) { if (\is_null($salt)) { $salt = ''; } return \hash_hkdf($hash, $ikm, $length, $info, $salt); } $digest_length = Core::ourStrlen(\hash_hmac($hash, '', '', true)); // Sanity-check the desired output length. Core::ensureTrue( !empty($length) && \is_int($length) && $length >= 0 && $length <= 255 * $digest_length, 'Bad output length requested of HDKF.' ); // "if [salt] not provided, is set to a string of HashLen zeroes." if (\is_null($salt)) { $salt = \str_repeat("\x00", $digest_length); } // HKDF-Extract: // PRK = HMAC-Hash(salt, IKM) // The salt is the HMAC key. $prk = \hash_hmac($hash, $ikm, $salt, true); // HKDF-Expand: // This check is useless, but it serves as a reminder to the spec. Core::ensureTrue(Core::ourStrlen($prk) >= $digest_length); // T(0) = '' $t = ''; $last_block = ''; for ($block_index = 1; Core::ourStrlen($t) < $length; ++$block_index) { // T(i) = HMAC-Hash(PRK, T(i-1) | info | 0x??) $last_block = \hash_hmac( $hash, $last_block . $info . \chr($block_index), $prk, true ); // T = T(1) | T(2) | T(3) | ... | T(N) $t .= $last_block; } // ORM = first L octets of T /** @var string $orm */ $orm = Core::ourSubstr($t, 0, $length); Core::ensureTrue(\is_string($orm)); return $orm; } /** * Checks if two equal-length strings are the same without leaking * information through side channels. * * @param string $expected * @param string $given * * @throws Ex\EnvironmentIsBrokenException * * @return bool */ public static function hashEquals($expected, $given) { static $native = null; if ($native === null) { $native = \function_exists('hash_equals'); } if ($native) { return \hash_equals($expected, $given); } // We can't just compare the strings with '==', since it would make // timing attacks possible. We could use the XOR-OR constant-time // comparison algorithm, but that may not be a reliable defense in an // interpreted language. So we use the approach of HMACing both strings // with a random key and comparing the HMACs. // We're not attempting to make variable-length string comparison // secure, as that's very difficult. Make sure the strings are the same // length. Core::ensureTrue(Core::ourStrlen($expected) === Core::ourStrlen($given)); $blind = Core::secureRandom(32); $message_compare = \hash_hmac(Core::HASH_FUNCTION_NAME, $given, $blind); $correct_compare = \hash_hmac(Core::HASH_FUNCTION_NAME, $expected, $blind); return $correct_compare === $message_compare; } /** * Throws an exception if the constant doesn't exist. * * @param string $name * @return void * * @throws Ex\EnvironmentIsBrokenException */ public static function ensureConstantExists($name) { Core::ensureTrue(\defined($name)); } /** * Throws an exception if the function doesn't exist. * * @param string $name * @return void * * @throws Ex\EnvironmentIsBrokenException */ public static function ensureFunctionExists($name) { Core::ensureTrue(\function_exists($name)); } /** * Throws an exception if the condition is false. * * @param bool $condition * @param string $message * @return void * * @throws Ex\EnvironmentIsBrokenException */ public static function ensureTrue($condition, $message = '') { if (!$condition) { throw new Ex\EnvironmentIsBrokenException($message); } } /* * We need these strlen() and substr() functions because when * 'mbstring.func_overload' is set in php.ini, the standard strlen() and * substr() are replaced by mb_strlen() and mb_substr(). */ /** * Computes the length of a string in bytes. * * @param string $str * * @throws Ex\EnvironmentIsBrokenException * * @return int */ public static function ourStrlen($str) { static $exists = null; if ($exists === null) { $exists = \extension_loaded('mbstring') && \ini_get('mbstring.func_overload') !== false && (int)\ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING; } if ($exists) { $length = \mb_strlen($str, '8bit'); Core::ensureTrue($length !== false); return $length; } else { return \strlen($str); } } /** * Behaves roughly like the function substr() in PHP 7 does. * * @param string $str * @param int $start * @param int $length * * @throws Ex\EnvironmentIsBrokenException * * @return string|bool */ public static function ourSubstr($str, $start, $length = null) { static $exists = null; if ($exists === null) { $exists = \extension_loaded('mbstring') && \ini_get('mbstring.func_overload') !== false && (int)\ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING; } // This is required to make mb_substr behavior identical to substr. // Without this, mb_substr() would return false, contra to what the // PHP documentation says (it doesn't say it can return false.) $input_len = Core::ourStrlen($str); if ($start === $input_len && !$length) { return ''; } if ($start > $input_len) { return false; } // mb_substr($str, 0, NULL, '8bit') returns an empty string on PHP 5.3, // so we have to find the length ourselves. Also, substr() doesn't // accept null for the length. if (! isset($length)) { if ($start >= 0) { $length = $input_len - $start; } else { $length = -$start; } } if ($length < 0) { throw new \InvalidArgumentException( "Negative lengths are not supported with ourSubstr." ); } if ($exists) { $substr = \mb_substr($str, $start, $length, '8bit'); // At this point there are two cases where mb_substr can // legitimately return an empty string. Either $length is 0, or // $start is equal to the length of the string (both mb_substr and // substr return an empty string when this happens). It should never // ever return a string that's longer than $length. if (Core::ourStrlen($substr) > $length || (Core::ourStrlen($substr) === 0 && $length !== 0 && $start !== $input_len)) { throw new Ex\EnvironmentIsBrokenException( 'Your version of PHP has bug #66797. Its implementation of mb_substr() is incorrect. See the details here: https://bugs.php.net/bug.php?id=66797' ); } return $substr; } return \substr($str, $start, $length); } /** * Computes the PBKDF2 password-based key derivation function. * * The PBKDF2 function is defined in RFC 2898. Test vectors can be found in * RFC 6070. This implementation of PBKDF2 was originally created by Taylor * Hornby, with improvements from http://www.variations-of-shadow.com/. * * @param string $algorithm The hash algorithm to use. Recommended: SHA256 * @param string $password The password. * @param string $salt A salt that is unique to the password. * @param int $count Iteration count. Higher is better, but slower. Recommended: At least 1000. * @param int $key_length The length of the derived key in bytes. * @param bool $raw_output If true, the key is returned in raw binary format. Hex encoded otherwise. * * @throws Ex\EnvironmentIsBrokenException * * @return string A $key_length-byte key derived from the password and salt. */ public static function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false) { // Type checks: if (! \is_string($algorithm)) { throw new \InvalidArgumentException( 'pbkdf2(): algorithm must be a string' ); } if (! \is_string($password)) { throw new \InvalidArgumentException( 'pbkdf2(): password must be a string' ); } if (! \is_string($salt)) { throw new \InvalidArgumentException( 'pbkdf2(): salt must be a string' ); } // Coerce strings to integers with no information loss or overflow $count += 0; $key_length += 0; $algorithm = \strtolower($algorithm); Core::ensureTrue( \in_array($algorithm, \hash_algos(), true), 'Invalid or unsupported hash algorithm.' ); // Whitelist, or we could end up with people using CRC32. $ok_algorithms = [ 'sha1', 'sha224', 'sha256', 'sha384', 'sha512', 'ripemd160', 'ripemd256', 'ripemd320', 'whirlpool', ]; Core::ensureTrue( \in_array($algorithm, $ok_algorithms, true), 'Algorithm is not a secure cryptographic hash function.' ); Core::ensureTrue($count > 0 && $key_length > 0, 'Invalid PBKDF2 parameters.'); if (\function_exists('hash_pbkdf2')) { // The output length is in NIBBLES (4-bits) if $raw_output is false! if (! $raw_output) { $key_length = $key_length * 2; } return \hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output); } $hash_length = Core::ourStrlen(\hash($algorithm, '', true)); $block_count = \ceil($key_length / $hash_length); $output = ''; for ($i = 1; $i <= $block_count; $i++) { // $i encoded as 4 bytes, big endian. $last = $salt . \pack('N', $i); // first iteration $last = $xorsum = \hash_hmac($algorithm, $last, $password, true); // perform the other $count - 1 iterations for ($j = 1; $j < $count; $j++) { $xorsum ^= ($last = \hash_hmac($algorithm, $last, $password, true)); } $output .= $xorsum; } if ($raw_output) { return (string) Core::ourSubstr($output, 0, $key_length); } else { return Encoding::binToHex((string) Core::ourSubstr($output, 0, $key_length)); } } } php-encryption-2.2.1/src/Crypto.php000066400000000000000000000335361332573277400173020ustar00rootroot00000000000000deriveKeys($salt); $ekey = $keys->getEncryptionKey(); $akey = $keys->getAuthenticationKey(); $iv = Core::secureRandom(Core::BLOCK_BYTE_SIZE); $ciphertext = Core::CURRENT_VERSION . $salt . $iv . self::plainEncrypt($plaintext, $ekey, $iv); $auth = \hash_hmac(Core::HASH_FUNCTION_NAME, $ciphertext, $akey, true); $ciphertext = $ciphertext . $auth; if ($raw_binary) { return $ciphertext; } return Encoding::binToHex($ciphertext); } /** * Decrypts a ciphertext to a string with either a key or a password. * * @param string $ciphertext * @param KeyOrPassword $secret * @param bool $raw_binary * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\WrongKeyOrModifiedCiphertextException * * @return string */ private static function decryptInternal($ciphertext, KeyOrPassword $secret, $raw_binary) { RuntimeTests::runtimeTest(); if (! $raw_binary) { try { $ciphertext = Encoding::hexToBin($ciphertext); } catch (Ex\BadFormatException $ex) { throw new Ex\WrongKeyOrModifiedCiphertextException( 'Ciphertext has invalid hex encoding.' ); } } if (Core::ourStrlen($ciphertext) < Core::MINIMUM_CIPHERTEXT_SIZE) { throw new Ex\WrongKeyOrModifiedCiphertextException( 'Ciphertext is too short.' ); } // Get and check the version header. /** @var string $header */ $header = Core::ourSubstr($ciphertext, 0, Core::HEADER_VERSION_SIZE); if ($header !== Core::CURRENT_VERSION) { throw new Ex\WrongKeyOrModifiedCiphertextException( 'Bad version header.' ); } // Get the salt. /** @var string $salt */ $salt = Core::ourSubstr( $ciphertext, Core::HEADER_VERSION_SIZE, Core::SALT_BYTE_SIZE ); Core::ensureTrue(\is_string($salt)); // Get the IV. /** @var string $iv */ $iv = Core::ourSubstr( $ciphertext, Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE, Core::BLOCK_BYTE_SIZE ); Core::ensureTrue(\is_string($iv)); // Get the HMAC. /** @var string $hmac */ $hmac = Core::ourSubstr( $ciphertext, Core::ourStrlen($ciphertext) - Core::MAC_BYTE_SIZE, Core::MAC_BYTE_SIZE ); Core::ensureTrue(\is_string($hmac)); // Get the actual encrypted ciphertext. /** @var string $encrypted */ $encrypted = Core::ourSubstr( $ciphertext, Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + Core::BLOCK_BYTE_SIZE, Core::ourStrlen($ciphertext) - Core::MAC_BYTE_SIZE - Core::SALT_BYTE_SIZE - Core::BLOCK_BYTE_SIZE - Core::HEADER_VERSION_SIZE ); Core::ensureTrue(\is_string($encrypted)); // Derive the separate encryption and authentication keys from the key // or password, whichever it is. $keys = $secret->deriveKeys($salt); if (self::verifyHMAC($hmac, $header . $salt . $iv . $encrypted, $keys->getAuthenticationKey())) { $plaintext = self::plainDecrypt($encrypted, $keys->getEncryptionKey(), $iv, Core::CIPHER_METHOD); return $plaintext; } else { throw new Ex\WrongKeyOrModifiedCiphertextException( 'Integrity check failed.' ); } } /** * Raw unauthenticated encryption (insecure on its own). * * @param string $plaintext * @param string $key * @param string $iv * * @throws Ex\EnvironmentIsBrokenException * * @return string */ protected static function plainEncrypt($plaintext, $key, $iv) { Core::ensureConstantExists('OPENSSL_RAW_DATA'); Core::ensureFunctionExists('openssl_encrypt'); /** @var string $ciphertext */ $ciphertext = \openssl_encrypt( $plaintext, Core::CIPHER_METHOD, $key, OPENSSL_RAW_DATA, $iv ); Core::ensureTrue(\is_string($ciphertext), 'openssl_encrypt() failed'); return $ciphertext; } /** * Raw unauthenticated decryption (insecure on its own). * * @param string $ciphertext * @param string $key * @param string $iv * @param string $cipherMethod * * @throws Ex\EnvironmentIsBrokenException * * @return string */ protected static function plainDecrypt($ciphertext, $key, $iv, $cipherMethod) { Core::ensureConstantExists('OPENSSL_RAW_DATA'); Core::ensureFunctionExists('openssl_decrypt'); /** @var string $plaintext */ $plaintext = \openssl_decrypt( $ciphertext, $cipherMethod, $key, OPENSSL_RAW_DATA, $iv ); Core::ensureTrue(\is_string($plaintext), 'openssl_decrypt() failed.'); return $plaintext; } /** * Verifies an HMAC without leaking information through side-channels. * * @param string $expected_hmac * @param string $message * @param string $key * * @throws Ex\EnvironmentIsBrokenException * * @return bool */ protected static function verifyHMAC($expected_hmac, $message, $key) { $message_hmac = \hash_hmac(Core::HASH_FUNCTION_NAME, $message, $key, true); return Core::hashEquals($message_hmac, $expected_hmac); } } php-encryption-2.2.1/src/DerivedKeys.php000066400000000000000000000014131332573277400202250ustar00rootroot00000000000000akey; } /** * Returns the encryption key. * @return string */ public function getEncryptionKey() { return $this->ekey; } /** * Constructor for DerivedKeys. * * @param string $akey * @param string $ekey */ public function __construct($akey, $ekey) { $this->akey = $akey; $this->ekey = $ekey; } } php-encryption-2.2.1/src/Encoding.php000066400000000000000000000220441332573277400175400ustar00rootroot00000000000000> 4; $hex .= \pack( 'CC', 87 + $b + ((($b - 10) >> 8) & ~38), 87 + $c + ((($c - 10) >> 8) & ~38) ); } return $hex; } /** * Converts a hexadecimal string into a byte string without leaking * information through side channels. * * @param string $hex_string * * @throws Ex\BadFormatException * @throws Ex\EnvironmentIsBrokenException * * @return string * @psalm-suppress TypeDoesNotContainType */ public static function hexToBin($hex_string) { $hex_pos = 0; $bin = ''; $hex_len = Core::ourStrlen($hex_string); $state = 0; $c_acc = 0; while ($hex_pos < $hex_len) { $c = \ord($hex_string[$hex_pos]); $c_num = $c ^ 48; $c_num0 = ($c_num - 10) >> 8; $c_alpha = ($c & ~32) - 55; $c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8; if (($c_num0 | $c_alpha0) === 0) { throw new Ex\BadFormatException( 'Encoding::hexToBin() input is not a hex string.' ); } $c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0); if ($state === 0) { $c_acc = $c_val * 16; } else { $bin .= \pack('C', $c_acc | $c_val); } $state ^= 1; ++$hex_pos; } return $bin; } /** * Remove trialing whitespace without table look-ups or branches. * * Calling this function may leak the length of the string as well as the * number of trailing whitespace characters through side-channels. * * @param string $string * @return string */ public static function trimTrailingWhitespace($string = '') { $length = Core::ourStrlen($string); if ($length < 1) { return ''; } do { $prevLength = $length; $last = $length - 1; $chr = \ord($string[$last]); /* Null Byte (0x00), a.k.a. \0 */ // if ($chr === 0x00) $length -= 1; $sub = (($chr - 1) >> 8 ) & 1; $length -= $sub; $last -= $sub; /* Horizontal Tab (0x09) a.k.a. \t */ $chr = \ord($string[$last]); // if ($chr === 0x09) $length -= 1; $sub = (((0x08 - $chr) & ($chr - 0x0a)) >> 8) & 1; $length -= $sub; $last -= $sub; /* New Line (0x0a), a.k.a. \n */ $chr = \ord($string[$last]); // if ($chr === 0x0a) $length -= 1; $sub = (((0x09 - $chr) & ($chr - 0x0b)) >> 8) & 1; $length -= $sub; $last -= $sub; /* Carriage Return (0x0D), a.k.a. \r */ $chr = \ord($string[$last]); // if ($chr === 0x0d) $length -= 1; $sub = (((0x0c - $chr) & ($chr - 0x0e)) >> 8) & 1; $length -= $sub; $last -= $sub; /* Space */ $chr = \ord($string[$last]); // if ($chr === 0x20) $length -= 1; $sub = (((0x1f - $chr) & ($chr - 0x21)) >> 8) & 1; $length -= $sub; } while ($prevLength !== $length && $length > 0); return (string) Core::ourSubstr($string, 0, $length); } /* * SECURITY NOTE ON APPLYING CHECKSUMS TO SECRETS: * * The checksum introduces a potential security weakness. For example, * suppose we apply a checksum to a key, and that an adversary has an * exploit against the process containing the key, such that they can * overwrite an arbitrary byte of memory and then cause the checksum to * be verified and learn the result. * * In this scenario, the adversary can extract the key one byte at * a time by overwriting it with their guess of its value and then * asking if the checksum matches. If it does, their guess was right. * This kind of attack may be more easy to implement and more reliable * than a remote code execution attack. * * This attack also applies to authenticated encryption as a whole, in * the situation where the adversary can overwrite a byte of the key * and then cause a valid ciphertext to be decrypted, and then * determine whether the MAC check passed or failed. * * By using the full SHA256 hash instead of truncating it, I'm ensuring * that both ways of going about the attack are equivalently difficult. * A shorter checksum of say 32 bits might be more useful to the * adversary as an oracle in case their writes are coarser grained. * * Because the scenario assumes a serious vulnerability, we don't try * to prevent attacks of this style. */ /** * INTERNAL USE ONLY: Applies a version header, applies a checksum, and * then encodes a byte string into a range of printable ASCII characters. * * @param string $header * @param string $bytes * * @throws Ex\EnvironmentIsBrokenException * * @return string */ public static function saveBytesToChecksummedAsciiSafeString($header, $bytes) { // Headers must be a constant length to prevent one type's header from // being a prefix of another type's header, leading to ambiguity. Core::ensureTrue( Core::ourStrlen($header) === self::SERIALIZE_HEADER_BYTES, 'Header must be ' . self::SERIALIZE_HEADER_BYTES . ' bytes.' ); return Encoding::binToHex( $header . $bytes . \hash( self::CHECKSUM_HASH_ALGO, $header . $bytes, true ) ); } /** * INTERNAL USE ONLY: Decodes, verifies the header and checksum, and returns * the encoded byte string. * * @param string $expected_header * @param string $string * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\BadFormatException * * @return string */ public static function loadBytesFromChecksummedAsciiSafeString($expected_header, $string) { // Headers must be a constant length to prevent one type's header from // being a prefix of another type's header, leading to ambiguity. Core::ensureTrue( Core::ourStrlen($expected_header) === self::SERIALIZE_HEADER_BYTES, 'Header must be 4 bytes.' ); /* If you get an exception here when attempting to load from a file, first pass your key to Encoding::trimTrailingWhitespace() to remove newline characters, etc. */ $bytes = Encoding::hexToBin($string); /* Make sure we have enough bytes to get the version header and checksum. */ if (Core::ourStrlen($bytes) < self::SERIALIZE_HEADER_BYTES + self::CHECKSUM_BYTE_SIZE) { throw new Ex\BadFormatException( 'Encoded data is shorter than expected.' ); } /* Grab the version header. */ $actual_header = (string) Core::ourSubstr($bytes, 0, self::SERIALIZE_HEADER_BYTES); if ($actual_header !== $expected_header) { throw new Ex\BadFormatException( 'Invalid header.' ); } /* Grab the bytes that are part of the checksum. */ $checked_bytes = (string) Core::ourSubstr( $bytes, 0, Core::ourStrlen($bytes) - self::CHECKSUM_BYTE_SIZE ); /* Grab the included checksum. */ $checksum_a = (string) Core::ourSubstr( $bytes, Core::ourStrlen($bytes) - self::CHECKSUM_BYTE_SIZE, self::CHECKSUM_BYTE_SIZE ); /* Re-compute the checksum. */ $checksum_b = \hash(self::CHECKSUM_HASH_ALGO, $checked_bytes, true); /* Check if the checksum matches. */ if (! Core::hashEquals($checksum_a, $checksum_b)) { throw new Ex\BadFormatException( "Data is corrupted, the checksum doesn't match" ); } return (string) Core::ourSubstr( $bytes, self::SERIALIZE_HEADER_BYTES, Core::ourStrlen($bytes) - self::SERIALIZE_HEADER_BYTES - self::CHECKSUM_BYTE_SIZE ); } } php-encryption-2.2.1/src/Exception/000077500000000000000000000000001332573277400172355ustar00rootroot00000000000000php-encryption-2.2.1/src/Exception/BadFormatException.php000066400000000000000000000001711332573277400234630ustar00rootroot00000000000000deriveKeys($file_salt); $ekey = $keys->getEncryptionKey(); $akey = $keys->getAuthenticationKey(); $ivsize = Core::BLOCK_BYTE_SIZE; $iv = Core::secureRandom($ivsize); /* Initialize a streaming HMAC state. */ /** @var resource $hmac */ $hmac = \hash_init(Core::HASH_FUNCTION_NAME, HASH_HMAC, $akey); Core::ensureTrue( \is_resource($hmac) || \is_object($hmac), 'Cannot initialize a hash context' ); /* Write the header, salt, and IV. */ self::writeBytes( $outputHandle, Core::CURRENT_VERSION . $file_salt . $iv, Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + $ivsize ); /* Add the header, salt, and IV to the HMAC. */ \hash_update($hmac, Core::CURRENT_VERSION); \hash_update($hmac, $file_salt); \hash_update($hmac, $iv); /* $thisIv will be incremented after each call to the encryption. */ $thisIv = $iv; /* How many blocks do we encrypt at a time? We increment by this value. */ $inc = (int) (Core::BUFFER_BYTE_SIZE / Core::BLOCK_BYTE_SIZE); /* Loop until we reach the end of the input file. */ $at_file_end = false; while (! (\feof($inputHandle) || $at_file_end)) { /* Find out if we can read a full buffer, or only a partial one. */ /** @var int */ $pos = \ftell($inputHandle); if (!\is_int($pos)) { throw new Ex\IOException( 'Could not get current position in input file during encryption' ); } if ($pos + Core::BUFFER_BYTE_SIZE >= $inputSize) { /* We're at the end of the file, so we need to break out of the loop. */ $at_file_end = true; $read = self::readBytes( $inputHandle, $inputSize - $pos ); } else { $read = self::readBytes( $inputHandle, Core::BUFFER_BYTE_SIZE ); } /* Encrypt this buffer. */ /** @var string */ $encrypted = \openssl_encrypt( $read, Core::CIPHER_METHOD, $ekey, OPENSSL_RAW_DATA, $thisIv ); Core::ensureTrue(\is_string($encrypted), 'OpenSSL encryption error'); /* Write this buffer's ciphertext. */ self::writeBytes($outputHandle, $encrypted, Core::ourStrlen($encrypted)); /* Add this buffer's ciphertext to the HMAC. */ \hash_update($hmac, $encrypted); /* Increment the counter by the number of blocks in a buffer. */ $thisIv = Core::incrementCounter($thisIv, $inc); /* WARNING: Usually, unless the file is a multiple of the buffer * size, $thisIv will contain an incorrect value here on the last * iteration of this loop. */ } /* Get the HMAC and append it to the ciphertext. */ $final_mac = \hash_final($hmac, true); self::writeBytes($outputHandle, $final_mac, Core::MAC_BYTE_SIZE); } /** * Decrypts a file-backed resource with either a key or a password. * * @param resource $inputHandle * @param resource $outputHandle * @param KeyOrPassword $secret * @return void * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\IOException * @throws Ex\WrongKeyOrModifiedCiphertextException */ public static function decryptResourceInternal($inputHandle, $outputHandle, KeyOrPassword $secret) { if (! \is_resource($inputHandle)) { throw new Ex\IOException( 'Input handle must be a resource!' ); } if (! \is_resource($outputHandle)) { throw new Ex\IOException( 'Output handle must be a resource!' ); } /* Make sure the file is big enough for all the reads we need to do. */ $stat = \fstat($inputHandle); if ($stat['size'] < Core::MINIMUM_CIPHERTEXT_SIZE) { throw new Ex\WrongKeyOrModifiedCiphertextException( 'Input file is too small to have been created by this library.' ); } /* Check the version header. */ $header = self::readBytes($inputHandle, Core::HEADER_VERSION_SIZE); if ($header !== Core::CURRENT_VERSION) { throw new Ex\WrongKeyOrModifiedCiphertextException( 'Bad version header.' ); } /* Get the salt. */ $file_salt = self::readBytes($inputHandle, Core::SALT_BYTE_SIZE); /* Get the IV. */ $ivsize = Core::BLOCK_BYTE_SIZE; $iv = self::readBytes($inputHandle, $ivsize); /* Derive the authentication and encryption keys. */ $keys = $secret->deriveKeys($file_salt); $ekey = $keys->getEncryptionKey(); $akey = $keys->getAuthenticationKey(); /* We'll store the MAC of each buffer-sized chunk as we verify the * actual MAC, so that we can check them again when decrypting. */ $macs = []; /* $thisIv will be incremented after each call to the decryption. */ $thisIv = $iv; /* How many blocks do we encrypt at a time? We increment by this value. */ $inc = (int) (Core::BUFFER_BYTE_SIZE / Core::BLOCK_BYTE_SIZE); /* Get the HMAC. */ if (\fseek($inputHandle, (-1 * Core::MAC_BYTE_SIZE), SEEK_END) === false) { throw new Ex\IOException( 'Cannot seek to beginning of MAC within input file' ); } /* Get the position of the last byte in the actual ciphertext. */ /** @var int $cipher_end */ $cipher_end = \ftell($inputHandle); if (!\is_int($cipher_end)) { throw new Ex\IOException( 'Cannot read input file' ); } /* We have the position of the first byte of the HMAC. Go back by one. */ --$cipher_end; /* Read the HMAC. */ /** @var string $stored_mac */ $stored_mac = self::readBytes($inputHandle, Core::MAC_BYTE_SIZE); /* Initialize a streaming HMAC state. */ /** @var resource $hmac */ $hmac = \hash_init(Core::HASH_FUNCTION_NAME, HASH_HMAC, $akey); Core::ensureTrue(\is_resource($hmac) || \is_object($hmac), 'Cannot initialize a hash context'); /* Reset file pointer to the beginning of the file after the header */ if (\fseek($inputHandle, Core::HEADER_VERSION_SIZE, SEEK_SET) === false) { throw new Ex\IOException( 'Cannot read seek within input file' ); } /* Seek to the start of the actual ciphertext. */ if (\fseek($inputHandle, Core::SALT_BYTE_SIZE + $ivsize, SEEK_CUR) === false) { throw new Ex\IOException( 'Cannot seek input file to beginning of ciphertext' ); } /* PASS #1: Calculating the HMAC. */ \hash_update($hmac, $header); \hash_update($hmac, $file_salt); \hash_update($hmac, $iv); /** @var resource $hmac2 */ $hmac2 = \hash_copy($hmac); $break = false; while (! $break) { /** @var int $pos */ $pos = \ftell($inputHandle); if (!\is_int($pos)) { throw new Ex\IOException( 'Could not get current position in input file during decryption' ); } /* Read the next buffer-sized chunk (or less). */ if ($pos + Core::BUFFER_BYTE_SIZE >= $cipher_end) { $break = true; $read = self::readBytes( $inputHandle, $cipher_end - $pos + 1 ); } else { $read = self::readBytes( $inputHandle, Core::BUFFER_BYTE_SIZE ); } /* Update the HMAC. */ \hash_update($hmac, $read); /* Remember this buffer-sized chunk's HMAC. */ /** @var resource $chunk_mac */ $chunk_mac = \hash_copy($hmac); Core::ensureTrue(\is_resource($chunk_mac) || \is_object($chunk_mac), 'Cannot duplicate a hash context'); $macs []= \hash_final($chunk_mac); } /* Get the final HMAC, which should match the stored one. */ /** @var string $final_mac */ $final_mac = \hash_final($hmac, true); /* Verify the HMAC. */ if (! Core::hashEquals($final_mac, $stored_mac)) { throw new Ex\WrongKeyOrModifiedCiphertextException( 'Integrity check failed.' ); } /* PASS #2: Decrypt and write output. */ /* Rewind to the start of the actual ciphertext. */ if (\fseek($inputHandle, Core::SALT_BYTE_SIZE + $ivsize + Core::HEADER_VERSION_SIZE, SEEK_SET) === false) { throw new Ex\IOException( 'Could not move the input file pointer during decryption' ); } $at_file_end = false; while (! $at_file_end) { /** @var int $pos */ $pos = \ftell($inputHandle); if (!\is_int($pos)) { throw new Ex\IOException( 'Could not get current position in input file during decryption' ); } /* Read the next buffer-sized chunk (or less). */ if ($pos + Core::BUFFER_BYTE_SIZE >= $cipher_end) { $at_file_end = true; $read = self::readBytes( $inputHandle, $cipher_end - $pos + 1 ); } else { $read = self::readBytes( $inputHandle, Core::BUFFER_BYTE_SIZE ); } /* Recalculate the MAC (so far) and compare it with the one we * remembered from pass #1 to ensure attackers didn't change the * ciphertext after MAC verification. */ \hash_update($hmac2, $read); /** @var resource $calc_mac */ $calc_mac = \hash_copy($hmac2); Core::ensureTrue(\is_resource($calc_mac) || \is_object($calc_mac), 'Cannot duplicate a hash context'); $calc = \hash_final($calc_mac); if (empty($macs)) { throw new Ex\WrongKeyOrModifiedCiphertextException( 'File was modified after MAC verification' ); } elseif (! Core::hashEquals(\array_shift($macs), $calc)) { throw new Ex\WrongKeyOrModifiedCiphertextException( 'File was modified after MAC verification' ); } /* Decrypt this buffer-sized chunk. */ /** @var string $decrypted */ $decrypted = \openssl_decrypt( $read, Core::CIPHER_METHOD, $ekey, OPENSSL_RAW_DATA, $thisIv ); Core::ensureTrue(\is_string($decrypted), 'OpenSSL decryption error'); /* Write the plaintext to the output file. */ self::writeBytes( $outputHandle, $decrypted, Core::ourStrlen($decrypted) ); /* Increment the IV by the amount of blocks in a buffer. */ /** @var string $thisIv */ $thisIv = Core::incrementCounter($thisIv, $inc); /* WARNING: Usually, unless the file is a multiple of the buffer * size, $thisIv will contain an incorrect value here on the last * iteration of this loop. */ } } /** * Read from a stream; prevent partial reads. * * @param resource $stream * @param int $num_bytes * @return string * * @throws Ex\IOException * @throws Ex\EnvironmentIsBrokenException * * @return string */ public static function readBytes($stream, $num_bytes) { Core::ensureTrue($num_bytes >= 0, 'Tried to read less than 0 bytes'); if ($num_bytes === 0) { return ''; } $buf = ''; $remaining = $num_bytes; while ($remaining > 0 && ! \feof($stream)) { /** @var string $read */ $read = \fread($stream, $remaining); if (!\is_string($read)) { throw new Ex\IOException( 'Could not read from the file' ); } $buf .= $read; $remaining -= Core::ourStrlen($read); } if (Core::ourStrlen($buf) !== $num_bytes) { throw new Ex\IOException( 'Tried to read past the end of the file' ); } return $buf; } /** * Write to a stream; prevents partial writes. * * @param resource $stream * @param string $buf * @param int $num_bytes * @return int * * @throws Ex\IOException * * @return string */ public static function writeBytes($stream, $buf, $num_bytes = null) { $bufSize = Core::ourStrlen($buf); if ($num_bytes === null) { $num_bytes = $bufSize; } if ($num_bytes > $bufSize) { throw new Ex\IOException( 'Trying to write more bytes than the buffer contains.' ); } if ($num_bytes < 0) { throw new Ex\IOException( 'Tried to write less than 0 bytes' ); } $remaining = $num_bytes; while ($remaining > 0) { /** @var int $written */ $written = \fwrite($stream, $buf, $remaining); if (!\is_int($written)) { throw new Ex\IOException( 'Could not write to the file' ); } $buf = (string) Core::ourSubstr($buf, $written, null); $remaining -= $written; } return $num_bytes; } /** * Returns the last PHP error's or warning's message string. * * @return string */ private static function getLastErrorMessage() { $error = error_get_last(); if ($error === null) { return '[no PHP error]'; } else { return $error['message']; } } } php-encryption-2.2.1/src/Key.php000066400000000000000000000043561332573277400165500ustar00rootroot00000000000000key_bytes ); } /** * Gets the raw bytes of the key. * * @return string */ public function getRawBytes() { return $this->key_bytes; } /** * Constructs a new Key object from a string of raw bytes. * * @param string $bytes * * @throws Ex\EnvironmentIsBrokenException */ private function __construct($bytes) { Core::ensureTrue( Core::ourStrlen($bytes) === self::KEY_BYTE_SIZE, 'Bad key length.' ); $this->key_bytes = $bytes; } } php-encryption-2.2.1/src/KeyOrPassword.php000066400000000000000000000105511332573277400205660ustar00rootroot00000000000000secret_type === self::SECRET_TYPE_KEY) { Core::ensureTrue($this->secret instanceof Key); /** * @psalm-suppress PossiblyInvalidMethodCall */ $akey = Core::HKDF( Core::HASH_FUNCTION_NAME, $this->secret->getRawBytes(), Core::KEY_BYTE_SIZE, Core::AUTHENTICATION_INFO_STRING, $salt ); /** * @psalm-suppress PossiblyInvalidMethodCall */ $ekey = Core::HKDF( Core::HASH_FUNCTION_NAME, $this->secret->getRawBytes(), Core::KEY_BYTE_SIZE, Core::ENCRYPTION_INFO_STRING, $salt ); return new DerivedKeys($akey, $ekey); } elseif ($this->secret_type === self::SECRET_TYPE_PASSWORD) { Core::ensureTrue(\is_string($this->secret)); /* Our PBKDF2 polyfill is vulnerable to a DoS attack documented in * GitHub issue #230. The fix is to pre-hash the password to ensure * it is short. We do the prehashing here instead of in pbkdf2() so * that pbkdf2() still computes the function as defined by the * standard. */ /** * @psalm-suppress PossiblyInvalidArgument */ $prehash = \hash(Core::HASH_FUNCTION_NAME, $this->secret, true); $prekey = Core::pbkdf2( Core::HASH_FUNCTION_NAME, $prehash, $salt, self::PBKDF2_ITERATIONS, Core::KEY_BYTE_SIZE, true ); $akey = Core::HKDF( Core::HASH_FUNCTION_NAME, $prekey, Core::KEY_BYTE_SIZE, Core::AUTHENTICATION_INFO_STRING, $salt ); /* Note the cryptographic re-use of $salt here. */ $ekey = Core::HKDF( Core::HASH_FUNCTION_NAME, $prekey, Core::KEY_BYTE_SIZE, Core::ENCRYPTION_INFO_STRING, $salt ); return new DerivedKeys($akey, $ekey); } else { throw new Ex\EnvironmentIsBrokenException('Bad secret type.'); } } /** * Constructor for KeyOrPassword. * * @param int $secret_type * @param mixed $secret (either a Key or a password string) */ private function __construct($secret_type, $secret) { // The constructor is private, so these should never throw. if ($secret_type === self::SECRET_TYPE_KEY) { Core::ensureTrue($secret instanceof Key); } elseif ($secret_type === self::SECRET_TYPE_PASSWORD) { Core::ensureTrue(\is_string($secret)); } else { throw new Ex\EnvironmentIsBrokenException('Bad secret type.'); } $this->secret_type = $secret_type; $this->secret = $secret; } } php-encryption-2.2.1/src/KeyProtectedByPassword.php000066400000000000000000000106301332573277400224300ustar00rootroot00000000000000saveToAsciiSafeString(), \hash(Core::HASH_FUNCTION_NAME, $password, true), true ); return new KeyProtectedByPassword($encrypted_key); } /** * Loads a KeyProtectedByPassword from its encoded form. * * @param string $saved_key_string * * @throws Ex\BadFormatException * * @return KeyProtectedByPassword */ public static function loadFromAsciiSafeString($saved_key_string) { $encrypted_key = Encoding::loadBytesFromChecksummedAsciiSafeString( self::PASSWORD_KEY_CURRENT_VERSION, $saved_key_string ); return new KeyProtectedByPassword($encrypted_key); } /** * Encodes the KeyProtectedByPassword into a string of printable ASCII * characters. * * @throws Ex\EnvironmentIsBrokenException * * @return string */ public function saveToAsciiSafeString() { return Encoding::saveBytesToChecksummedAsciiSafeString( self::PASSWORD_KEY_CURRENT_VERSION, $this->encrypted_key ); } /** * Decrypts the protected key, returning an unprotected Key object that can * be used for encryption and decryption. * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\WrongKeyOrModifiedCiphertextException * * @param string $password * @return Key */ public function unlockKey($password) { try { $inner_key_encoded = Crypto::decryptWithPassword( $this->encrypted_key, \hash(Core::HASH_FUNCTION_NAME, $password, true), true ); return Key::loadFromAsciiSafeString($inner_key_encoded); } catch (Ex\BadFormatException $ex) { /* This should never happen unless an attacker replaced the * encrypted key ciphertext with some other ciphertext that was * encrypted with the same password. We transform the exception type * here in order to make the API simpler, avoiding the need to * document that this method might throw an Ex\BadFormatException. */ throw new Ex\WrongKeyOrModifiedCiphertextException( "The decrypted key was found to be in an invalid format. " . "This very likely indicates it was modified by an attacker." ); } } /** * Changes the password. * * @param string $current_password * @param string $new_password * * @throws Ex\EnvironmentIsBrokenException * @throws Ex\WrongKeyOrModifiedCiphertextException * * @return KeyProtectedByPassword */ public function changePassword($current_password, $new_password) { $inner_key = $this->unlockKey($current_password); /* The password is hashed as a form of poor-man's domain separation * between this use of encryptWithPassword() and other uses of * encryptWithPassword() that the user may also be using as part of the * same protocol. */ $encrypted_key = Crypto::encryptWithPassword( $inner_key->saveToAsciiSafeString(), \hash(Core::HASH_FUNCTION_NAME, $new_password, true), true ); $this->encrypted_key = $encrypted_key; return $this; } /** * Constructor for KeyProtectedByPassword. * * @param string $encrypted_key */ private function __construct($encrypted_key) { $this->encrypted_key = $encrypted_key; } } php-encryption-2.2.1/src/RuntimeTests.php000066400000000000000000000202671332573277400204650ustar00rootroot00000000000000getRawBytes()) === Core::KEY_BYTE_SIZE); Core::ensureTrue(Core::ENCRYPTION_INFO_STRING !== Core::AUTHENTICATION_INFO_STRING); } catch (Ex\EnvironmentIsBrokenException $ex) { // Do this, otherwise it will stay in the "tests are running" state. $test_state = 3; throw $ex; } // Change this to '0' make the tests always re-run (for benchmarking). $test_state = 1; } /** * High-level tests of Crypto operations. * * @throws Ex\EnvironmentIsBrokenException * @return void */ private static function testEncryptDecrypt() { $key = Key::createNewRandomKey(); $data = "EnCrYpT EvErYThInG\x00\x00"; // Make sure encrypting then decrypting doesn't change the message. $ciphertext = Crypto::encrypt($data, $key, true); try { $decrypted = Crypto::decrypt($ciphertext, $key, true); } catch (Ex\WrongKeyOrModifiedCiphertextException $ex) { // It's important to catch this and change it into a // Ex\EnvironmentIsBrokenException, otherwise a test failure could trick // the user into thinking it's just an invalid ciphertext! throw new Ex\EnvironmentIsBrokenException(); } Core::ensureTrue($decrypted === $data); // Modifying the ciphertext: Appending a string. try { Crypto::decrypt($ciphertext . 'a', $key, true); throw new Ex\EnvironmentIsBrokenException(); } catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */ } // Modifying the ciphertext: Changing an HMAC byte. $indices_to_change = [ 0, // The header. Core::HEADER_VERSION_SIZE + 1, // the salt Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + 1, // the IV Core::HEADER_VERSION_SIZE + Core::SALT_BYTE_SIZE + Core::BLOCK_BYTE_SIZE + 1, // the ciphertext ]; foreach ($indices_to_change as $index) { try { $ciphertext[$index] = \chr((\ord($ciphertext[$index]) + 1) % 256); Crypto::decrypt($ciphertext, $key, true); throw new Ex\EnvironmentIsBrokenException(); } catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */ } } // Decrypting with the wrong key. $key = Key::createNewRandomKey(); $data = 'abcdef'; $ciphertext = Crypto::encrypt($data, $key, true); $wrong_key = Key::createNewRandomKey(); try { Crypto::decrypt($ciphertext, $wrong_key, true); throw new Ex\EnvironmentIsBrokenException(); } catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */ } // Ciphertext too small. $key = Key::createNewRandomKey(); $ciphertext = \str_repeat('A', Core::MINIMUM_CIPHERTEXT_SIZE - 1); try { Crypto::decrypt($ciphertext, $key, true); throw new Ex\EnvironmentIsBrokenException(); } catch (Ex\WrongKeyOrModifiedCiphertextException $e) { /* expected */ } } /** * Test HKDF against test vectors. * * @throws Ex\EnvironmentIsBrokenException * @return void */ private static function HKDFTestVector() { // HKDF test vectors from RFC 5869 // Test Case 1 $ikm = \str_repeat("\x0b", 22); $salt = Encoding::hexToBin('000102030405060708090a0b0c'); $info = Encoding::hexToBin('f0f1f2f3f4f5f6f7f8f9'); $length = 42; $okm = Encoding::hexToBin( '3cb25f25faacd57a90434f64d0362f2a' . '2d2d0a90cf1a5a4c5db02d56ecc4c5bf' . '34007208d5b887185865' ); $computed_okm = Core::HKDF('sha256', $ikm, $length, $info, $salt); Core::ensureTrue($computed_okm === $okm); // Test Case 7 $ikm = \str_repeat("\x0c", 22); $length = 42; $okm = Encoding::hexToBin( '2c91117204d745f3500d636a62f64f0a' . 'b3bae548aa53d423b0d1f27ebba6f5e5' . '673a081d70cce7acfc48' ); $computed_okm = Core::HKDF('sha1', $ikm, $length, '', null); Core::ensureTrue($computed_okm === $okm); } /** * Test HMAC against test vectors. * * @throws Ex\EnvironmentIsBrokenException * @return void */ private static function HMACTestVector() { // HMAC test vector From RFC 4231 (Test Case 1) $key = \str_repeat("\x0b", 20); $data = 'Hi There'; $correct = 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7'; Core::ensureTrue( \hash_hmac(Core::HASH_FUNCTION_NAME, $data, $key) === $correct ); } /** * Test AES against test vectors. * * @throws Ex\EnvironmentIsBrokenException * @return void */ private static function AESTestVector() { // AES CTR mode test vector from NIST SP 800-38A $key = Encoding::hexToBin( '603deb1015ca71be2b73aef0857d7781' . '1f352c073b6108d72d9810a30914dff4' ); $iv = Encoding::hexToBin('f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'); $plaintext = Encoding::hexToBin( '6bc1bee22e409f96e93d7e117393172a' . 'ae2d8a571e03ac9c9eb76fac45af8e51' . '30c81c46a35ce411e5fbc1191a0a52ef' . 'f69f2445df4f9b17ad2b417be66c3710' ); $ciphertext = Encoding::hexToBin( '601ec313775789a5b7a7f504bbf3d228' . 'f443e3ca4d62b59aca84e990cacaf5c5' . '2b0930daa23de94ce87017ba2d84988d' . 'dfc9c58db67aada613c2dd08457941a6' ); $computed_ciphertext = Crypto::plainEncrypt($plaintext, $key, $iv); Core::ensureTrue($computed_ciphertext === $ciphertext); $computed_plaintext = Crypto::plainDecrypt($ciphertext, $key, $iv, Core::CIPHER_METHOD); Core::ensureTrue($computed_plaintext === $plaintext); } }