├── .devcontainer ├── Dockerfile ├── devcontainer.json └── php.ini ├── .github ├── FUNDING.yml └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── LICENCE ├── README.md ├── README.old.md ├── benchmarks ├── Base64Bench.php ├── DecryptionBench.php ├── EncryptionBench.php ├── KeyKeyLoader.php ├── encrypted-aes256gcm ├── encrypted-laravel ├── encrypted-xchacha └── key ├── composer.json ├── composer.lock ├── config └── crypto.php ├── docs ├── Commands.md ├── Encryption.md ├── Hashing.md ├── Signing.md └── Utilities.md ├── phpbench.json ├── phpunit.xml ├── src ├── Console │ └── GenerateCryptoKeysCommand.php ├── Contracts │ ├── Encoder.php │ ├── Hashing.php │ ├── KeyGenerator.php │ ├── KeyLoader.php │ ├── PublicKeySigning.php │ └── Signing.php ├── Encoder │ ├── IgbinaryEncoder.php │ ├── JsonEncoder.php │ ├── MessagePackEncoder.php │ └── PhpEncoder.php ├── Encryption │ ├── AesGcm256Encrypter.php │ └── XChaCha20Poly1305Encrypter.php ├── Enums │ └── Encryption.php ├── Facades │ ├── Hashing.php │ └── Sign.php ├── Hashing │ ├── Blake2b.php │ ├── HashingManager.php │ ├── Sha256.php │ ├── Sha512.php │ └── Traits │ │ ├── Blake2b.php │ │ ├── Hash.php │ │ ├── Sha256.php │ │ └── Sha512.php ├── Keys │ ├── Generators │ │ ├── AppKeyGenerator.php │ │ ├── Blake2BHashingKeyGenerator.php │ │ ├── EdDSASignerKeyGenerator.php │ │ └── HmacKeyGenerator.php │ └── Loaders │ │ ├── AppKeyLoader.php │ │ ├── Blake2BHashingKeyLoader.php │ │ ├── EdDSASignerKeyLoader.php │ │ └── HmacKeyLoader.php ├── ServiceProvider.php ├── Signing │ ├── EdDSA │ │ └── EdDSA.php │ ├── Hmac │ │ ├── Blake2b.php │ │ ├── Sha256.php │ │ └── Sha512.php │ ├── SigningManager.php │ └── Traits │ │ ├── Blake2b.php │ │ ├── EdDSA.php │ │ ├── Hmac256.php │ │ ├── Hmac512.php │ │ └── Signing.php ├── Support │ ├── Base64.php │ ├── Random.php │ └── Random82.php └── Traits │ ├── ConstantTimeCompare.php │ ├── Crypto.php │ ├── EnvKeySaver.php │ └── LaravelKeyParser.php ├── testbench.yaml ├── tests ├── Architecture │ ├── CommandsTest.php │ ├── ContractsTest.php │ ├── EncodersTest.php │ ├── EncryptionTest.php │ ├── EnumTest.php │ ├── FacadesTest.php │ ├── GlobalTest.php │ ├── HashingTest.php │ ├── KeysTest.php │ ├── SigningTest.php │ └── TraitTest.php ├── Feature │ ├── Encryption │ │ ├── AesGcm256EncryptorTest.php │ │ └── XChaCha20Poly1305EncryptorTest.php │ └── ServiceProviderLoadingTest.php ├── InMemoryAppKeyKeyLoader.php ├── Pest.php ├── TestCase.php └── Unit │ ├── Encoders │ ├── IgbinaryEncoderTest.php │ ├── JsonEncoderTest.php │ └── PhpEncoderTest.php │ ├── Encryption │ └── CryptoTraitTest.php │ ├── Hashing │ ├── Blake2bTest.php │ ├── Sha256Test.php │ └── Sha512Test.php │ └── Support │ └── Base64Test.php └── workbench ├── .gitignore ├── app ├── Models │ └── .gitkeep └── Providers │ └── WorkbenchServiceProvider.php ├── config └── app.php ├── database ├── factories │ └── .gitkeep ├── migrations │ └── .gitkeep └── seeders │ ├── .gitkeep │ └── DatabaseSeeder.php ├── resources └── views │ └── .gitkeep └── routes ├── .gitkeep ├── api.php ├── console.php └── web.php /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/php:1-8.1-bookworm 2 | 3 | ENV TZ=UTC 4 | 5 | COPY ./.devcontainer/php.ini /usr/local/etc/php/conf.d/99-php.ini 6 | 7 | RUN ln -snf "/usr/share/zoneinfo/$TZ" /etc/localtime && echo "$TZ" > /etc/timezone 8 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LaravelCrypto", 3 | "dockerFile": "./Dockerfile", 4 | "context": "../", 5 | "features": { 6 | "ghcr.io/devcontainers/features/common-utils:2": { 7 | "installZsh": true, 8 | "configureZshAsDefaultShell": true, 9 | "nonFreePackages": true 10 | }, 11 | "ghcr.io/devcontainers-contrib/features/composer:1": {}, 12 | "ghcr.io/devcontainers-contrib/features/act:1": {}, 13 | "ghcr.io/devcontainers-contrib/features/protoc-asdf:1": {}, 14 | "ghcr.io/devcontainers-contrib/features/curl-apt-get:1": {}, 15 | "ghcr.io/devcontainers-contrib/features/exa:1": {}, 16 | "ghcr.io/devcontainers-contrib/features/fd:1": {}, 17 | "ghcr.io/devcontainers-contrib/features/fzf:1": {}, 18 | "ghcr.io/devcontainers-contrib/features/ripgrep:1": {}, 19 | "ghcr.io/christophermacgown/devcontainer-features/direnv:1": {}, 20 | "ghcr.io/rocker-org/devcontainer-features/apt-packages:1": { 21 | "packages": [ 22 | "ca-certificates", 23 | "procps", 24 | "dnsutils", 25 | "gnupg", 26 | "build-essential", 27 | "unzip", 28 | "zip" 29 | ], 30 | "upgradePackages": true 31 | } 32 | }, 33 | "customizations": { 34 | "vscode": { 35 | "extensions": [ 36 | "xdebug.php-debug", 37 | "bmewburn.vscode-intelephense-client", 38 | "mikestead.dotenv", 39 | "usernamehw.errorlens", 40 | "m1guelpf.better-pest", 41 | "calebporzio.better-phpunit", 42 | "DEVSENSE.composer-php-vscode", 43 | "muath-ye.composer-Intelephense", 44 | "ms-azuretools.vscode-docker", 45 | "p1c2u.docker-compose", 46 | "github.vscode-github-actions", 47 | "eamodio.gitlens", 48 | "ryannaddy.laravel-artisan", 49 | "amiralizadeh9480.laravel-extra-intellisense", 50 | "georgykurian.laravel-ide-helper", 51 | "porifa.laravel-intelephense", 52 | "mohamedbenhida.laravel-intellisense", 53 | "open-southeners.laravel-pint", 54 | "christian-kohler.path-intellisense", 55 | "ctf0.php-array-symbols", 56 | "MehediDracula.php-constructor", 57 | "neilbrayfield.php-docblocker", 58 | "marabesi.php-import-checker", 59 | "MehediDracula.php-namespace-resolver", 60 | "MrChetan.phpstorm-parameter-hints-in-vscode", 61 | "recca0120.vscode-phpunit", 62 | "santigarcor.phpunit-extended", 63 | "emallin.phpunit", 64 | "hbenl.vscode-test-explorer" 65 | ], 66 | "settings": { 67 | "php.suggest.basic": false, 68 | "php.validate.enable": true, 69 | "php.validate.run": "onType", 70 | "intelephense.stubs": [ 71 | "apache", 72 | "redis", 73 | "uv", 74 | "bcmath", 75 | "bz2", 76 | "calendar", 77 | "Core", 78 | "ctype", 79 | "curl", 80 | "date", 81 | "dom", 82 | "enchant", 83 | "exif", 84 | "fileinfo", 85 | "filter", 86 | "gd", 87 | "cassandra", 88 | "FFI", 89 | "geoip", 90 | "event", 91 | "amqp", 92 | "apcu", 93 | "gettext", 94 | "igbinary", 95 | "imagick", 96 | "libsodium", 97 | "memcached", 98 | "mongodb", 99 | "parallel", 100 | "sodium", 101 | "yaml", 102 | "hash", 103 | "iconv", 104 | "intl", 105 | "json", 106 | "mbstring", 107 | "meta", 108 | "mssql", 109 | "mysqli", 110 | "openssl", 111 | "pcntl", 112 | "pcre", 113 | "PDO", 114 | "ds", 115 | "pdo_mysql", 116 | "pdo_pgsql", 117 | "pdo_sqlite", 118 | "pgsql", 119 | "Phar", 120 | "posix", 121 | "pspell", 122 | "readline", 123 | "recode", 124 | "Reflection", 125 | "regex", 126 | "session", 127 | "SimpleXML", 128 | "sockets", 129 | "sodium", 130 | "SPL", 131 | "sqlite3", 132 | "standard", 133 | "superglobals", 134 | "tidy", 135 | "uv", 136 | "tokenizer", 137 | "wddx", 138 | "swoole", 139 | "Zend OPcache", 140 | "zip", 141 | "zlib" 142 | ], 143 | "intelephense.telemetry.enabled": true, 144 | "intelephense.format.enable": true, 145 | "intelephense.diagnostics.embeddedLanguages": true, 146 | "intelephense.diagnostics.languageConstraints": true, 147 | "intelephense.diagnostics.typeErrors": true, 148 | "intelephense.compatibility.correctForArrayAccessArrayAndTraversableArrayUnionTypes": true, 149 | "intelephense.compatibility.correctForBaseClassStaticUnionTypes": true, 150 | "intelephense.diagnostics.implementationErrors": true, 151 | "intelephense.diagnostics.undefinedClassConstants": true, 152 | "intelephense.diagnostics.unusedSymbols": false, 153 | "intelephense.completion.insertUseDeclaration": true, 154 | "intelephense.completion.fullyQualifyGlobalConstantsAndFunctions": true, 155 | "intelephense.trace.server": "off", 156 | "intelephense.files.exclude": [ 157 | "**/.git/**", 158 | "**/.svn/**", 159 | "**/.hg/**", 160 | "**/CVS/**", 161 | "**/.DS_Store/**", 162 | "**/node_modules/**", 163 | "**/bower_components/**", 164 | "**/storage", 165 | "**/storage/**" 166 | ] 167 | } 168 | }, 169 | "jetbrains": { 170 | "backend": "PhpStorm", 171 | "settings": { 172 | "DockerSettings.useComposeV2": true 173 | } 174 | } 175 | }, 176 | "postCreateCommand": "sudo mkdir -p /var/log/xdebug && sudo chmod -R 777 /var/log/xdebug", 177 | "postStartCommand": "composer install --prefer-dist --no-interaction" 178 | } -------------------------------------------------------------------------------- /.devcontainer/php.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | expose_php = On 3 | error_reporting = E_ALL 4 | display_errors = On 5 | report_memleaks = On 6 | html_errors = On 7 | auto_globals_jit = Off 8 | max_input_time = 60 9 | max_execution_time = -1 10 | post_max_size = 100M 11 | upload_max_filesize = 100M 12 | variables_order = EGPCS 13 | output_buffering = 4096 14 | register_argc_argv = On 15 | request_order = GP 16 | short_open_tag = Off 17 | precision = 14 18 | max_input_nesting_level = 32 19 | implicit_flush = Off 20 | serialize_precision = 14 21 | ignore_user_abort = On 22 | realpath_cache_size = 4096k 23 | zend.enable_gc = On 24 | zend.exception_ignore_args = On 25 | log_errors = On 26 | ignore_repeated_errors = Off 27 | default_mimetype = "text/html" 28 | default_charset = "UTF-8" 29 | enable_dl = Off 30 | file_uploads = On 31 | max_file_uploads = 20 32 | allow_url_fopen = On 33 | allow_url_include = 0 34 | default_socket_timeout = 60 35 | user_agent = "" 36 | 37 | [opcache] 38 | opcache.jit = disable 39 | 40 | [Assertion] 41 | zend.assertions = 1 42 | assert.exception = On 43 | assert.warning = On 44 | 45 | [xdebug] 46 | xdebug.discover_client_host = 1 47 | xdebug.log = /var/log/xdebug/xdebug.log 48 | xdebug.dump_globals = 1 49 | xdebug.dump_once = 1 -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [CodeLieutenant] 2 | ko-fi: codelieutenant 3 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: 'Create Release' 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | create_release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 1 15 | - name: Get Tag 16 | if: startsWith(github.ref, 'refs/tags/v') 17 | uses: olegtarasov/get-tag@v2.1.3 18 | id: version_tag 19 | with: 20 | tagRegex: "v(.*)" 21 | - name: Upload Release Asset 22 | uses: softprops/action-gh-release@v1 23 | id: release 24 | with: 25 | name: "v${{ steps.version_tag.outputs.tag }}" 26 | tag_name: "v${{ steps.version_tag.outputs.tag }}" 27 | generate_release_notes: true 28 | append_body: true 29 | prerelease: false 30 | fail_on_unmatched_files: true 31 | - name: "Generate release changelog" 32 | uses: heinrichreimer/action-github-changelog-generator@v2.3 33 | with: 34 | token: ${{ secrets.GITHUB_TOKEN }} 35 | author: true 36 | releaseUrl: ${{ steps.release.outputs.url }} 37 | issues: false 38 | pullRequests: true 39 | - uses: stefanzweifel/git-auto-commit-action@v5 40 | with: 41 | commit_message: "Update CHANGELOG.md" 42 | branch: master 43 | commit_options: '--no-verify --signoff' 44 | file_pattern: CHANGELOG.md 45 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "Run Tests" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | path: 8 | - 'src/**' 9 | - 'tests/**' 10 | pull_request: 11 | branches: 12 | - master 13 | paths: 14 | - 'src/**' 15 | - 'tests/**' 16 | 17 | env: 18 | XDEBUG_MODE: "coverage" 19 | GITHUB_WORKSPACE: /var/www/html 20 | 21 | jobs: 22 | testing: 23 | runs-on: ubuntu-latest 24 | strategy: 25 | matrix: 26 | operating-system: ['ubuntu-latest'] 27 | php-versions: ['8.1', '8.2', '8.3'] 28 | phpts: [true, false] 29 | env: 30 | extensions: mbstring, intl, sodium, xsl, zip, pdo, pdo_sqlite, xdebug, curl, igbinary, msgpack 31 | key: extensions-cache 32 | steps: 33 | - uses: actions/checkout@v4 34 | with: 35 | fetch-depth: 0 36 | 37 | - name: Cache Vendor 38 | uses: actions/cache@v3 39 | with: 40 | path: vendor 41 | key: ${{ runner.os }}-vendor-${{ hashFiles('**/composer.lock') }} 42 | 43 | - name: Setup cache environment 44 | id: extcache 45 | uses: shivammathur/cache-extensions@v1 46 | with: 47 | php-version: ${{ matrix.php-versions }} 48 | extensions: ${{ env.extensions }} 49 | key: ${{ env.key }} 50 | 51 | - name: Cache extensions 52 | uses: actions/cache@v3 53 | with: 54 | path: ${{ steps.extcache.outputs.dir }} 55 | key: ${{ steps.extcache.outputs.key }}-${{ env.phpts }} 56 | restore-keys: ${{ steps.extcache.outputs.key }}-${{ env.phpts }} 57 | 58 | - name: Setup PHP 59 | id: setup-php 60 | uses: shivammathur/setup-php@v2 61 | with: 62 | php-version: ${{ matrix.php-versions }} 63 | phpts: ${{ matrix.phpts }} 64 | extensions: ${{ env.extensions }} 65 | tools: php-cs-fixer, phpcs, composer 66 | update: true 67 | coverage: xdebug 68 | 69 | - name: Composer install 70 | run: composer install -q --no-ansi --prefer-dist --no-interaction --no-progress 71 | 72 | - name: Run Tests 73 | run: vendor/bin/pest --coverage-clover=coverage.xml --coverage --colors=always --fail-on-risky --fail-on-warning --fail-on-deprecation --strict-coverage 74 | 75 | - uses: codecov/codecov-action@v4.0.1 76 | with: 77 | file: ./coverage.xml 78 | token: ${{ secrets.CODECOV_TOKEN }} 79 | slug: dmalusev/laravel-crypto 80 | name: laravel-crypto 81 | fail_ci_if_error: false 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | .idea/ 3 | .phpbench/ 4 | coverage.xml 5 | teamcity.txt 6 | junit.xml -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v1.0.0](https://github.com/CodeLieutenant/laravel-crypto/releases/tag/v1.0.0) (2024-03-15) 4 | 5 | [Full Changelog](https://github.com/CodeLieutenant/laravel-crypto/compare/v1.0.0-rc1...v1.0.0) 6 | 7 | **Implemented enhancements:** 8 | 9 | - Github Actions for Releasing package [\#16](https://github.com/CodeLieutenant/laravel-crypto/issues/16) 10 | - Add CHANGELOG for releases [\#15](https://github.com/CodeLieutenant/laravel-crypto/issues/15) 11 | - Rewrite tests from PHP Unit to Pest [\#6](https://github.com/CodeLieutenant/laravel-crypto/issues/6) 12 | - \*BREAKING\* Change Namespace to LaravelCrypto [\#5](https://github.com/CodeLieutenant/laravel-crypto/issues/5) 13 | - Migrate to Github Actions [\#4](https://github.com/CodeLieutenant/laravel-crypto/issues/4) 14 | - Minimum PHP version 8.1 [\#3](https://github.com/CodeLieutenant/laravel-crypto/issues/3) 15 | - Add support for laravel 11 [\#27](https://github.com/CodeLieutenant/laravel-crypto/pull/27) ([CodeLieutenant](https://github.com/CodeLieutenant)) 16 | - Refactor Keys \*\*BREAKING\*\* [\#26](https://github.com/CodeLieutenant/laravel-crypto/pull/26) ([CodeLieutenant](https://github.com/CodeLieutenant)) 17 | - Chore/traits \*\*BREAKING\*\* [\#25](https://github.com/CodeLieutenant/laravel-crypto/pull/25) ([CodeLieutenant](https://github.com/CodeLieutenant)) 18 | - chore: move Encoder to Contacts namespace \*\*BREAKING\*\* [\#24](https://github.com/CodeLieutenant/laravel-crypto/pull/24) ([CodeLieutenant](https://github.com/CodeLieutenant)) 19 | - chore: rename \*Encryptor to \*Encrypter per Laravel convention \*\*BREAKING\*\* [\#23](https://github.com/CodeLieutenant/laravel-crypto/pull/23) ([CodeLieutenant](https://github.com/CodeLieutenant)) 20 | - chore: move Cryto trait to Traits, move Encryption enum to Enums \*\*BREAKING\*\* [\#22](https://github.com/CodeLieutenant/laravel-crypto/pull/22) ([CodeLieutenant](https://github.com/CodeLieutenant)) 21 | - Feat/gihub actions [\#19](https://github.com/CodeLieutenant/laravel-crypto/pull/19) ([CodeLieutenant](https://github.com/CodeLieutenant)) 22 | - V1 [\#7](https://github.com/CodeLieutenant/laravel-crypto/pull/7) ([CodeLieutenant](https://github.com/CodeLieutenant)) 23 | 24 | **Fixed bugs:** 25 | 26 | - fix: implement Encrypter and StringEncrypter interfaces on XChaChaPoly1305Encryptor class [\#21](https://github.com/CodeLieutenant/laravel-crypto/pull/21) ([CodeLieutenant](https://github.com/CodeLieutenant)) 27 | 28 | **Merged pull requests:** 29 | 30 | - Change namespace [\#20](https://github.com/CodeLieutenant/laravel-crypto/pull/20) ([CodeLieutenant](https://github.com/CodeLieutenant)) 31 | - Feat/pest tests [\#14](https://github.com/CodeLieutenant/laravel-crypto/pull/14) ([CodeLieutenant](https://github.com/CodeLieutenant)) 32 | 33 | ## [v1.0.0-rc1](https://github.com/CodeLieutenant/laravel-crypto/releases/tag/v1.0.0) (2024-03-05) 34 | 35 | [Full Changelog](https://github.com/CodeLieutenant/laravel-crypto/compare/v1.0.0-beta2...v1.0.0-rc1) 36 | 37 | **Implemented enhancements:** 38 | 39 | - Refactor Keys \*\*BREAKING\*\* [\#26](https://github.com/CodeLieutenant/laravel-crypto/pull/26) ([CodeLieutenant](https://github.com/CodeLieutenant)) 40 | - Chore/traits \*\*BREAKING\*\* [\#25](https://github.com/CodeLieutenant/laravel-crypto/pull/25) ([CodeLieutenant](https://github.com/CodeLieutenant)) 41 | - chore: move Encoder to Contacts namespace \*\*BREAKING\*\* [\#24](https://github.com/CodeLieutenant/laravel-crypto/pull/24) ([CodeLieutenant](https://github.com/CodeLieutenant)) 42 | - chore: rename \*Encryptor to \*Encrypter per Laravel convention \*\*BREAKING\*\* [\#23](https://github.com/CodeLieutenant/laravel-crypto/pull/23) ([CodeLieutenant](https://github.com/CodeLieutenant)) 43 | - chore: move Cryto trait to Traits, move Encryption enum to Enums \*\*BREAKING\*\* [\#22](https://github.com/CodeLieutenant/laravel-crypto/pull/22) ([CodeLieutenant](https://github.com/CodeLieutenant)) 44 | 45 | **Fixed bugs:** 46 | 47 | - fix: implement Encrypter and StringEncrypter interfaces on XChaChaPoly1305Encryptor class [\#21](https://github.com/CodeLieutenant/laravel-crypto/pull/21) ([CodeLieutenant](https://github.com/CodeLieutenant)) 48 | 49 | ## [v1.0.0-beta2](https://github.com/CodeLieutenant/laravel-crypto/releases/tag/v1.0.0) (2024-03-05) 50 | 51 | [Full Changelog](https://github.com/CodeLieutenant/laravel-crypto/compare/v1.0.0-beta1...v1.0.0-beta2) 52 | 53 | ## [v1.0.0-beta1](https://github.com/CodeLieutenant/laravel-crypto/releases/tag/v1.0.0) (2024-02-28) 54 | 55 | [Full Changelog](https://github.com/CodeLieutenant/laravel-crypto/compare/v1.0.0-alpha2...v1.0.0-beta1) 56 | 57 | ## [v1.0.0-alpha2](https://github.com/CodeLieutenant/laravel-crypto/releases/tag/v1.0.0) (2024-02-28) 58 | 59 | [Full Changelog](https://github.com/CodeLieutenant/laravel-crypto/compare/v1.0.0-alpha1...v1.0.0-alpha2) 60 | 61 | **Implemented enhancements:** 62 | 63 | - Github Actions for Releasing package [\#16](https://github.com/CodeLieutenant/laravel-crypto/issues/16) 64 | - Add CHANGELOG for releases [\#15](https://github.com/CodeLieutenant/laravel-crypto/issues/15) 65 | 66 | ## [v1.0.0-alpha1](https://github.com/CodeLieutenant/laravel-crypto/releases/tag/v1.0.0) (2024-02-27) 67 | 68 | [Full Changelog](https://github.com/CodeLieutenant/laravel-crypto/compare/v0.1.0...v1.0.0-alpha1) 69 | 70 | **Implemented enhancements:** 71 | 72 | - Rewrite tests from PHP Unit to Pest [\#6](https://github.com/CodeLieutenant/laravel-crypto/issues/6) 73 | - \*BREAKING\* Change Namespace to LaravelCrypto [\#5](https://github.com/CodeLieutenant/laravel-crypto/issues/5) 74 | - Migrate to Github Actions [\#4](https://github.com/CodeLieutenant/laravel-crypto/issues/4) 75 | - Minimum PHP version 8.1 [\#3](https://github.com/CodeLieutenant/laravel-crypto/issues/3) 76 | - Feat/gihub actions [\#19](https://github.com/CodeLieutenant/laravel-crypto/pull/19) ([CodeLieutenant](https://github.com/CodeLieutenant)) 77 | - V1 [\#7](https://github.com/CodeLieutenant/laravel-crypto/pull/7) ([CodeLieutenant](https://github.com/CodeLieutenant)) 78 | 79 | **Merged pull requests:** 80 | 81 | - Change namespace [\#20](https://github.com/CodeLieutenant/laravel-crypto/pull/20) ([CodeLieutenant](https://github.com/CodeLieutenant)) 82 | - Feat/pest tests [\#14](https://github.com/CodeLieutenant/laravel-crypto/pull/14) ([CodeLieutenant](https://github.com/CodeLieutenant)) 83 | 84 | ## [v0.1.0](https://github.com/CodeLieutenant/laravel-crypto/releases/tag/v1.0.0) (2020-08-07) 85 | 86 | [Full Changelog](https://github.com/CodeLieutenant/laravel-crypto/compare/v0.0.2...v0.1.0) 87 | 88 | ## [v0.0.2](https://github.com/CodeLieutenant/laravel-crypto/releases/tag/v1.0.0) (2020-08-07) 89 | 90 | [Full Changelog](https://github.com/CodeLieutenant/laravel-crypto/compare/v0.0.1...v0.0.2) 91 | 92 | ## [v0.0.1](https://github.com/CodeLieutenant/laravel-crypto/releases/tag/v1.0.0) (2020-08-05) 93 | 94 | [Full Changelog](https://github.com/CodeLieutenant/laravel-crypto/compare/8d1abddf068bb7235bf78811b3e1ff2f7c207d10...v0.0.1) 95 | 96 | 97 | 98 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 99 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dusan Malusev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Crypto 2 | 3 | [![Run Tests](https://github.com/dmalusev/laravel-crypto/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/dmalusev/laravel-crypto/actions/workflows/test.yml) 4 | [![GitHub issues](https://img.shields.io/github/issues/malusev998/LaravelCrypto?label=Github%20Issues)](https://github.com/malusev998/LaravelCrypto/issues) 5 | [![GitHub stars](https://img.shields.io/github/stars/malusev998/LaravelCrypto?label=Github%20Stars)](https://github.com/malusev998/LaravelCrypto/stargazers) 6 | [![GitHub license](https://img.shields.io/github/license/malusev998/LaravelCrypto?label=Licence)](https://github.com/malusev998/LaravelCrypto) 7 | 8 | ## What's Laravel Crypto and why should I use it? 9 | 10 | Laravel Crypto is a library that provides easy to use API for most common cryptographic functions. 11 | It is designed to be easy to use and secure. It uses the best and most secure algorithms available today. 12 | 13 | Laravel's default encryption is secure, but it is slow. Laravel Crypto provides faster and more secure algorithms for 14 | encryption and hashing. 15 | It's drop in replacement for Laravel's `EncryptionServiceProvider` and it uses `libsodium` under the hood. 16 | As long as you use default laravel encryption, you don't need to change anything in your code. 17 | 18 | ## Getting started 19 | 20 | ### Installing 21 | 22 | ```shell script 23 | composer require codelieutenant/laravel-crypto 24 | ``` 25 | 26 | ### Publishing config file 27 | 28 | ```shell script 29 | php artisan vendor:publish --provider="CodeLieutenant\LaravelCrypto\ServiceProvider" 30 | ``` 31 | 32 | ### Replacing Laravel's EncryptionServiceProvider with LaravelCrypto's ServiceProvider 33 | 34 | In order to activate this package, you need to replace Laravel's `EncryptionServiceProvider` 35 | with `LaravelCryptoServiceProvider`. 36 | 37 | In `config/app.php` replace `Illuminate\Encryption\EncryptionServiceProvider::class` 38 | with `CodeLieutenant\LaravelCrypto\ServiceProvider::class` 39 | Depending on the laravel version you are using, you can do it in two ways. 40 | 41 | Laravel 9.0 and above: 42 | 43 | ```php 44 | use CodeLieutenant\LaravelCrypto\ServiceProvider as LaravelCryptoServiceProvider; 45 | use Illuminate\Encryption\EncryptionServiceProvider as LaravelEncryptionServiceProvider; 46 | 47 | // ... 48 | 'providers' => ServiceProvider::defaultProviders() 49 | + ->replace([ 50 | + LaravelEncryptionServiceProvider::class => LaravelCryptoServiceProvider::class, 51 | + ]) 52 | ->merge([ 53 | // ... 54 | ]) 55 | ->toArray(), 56 | 57 | // ... 58 | ``` 59 | 60 | Laravel 8.0: 61 | 62 | ```php 63 | use CodeLieutenant\LaravelCrypto\ServiceProvider as LaravelCryptoServiceProvider; 64 | 65 | 'providers' => [ 66 | // ... 67 | - Illuminate\Encryption\EncryptionServiceProvider::class, 68 | + LaravelCryptoServiceProvider::class, 69 | // ... 70 | ], 71 | ``` 72 | 73 | ### Configuration 74 | 75 | In order to use Laravel Crypto, you need to change `cipher` in the `config/app.php` file. 76 | Possible values: 77 | 78 | **Unique to Laravel Crypto:** 79 | 80 | - Sodium_AES256GCM 81 | - Sodium_XChaCha20Poly1305 82 | - Sodium_AEGIS128 (Planned on php8.4) 83 | - Sodium_AEGIS256 (Planned on php8.4) 84 | 85 | **Coming from Laravel Encryption (supported as LaravelCrypto falls back to EncryptionServiceProvider implementation):** 86 | 87 | - AES-256-GCM 88 | - AES-128-GCM 89 | - AES-256-CBC (default) 90 | - AES-128-CBC 91 | 92 | ```php 93 | 'cipher' => 'Sodium_AES256GCM', 94 | ``` 95 | 96 | ### Generating Keys 97 | 98 | For encryption Laravel command `php artisan key:generate` is good and can be used, but since this package 99 | can be used for hashing and signing the data, command for generating keys is provided. 100 | 101 | ```shell script 102 | php artisan crypto:keys 103 | ``` 104 | 105 | It generates backwards compatible keys for laravel `cipher` configuration and keys for `Sodium` algorithms. 106 | There are multiple option for this command, you can check them by running `php artisan crypto:keys --help`, 107 | so this command can be used as a drop in replacement for `key:generate`. 108 | 109 | ### Using in existing laravel project 110 | 111 | This package does not provide backward compatibility with Laravel's default encryption (if configuration is changed). 112 | If you want to use Laravel Crypto in an existing project, you need to re-encrypt all your data. 113 | -------------------------------------------------------------------------------- /README.old.md: -------------------------------------------------------------------------------- 1 | # Laravel General Hashing 2 | > Wrapper classes for common hashing functions 3 | 4 | [![Build Status](https://dev.azure.com/BrosSquad/LaravelHashing/_apis/build/status/malusev998.LaravelHashing?branchName=master)](https://dev.azure.com/BrosSquad/LaravelHashing/_build/latest?definitionId=8&branchName=master) 5 | [![GitHub issues](https://img.shields.io/github/issues/malusev998/LaravelCrypto?label=Github%20Issues)](https://github.com/malusev998/LaravelCrypto/issues) 6 | [![GitHub stars](https://img.shields.io/github/stars/malusev998/LaravelCrypto?label=Github%20Stars)](https://github.com/malusev998/LaravelCrypto/stargazers) 7 | [![GitHub license](https://img.shields.io/github/license/malusev998/LaravelCrypto?label=Licence)](https://github.com/malusev998/LaravelCrypto) 8 | 9 | 10 | - [Laravel General Hashing](#laravel-general-hashing) 11 | - [Introduction](#introduction) 12 | - [Getting started](#getting-started) 13 | - [Installing](#installing) 14 | - [Publishing config file](#publishing-config-file) 15 | - [Generating EdDSA private and public key](#generating-eddsa-private-and-public-key) 16 | - [Utilities](#utilities) 17 | - [Encoding](#encoding) 18 | - [Base64 Encoding](#base64-encoding) 19 | - [Generating random data](#generating-random-data) 20 | - [General Hashing](#general-hashing) 21 | - [Using facade](#using-facade) 22 | - [Using dependency injection](#using-dependency-injection) 23 | - [Shared Key signatures](#shared-key-signatures) 24 | - [Using Facade](#using-facade-1) 25 | - [Using Dependency Injection](#using-dependency-injection-1) 26 | - [Public Key signatures](#public-key-signatures) 27 | - [Using Facade](#using-facade-2) 28 | - [Using Dependency Injection](#using-dependency-injection-2) 29 | - [Advanced](#advanced) 30 | - [Encryption](#encryption) 31 | - [Benchmakrs](#benchmakrs) 32 | - [Encryption](#encryption-1) 33 | - [Decryption](#decryption) 34 | - [SHA256](#sha256) 35 | - [Using Hashing Facade](#using-hashing-facade) 36 | - [Using Dependency Injection](#using-dependency-injection-3) 37 | - [SHA512](#sha512) 38 | - [Using Hashing Facade](#using-hashing-facade-1) 39 | - [Using Dependency Injection](#using-dependency-injection-4) 40 | 41 | ## Introduction 42 | 43 | Many web applications use some kind of cryptography, but most programmers do not know what algorithm to use. 44 | 45 | There are a lot of choices, but most of them are terrible these days **(Looking at MD4 and MD5)**. Most of the good 46 | functions have really confusing API (like libsodium), so I wanted a clean "Laravel Way" and OOP access to these API's. 47 | I chose to write Laravel library (because Laravel is most commonly used php framework) that will help programmers 48 | (especially new ones) when they need cryptography. 49 | 50 | This library provides **Hashing** and **Signature** algorithms with easy to use API. 51 | 52 | 53 | ## Getting started 54 | 55 | ### Installing 56 | 57 | ```shell script 58 | $ composer require brossquad/laravel-crypto 59 | ``` 60 | ### Publishing config file 61 | 62 | ```shell script 63 | php artisan vendor:publish --provider="CodeLieutenant\LaravelCrypto\HashingServiceProvider" 64 | ``` 65 | 66 | ### Generating EdDSA private and public key 67 | 68 | Artisan command will generate private and public key inside ```PUBLIC_CRYPTO_PRIVATE_KEY``` and ```PUBLIC_CRYPTO_PUBLIC_KEY``` environmental variables (defaults to ```storage_path('crypto_public|private.key'))``` 69 | 70 | ```shell script 71 | $ php artisan crypto:keys 72 | ``` 73 | 74 | ## Utilities 75 | 76 | ### Encoding 77 | 78 | #### Base64 Encoding 79 | 80 | ```php 81 | use CodeLieutenant\LaravelCrypto\Support\Base64; 82 | 83 | $binaryData = random_bytes(32); 84 | 85 | // STANDARD VERSION 86 | 87 | // Standard encoding 88 | $base64 = Base64::encode($binaryData); 89 | 90 | // Url encoding 91 | $base64 = Base64::urlEncode($binaryData); 92 | 93 | // Standard decoding 94 | $binary = Base64::decode($base64); 95 | 96 | // Url decoding 97 | $binary = Base64::urlDecode($base64); 98 | 99 | 100 | // CONSTANT TIME ENCODING AND DECODING 101 | 102 | // Standard encoding 103 | $base64 = Base64::constantEncode($binaryData); 104 | 105 | // Url encoding 106 | $base64 = Base64::constantUrlEncode($binaryData); 107 | 108 | // Url encoding with no padding (=) 109 | $base64 = Base64::constantUrlEncodeNoPadding($binaryData); 110 | 111 | // Standard decoding 112 | $binary = Base64::constantDecode($base64); 113 | 114 | // Url decoding 115 | $binary = Base64::constantUrlDecode($base64); 116 | 117 | // Url decoding with no padding 118 | $binary = Base64::constantUrlDecodeNoPadding($base64); 119 | 120 | 121 | 122 | // LENGTH CHECKING 123 | 124 | // Get maximum length for the byte buffer from base64 encoded string 125 | $bufferLength = Base64::maxEncodedLengthToBytes($base64Length); 126 | 127 | // Get exact length for byte buffer from base64 encoded 128 | $bufferLength = Base64::encodedLengthToBytes($base64String); 129 | 130 | // Get base64 string length from byte buffer length 131 | $base64Length = Base64::encodedLength($bufferLength, $hasPadding); 132 | ``` 133 | 134 | ### Generating random data 135 | 136 | ```php 137 | use CodeLieutenant\LaravelCrypto\Support\Random; 138 | 139 | // Generate random string 140 | $randomString = Random::string(60); // Generates random string base64 url encoded with no padding 141 | 142 | // Generate random bytes 143 | $randomBytes = Random::bytes(32); // Generates buffer filled with crypto secure random bytes 144 | 145 | // Generate random int 146 | $randomImt = Random::int($min, $max); 147 | ``` 148 | 149 | ## General Hashing 150 | 151 | Laravel crypto library uses the latest and best hashing algorithms. (Blake2b) 152 | 153 | #### Using facade 154 | 155 | ```php 156 | namespace App\Services; 157 | 158 | use CodeLieutenant\LaravelCrypto\Facades\Hashing; 159 | 160 | class Service 161 | { 162 | public function hashing(): void 163 | { 164 | $data = 'Hello World'; 165 | $blake2bHash = Hashing::hash($data); // Base64 Encoded string 166 | $blake2HashBinary = Hashing::hashRaw($data); // Binary Data, outputs 64 bytes of Blake2 hash 167 | // To check the length of binary strings use mb_strlen($binary, '8bit') function 168 | } 169 | 170 | public function checkingHash(): void 171 | { 172 | $hash1 = Hashing::hash('String 1'); 173 | $hash2 = Hashing::hash('String 2'); 174 | // Uses constant time compare to check 175 | // if two hashes are equal 176 | // !! It supports binary data !! 177 | if(Hashing::equals($hash1, $hash2)) { 178 | // Hashes are equal 179 | } 180 | } 181 | 182 | public function hashVerification(): void { 183 | $data = 'Hello World'; 184 | $hash = Hashing::hash($data); 185 | 186 | // !! Does not support binary hash !! 187 | // !! For binary data use verifyRaw !! 188 | if(Hashing::verify($hash,$data)) { 189 | // When data is hashed, hashes are same 190 | } 191 | } 192 | } 193 | ``` 194 | 195 | #### Using dependency injection 196 | 197 | > When you use dependency injection, it will always use best algorithm possible. It removes room for errors; 198 | 199 | ```php 200 | namespace App\Services; 201 | 202 | use \CodeLieutenant\LaravelCrypto\Contracts\Hashing; 203 | 204 | class Service 205 | { 206 | protected Hashing $hashing; 207 | 208 | public function __construct(Hashing $hashing) 209 | { 210 | $this->hashing = $hashing; 211 | } 212 | 213 | public function hashing(): void 214 | { 215 | $data = 'Hello World'; 216 | $blake2bHash = $this->hashing->hash($data); // Base64 Encoded string 217 | $blake2HashBinary = $this->hashing->hashRaw($data); // Binary Data, outputs 64 bytes of Blake2 hash 218 | // To check the length of binary strings use mb_strlen($binary, '8bit') function 219 | } 220 | 221 | public function checkingHash(): void 222 | { 223 | $hash1 = $this->hashing->hash('String 1'); 224 | $hash2 = $this->hashing->hash('String 2'); 225 | // Uses constant time compare to check 226 | // if two hashes are equal 227 | // !! It supports binary data !! 228 | if($this->hashing->equals($hash1, $hash2)) { 229 | // Hashes are equal 230 | } 231 | } 232 | 233 | public function hashVerification(): void { 234 | $data = 'Hello World'; 235 | $hash = $this->hashing->hash($data); 236 | 237 | // !! Does not support binary hash !! 238 | // !! For binary data use verifyRaw !! 239 | if($this->hashing->verify($hash,$data)) { 240 | // When data is hashed, hashes are same 241 | } 242 | } 243 | } 244 | ``` 245 | 246 | 247 | **When ever possible use default hashing algorithm (BLAKE2B). It is the most secure hash now, and it is faster than any other currently is use in industry (Except BLAKE3 which is not implemented in libsodium yet).** 248 | 249 | Default hashing API is wrapper on libsodium function. It provides nice API to work with in Laravel projects. 250 | 251 | **These functions should not be used for password hashing. NEVER!! For password hashing use laravel default Hash facade or Hasher interface** 252 | 253 | ## Shared Key signatures 254 | 255 | Shared key signatures are done using SHA512/256 HMAC. 256 | (Sha512/256 -> SHA512 is optimized for X86_64 architecture which most computers and servers run these day, but SHA512 is to long, so it's trimmed to 256bits (64 bytes) hence the name SHA512/256, offers same security as SHA512 but it's shorter) 257 | 258 | Read more on [HMAC](https://en.wikipedia.org/wiki/HMAC) 259 | 260 | ### Using Facade 261 | 262 | ```php 263 | 264 | namespace App\Service; 265 | 266 | use CodeLieutenant\LaravelCrypto\Facades\Sign; 267 | 268 | class Service 269 | { 270 | public function createSignature() 271 | { 272 | $data = 'Hello World'; 273 | 274 | $signature = Sign::sign($data); // Base64 Encoded encoded signature 275 | 276 | $signature = Sign::signRaw($data); // Raw bytes for signature 277 | 278 | // Rest of the code 279 | } 280 | 281 | public function verifySigunature(string $signature) 282 | { 283 | $data = 'Hello World'; 284 | 285 | if(Sign::verify($data, $signature)) { 286 | // Signature is valid 287 | } else { 288 | // Signature is invalid 289 | } 290 | } 291 | } 292 | 293 | ``` 294 | 295 | ### Using Dependency Injection 296 | 297 | ```php 298 | namespace App\Service; 299 | 300 | use CodeLieutenant\LaravelCrypto\Contracts\Signing; 301 | 302 | class Service 303 | { 304 | private Signing $hmac; 305 | 306 | public function __construct(Signing $hmac) 307 | { 308 | $this->hmac = $hmac; 309 | } 310 | 311 | public function createSignature() 312 | { 313 | $data = 'Hello World'; 314 | 315 | $signature = $this->hmac->sign($data); // Base64 Encoded encoded signature 316 | 317 | $signature = $this->hmac->signRaw($data); // Raw bytes for signature 318 | 319 | // Rest of the code 320 | } 321 | 322 | public function verifySigunature(string $signature) 323 | { 324 | $data = 'Hello World'; 325 | 326 | if($this->hmac->verify($data, $signature)) { 327 | // Signature is valid 328 | } else { 329 | // Signature is invalid 330 | } 331 | } 332 | } 333 | 334 | ``` 335 | 336 | ## Public Key signatures 337 | 338 | Public key signing uses state of the art in public key cryptography -> EdDSA or Ed25519 Algorithms developed by famous crytographer Daniel Bernstein. It is based on Edwards Curve, and it is much faster then RSA (many even more serure). Public and privete keys are short, this allows the algorithm to be much faster. When ever you can use EdDSA algorithm for public key signatures 339 | 340 | **Before you start using EdDSA, generate private and public keys with artisan console command ```$ php artisan crypto:keys ```** 341 | 342 | 343 | ### Using Facade 344 | 345 | ```php 346 | 347 | namespace App\Service; 348 | 349 | use CodeLieutenant\LaravelCrypto\Facades\EdDSA; 350 | 351 | class Service 352 | { 353 | public function createSignature() 354 | { 355 | $data = 'Hello World'; 356 | 357 | $signature = EdDSA::sign($data); // Base64 Encoded encoded signature 358 | 359 | $signature = EdDSA::signRaw($data); // Raw bytes for signature 360 | 361 | // Rest of the code 362 | } 363 | 364 | public function verifySigunature(string $signature) 365 | { 366 | $data = 'Hello World'; 367 | 368 | if(EdDSA::verify($data, $signature)) { 369 | // Signature is valid 370 | } else { 371 | // Signature is invalid 372 | } 373 | } 374 | } 375 | 376 | ``` 377 | 378 | ### Using Dependency Injection 379 | 380 | 381 | ```php 382 | namespace App\Service; 383 | 384 | use CodeLieutenant\LaravelCrypto\Contracts\PublicKeySigning; 385 | 386 | class Service 387 | { 388 | private PublicKeySigning $signing; 389 | 390 | public function __construct(PublicKeySigning $signing) 391 | { 392 | $this->signing = $signing; 393 | } 394 | 395 | public function createSignature() 396 | { 397 | $data = 'Hello World'; 398 | 399 | $signature = $this->signing->sign($data); // Base64 Encoded encoded signature 400 | 401 | $signature = $this->hmac->signRaw($data); // Raw bytes for signature 402 | 403 | // Rest of the code 404 | } 405 | 406 | public function verifySigunature(string $signature) 407 | { 408 | $data = 'Hello World'; 409 | 410 | if($this->hmac->verify($data, $signature)) { 411 | // Signature is valid 412 | } else { 413 | // Signature is invalid 414 | } 415 | } 416 | } 417 | 418 | ``` 419 | 420 | ## Advanced 421 | 422 | ### Encryption 423 | 424 | LaravelCrypto library provides **2 additional encryption algorithms**. It uses default laravel Encrypter interface and key so it does not require any code change, exept in config file 425 | 426 | **Use this only in new applications.** 427 | 428 | > Use in older applications 429 | 430 | **If you have application which uses laravel default encryption and you have stored encrypted data in database, you will need to reencrypt the data with new algorithm!** 431 | 432 | ```php 433 | // app.cofig 434 | return [ 435 | // ... 436 | 437 | // XChaCha20Poly1305 Algorithm 438 | 'cipher' => 'XChaCha20Poly1305', 439 | 440 | // AES 256 GCM 441 | //!! Make sure you have hardware acceleration for 442 | // AES-256-GCM, it wont work if your sever does not support it !! 443 | 'cipher' => 'AES-256-GCM', 444 | 445 | // .. 446 | ] 447 | 448 | ``` 449 | 450 | #### Benchmakrs 451 | 452 | 453 | ##### Encryption 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 |
SubjectDescriptionRevsIterationsMemory PeakBest TimeAverage TimeWorst Time
benchLaravelEncryptionDefault Laravel Encrypter (AES-256-CBC)100102,259,976b952.012μs957.365μs974.771μs
benchXChaCha20Poly1305XChaCha20Poly1305 Encryption100102,458,008b265.780μs267.313μs270.197μs
benchAes256gcmAES 256 GCM Encryption100102,457,984b252.650μs254.105μs256.572μs
501 | 502 | 503 | 504 | ##### Decryption 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 |
SubjectDescriptionRevsIterationsMemory PeakBest TimeAverage TimeWorst Time
benchLaravelDecryptionDefault Laravel Decrypter (AES-256-CBC)100102,508,208b1,661.467μs1,666.177μs1,677.097μs
benchXChaCha20Poly1305DecryptionXChaCha20Poly1305 Decryption100102,529,600b455.560μs458.140μs465.492μs
benchAes256gcmDecryptionAES 256 GCM Decryption100102,529,576b447.280μs449.552μs453.438μs
552 | 553 | ### SHA256 554 | 555 | #### Using Hashing Facade 556 | 557 | #### Using Dependency Injection 558 | 559 | ### SHA512 560 | 561 | #### Using Hashing Facade 562 | 563 | #### Using Dependency Injection 564 | -------------------------------------------------------------------------------- /benchmarks/Base64Bench.php: -------------------------------------------------------------------------------- 1 | laravelEncrypter = new Encrypter($key, 'AES-256-CBC'); 24 | $this->xchacha = new XChaCha20Poly1305Encrypter($key); 25 | $this->aes256gcm = new AesGcm256Encrypter($key); 26 | $this->xChaChaData = file_get_contents(__DIR__.'/encrypted-xchacha'); 27 | $this->laravelData = file_get_contents(__DIR__.'/encrypted-laravel'); 28 | $this->aes256GcmData = file_get_contents(__DIR__.'/encrypted-aes256gcm'); 29 | } 30 | 31 | 32 | public function benchLaravelDecryption(): void 33 | { 34 | $this->laravelEncrypter->decryptString($this->laravelData); 35 | } 36 | 37 | public function benchXChaCha20Poly1305Decryption(): void 38 | { 39 | $this->xchacha->decryptString($this->xChaChaData); 40 | } 41 | 42 | public function benchAes256gcmDecryption(): void 43 | { 44 | $this->aes256gcm->decryptString($this->aes256GcmData); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /benchmarks/EncryptionBench.php: -------------------------------------------------------------------------------- 1 | encryptString($data); 28 | } 29 | 30 | 31 | #[ParamProviders('provideLaravelEncryption')] 32 | public function benchLaravelEncryptionWithSerialization(array $params): void 33 | { 34 | /** @var array $data */ 35 | $data = $params['data']; 36 | /** @var Encrypter $encrypter */ 37 | $encrypter = $params['encrypter']; 38 | 39 | $value = $encrypter->encrypt($data); 40 | } 41 | 42 | public function providerChaCha20Crypto(): Generator 43 | { 44 | $logger = new Logger('benchmark'); 45 | 46 | $encoders = [ 47 | 'PHP' => new PhpEncoder(), 48 | 'JSON' => new JsonEncoder(), 49 | 'IGBINARY' => new IgbinaryEncoder(), 50 | ]; 51 | 52 | $data = [ 53 | '32' => Random::bytes(32), // 32B 54 | '128' => Random::bytes(128), // 128B 55 | '256' => Random::bytes(256), // 256B 56 | '1KiB' => Random::bytes(1024), // 1KB 57 | '32KiB' => Random::bytes(32 * 1024), // 32KB 58 | '1MiB' => Random::bytes(1024 * 1024), // 1MB 59 | ]; 60 | 61 | foreach ($data as $dataName => $dataValue) { 62 | foreach ($encoders as $encoderName => $encoder) { 63 | yield 'ChaCha20-' . $encoderName . '-' . $dataName => [ 64 | 'data' => $dataValue, 65 | 'encrypter' => new XChaCha20Poly1305Encrypter(new KeyKeyLoader(Random::bytes(32)), $logger, $encoder), 66 | ]; 67 | } 68 | } 69 | } 70 | 71 | public function providerAESGCMCrypto(): Generator 72 | { 73 | $logger = new Logger('benchmark'); 74 | 75 | $encoders = [ 76 | 'PHP' => new PhpEncoder(), 77 | 'JSON' => new JsonEncoder(), 78 | 'IGBINARY' => new IgbinaryEncoder(), 79 | ]; 80 | 81 | $data = [ 82 | '32' => Random::bytes(32), // 32B 83 | '128' => Random::bytes(128), // 128B 84 | '256' => Random::bytes(256), // 256B 85 | '1KiB' => Random::bytes(1024), // 1KB 86 | '32KiB' => Random::bytes(32 * 1024), // 32KB 87 | '1MiB' => Random::bytes(1024 * 1024), // 1MB 88 | ]; 89 | 90 | foreach ($data as $dataName => $dataValue) { 91 | foreach ($encoders as $encoderName => $encoder) { 92 | yield 'ChaCha20-' . $encoderName . '-' . $dataName => [ 93 | 'data' => $dataValue, 94 | 'encrypter' => new AesGcm256Encrypter(new KeyKeyLoader(Random::bytes(32)), $logger, $encoder), 95 | ]; 96 | } 97 | } 98 | } 99 | 100 | 101 | public function provideLaravelEncryption(): Generator 102 | { 103 | $data = [ 104 | '32' => Random::bytes(32), // 32B 105 | '128' => Random::bytes(128), // 128B 106 | '256' => Random::bytes(256), // 256B 107 | '1KiB' => Random::bytes(1024), // 1KB 108 | '32KiB' => Random::bytes(32 * 1024), // 32KB 109 | '1MiB' => Random::bytes(1024 * 1024), // 1MB 110 | ]; 111 | 112 | $encrypters = [ 113 | 'AES-256-CBC' => new Encrypter(Random::bytes(32), 'AES-256-CBC'), 114 | 'AES-128-CBC' => new Encrypter(Random::bytes(32), 'AES-128-CBC'), 115 | 'AES-256-GCM' => new Encrypter(Random::bytes(32), 'AES-256-GCM'), 116 | 'AES-128-GCM' => new Encrypter(Random::bytes(32), 'AES-128-GCM'), 117 | ]; 118 | 119 | foreach ($data as $dataName => $dataValue) { 120 | foreach ($encrypters as $encrypterName => $encrypter) { 121 | yield 'Laravel-'. $encrypterName . '-' . $dataName => [ 122 | 'data' => $dataValue, 123 | 'encrypter' => $encrypter, 124 | ]; 125 | } 126 | } 127 | } 128 | // 129 | // public function benchXChaCha20Poly1305(): void 130 | // { 131 | // $this->xchacha->encryptString($this->data); 132 | // } 133 | // 134 | // public function benchAes256gcm(): void 135 | // { 136 | // $this->aes256gcm->encryptString($this->data); 137 | // } 138 | } 139 | -------------------------------------------------------------------------------- /benchmarks/KeyKeyLoader.php: -------------------------------------------------------------------------------- 1 | key; 19 | } 20 | } -------------------------------------------------------------------------------- /benchmarks/key: -------------------------------------------------------------------------------- 1 | IwE3HBCyj4WhtOJ1CGKZPX6tsQDKUifYv80ZHZ5GAmI= -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codelieutenant/laravel-crypto", 3 | "description": "Laravel Crypto is a package that provides a simple and easy to use API for encrypting, decrypting, hashing, and signing data using the latest PHP and Laravel features.", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Dusan Malusev", 9 | "email": "dusan@dusanmalusev.dev", 10 | "homepage": "https://www.dusanmalusev.dev", 11 | "role": "Developer" 12 | } 13 | ], 14 | "keywords": [ 15 | "laravel", 16 | "security", 17 | "hashing", 18 | "signing", 19 | "encryption", 20 | "decryption", 21 | "crypto", 22 | "cryptography", 23 | "sodium" 24 | ], 25 | "minimum-stability": "stable", 26 | "require": { 27 | "php": ">=8.1", 28 | "ext-sodium": "*", 29 | "illuminate/collections": "^8|^9|^10|^11", 30 | "illuminate/config": "^8|^9|^10|^11", 31 | "illuminate/contracts": "^8|^9|^10|^11", 32 | "illuminate/encryption": "^8|^9|^10|^11", 33 | "illuminate/hashing": "^8|^9|^10|^11", 34 | "illuminate/support": "^8|^9|^10|^11" 35 | }, 36 | "require-dev": { 37 | "orchestra/testbench": "^8.21", 38 | "pestphp/pest": "^2.34", 39 | "pestphp/pest-plugin-laravel": "^2.3", 40 | "phpbench/phpbench": "^1.2" 41 | }, 42 | "autoload": { 43 | "psr-4": { 44 | "CodeLieutenant\\LaravelCrypto\\": "src/" 45 | } 46 | }, 47 | "autoload-dev": { 48 | "psr-4": { 49 | "CodeLieutenant\\LaravelCrypto\\Tests\\": "tests/", 50 | "CodeLieutenant\\LaravelCrypto\\Benchmarks\\": "benchmarks/", 51 | "Workbench\\App\\": "workbench/app/", 52 | "Workbench\\Database\\Factories\\": "workbench/database/factories/", 53 | "Workbench\\Database\\Seeders\\": "workbench/database/seeders/" 54 | } 55 | }, 56 | "suggest": { 57 | "ext-json": "Required for Json Serializer", 58 | "ext-msgpack": "Required for MessagePack Serializer", 59 | "ext-igbinary": "Required for Igbinary Serializer" 60 | }, 61 | "config": { 62 | "preferred-install": "dist", 63 | "sort-packages": true, 64 | "allow-plugins": { 65 | "pestphp/pest-plugin": true 66 | } 67 | }, 68 | "extra": { 69 | "laravel": { 70 | "aliases": { 71 | "Hashing": "CodeLieutenant\\LaravelCrypto\\Facades\\Hashing", 72 | "Signing": "CodeLieutenant\\LaravelCrypto\\Facades\\Sign" 73 | } 74 | } 75 | }, 76 | "scripts": { 77 | "post-autoload-dump": [ 78 | "@clear", 79 | "@prepare" 80 | ], 81 | "clear": "@php vendor/bin/testbench package:purge-skeleton --ansi", 82 | "prepare": "@php vendor/bin/testbench package:discover --ansi", 83 | "build": "@php vendor/bin/testbench workbench:build --ansi", 84 | "serve": [ 85 | "Composer\\Config::disableProcessTimeout", 86 | "@build", 87 | "@php vendor/bin/testbench serve" 88 | ], 89 | "lint": [ 90 | "@php vendor/bin/phpstan analyse" 91 | ], 92 | "test": [ 93 | "@php vendor/bin/phpunit" 94 | ] 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /config/crypto.php: -------------------------------------------------------------------------------- 1 | [ 26 | 'driver' => PhpEncoder::class, 27 | 'config' => [ 28 | PhpEncoder::class => [ 29 | 'allowed_classes' => true, 30 | ], 31 | JsonEncoder::class => [ 32 | 'decode_as_array' => true, 33 | ] 34 | ], 35 | ], 36 | 37 | /* 38 | |-------------------------------------------------------------------------- 39 | | Hashing 40 | |-------------------------------------------------------------------------- 41 | | 42 | | This option controls the default hashing algorithm that will be used 43 | | to hash data. Can be any implementing class of `Hashing` interface. 44 | | Use `config` to pass any configuration to the underlying hashing. 45 | | 46 | | In `blake2b` case, you can pass `key` and `outputLength` to the config. 47 | | `key` is used to have unique hash for your application even if the data 48 | | is the same. There is no difference between `HMAC` version and `Hash` 49 | | version of the algorithm when `key` is used. 50 | | 51 | */ 52 | 'hashing' => [ 53 | 'driver' => Blake2bHash::class, 54 | 'config' => [ 55 | Blake2bHash::class => [ 56 | 'key' => env('CRYPTO_BLAKE2B_HASHING_KEY'), 57 | 'outputLength' => 32, 58 | ], 59 | ], 60 | ], 61 | 62 | /* 63 | |-------------------------------------------------------------------------- 64 | | Crypto Signing 65 | |-------------------------------------------------------------------------- 66 | | 67 | | Used to crypto sign the data. Can be any implementing class of `Signer` 68 | | interface. 69 | | For the signer there is implementation with asymmetric and symmetric 70 | | MAC algorithm. For the asymmetric algorithm, you can use `EdDSA` 71 | | and for the symmetric MAC algorithm, you can use `Blake2b`, `Sha256` and `Sha512`. 72 | | 73 | | `Sha256` uses `sha512/256` implemented in `libsodium`. 74 | | 75 | */ 76 | 'signing' => [ 77 | 'driver' => Blake2bHMAC::class, 78 | 'keys' => [ 79 | 'eddsa' => env('CRYPTO_EDDSA_PUBLIC_CRYPTO_KEY', storage_path('keys/eddsa.key')), 80 | 'hmac' => env('CRYPTO_HMAC_KEY'), 81 | ], 82 | 'config' => [ 83 | Blake2bHMAC::class => [ 84 | 'outputLength' => 32, 85 | ], 86 | ], 87 | ], 88 | ]; 89 | -------------------------------------------------------------------------------- /docs/Commands.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MalusevDevelopment/laravel-crypto/4517b2ecbb690c370e90bc538851411539c2fa7e/docs/Commands.md -------------------------------------------------------------------------------- /docs/Encryption.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MalusevDevelopment/laravel-crypto/4517b2ecbb690c370e90bc538851411539c2fa7e/docs/Encryption.md -------------------------------------------------------------------------------- /docs/Hashing.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MalusevDevelopment/laravel-crypto/4517b2ecbb690c370e90bc538851411539c2fa7e/docs/Hashing.md -------------------------------------------------------------------------------- /docs/Signing.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MalusevDevelopment/laravel-crypto/4517b2ecbb690c370e90bc538851411539c2fa7e/docs/Signing.md -------------------------------------------------------------------------------- /docs/Utilities.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MalusevDevelopment/laravel-crypto/4517b2ecbb690c370e90bc538851411539c2fa7e/docs/Utilities.md -------------------------------------------------------------------------------- /phpbench.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema":"./vendor/phpbench/phpbench/phpbench.schema.json", 3 | "runner.bootstrap": "vendor/autoload.php" 4 | } 5 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | src 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ./tests/Unit 27 | 28 | 29 | ./tests/Feature 30 | 31 | 32 | ./tests/Architecture 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/Console/GenerateCryptoKeysCommand.php: -------------------------------------------------------------------------------- 1 | option('show'); 38 | $eddsa = !$this->option('no-eddsa'); 39 | $app = !$this->option('no-app'); 40 | $blake2b = !$this->option('no-blake2b'); 41 | $hmac = !$this->option('no-hmac'); 42 | 43 | $proceed = $this->confirmToProceed('This operation will overwrite existing keys', function () { 44 | return $this->getLaravel()->environment() === 'production'; 45 | }); 46 | 47 | if (!$proceed) { 48 | return self::FAILURE; 49 | } 50 | 51 | $write = $show ? null : app()->environmentFilePath(); 52 | 53 | try { 54 | if ($eddsa) { 55 | $eddsaKey = $edDSAGenerator->generate($write); 56 | 57 | if ($show) { 58 | $this->info('EdDSA Key: ' . $eddsaKey); 59 | } 60 | } 61 | 62 | if ($app) { 63 | $appKey = $appKeyGenerator->generate($write); 64 | 65 | if ($show) { 66 | $this->info('App Key: ' . $appKey); 67 | } 68 | } 69 | 70 | if ($blake2b) { 71 | $blake2bKey = $blake2bKeyGenerator->generate($write); 72 | 73 | if ($show) { 74 | $this->info('Blake2b Key: ' . $blake2bKey); 75 | } 76 | } 77 | 78 | if ($hmac) { 79 | $hmacKey = $hmacKeyGenerator->generate($write); 80 | 81 | if ($show) { 82 | $this->info('HMAC Key: ' . $hmacKey); 83 | } 84 | } 85 | } catch (Exception $e) { 86 | $this->error($e->getMessage()); 87 | return self::FAILURE; 88 | } 89 | 90 | return self::SUCCESS; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Contracts/Encoder.php: -------------------------------------------------------------------------------- 1 | asArray, 25 | 512, 26 | JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE 27 | ); 28 | } 29 | } -------------------------------------------------------------------------------- /src/Encoder/MessagePackEncoder.php: -------------------------------------------------------------------------------- 1 | options = $options; 16 | } 17 | 18 | public function encode(mixed $value): string 19 | { 20 | return serialize($value); 21 | } 22 | 23 | public function decode(string $value): mixed 24 | { 25 | return unserialize($value, $this->options); 26 | } 27 | } -------------------------------------------------------------------------------- /src/Encryption/AesGcm256Encrypter.php: -------------------------------------------------------------------------------- 1 | $this->encoder->encode($value), 34 | false => $value, 35 | }; 36 | 37 | try { 38 | $nonce = $this->generateNonce(); 39 | $encrypted = sodium_crypto_aead_aes256gcm_encrypt($serialized, $nonce, $nonce, $this->getKey()); 40 | return Base64::constantUrlEncodeNoPadding($nonce . $encrypted); 41 | } catch (Exception $e) { 42 | $this->logger?->error($e->getMessage(), [ 43 | 'exception' => $e, 44 | 'stack' => $e->getTraceAsString(), 45 | ]); 46 | throw new EncryptException('Value cannot be encrypted'); 47 | } 48 | } 49 | 50 | public function decrypt($payload, $unserialize = true) 51 | { 52 | $decoded = Base64::constantUrlDecodeNopadding($payload); 53 | $nonce = substr($decoded, 0, self::nonceSize()); 54 | $cipherText = substr($decoded, self::nonceSize()); 55 | 56 | try { 57 | $decrypted = sodium_crypto_aead_aes256gcm_decrypt($cipherText, $nonce, $nonce, $this->keyLoader->getKey()); 58 | } catch (Exception $e) { 59 | $this->logger?->error($e->getMessage(), [ 60 | 'stack' => $e->getTraceAsString(), 61 | 'exception' => $e, 62 | ]); 63 | throw new DecryptException('Payload cannot be decrypted'); 64 | } 65 | 66 | return match ($unserialize) { 67 | true => $this->encoder->decode($decrypted), 68 | false => $decrypted, 69 | }; 70 | } 71 | 72 | public static function nonceSize(): int 73 | { 74 | return SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Encryption/XChaCha20Poly1305Encrypter.php: -------------------------------------------------------------------------------- 1 | encoder->encode($value); 34 | } 35 | 36 | try { 37 | $nonce = $this->generateNonce(); 38 | $encrypted = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt( 39 | $value, 40 | $nonce, 41 | $nonce, 42 | $this->keyLoader->getKey() 43 | ); 44 | return Base64::constantUrlEncodeNoPadding($nonce . $encrypted); 45 | } catch (Exception $e) { 46 | $this->logger?->error($e->getMessage(), [ 47 | 'exception' => $e, 48 | 'value' => $value, 49 | 'serialize' => $serialize, 50 | ]); 51 | throw new EncryptException('Value cannot be encrypted ' . $e->getMessage()); 52 | } 53 | } 54 | 55 | public function decrypt($payload, $unserialize = true) 56 | { 57 | $decoded = Base64::constantUrlDecodeNoPadding($payload); 58 | $nonce = substr($decoded, 0, self::nonceSize()); 59 | $cipherText = substr($decoded, self::nonceSize(), null); 60 | 61 | try { 62 | $decrypted = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt( 63 | $cipherText, 64 | $nonce, 65 | $nonce, 66 | $this->keyLoader->getKey() 67 | ); 68 | } catch (Exception $e) { 69 | $this->logger?->error($e->getMessage(), [ 70 | 'exception' => $e, 71 | 'serialize' => $unserialize, 72 | ]); 73 | throw new DecryptException('Payload cannot be decrypted'); 74 | } 75 | 76 | if ($unserialize) { 77 | return $this->encoder->decode($decrypted); 78 | } 79 | 80 | return $decrypted; 81 | } 82 | 83 | public static function nonceSize(): int 84 | { 85 | return SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Enums/Encryption.php: -------------------------------------------------------------------------------- 1 | SODIUM_CRYPTO_AEAD_AES256GCM_KEYBYTES, 16 | self::SodiumXChaCha20Poly1305 => SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES, 17 | }; 18 | } 19 | } -------------------------------------------------------------------------------- /src/Facades/Hashing.php: -------------------------------------------------------------------------------- 1 | hashRaw($data)); 28 | } 29 | 30 | public function hashRaw(string $data): string 31 | { 32 | return sodium_crypto_generichash($data, $this->key ?? '', $this->outputLength); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Hashing/HashingManager.php: -------------------------------------------------------------------------------- 1 | driver()->hash($data); 21 | } 22 | 23 | public function hashRaw(string $data): string 24 | { 25 | return $this->driver()->hashRaw($data); 26 | } 27 | 28 | public function verify(string $hash, string $data): bool 29 | { 30 | return $this->driver()->verify($hash, $data); 31 | } 32 | 33 | public function verifyRaw(string $hash, string $data): bool 34 | { 35 | return $this->driver()->verifyRaw($hash, $data); 36 | } 37 | 38 | public function getDefaultDriver() 39 | { 40 | return $this->config->get('crypto.hashing.driver', 'blake2b'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Hashing/Sha256.php: -------------------------------------------------------------------------------- 1 | createBlake2bDriver()->hash($data); 16 | } 17 | 18 | public function blake2bRaw(string $data): string 19 | { 20 | return $this->createBlake2bDriver()->hashRaw($data); 21 | } 22 | 23 | public function blake2bVerify(string $hash, string $data): bool 24 | { 25 | return $this->createBlake2bDriver()->verify($hash, $data); 26 | } 27 | 28 | public function blake2bVerifyRaw(string $hash, string $data): bool 29 | { 30 | return $this->createBlake2bDriver()->verifyRaw($hash, $data); 31 | } 32 | 33 | 34 | public function createBlake2bDriver(): Blake2bHashing 35 | { 36 | if ($this->blake2b === null) { 37 | $this->blake2b = $this->container->make(Blake2b::class); 38 | } 39 | 40 | return $this->blake2b; 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/Hashing/Traits/Hash.php: -------------------------------------------------------------------------------- 1 | equals($hash, $this->hash($data)); 14 | } 15 | 16 | public function verifyRaw(string $hash, string $data): bool 17 | { 18 | return $this->equals($hash, $this->hashRaw($data)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Hashing/Traits/Sha256.php: -------------------------------------------------------------------------------- 1 | createSha256Driver()->hash($data); 14 | } 15 | 16 | public function sha256Raw(string $data): string 17 | { 18 | return $this->createSha256Driver()->hashRaw($data); 19 | } 20 | 21 | public function sha256Verify(string $hash, string $data): bool 22 | { 23 | return $this->createSha256Driver()->verify($hash, $data); 24 | } 25 | 26 | public function sha256VerifyRaw(string $hash, string $data): bool 27 | { 28 | return $this->createSha256Driver()->verifyRaw($hash, $data); 29 | } 30 | 31 | public function createSha256Driver(): \CodeLieutenant\LaravelCrypto\Hashing\Sha256 32 | { 33 | if ($this->sha256 === null) { 34 | $this->sha256 = $this->container->make(Sha256::class); 35 | } 36 | 37 | return $this->sha256; 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/Hashing/Traits/Sha512.php: -------------------------------------------------------------------------------- 1 | createSha512Driver()->hash($data); 14 | } 15 | 16 | public function sha512Raw(string $data): string 17 | { 18 | return $this->createSha512Driver()->hashRaw($data); 19 | } 20 | 21 | public function sha512Verify(string $hash, string $data): bool 22 | { 23 | return $this->createSha512Driver()->verify($hash, $data); 24 | } 25 | 26 | public function sha512VerifyRaw(string $hash, string $data): bool 27 | { 28 | return $this->createSha512Driver()->verifyRaw($hash, $data); 29 | } 30 | 31 | public function createSha512Driver(): \CodeLieutenant\LaravelCrypto\Hashing\Sha512 32 | { 33 | if ($this->sha512 === null) { 34 | $this->sha512 = $this->container->make(Sha512::class); 35 | } 36 | 37 | return $this->sha512; 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/Keys/Generators/AppKeyGenerator.php: -------------------------------------------------------------------------------- 1 | config->get(static::CONFIG_KEY_PATH); 31 | $cipher = $this->config->get(static::CONFIG_CIPHER_PATH); 32 | 33 | $new = $this->formatKey( 34 | match (Encryption::tryFrom($cipher)) { 35 | Encryption::SodiumAES256GCM => sodium_crypto_aead_aes256gcm_keygen(), 36 | Encryption::SodiumXChaCha20Poly1305 => sodium_crypto_aead_xchacha20poly1305_ietf_keygen(), 37 | default => Encrypter::generateKey($cipher), 38 | } 39 | ); 40 | 41 | if ($write === null) { 42 | return $new; 43 | } 44 | 45 | $this->config->set(static::CONFIG_KEY_PATH, $new); 46 | 47 | $this->writeNewEnvironmentFileWith($write, [ 48 | static::ENV => [ 49 | 'old' => $old, 50 | 'new' => $new, 51 | ], 52 | ]); 53 | 54 | return null; 55 | } 56 | } -------------------------------------------------------------------------------- /src/Keys/Generators/Blake2BHashingKeyGenerator.php: -------------------------------------------------------------------------------- 1 | config->get(static::CONFIG_KEY_PATH); 28 | $new = $this->formatKey(Random::bytes(static::KEY_SIZE)); 29 | 30 | $this->config->set(static::CONFIG_KEY_PATH, $new); 31 | 32 | if ($write === null) { 33 | return $new; 34 | } 35 | 36 | $this->writeNewEnvironmentFileWith($write, [ 37 | static::ENV => [ 38 | 'old' => $old ?? '', 39 | 'new' => $new, 40 | ], 41 | ]); 42 | 43 | return null; 44 | } 45 | } -------------------------------------------------------------------------------- /src/Keys/Generators/EdDSASignerKeyGenerator.php: -------------------------------------------------------------------------------- 1 | config->get(self::CONFIG_KEY_PATH); 36 | 37 | if ($path === null) { 38 | throw new RuntimeException('File for EdDSA signer is not set'); 39 | } 40 | 41 | if (!@file_exists($concurrentDirectory = dirname($path)) && !@mkdir( 42 | $concurrentDirectory, 43 | 0740, 44 | true 45 | ) && !is_dir( 46 | $concurrentDirectory 47 | )) { 48 | throw new RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory)); 49 | } 50 | 51 | $file = new SplFileObject($path, 'wb'); 52 | 53 | if ($file->flock(LOCK_EX) === false) { 54 | throw new RuntimeException('Error while locking file (exclusive/writing)'); 55 | } 56 | 57 | try { 58 | if ($file->fwrite($key) === false) { 59 | throw new RuntimeException('Error while writing public key to file'); 60 | } 61 | } finally { 62 | if ($file->flock(LOCK_UN) === false) { 63 | $this->logger->warning('Error while unlocking file'); 64 | } 65 | 66 | sodium_memzero($privateKey); 67 | sodium_memzero($publicKey); 68 | sodium_memzero($keyPair); 69 | sodium_memzero($key); 70 | } 71 | 72 | return null; 73 | } 74 | } -------------------------------------------------------------------------------- /src/Keys/Generators/HmacKeyGenerator.php: -------------------------------------------------------------------------------- 1 | get(static::CONFIG_KEY_PATH)); 23 | } 24 | 25 | return new static(); 26 | } 27 | 28 | public function getKey(): string|array 29 | { 30 | return self::$key; 31 | } 32 | } -------------------------------------------------------------------------------- /src/Keys/Loaders/Blake2BHashingKeyLoader.php: -------------------------------------------------------------------------------- 1 | get(static::CONFIG_KEY_PATH); 30 | 31 | if ($path === null) { 32 | throw new MissingAppKeyException('File for EdDSA signer is not set'); 33 | } 34 | 35 | [static::$publicKey, static::$privateKey] = static::parseKeys($path, $logger); 36 | } 37 | 38 | return new static(); 39 | } 40 | 41 | protected static function parseKeys(string $keyPath, LoggerInterface $logger): array 42 | { 43 | $file = new SplFileObject($keyPath, 'rb'); 44 | if ($file->flock(LOCK_SH) === false) { 45 | throw new RuntimeException('Error while locking file (shared/reading)'); 46 | } 47 | 48 | try { 49 | $keys = $file->fread(self::KEY_LENGTH * 2 + 1); 50 | 51 | if ($keys === false) { 52 | throw new RuntimeException('Error while reading key'); 53 | } 54 | } finally { 55 | if ($file->flock(LOCK_UN) === false) { 56 | $logger->warning('Error while unlocking file'); 57 | } 58 | } 59 | 60 | [$publicKey, $privateKey] = explode(PHP_EOL, $keys, 2); 61 | 62 | return [hex2bin($publicKey), hex2bin($privateKey)]; 63 | } 64 | 65 | public function getKey(): string|array 66 | { 67 | return [self::$publicKey, self::$privateKey]; 68 | } 69 | 70 | 71 | } -------------------------------------------------------------------------------- /src/Keys/Loaders/HmacKeyLoader.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 49 | $this->publishes([$this->getConfigPath() => config_path('crypto.php')]); 50 | } 51 | } 52 | 53 | public function register(): void 54 | { 55 | if ($this->app->runningInConsole()) { 56 | $this->registerGenerators(); 57 | $this->commands([GenerateCryptoKeysCommand::class]); 58 | } 59 | 60 | $this->mergeConfigFrom($this->getConfigPath(), 'crypto'); 61 | 62 | $this->registerEncoder(); 63 | $this->registerKeyLoaders(); 64 | $this->registerSigners(); 65 | $this->registerHashers(); 66 | parent::register(); 67 | } 68 | 69 | protected function registerEncoder(): void 70 | { 71 | $encoders = [ 72 | PhpEncoder::class, 73 | JsonEncoder::class, 74 | MessagePackEncoder::class, 75 | IgbinaryEncoder::class, 76 | ]; 77 | 78 | foreach ($encoders as $encoder) { 79 | $this->app->singleton($encoder, function (Application $app) use ($encoder) { 80 | $config = $app->make(Repository::class)->get('crypto.encoder.config.' . $encoder); 81 | return new $encoder(...$config); 82 | }); 83 | } 84 | 85 | $this->app->singleton( 86 | Contracts\Encoder::class, 87 | $this->app->make(Repository::class)->get('crypto.encoder.driver') 88 | ); 89 | } 90 | 91 | protected function registerKeyLoaders(): void 92 | { 93 | $this->app->singleton( 94 | AppKeyLoader::class, 95 | fn(Application $app) => AppKeyLoader::make($app->make(Repository::class)) 96 | ); 97 | $this->app->singleton( 98 | Blake2BHashingKeyLoader::class, 99 | fn(Application $app) => Blake2BHashingKeyLoader::make($app->make(Repository::class)) 100 | ); 101 | 102 | $this->app->singleton( 103 | HmacKeyLoader::class, 104 | fn(Application $app) => HmacKeyLoader::make($app->make(Repository::class)) 105 | ); 106 | 107 | $this->app->singleton( 108 | EdDSASignerKeyLoader::class, 109 | fn(Application $app) => EdDSASignerKeyLoader::make( 110 | $app->make(Repository::class), 111 | $app->make(LoggerInterface::class) 112 | ) 113 | ); 114 | } 115 | 116 | protected function registerSigners(): void 117 | { 118 | $this->app->singleton(SigningManager::class); 119 | 120 | $this->app->when(EdDSA::class) 121 | ->needs(KeyLoader::class) 122 | ->give(EdDSASignerKeyLoader::class); 123 | 124 | $hmacSigners = [ 125 | HmacBlake2b::class, 126 | HmacSha256::class, 127 | HmacSha512::class, 128 | ]; 129 | 130 | foreach ($hmacSigners as $signer) { 131 | $this->app->singleton($signer, function (Application $app) use ($signer) { 132 | $config = $app->make(Repository::class)->get('crypto.signing.config.' . $signer); 133 | $keyLoader = $app->make(HmacKeyLoader::class); 134 | 135 | return $config !== null ? new $signer($keyLoader, $config) : new $signer($keyLoader); 136 | }); 137 | } 138 | 139 | $this->app->singleton(Signing::class, static function (Application $app) { 140 | return $app->make($app->make(Repository::class)->get('crypto.signing.driver')); 141 | }); 142 | 143 | $this->app->singleton(PublicKeySigning::class, EdDSA::class); 144 | } 145 | 146 | protected function registerHashers(): void 147 | { 148 | $hashers = [ 149 | Blake2b::class, 150 | Sha256::class, 151 | Sha512::class, 152 | ]; 153 | 154 | foreach ($hashers as $hasher) { 155 | $this->app->singleton($hasher, static function (Application $app) use ($hasher) { 156 | $params = $app->make(Repository::class)->get('crypto.hashing.config.' . $hasher); 157 | 158 | return $params === null ? new $hasher() : new $hasher(...$params); 159 | }); 160 | } 161 | 162 | $this->app->singleton(Hashing::class, static function (Application $app) { 163 | return $app->make($app->make(Repository::class)->get('crypto.hashing.driver')); 164 | }); 165 | 166 | $this->app->singleton(HashingManager::class); 167 | } 168 | 169 | protected function getConfigPath(): string 170 | { 171 | return __DIR__ . '/../config/crypto.php'; 172 | } 173 | 174 | protected function registerEncrypter(): void 175 | { 176 | foreach ([AesGcm256Encrypter::class, XChaCha20Poly1305Encrypter::class] as $encryptor) { 177 | $this->app->singleton($encryptor); 178 | $this->app->when($encryptor) 179 | ->needs(KeyLoader::class) 180 | ->give(AppKeyLoader::class); 181 | } 182 | 183 | $func = static function (Application $app) { 184 | $cipher = $app->make('config')->get('app.cipher'); 185 | 186 | $enc = Encryption::tryFrom($cipher); 187 | 188 | if ($enc === null) { 189 | return new LaravelConcreteEncrypter($app->make(AppKeyLoader::class)->getKey(), $cipher); 190 | } 191 | 192 | return match ($enc) { 193 | Encryption::SodiumAES256GCM => $app->make(AesGcm256Encrypter::class), 194 | Encryption::SodiumXChaCha20Poly1305 => $app->make(XChaCha20Poly1305Encrypter::class), 195 | }; 196 | }; 197 | 198 | $this->app->singleton(Encrypter::class, $func); 199 | $this->app->singleton('encrypter', $func); 200 | } 201 | 202 | protected function registerGenerators(): void 203 | { 204 | $this->app->singleton(AppKeyGenerator::class); 205 | $this->app->singleton(Blake2BHashingKeyGenerator::class); 206 | $this->app->singleton(HmacKeyGenerator::class); 207 | $this->app->singleton(EdDSASignerKeyGenerator::class); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/Signing/EdDSA/EdDSA.php: -------------------------------------------------------------------------------- 1 | loader->getKey(); 24 | return sodium_crypto_sign_detached($data, $private); 25 | } 26 | 27 | public function verify(string $message, string $hmac, bool $decodeSignature = true): bool 28 | { 29 | [$public] = $this->loader->getKey(); 30 | return sodium_crypto_sign_verify_detached( 31 | !$decodeSignature ? $hmac : Base64::urlDecode($hmac), 32 | $message, 33 | $public 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Signing/Hmac/Blake2b.php: -------------------------------------------------------------------------------- 1 | loader->getKey(), $this->outputSize); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Signing/Hmac/Sha256.php: -------------------------------------------------------------------------------- 1 | loader->getKey()); 24 | } 25 | 26 | public function verify(string $message, string $hmac, bool $decodeSignature = true): bool 27 | { 28 | return sodium_crypto_auth_verify( 29 | !$decodeSignature ? $hmac : Base64::constantUrlDecodeNoPadding($hmac), 30 | $message, 31 | $this->loader->getKey() 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Signing/Hmac/Sha512.php: -------------------------------------------------------------------------------- 1 | loader->getKey(), true); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Signing/SigningManager.php: -------------------------------------------------------------------------------- 1 | driver()->sign($data); 20 | } 21 | 22 | public function signRaw(string $data): string 23 | { 24 | return $this->driver()->signRaw($data); 25 | } 26 | 27 | public function verify(string $message, string $hmac, bool $decodeSignature = true): bool 28 | { 29 | return $this->driver()->verify($message, $hmac, $decodeSignature); 30 | } 31 | 32 | public function getDefaultDriver() 33 | { 34 | return $this->config->get('crypto.signing.driver', 'blake2b'); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/Signing/Traits/Blake2b.php: -------------------------------------------------------------------------------- 1 | createBlake2bDriver()->sign($data); 14 | } 15 | 16 | public function blake2bSignRaw(string $data): string 17 | { 18 | return $this->createBlake2bDriver()->signRaw($data); 19 | } 20 | 21 | public function blake2bVerify(string $message, string $hmac): bool 22 | { 23 | return $this->createBlake2bDriver()->verify($message, $hmac); 24 | } 25 | 26 | public function createBlake2bDriver(): \CodeLieutenant\LaravelCrypto\Signing\Hmac\Blake2b 27 | { 28 | if ($this->blake2b === null) { 29 | $this->blake2b = $this->container->get(Blake2b::class); 30 | } 31 | 32 | return $this->blake2b; 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/Signing/Traits/EdDSA.php: -------------------------------------------------------------------------------- 1 | createEdDSADriver()->sign($data); 14 | } 15 | 16 | public function eddsaSignRaw(string $data): string 17 | { 18 | return $this->createEdDSADriver()->signRaw($data); 19 | } 20 | 21 | public function eddsaVerify(string $message, string $hmac): bool 22 | { 23 | return $this->createEdDSADriver()->verify($message, $hmac); 24 | } 25 | 26 | public function createEdDSADriver(): \CodeLieutenant\LaravelCrypto\Signing\EdDSA\EdDSA 27 | { 28 | if ($this->eddsa === null) { 29 | $this->eddsa = $this->container->get(EdDSA::class); 30 | } 31 | 32 | return $this->eddsa; 33 | } 34 | } -------------------------------------------------------------------------------- /src/Signing/Traits/Hmac256.php: -------------------------------------------------------------------------------- 1 | createHmac256Driver()->sign($data); 16 | } 17 | 18 | public function hmac256SignRaw(string $data): string 19 | { 20 | return $this->createHmac256Driver()->signRaw($data); 21 | } 22 | 23 | public function hmac256Verify(string $message, string $hmac): bool 24 | { 25 | return $this->createHmac256Driver()->verify($message, $hmac); 26 | } 27 | 28 | public function createHmac256Driver(): Sha256 29 | { 30 | if ($this->hmac256 === null) { 31 | $this->hmac256 = $this->container->get(Sha256::class); 32 | } 33 | 34 | return $this->hmac256; 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/Signing/Traits/Hmac512.php: -------------------------------------------------------------------------------- 1 | createHmac512Driver()->sign($data); 16 | } 17 | 18 | public function hmac512SignRaw(string $data): string 19 | { 20 | return $this->createHmac512Driver()->signRaw($data); 21 | } 22 | 23 | 24 | public function hmac512Verify(string $message, string $hmac): bool 25 | { 26 | return $this->createHmac512Driver()->verify($message, $hmac); 27 | } 28 | 29 | public function createHmac512Driver(): Sha512 30 | { 31 | if ($this->hmac512 === null) { 32 | $this->hmac512 = $this->container->get(Sha512::class); 33 | } 34 | 35 | return $this->hmac512; 36 | } 37 | } -------------------------------------------------------------------------------- /src/Signing/Traits/Signing.php: -------------------------------------------------------------------------------- 1 | equals( 17 | $this->signRaw($message), 18 | !$decodeSignature ? $hmac : Base64::constantUrlDecodeNoPadding($hmac) 19 | ); 20 | } 21 | 22 | public function sign(string $data): string 23 | { 24 | return Base64::constantUrlEncodeNoPadding($this->signRaw($data)); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/Support/Base64.php: -------------------------------------------------------------------------------- 1 | intdiv($bufferLength, 4) * 3, 78 | false => intdiv($bufferLength * 6, 8), 79 | }; 80 | } 81 | 82 | public static function encodedLength(int $bufferLength, bool $hasPadding = true): int 83 | { 84 | return match ($hasPadding) { 85 | true => intdiv($bufferLength + 2, 3) * 3, 86 | false => intdiv(($bufferLength * 8 + 5), 6), 87 | }; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Support/Random.php: -------------------------------------------------------------------------------- 1 | getBytes($length); 25 | } 26 | 27 | public static function string(int $length): ?string 28 | { 29 | return Base64::urlEncodeNoPadding(self::randomizer()->getBytes(Base64::maxEncodedLengthToBytes($length))); 30 | } 31 | 32 | public static function int(int $min = PHP_INT_MIN, int $max = PHP_INT_MAX): ?int 33 | { 34 | return self::randomizer()->getInt($min, $max); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/Traits/ConstantTimeCompare.php: -------------------------------------------------------------------------------- 1 | $len2) { 15 | $hash2 = sodium_pad($hash2, $len1); 16 | } elseif ($len2 > $len1) { 17 | $hash1 = sodium_pad($hash1, $len2); 18 | } 19 | 20 | return sodium_memcmp($hash1, $hash2) === 0; 21 | } 22 | } -------------------------------------------------------------------------------- /src/Traits/Crypto.php: -------------------------------------------------------------------------------- 1 | keyLoader->getKey(); 18 | } 19 | 20 | public function getAllKeys() 21 | { 22 | return [$this->getKey()]; 23 | } 24 | 25 | public function getPreviousKeys() 26 | { 27 | return []; 28 | } 29 | 30 | public static function supported(string $key, string $cipher): bool 31 | { 32 | $encType = Encryption::tryFrom($cipher); 33 | 34 | if ($encType === null) { 35 | return LaravelEncrypter::supported($key, $cipher); 36 | } 37 | 38 | if ($encType === Encryption::SodiumAES256GCM && !sodium_crypto_aead_aes256gcm_is_available()) { 39 | return false; 40 | } 41 | 42 | return strlen($key) === $encType->keySize(); 43 | } 44 | 45 | public function encryptString($value): string 46 | { 47 | return $this->encrypt($value, false); 48 | } 49 | 50 | public function decryptString($payload): string 51 | { 52 | return $this->decrypt($payload, false); 53 | } 54 | 55 | public function generateNonce(?string $previous = null): string 56 | { 57 | if ($previous !== null) { 58 | $copy = $previous; 59 | sodium_increment($copy); 60 | return $copy; 61 | } 62 | 63 | return Random::bytes(static::nonceSize()); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/Traits/EnvKeySaver.php: -------------------------------------------------------------------------------- 1 | $value) { 24 | $match[] = $this->keyReplacementPattern($env, $value['old']); 25 | $replacement[] = $env . '=' . $value['new']; 26 | } 27 | 28 | $replaced = preg_replace($match, $replacement, $input); 29 | 30 | if ($replaced === $input || $replaced === null) { 31 | $replaced = $input; 32 | 33 | foreach ($values as $env => $value) { 34 | $replaced .= "\n" . $env . '=' . $value['new']; 35 | } 36 | 37 | $replaced .= "\n"; 38 | } 39 | 40 | if (@file_put_contents($file, $replaced) === false) { 41 | throw new RuntimeException('Error while writing environment file: ' . $file); 42 | } 43 | } 44 | 45 | protected function keyReplacementPattern(string $env, string $value): string 46 | { 47 | $env = preg_quote($env, '/'); 48 | $value = preg_quote($value, '/'); 49 | 50 | return "/^$env=$value/m"; 51 | } 52 | 53 | protected function formatKey(string $key): string 54 | { 55 | return 'base64:' . Base64::encode($key); 56 | } 57 | } -------------------------------------------------------------------------------- /src/Traits/LaravelKeyParser.php: -------------------------------------------------------------------------------- 1 | expect('CodeLieutenant\LaravelCrypto\Console') 10 | ->toHaveAttribute(AsCommand::class) 11 | ->toExtend(Command::class) 12 | ->toHaveMethod('handle') 13 | ->toHaveSuffix('Command'); 14 | -------------------------------------------------------------------------------- /tests/Architecture/ContractsTest.php: -------------------------------------------------------------------------------- 1 | expect('CodeLieutenant\LaravelCrypto\Contracts') 7 | ->toBeInterfaces(); 8 | 9 | -------------------------------------------------------------------------------- /tests/Architecture/EncodersTest.php: -------------------------------------------------------------------------------- 1 | expect('CodeLieutenant\LaravelCrypto\Encoders') 9 | ->toBeClasses() 10 | ->toHaveSuffix('Encoder') 11 | ->toImplement(Encoder::class); 12 | -------------------------------------------------------------------------------- /tests/Architecture/EncryptionTest.php: -------------------------------------------------------------------------------- 1 | expect('CodeLieutenant\LaravelCrypto\Encryption') 10 | ->toOnlyImplement([Encrypter::class, StringEncrypter::class]) 11 | ->toBeClasses() 12 | ->toHaveSuffix('Encrypter') 13 | ->toBeFinal(); -------------------------------------------------------------------------------- /tests/Architecture/EnumTest.php: -------------------------------------------------------------------------------- 1 | expect('CodeLieutenant\LaravelCrypto\Enums') 7 | ->toBeEnums(); -------------------------------------------------------------------------------- /tests/Architecture/FacadesTest.php: -------------------------------------------------------------------------------- 1 | expect('CodeLieutenant\LaravelCrypto\Facades') 9 | ->toBeClasses() 10 | ->toExtend(Facade::class); 11 | -------------------------------------------------------------------------------- /tests/Architecture/GlobalTest.php: -------------------------------------------------------------------------------- 1 | expect(['dd', 'dump', 'ray']) 7 | ->not->toBeUsed(); 8 | 9 | arch('strict types') 10 | ->expect('CodeLieutenant\LaravelCrypto') 11 | ->toUseStrictTypes(); -------------------------------------------------------------------------------- /tests/Architecture/HashingTest.php: -------------------------------------------------------------------------------- 1 | expect('CodeLieutenant\LaravelCrypto\Keys\Loaders') 10 | ->toBeClasses() 11 | ->toOnlyImplement(KeyLoader::class) 12 | ->toHaveSuffix('KeyLoader'); 13 | 14 | arch('key generators') 15 | ->expect('CodeLieutenant\LaravelCrypto\Keys\Generators') 16 | ->toBeClasses() 17 | ->toImplement(KeyGenerator::class) 18 | ->toHaveSuffix('KeyGenerator'); 19 | -------------------------------------------------------------------------------- /tests/Architecture/SigningTest.php: -------------------------------------------------------------------------------- 1 | expect('CodeLieutenant\LaravelCrypto\Traits') 7 | ->toBeTraits(); 8 | 9 | arch('hashing traits') 10 | ->expect('CodeLieutenant\LaravelCrypto\Hashing\Traits') 11 | ->toBeTraits(); 12 | 13 | arch('signing traits') 14 | ->expect('CodeLieutenant\LaravelCrypto\Signing\Traits') 15 | ->toBeTraits(); 16 | 17 | -------------------------------------------------------------------------------- /tests/Feature/Encryption/AesGcm256EncryptorTest.php: -------------------------------------------------------------------------------- 1 | encrypt($data, $serialize); 11 | 12 | expect($encrypted) 13 | ->toBeString() 14 | ->and($encryptor->decrypt($encrypted, $serialize)) 15 | ->toBe($data); 16 | })->with([true, false]); 17 | 18 | it('should encrypt/decrypt string', function () { 19 | $encryptor = new AesGcm256Encrypter(inMemoryKeyLoader()); 20 | $data = 'hello world'; 21 | $encrypted = $encryptor->encryptString($data); 22 | 23 | expect($encrypted) 24 | ->toBeString() 25 | ->and($encryptor->decryptString($encrypted)) 26 | ->toBe($data); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/Feature/Encryption/XChaCha20Poly1305EncryptorTest.php: -------------------------------------------------------------------------------- 1 | encrypt($data, $serialize); 11 | 12 | expect($encrypted) 13 | ->toBeString() 14 | ->and($encryptor->decrypt($encrypted, $serialize)) 15 | ->toBe($data); 16 | })->with([true, false]); 17 | 18 | it('should encrypt/decrypt string', function () { 19 | $encryptor = new XChaCha20Poly1305Encrypter(inMemoryKeyLoader()); 20 | $data = 'hello world'; 21 | $encrypted = $encryptor->encryptString($data); 22 | 23 | expect($encrypted) 24 | ->toBeString() 25 | ->and($encryptor->decryptString($encrypted)) 26 | ->toBe($data); 27 | }); -------------------------------------------------------------------------------- /tests/Feature/ServiceProviderLoadingTest.php: -------------------------------------------------------------------------------- 1 | app->make('encrypter'); 15 | 16 | expect($encrypter)->toBeInstanceOf($instance); 17 | })->with([ 18 | ['AES-256-GCM', Encrypter::class], 19 | ['AES-256-CBC', Encrypter::class], 20 | [Encryption::SodiumAES256GCM->value, AesGcm256Encrypter::class], 21 | [Encryption::SodiumXChaCha20Poly1305->value, XChaCha20Poly1305Encrypter::class], 22 | ]); 23 | -------------------------------------------------------------------------------- /tests/InMemoryAppKeyKeyLoader.php: -------------------------------------------------------------------------------- 1 | key) 19 | ->remove('base64:', $this->key) 20 | ->fromBase64() 21 | ->toString(); 22 | } 23 | } -------------------------------------------------------------------------------- /tests/Pest.php: -------------------------------------------------------------------------------- 1 | in(__DIR__); 10 | 11 | function inMemoryKeyLoader(): KeyLoader 12 | { 13 | return new InMemoryAppKeyKeyLoader(config('app.key')); 14 | } 15 | 16 | expect()->extend('toBeBase64', function () { 17 | if (!preg_match('/^[-A-Za-z0-9+\/]+={0,3}$/', preg_quote($this->value, '/'))) { 18 | throw new RuntimeException(sprintf('Value %s is not a valid base64 string', $this->value)); 19 | } 20 | 21 | return $this; 22 | }); 23 | 24 | expect()->extend('toBeBase64NoPadding', function () { 25 | if (!preg_match('/^[-A-Za-z0-9+\/]+$/', preg_quote($this->value, '/'))) { 26 | throw new RuntimeException(sprintf('Value %s is not a valid base64 string', $this->value)); 27 | } 28 | return $this; 29 | }); 30 | 31 | expect()->extend('toBeBase64Url', function () { 32 | if (!preg_match('/^[-A-Za-z0-9_-]+={0,3}$/', $this->value)) { 33 | throw new RuntimeException(sprintf('Value %s is not a valid base64 string', $this->value)); 34 | } 35 | 36 | return $this; 37 | }); 38 | 39 | expect()->extend('toBeBase64UrlNoPadding', function () { 40 | if (!preg_match('/^[-A-Za-z0-9_-]+$/', $this->value)) { 41 | throw new RuntimeException(sprintf('Value %s is not a valid base64 string', $this->value)); 42 | } 43 | 44 | return $this; 45 | }); -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | > 19 | */ 20 | protected function getPackageProviders($app) 21 | { 22 | return [ 23 | \CodeLieutenant\LaravelCrypto\ServiceProvider::class, 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Unit/Encoders/IgbinaryEncoderTest.php: -------------------------------------------------------------------------------- 1 | 'John Doe', 'age' => 25]; 11 | 12 | $encoded = $encoder->encode($data); 13 | 14 | expect($encoded)->toBe(igbinary_serialize($data)); 15 | }); 16 | 17 | 18 | test('decode', function () { 19 | $encoder = new IgbinaryEncoder(); 20 | 21 | $data = igbinary_serialize(['name' => 'John Doe', 'age' => 25]); 22 | 23 | $decoded = $encoder->decode($data); 24 | 25 | expect($decoded)->toBe(['name' => 'John Doe', 'age' => 25]); 26 | }); 27 | -------------------------------------------------------------------------------- /tests/Unit/Encoders/JsonEncoderTest.php: -------------------------------------------------------------------------------- 1 | 'John Doe', 'age' => 25]; 11 | 12 | $encoded = $encoder->encode($data); 13 | 14 | expect($encoded)->toBe('{"name":"John Doe","age":25}'); 15 | }); 16 | 17 | 18 | test('decode as array', function () { 19 | $encoder = new JsonEncoder(asArray: true); 20 | 21 | $data = '{"name":"John Doe","age":25}'; 22 | 23 | $decoded = $encoder->decode($data); 24 | 25 | expect($decoded)->toBe(['name' => 'John Doe', 'age' => 25]); 26 | }); 27 | 28 | test('decode as object', function () { 29 | $encoder = new JsonEncoder(asArray: false); 30 | 31 | $data = '{"name":"John Doe","age":25}'; 32 | 33 | $decoded = $encoder->decode($data); 34 | 35 | $class = new stdClass(); 36 | $class->name = 'John Doe'; 37 | $class->age = 25; 38 | 39 | expect($decoded)->toBeObject() 40 | ->toEqual($class); 41 | }); -------------------------------------------------------------------------------- /tests/Unit/Encoders/PhpEncoderTest.php: -------------------------------------------------------------------------------- 1 | 'John Doe', 'age' => 25]; 11 | 12 | $encoded = $encoder->encode($data); 13 | 14 | expect($encoded)->toBe('a:2:{s:4:"name";s:8:"John Doe";s:3:"age";i:25;}'); 15 | }); 16 | 17 | 18 | test('decode', function () { 19 | $encoder = new PhpEncoder(); 20 | 21 | $data = 'a:2:{s:4:"name";s:8:"John Doe";s:3:"age";i:25;}'; 22 | 23 | $decoded = $encoder->decode($data); 24 | 25 | expect($decoded)->toBe(['name' => 'John Doe', 'age' => 25]); 26 | }); 27 | 28 | test('decode as object', function () { 29 | $encoder = new PhpEncoder(); 30 | 31 | $data = 'O:8:"stdClass":2:{s:4:"name";s:8:"John Doe";s:3:"age";i:25;}'; 32 | 33 | $decoded = $encoder->decode($data); 34 | 35 | $class = new stdClass(); 36 | $class->name = 'John Doe'; 37 | $class->age = 25; 38 | 39 | expect($decoded)->toBeObject() 40 | ->toEqual($class); 41 | }); -------------------------------------------------------------------------------- /tests/Unit/Encryption/CryptoTraitTest.php: -------------------------------------------------------------------------------- 1 | without previous', function () { 19 | $testCrypto = new TestTraitImpl(); 20 | $nonce = $testCrypto->generateNonce(); 21 | $nonce2 = $testCrypto->generateNonce(); 22 | 23 | expect($nonce)->toBeString() 24 | ->and(strlen($nonce)) 25 | ->toBe(16) 26 | ->and($nonce2) 27 | ->toBeString() 28 | ->and(strlen($nonce2)) 29 | ->toBe(16) 30 | ->and($nonce)->not->toBe($nonce2); 31 | }); 32 | 33 | test('generate nonce -> with previous', function () { 34 | $testCrypto = new TestTraitImpl(); 35 | $nonce = $testCrypto->generateNonce(); 36 | $nonce2 = $testCrypto->generateNonce($nonce); 37 | 38 | expect($nonce)->toBeString() 39 | ->and(strlen($nonce)) 40 | ->toBe(16) 41 | ->and($nonce2) 42 | ->toBeString() 43 | ->and(strlen($nonce2)) 44 | ->toBe(16) 45 | ->and(ord($nonce[0]) + 1)->toBe(ord($nonce2[0])) 46 | ->and(substr($nonce, 1))->toBe(substr($nonce2, 1)); 47 | }); 48 | 49 | 50 | test('supported algorithms', function (int $keyLength, string $cipher) { 51 | $key = random_bytes($keyLength); 52 | expect(TestTraitImpl::supported($key, $cipher))->toBetrue(); 53 | })->with([ 54 | [Encryption::SodiumAES256GCM->keySize(), Encryption::SodiumAES256GCM->value], 55 | [Encryption::SodiumXChaCha20Poly1305->keySize(), Encryption::SodiumXChaCha20Poly1305->value], 56 | [32, 'AES-256-GCM'], 57 | [32, 'AES-256-CBC'], 58 | [16, 'AES-128-CBC'], 59 | [16, 'AES-128-GCM'], 60 | ]); 61 | 62 | test('not supported algorithms', function (int $keyLength, string $cipher) { 63 | $key = random_bytes($keyLength); 64 | expect(TestTraitImpl::supported($key, $cipher))->toBeFalse(); 65 | })->with([ 66 | [16, 'invalid algorithm'], 67 | [32, 'AES-128-CBC'], 68 | [32, 'AES-128-GCM'], 69 | [16, 'AES-256-GCM'], 70 | [16, 'AES-256-CBC'], 71 | ]); -------------------------------------------------------------------------------- /tests/Unit/Hashing/Blake2bTest.php: -------------------------------------------------------------------------------- 1 | hash('hello world'))->toBe(bcryptHashEncoded('hello world', '', $outputLength)); 25 | }); 26 | 27 | test('hashing raw', function () { 28 | $outputLength = 32; 29 | $hasher = new Blake2b(outputLength: $outputLength); 30 | expect($hasher->hashRaw('hello world'))->toBe( 31 | bcryptHash('hello world', '', $outputLength), 32 | ); 33 | }); 34 | 35 | test('hashing with key', function () { 36 | $key = random_bytes(32); 37 | $outputLength = 32; 38 | $hasher = new Blake2b($key, $outputLength); 39 | 40 | expect($hasher->hash('hello world'))->toBe(bcryptHashEncoded('hello world', $key, $outputLength)); 41 | }); 42 | 43 | test('hashing raw with key', function () { 44 | $key = random_bytes(32); 45 | $outputLength = 32; 46 | $hasher = new Blake2b($key, $outputLength); 47 | 48 | expect($hasher->hashRaw('hello world'))->toBe(bcryptHash('hello world', $key, $outputLength)); 49 | }); 50 | 51 | test('hashing verify', function () { 52 | $outputLength = 32; 53 | $hasher = new Blake2b(outputLength: $outputLength); 54 | 55 | $data = 'hello world'; 56 | $hash = bcryptHashEncoded($data, '', $outputLength); 57 | 58 | expect($hasher->verify($hash, $data)) 59 | ->toBeTrue() 60 | ->and($hasher->verify($hash, 'wrong input')) 61 | ->toBeFalse(); 62 | }); 63 | 64 | test('hashing raw verify', function () { 65 | $outputLength = 32; 66 | $hasher = new Blake2b(outputLength: $outputLength); 67 | 68 | $data = 'hello world'; 69 | $hash = bcryptHash($data, '', $outputLength); 70 | 71 | expect($hasher->verifyRaw($hash, $data)) 72 | ->toBeTrue() 73 | ->and($hasher->verifyRaw($hash, 'wrong input')) 74 | ->toBeFalse(); 75 | }); 76 | -------------------------------------------------------------------------------- /tests/Unit/Hashing/Sha256Test.php: -------------------------------------------------------------------------------- 1 | hash('hello world'))->toBe(hash256Encoded('hello world')); 20 | }); 21 | 22 | test('hashing raw', function () { 23 | $hasher = new Sha256(); 24 | expect($hasher->hashRaw('hello world'))->toBe( 25 | hash256('hello world'), 26 | ); 27 | }); 28 | 29 | test('hashing verify', function () { 30 | $hasher = new Sha256(); 31 | 32 | $data = 'hello world'; 33 | $hash = hash256Encoded($data); 34 | 35 | expect($hasher->verify($hash, $data)) 36 | ->toBeTrue() 37 | ->and($hasher->verify($hash, 'wrong input')) 38 | ->toBeFalse(); 39 | }); 40 | 41 | test('hashing raw verify', function () { 42 | $hasher = new Sha256(); 43 | 44 | $data = 'hello world'; 45 | $hash = hash256($data); 46 | 47 | expect($hasher->verifyRaw($hash, $data)) 48 | ->toBeTrue() 49 | ->and($hasher->verifyRaw($hash, 'wrong input')) 50 | ->toBeFalse(); 51 | }); 52 | -------------------------------------------------------------------------------- /tests/Unit/Hashing/Sha512Test.php: -------------------------------------------------------------------------------- 1 | hash('hello world'))->toBe(hash512Encoded('hello world')); 20 | }); 21 | 22 | test('hashing raw', function () { 23 | $hasher = new Sha512(); 24 | expect($hasher->hashRaw('hello world'))->toBe( 25 | hash512('hello world'), 26 | ); 27 | }); 28 | 29 | test('hashing verify', function () { 30 | $hasher = new Sha512(); 31 | 32 | $data = 'hello world'; 33 | $hash = hash512Encoded($data); 34 | 35 | expect($hasher->verify($hash, $data)) 36 | ->toBeTrue() 37 | ->and($hasher->verify($hash, 'wrong input')) 38 | ->toBeFalse(); 39 | }); 40 | 41 | test('hashing raw verify', function () { 42 | $hasher = new Sha512(); 43 | 44 | $data = 'hello world'; 45 | $hash = hash512($data); 46 | 47 | expect($hasher->verifyRaw($hash, $data)) 48 | ->toBeTrue() 49 | ->and($hasher->verifyRaw($hash, 'wrong input')) 50 | ->toBeFalse(); 51 | }); 52 | -------------------------------------------------------------------------------- /tests/Unit/Support/Base64Test.php: -------------------------------------------------------------------------------- 1 | toBe(base64_encode($data)); 13 | }); 14 | -------------------------------------------------------------------------------- /workbench/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .env.dusk 3 | -------------------------------------------------------------------------------- /workbench/app/Models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MalusevDevelopment/laravel-crypto/4517b2ecbb690c370e90bc538851411539c2fa7e/workbench/app/Models/.gitkeep -------------------------------------------------------------------------------- /workbench/app/Providers/WorkbenchServiceProvider.php: -------------------------------------------------------------------------------- 1 | env('APP_NAME', ' Laravel'), 23 | 24 | /* 25 | |-------------------------------------------------------------------------- 26 | | Application Environment 27 | |-------------------------------------------------------------------------- 28 | | 29 | | This value determines the "environment" your application is currently 30 | | running in. This may determine how you prefer to configure various 31 | | services the application utilizes. Set this in your ".env" file. 32 | | 33 | */ 34 | 35 | 'env' => env('APP_ENV', 'testing'), 36 | 37 | /* 38 | |-------------------------------------------------------------------------- 39 | | Application Debug Mode 40 | |-------------------------------------------------------------------------- 41 | | 42 | | When your application is in debug mode, detailed error messages with 43 | | stack traces will be shown on every error that occurs within your 44 | | application. If disabled, a simple generic error page is shown. 45 | | 46 | */ 47 | 48 | 'debug' => (bool)env('APP_DEBUG', false), 49 | 50 | /* 51 | |-------------------------------------------------------------------------- 52 | | Application URL 53 | |-------------------------------------------------------------------------- 54 | | 55 | | This URL is used by the console to properly generate URLs when using 56 | | the Artisan command line tool. You should set this to the root of 57 | | your application so that it is used when running Artisan tasks. 58 | | 59 | */ 60 | 61 | 'url' => env('APP_URL', 'http://localhost'), 62 | 63 | 'asset_url' => env('ASSET_URL'), 64 | 65 | /* 66 | |-------------------------------------------------------------------------- 67 | | Application Timezone 68 | |-------------------------------------------------------------------------- 69 | | 70 | | Here you may specify the default timezone for your application, which 71 | | will be used by the PHP date and date-time functions. We have gone 72 | | ahead and set this to a sensible default for you out of the box. 73 | | 74 | */ 75 | 76 | 'timezone' => 'UTC', 77 | 78 | /* 79 | |-------------------------------------------------------------------------- 80 | | Application Locale Configuration 81 | |-------------------------------------------------------------------------- 82 | | 83 | | The application locale determines the default locale that will be used 84 | | by the translation service provider. You are free to set this value 85 | | to any of the locales which will be supported by the application. 86 | | 87 | */ 88 | 89 | 'locale' => 'en', 90 | 91 | /* 92 | |-------------------------------------------------------------------------- 93 | | Application Fallback Locale 94 | |-------------------------------------------------------------------------- 95 | | 96 | | The fallback locale determines the locale to use when the current one 97 | | is not available. You may change the value to correspond to any of 98 | | the language folders that are provided through your application. 99 | | 100 | */ 101 | 102 | 'fallback_locale' => 'en', 103 | 104 | /* 105 | |-------------------------------------------------------------------------- 106 | | Faker Locale 107 | |-------------------------------------------------------------------------- 108 | | 109 | | This locale will be used by the Faker PHP library when generating fake 110 | | data for your database seeds. For example, this will be used to get 111 | | localized telephone numbers, street address information and more. 112 | | 113 | */ 114 | 115 | 'faker_locale' => 'en_US', 116 | 117 | /* 118 | |-------------------------------------------------------------------------- 119 | | Encryption Key 120 | |-------------------------------------------------------------------------- 121 | | 122 | | This key is used by the Illuminate encrypter service and should be set 123 | | to a random, 32 character string, otherwise these encrypted strings 124 | | will not be safe. Please do this before deploying an application! 125 | | 126 | */ 127 | 128 | 'key' => env('APP_KEY'), 129 | 130 | 'cipher' => Encryption::SodiumAES256GCM->value, 131 | 132 | /* 133 | |-------------------------------------------------------------------------- 134 | | Maintenance Mode Driver 135 | |-------------------------------------------------------------------------- 136 | | 137 | | These configuration options determine the driver used to determine and 138 | | manage Laravel's "maintenance mode" status. The "cache" driver will 139 | | allow maintenance mode to be controlled across multiple machines. 140 | | 141 | | Supported drivers: "file", "cache" 142 | | 143 | */ 144 | 145 | 'maintenance' => [ 146 | 'driver' => 'file', 147 | // 'store' => 'redis', 148 | ], 149 | 150 | /* 151 | |-------------------------------------------------------------------------- 152 | | Autoloaded Service Providers 153 | |-------------------------------------------------------------------------- 154 | | 155 | | The service providers listed here will be automatically loaded on the 156 | | request to your application. Feel free to add your own services to 157 | | this array to grant expanded functionality to your applications. 158 | | 159 | */ 160 | 161 | 'providers' => ServiceProvider::defaultProviders() 162 | ->replace([ 163 | EncryptionServiceProvider::class => CodeLieutenant\LaravelCrypto\ServiceProvider::class, 164 | ]) 165 | ->merge([ 166 | WorkbenchServiceProvider::class, 167 | ]) 168 | ->toArray(), 169 | 170 | /* 171 | |-------------------------------------------------------------------------- 172 | | Class Aliases 173 | |-------------------------------------------------------------------------- 174 | | 175 | | This array of class aliases will be registered when this application 176 | | is started. However, feel free to register as many as you wish as 177 | | the aliases are "lazy" loaded so they don't hinder performance. 178 | | 179 | */ 180 | 181 | 'aliases' => Facade::defaultAliases()->toArray(), 182 | 183 | ]; 184 | -------------------------------------------------------------------------------- /workbench/database/factories/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MalusevDevelopment/laravel-crypto/4517b2ecbb690c370e90bc538851411539c2fa7e/workbench/database/factories/.gitkeep -------------------------------------------------------------------------------- /workbench/database/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MalusevDevelopment/laravel-crypto/4517b2ecbb690c370e90bc538851411539c2fa7e/workbench/database/migrations/.gitkeep -------------------------------------------------------------------------------- /workbench/database/seeders/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MalusevDevelopment/laravel-crypto/4517b2ecbb690c370e90bc538851411539c2fa7e/workbench/database/seeders/.gitkeep -------------------------------------------------------------------------------- /workbench/database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | get('/user', function (Request $request) { 18 | // return $request->user(); 19 | // }); 20 | -------------------------------------------------------------------------------- /workbench/routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 19 | // })->purpose('Display an inspiring quote'); 20 | -------------------------------------------------------------------------------- /workbench/routes/web.php: -------------------------------------------------------------------------------- 1 |