Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9a447b4

Browse filesBrowse files
committed
Added phpseclib encryption
1 parent ecb2c14 commit 9a447b4
Copy full SHA for 9a447b4

11 files changed

+380
-49
lines changed

‎composer.json

Copy file name to clipboardExpand all lines: composer.json
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@
135135
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
136136
"twig/cssinliner-extra": "^2.12",
137137
"twig/inky-extra": "^2.12",
138-
"twig/markdown-extra": "^2.12"
138+
"twig/markdown-extra": "^2.12",
139+
"phpseclib/phpseclib": "^2.0.29"
139140
},
140141
"conflict": {
141142
"async-aws/core": "<1.5",

‎src/Symfony/Bundle/SecurityBundle/Resources/config/security.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Resources/config/security.php
+11-3Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1313

14+
use phpseclib\Crypt\AES;
1415
use Symfony\Bundle\SecurityBundle\CacheWarmer\ExpressionCacheWarmer;
1516
use Symfony\Bundle\SecurityBundle\EventListener\FirewallEventBubblingListener;
1617
use Symfony\Bundle\SecurityBundle\EventListener\FirewallListener;
@@ -37,6 +38,7 @@
3738
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoder;
3839
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
3940
use Symfony\Component\Security\Core\Encryption\AsymmetricEncryptionInterface;
41+
use Symfony\Component\Security\Core\Encryption\PhpseclibEncryption;
4042
use Symfony\Component\Security\Core\Encryption\SodiumEncryption;
4143
use Symfony\Component\Security\Core\Encryption\SymmetricEncryptionInterface;
4244
use Symfony\Component\Security\Core\Role\RoleHierarchy;
@@ -61,6 +63,9 @@
6163
->set('security.role_hierarchy.roles', [])
6264
;
6365

66+
$sodiumInstalled = \function_exists('sodium_crypto_box_keypair');
67+
$phpseclibInstalled = class_exists(AES::class);
68+
6469
$container->services()
6570
->set('security.authorization_checker', AuthorizationChecker::class)
6671
->public()
@@ -100,12 +105,15 @@
100105
->tag('controller.argument_value_resolver', ['priority' => 40])
101106

102107
->set('security.encryption.sodium', SodiumEncryption::class)
103-
->abstract()
104108
->args([
105109
'%kernel.secret%',
106110
])
107-
->alias(SymmetricEncryptionInterface::class, 'security.encryption.sodium')
108-
->alias(AsymmetricEncryptionInterface::class, 'security.encryption.sodium')
111+
->set('security.encryption.phpseclib', PhpseclibEncryption::class)
112+
->args([
113+
'%kernel.secret%',
114+
])
115+
->alias(SymmetricEncryptionInterface::class, $phpseclibInstalled && !$sodiumInstalled ? 'security.encryption.phpseclib' : 'security.encryption.sodium')
116+
->alias(AsymmetricEncryptionInterface::class, $phpseclibInstalled && !$sodiumInstalled ? 'security.encryption.phpseclib' : 'security.encryption.sodium')
109117

110118
// Authentication related services
111119
->set('security.authentication.trust_resolver', AuthenticationTrustResolver::class)
+160Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Core\Encryption;
13+
14+
use phpseclib\Crypt\AES;
15+
use phpseclib\Crypt\RSA;
16+
use Symfony\Component\Security\Core\Exception\EncryptionException;
17+
use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
18+
use Symfony\Component\Security\Core\Exception\MalformedCipherException;
19+
use Symfony\Component\Security\Core\Exception\UnsupportedAlgorithmException;
20+
use Symfony\Component\Security\Core\Exception\WrongEncryptionKeyException;
21+
22+
if (!class_exists(RSA::class)) {
23+
throw new \LogicException('You cannot use "Symfony\Component\Security\Core\Encryption\PhpseclibEncryption" as the "phpseclib/phpseclib:2.x" package is not installed. Try running "composer require phpseclib/phpseclib:^2".');
24+
}
25+
26+
/**
27+
* The secret key length should be 32 bytes, but other sizes are accepted.
28+
*
29+
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
30+
* @experimental in 5.2
31+
*/
32+
class PhpseclibEncryption implements SymmetricEncryptionInterface, AsymmetricEncryptionInterface
33+
{
34+
/**
35+
* @var string application secret
36+
*/
37+
private $secret;
38+
39+
public function __construct(string $secret)
40+
{
41+
$this->secret = $secret;
42+
}
43+
44+
public function generateKeypair(): array
45+
{
46+
$rsa = new RSA();
47+
$key = $rsa->createKey();
48+
49+
if ($key['partialkey']) {
50+
throw new EncryptionException('Failed to generate RSA keypair.');
51+
}
52+
53+
return [
54+
'public' => $key['publickey'],
55+
'private' => $key['privatekey'],
56+
];
57+
}
58+
59+
public function encrypt(string $message, ?string $publicKey = null, ?string $privateKey = null): string
60+
{
61+
set_error_handler(__CLASS__.'::throwError');
62+
$nonce = random_bytes(16);
63+
try {
64+
if (null === $publicKey) {
65+
$algorithm = 'aes';
66+
$aes = new AES();
67+
$aes->setKey($this->secret);
68+
$cipher = $aes->encrypt($message);
69+
} elseif (null === $privateKey) {
70+
$algorithm = 'rsa';
71+
$rsa = new RSA();
72+
$rsa->loadKey($publicKey);
73+
$cipher = $rsa->encrypt($message);
74+
} elseif (null !== $publicKey && null !== $privateKey) {
75+
$algorithm = 'rsa_signature_pss';
76+
$rsa = new RSA();
77+
$rsa->loadKey($publicKey);
78+
$cipher = $rsa->encrypt($message);
79+
80+
// Load private key after encryption
81+
$rsa->loadKey($privateKey);
82+
$rsa->setSignatureMode(RSA::SIGNATURE_PSS);
83+
$nonce = $rsa->sign($cipher);
84+
} else {
85+
throw new InvalidArgumentException('Private key cannot have a value when no public key is provided.');
86+
}
87+
} catch (\ErrorException $exception) {
88+
throw new EncryptionException(sprintf('Failed to encrypt message with algorithm "%s".', $algorithm), 0, $exception);
89+
} finally {
90+
restore_error_handler();
91+
}
92+
93+
return sprintf('%s.%s.%s', base64_encode($cipher), base64_encode($algorithm), base64_encode($nonce));
94+
}
95+
96+
public function decrypt(string $message, ?string $privateKey = null, ?string $publicKey = null): string
97+
{
98+
// Make sure the message has two periods
99+
$parts = explode('.', $message);
100+
if (false === $parts || 3 !== \count($parts)) {
101+
throw new MalformedCipherException();
102+
}
103+
104+
[$cipher, $algorithm, $nonce] = $parts;
105+
$algorithm = base64_decode($algorithm);
106+
$ciphertext = base64_decode($cipher, true);
107+
$nonce = base64_decode($nonce, true);
108+
109+
set_error_handler(__CLASS__.'::throwError');
110+
try {
111+
if ('rsa' === $algorithm) {
112+
if (null !== $publicKey) {
113+
throw new WrongEncryptionKeyException();
114+
}
115+
116+
$rsa = new RSA();
117+
$rsa->loadKey($privateKey);
118+
$output = $rsa->decrypt($ciphertext);
119+
} elseif ('rsa_signature_pss' === $algorithm) {
120+
if (null === $publicKey) {
121+
throw new WrongEncryptionKeyException();
122+
}
123+
$rsa = new RSA();
124+
$rsa->loadKey($publicKey);
125+
$verify = $rsa->verify($ciphertext, $nonce);
126+
if (!$verify) {
127+
throw new WrongEncryptionKeyException();
128+
}
129+
130+
// Load private key after verification
131+
$rsa->loadKey($privateKey);
132+
$output = $rsa->decrypt($ciphertext);
133+
} elseif ('aes' === $algorithm) {
134+
$aes = new AES();
135+
$aes->setKey($this->secret);
136+
$output = $aes->decrypt($ciphertext);
137+
} else {
138+
throw new UnsupportedAlgorithmException($algorithm);
139+
}
140+
} catch (\ErrorException $exception) {
141+
throw new EncryptionException(sprintf('Failed to decrypt message with algorithm "%s".', $algorithm), 0, $exception);
142+
} finally {
143+
restore_error_handler();
144+
}
145+
146+
if (false === $output) {
147+
throw new WrongEncryptionKeyException();
148+
}
149+
150+
return $output;
151+
}
152+
153+
/**
154+
* @internal
155+
*/
156+
public static function throwError($type, $message, $file, $line)
157+
{
158+
throw new \ErrorException($message, 0, $type, $file, $line);
159+
}
160+
}

‎src/Symfony/Component/Security/Core/Exception/WrongEncryptionKeyException.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Core/Exception/WrongEncryptionKeyException.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
/**
1515
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
1616
*/
17-
class WrongEncryptionKeyException extends \Exception implements EncryptionExceptionInterface
17+
class WrongEncryptionKeyException extends EncryptionException implements EncryptionExceptionInterface
1818
{
1919
public function __construct(\Throwable $previous = null)
2020
{

‎src/Symfony/Component/Security/Core/Tests/Encryption/SodiumEncryptionTest.php renamed to ‎src/Symfony/Component/Security/Core/Tests/Encryption/AbstractAsymmetricEncryptionTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Core/Tests/Encryption/AbstractAsymmetricEncryptionTest.php
+21-43Lines changed: 21 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,20 @@
1212
namespace Symfony\Component\Security\Core\Tests\Encryption;
1313

1414
use PHPUnit\Framework\TestCase;
15-
use Symfony\Component\Security\Core\Encryption\SodiumEncryption;
15+
use Symfony\Component\Security\Core\Encryption\AsymmetricEncryptionInterface;
16+
use Symfony\Component\Security\Core\Exception\EncryptionException;
1617
use Symfony\Component\Security\Core\Exception\MalformedCipherException;
1718
use Symfony\Component\Security\Core\Exception\UnsupportedAlgorithmException;
1819
use Symfony\Component\Security\Core\Exception\WrongEncryptionKeyException;
1920

20-
class SodiumEncryptionTest extends TestCase
21+
/**
22+
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
23+
*/
24+
abstract class AbstractAsymmetricEncryptionTest extends TestCase
2125
{
22-
public function testEncryption()
23-
{
24-
$sodium = new SodiumEncryption('s3cr3t');
25-
$cipher = $sodium->encrypt('');
26-
$this->assertNotEmpty('input', $cipher);
27-
$this->assertTrue(\strlen($cipher) > 10);
28-
$this->assertNotEquals('input', $sodium->encrypt('input'));
29-
30-
$cipher = $sodium->encrypt($input = 'random_string');
31-
$sodium = new SodiumEncryption('different_secret');
32-
$this->assertNotEquals($cipher, $sodium->encrypt($input));
33-
}
34-
3526
public function testAsymmetricEncryption()
3627
{
37-
$sodium = new SodiumEncryption('s3cr3t');
28+
$sodium = $this->getAsymmetricEncryption();
3829
['public' => $alicePublic, 'private' => $alicePrivate] = $sodium->generateKeypair();
3930
['public' => $bobPublic, 'private' => $bobPrivate] = $sodium->generateKeypair();
4031

@@ -46,7 +37,7 @@ public function testAsymmetricEncryption()
4637

4738
public function testAsymmetricEncryptionWithIdentification()
4839
{
49-
$sodium = new SodiumEncryption('s3cr3t');
40+
$sodium = $this->getAsymmetricEncryption();
5041
['public' => $alicePublic, 'private' => $alicePrivate] = $sodium->generateKeypair();
5142
['public' => $bobPublic, 'private' => $bobPrivate] = $sodium->generateKeypair();
5243

@@ -57,17 +48,9 @@ public function testAsymmetricEncryptionWithIdentification()
5748
$this->assertNotEquals('input', $sodium->encrypt('input', $bobPublic, $alicePrivate));
5849
}
5950

60-
public function testDecryption()
61-
{
62-
$sodium = new SodiumEncryption('s3cr3t');
63-
64-
$this->assertEquals($input = '', $sodium->decrypt($sodium->encrypt($input)));
65-
$this->assertEquals($input = 'foobar', $sodium->decrypt($sodium->encrypt($input)));
66-
}
67-
6851
public function testAsymmetricDecryption()
6952
{
70-
$sodium = new SodiumEncryption('s3cr3t');
53+
$sodium = $this->getAsymmetricEncryption();
7154
['public' => $alicePublic, 'private' => $alicePrivate] = $sodium->generateKeypair();
7255

7356
$cipher = $sodium->encrypt($input = 'input', $alicePublic);
@@ -76,7 +59,7 @@ public function testAsymmetricDecryption()
7659

7760
public function testAsymmetricDecryptionWithIdentification()
7861
{
79-
$sodium = new SodiumEncryption('s3cr3t');
62+
$sodium = $this->getAsymmetricEncryption();
8063
['public' => $alicePublic, 'private' => $alicePrivate] = $sodium->generateKeypair();
8164
['public' => $bobPublic, 'private' => $bobPrivate] = $sodium->generateKeypair();
8265

@@ -86,7 +69,7 @@ public function testAsymmetricDecryptionWithIdentification()
8669

8770
public function testAsymmetricDecryptionUnableToVerifySender()
8871
{
89-
$sodium = new SodiumEncryption('s3cr3t');
72+
$sodium = $this->getAsymmetricEncryption();
9073
['public' => $alicePublic, 'private' => $alicePrivate] = $sodium->generateKeypair();
9174
['public' => $bobPublic, 'private' => $bobPrivate] = $sodium->generateKeypair();
9275

@@ -98,7 +81,7 @@ public function testAsymmetricDecryptionUnableToVerifySender()
9881

9982
public function testAsymmetricDecryptionIgnoreToVerifySender()
10083
{
101-
$sodium = new SodiumEncryption('s3cr3t');
84+
$sodium = $this->getAsymmetricEncryption();
10285
['public' => $alicePublic, 'private' => $alicePrivate] = $sodium->generateKeypair();
10386
['public' => $bobPublic, 'private' => $bobPrivate] = $sodium->generateKeypair();
10487

@@ -108,48 +91,43 @@ public function testAsymmetricDecryptionIgnoreToVerifySender()
10891
$this->assertEquals($input, $sodium->decrypt($cipher, $bobPrivate));
10992
}
11093

111-
public function testDecryptionThrowsOnMalformedCipher()
112-
{
113-
$sodium = new SodiumEncryption('s3cr3t');
114-
$this->expectException(MalformedCipherException::class);
115-
$sodium->decrypt('foo');
116-
}
117-
11894
public function testAsymmetricDecryptionThrowsOnMalformedCipher()
11995
{
120-
$sodium = new SodiumEncryption('s3cr3t');
96+
$sodium = $this->getAsymmetricEncryption();
12197
$this->expectException(MalformedCipherException::class);
12298
$sodium->decrypt('foo', 'private', 'public');
12399
}
124100

125101
public function testDecryptionThrowsOnUnsupportedAlgorithm()
126102
{
127-
$sodium = new SodiumEncryption('s3cr3t');
103+
$sodium = $this->getAsymmetricEncryption();
128104
$this->expectException(UnsupportedAlgorithmException::class);
129-
$sodium->decrypt('foo.bar.baz');
105+
$sodium->decrypt('foo.bar.baz', 'private', 'public');
130106
}
131107

132108
public function testAsymmetricDecryptionThrowsExceptionOnWrongPublicKey()
133109
{
134-
$sodium = new SodiumEncryption('s3cr3t');
110+
$sodium = $this->getAsymmetricEncryption();
135111
['public' => $alicePublic, 'private' => $alicePrivate] = $sodium->generateKeypair();
136112
['public' => $bobPublic, 'private' => $bobPrivate] = $sodium->generateKeypair();
137113
['public' => $evePublic, 'private' => $evePrivate] = $sodium->generateKeypair();
138114

139115
$cipher = $sodium->encrypt('input', $bobPublic, $alicePrivate);
140-
$this->expectException(WrongEncryptionKeyException::class);
116+
$this->expectException(EncryptionException::class);
141117
$sodium->decrypt($cipher, $bobPrivate, $evePublic);
142118
}
143119

144120
public function testAsymmetricDecryptionThrowsExceptionOnWrongPrivateKey()
145121
{
146-
$sodium = new SodiumEncryption('s3cr3t');
122+
$sodium = $this->getAsymmetricEncryption();
147123
['public' => $alicePublic, 'private' => $alicePrivate] = $sodium->generateKeypair();
148124
['public' => $bobPublic, 'private' => $bobPrivate] = $sodium->generateKeypair();
149125
['public' => $evePublic, 'private' => $evePrivate] = $sodium->generateKeypair();
150126

151127
$cipher = $sodium->encrypt('input', $bobPublic, $alicePrivate);
152-
$this->expectException(WrongEncryptionKeyException::class);
128+
$this->expectException(EncryptionException::class);
153129
$sodium->decrypt($cipher, $evePrivate, $alicePublic);
154130
}
131+
132+
abstract protected function getAsymmetricEncryption(): AsymmetricEncryptionInterface;
155133
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.