├── .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 | [](https://github.com/dmalusev/laravel-crypto/actions/workflows/test.yml)
4 | [](https://github.com/malusev998/LaravelCrypto/issues)
5 | [](https://github.com/malusev998/LaravelCrypto/stargazers)
6 | [](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 | [](https://dev.azure.com/BrosSquad/LaravelHashing/_build/latest?definitionId=8&branchName=master)
5 | [](https://github.com/malusev998/LaravelCrypto/issues)
6 | [](https://github.com/malusev998/LaravelCrypto/stargazers)
7 | [](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 | Subject |
459 | Description |
460 | Revs |
461 | Iterations |
462 | Memory Peak |
463 | Best Time |
464 | Average Time |
465 | Worst Time |
466 |
467 |
468 |
469 |
470 | benchLaravelEncryption |
471 | Default Laravel Encrypter (AES-256-CBC) |
472 | 100 |
473 | 10 |
474 | 2,259,976b |
475 | 952.012μs |
476 | 957.365μs |
477 | 974.771μs |
478 |
479 |
480 | benchXChaCha20Poly1305 |
481 | XChaCha20Poly1305 Encryption |
482 | 100 |
483 | 10 |
484 | 2,458,008b |
485 | 265.780μs |
486 | 267.313μs |
487 | 270.197μs |
488 |
489 |
490 | benchAes256gcm |
491 | AES 256 GCM Encryption |
492 | 100 |
493 | 10 |
494 | 2,457,984b |
495 | 252.650μs |
496 | 254.105μs |
497 | 256.572μs |
498 |
499 |
500 |
501 |
502 |
503 |
504 | ##### Decryption
505 |
506 |
507 |
508 |
509 | Subject |
510 | Description |
511 | Revs |
512 | Iterations |
513 | Memory Peak |
514 | Best Time |
515 | Average Time |
516 | Worst Time |
517 |
518 |
519 |
520 |
521 | benchLaravelDecryption |
522 | Default Laravel Decrypter (AES-256-CBC) |
523 | 100 |
524 | 10 |
525 | 2,508,208b |
526 | 1,661.467μs |
527 | 1,666.177μs |
528 | 1,677.097μs |
529 |
530 |
531 | benchXChaCha20Poly1305Decryption |
532 | XChaCha20Poly1305 Decryption |
533 | 100 |
534 | 10 |
535 | 2,529,600b |
536 | 455.560μs |
537 | 458.140μs |
538 | 465.492μs |
539 |
540 |
541 | benchAes256gcmDecryption |
542 | AES 256 GCM Decryption |
543 | 100 |
544 | 10 |
545 | 2,529,576b |
546 | 447.280μs |
547 | 449.552μs |
548 | 453.438μs |
549 |
550 |
551 |
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 |