Skip to content

Commit 44e5d2f

Browse files
authored
Native Scalar Multiplication support (#176)
1 parent 7d1ec31 commit 44e5d2f

File tree

5 files changed

+125
-109
lines changed

5 files changed

+125
-109
lines changed

performance/JWE/ECDHESA128KWBench.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@ public function dataRecipientPublicKeys(): array
154154
'crv' => 'X25519',
155155
'kty' => 'OKP',
156156
'x' => 'LD7PfRPxq03bd0WJyf_1z-LQevmrbcYx7jJafep3gmk',
157-
'd' => 'pSdgXFRYMvOa7giAm3Rrf5Mf8GnvLz7HtZKu_KN06KY',
158157
],
159158
],
160159
];

performance/JWE/ECDHESA192KWBench.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@ public function dataRecipientPublicKeys(): array
154154
'crv' => 'X25519',
155155
'kty' => 'OKP',
156156
'x' => 'LD7PfRPxq03bd0WJyf_1z-LQevmrbcYx7jJafep3gmk',
157-
'd' => 'pSdgXFRYMvOa7giAm3Rrf5Mf8GnvLz7HtZKu_KN06KY',
158157
],
159158
],
160159
];

performance/JWE/ECDHESA256KWBench.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@ public function dataRecipientPublicKeys(): array
154154
'crv' => 'X25519',
155155
'kty' => 'OKP',
156156
'x' => 'LD7PfRPxq03bd0WJyf_1z-LQevmrbcYx7jJafep3gmk',
157-
'd' => 'pSdgXFRYMvOa7giAm3Rrf5Mf8GnvLz7HtZKu_KN06KY',
158157
],
159158
],
160159
];

src/Component/Core/Util/ECKey.php

Lines changed: 115 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Jose\Component\Core\Util\Ecc\Curve;
2020
use Jose\Component\Core\Util\Ecc\NistCurve;
2121
use RuntimeException;
22+
use Throwable;
2223

2324
/**
2425
* @internal
@@ -29,9 +30,9 @@ public static function convertToPEM(JWK $jwk): string
2930
{
3031
if ($jwk->has('d')) {
3132
return self::convertPrivateKeyToPEM($jwk);
32-
} else {
33-
return self::convertPublicKeyToPEM($jwk);
3433
}
34+
35+
return self::convertPublicKeyToPEM($jwk);
3536
}
3637

3738
public static function convertPublicKeyToPEM(JWK $jwk): string
@@ -50,11 +51,11 @@ public static function convertPublicKeyToPEM(JWK $jwk): string
5051

5152
break;
5253
default:
53-
throw new \InvalidArgumentException('Unsupported curve.');
54+
throw new InvalidArgumentException('Unsupported curve.');
5455
}
5556
$der .= self::getKey($jwk);
5657
$pem = '-----BEGIN PUBLIC KEY-----'.PHP_EOL;
57-
$pem .= \chunk_split(\base64_encode($der), 64, PHP_EOL);
58+
$pem .= chunk_split(base64_encode($der), 64, PHP_EOL);
5859
$pem .= '-----END PUBLIC KEY-----'.PHP_EOL;
5960

6061
return $pem;
@@ -76,11 +77,11 @@ public static function convertPrivateKeyToPEM(JWK $jwk): string
7677

7778
break;
7879
default:
79-
throw new \InvalidArgumentException('Unsupported curve.');
80+
throw new InvalidArgumentException('Unsupported curve.');
8081
}
8182
$der .= self::getKey($jwk);
8283
$pem = '-----BEGIN EC PRIVATE KEY-----'.PHP_EOL;
83-
$pem .= \chunk_split(\base64_encode($der), 64, PHP_EOL);
84+
$pem .= chunk_split(base64_encode($der), 64, PHP_EOL);
8485
$pem .= '-----END EC PRIVATE KEY-----'.PHP_EOL;
8586

8687
return $pem;
@@ -96,27 +97,40 @@ public static function createECKey(string $curve, array $values = []): JWK
9697
{
9798
try {
9899
$jwk = self::createECKeyUsingOpenSSL($curve);
99-
} catch (\Exception $e) {
100+
} catch (Throwable $e) {
100101
$jwk = self::createECKeyUsingPurePhp($curve);
101102
}
102-
$values = \array_merge($values, $jwk);
103+
$values = array_merge($values, $jwk);
103104

104105
return new JWK($values);
105106
}
106107

108+
private static function getNistCurve(string $curve): Curve
109+
{
110+
switch ($curve) {
111+
case 'P-256':
112+
return NistCurve::curve256();
113+
case 'P-384':
114+
return NistCurve::curve384();
115+
case 'P-521':
116+
return NistCurve::curve521();
117+
default:
118+
throw new InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve));
119+
}
120+
}
121+
107122
private static function createECKeyUsingPurePhp(string $curve): array
108123
{
109-
$nistCurve = static::getCurve($curve);
110-
$componentSize = (int) ceil($nistCurve->getSize() / 8);
124+
$nistCurve = self::getNistCurve($curve);
111125
$privateKey = $nistCurve->createPrivateKey();
112126
$publicKey = $nistCurve->createPublicKey($privateKey);
113127

114128
return [
115129
'kty' => 'EC',
116130
'crv' => $curve,
117-
'd' => Base64Url::encode(str_pad(gmp_export($privateKey->getSecret()), $componentSize, "\0", STR_PAD_LEFT)),
118-
'x' => Base64Url::encode(str_pad(gmp_export($publicKey->getPoint()->getX()), $componentSize, "\0", STR_PAD_LEFT)),
119-
'y' => Base64Url::encode(str_pad(gmp_export($publicKey->getPoint()->getY()), $componentSize, "\0", STR_PAD_LEFT)),
131+
'x' => Base64Url::encode(str_pad(gmp_export($publicKey->getPoint()->getX()), (int) ceil($nistCurve->getSize() / 8), "\0", STR_PAD_LEFT)),
132+
'y' => Base64Url::encode(str_pad(gmp_export($publicKey->getPoint()->getY()), (int) ceil($nistCurve->getSize() / 8), "\0", STR_PAD_LEFT)),
133+
'd' => Base64Url::encode(str_pad(gmp_export($privateKey->getSecret()), (int) ceil($nistCurve->getSize() / 8), "\0", STR_PAD_LEFT)),
120134
];
121135
}
122136

@@ -126,40 +140,29 @@ private static function createECKeyUsingOpenSSL(string $curve): array
126140
'curve_name' => self::getOpensslCurveName($curve),
127141
'private_key_type' => OPENSSL_KEYTYPE_EC,
128142
]);
129-
$res = openssl_pkey_export($key, $out);
130-
if (false === $res) {
143+
if (false === $key) {
144+
throw new RuntimeException('Unable to create the key');
145+
}
146+
$result = openssl_pkey_export($key, $out);
147+
if (false === $result) {
131148
throw new RuntimeException('Unable to create the key');
132149
}
133150
$res = openssl_pkey_get_private($out);
134-
151+
if (false === $res) {
152+
throw new RuntimeException('Unable to create the key');
153+
}
135154
$details = openssl_pkey_get_details($res);
136-
137-
$nistCurve = static::getCurve($curve);
138-
$componentSize = (int) ceil($nistCurve->getSize() / 8);
155+
$nistCurve = self::getNistCurve($curve);
139156

140157
return [
141158
'kty' => 'EC',
142159
'crv' => $curve,
143-
'x' => Base64Url::encode(str_pad($details['ec']['x'], $componentSize, "\0", STR_PAD_LEFT)),
144-
'y' => Base64Url::encode(str_pad($details['ec']['y'], $componentSize, "\0", STR_PAD_LEFT)),
145-
'd' => Base64Url::encode(str_pad($details['ec']['d'], $componentSize, "\0", STR_PAD_LEFT)),
160+
'd' => Base64Url::encode(str_pad($details['ec']['d'], (int) ceil($nistCurve->getSize() / 8), "\0", STR_PAD_LEFT)),
161+
'x' => Base64Url::encode(str_pad($details['ec']['x'], (int) ceil($nistCurve->getSize() / 8), "\0", STR_PAD_LEFT)),
162+
'y' => Base64Url::encode(str_pad($details['ec']['y'], (int) ceil($nistCurve->getSize() / 8), "\0", STR_PAD_LEFT)),
146163
];
147164
}
148165

149-
private static function getCurve(string $curve): Curve
150-
{
151-
switch ($curve) {
152-
case 'P-256':
153-
return NistCurve::curve256();
154-
case 'P-384':
155-
return NistCurve::curve384();
156-
case 'P-521':
157-
return NistCurve::curve521();
158-
default:
159-
throw new InvalidArgumentException(\sprintf('The curve "%s" is not supported.', $curve));
160-
}
161-
}
162-
163166
private static function getOpensslCurveName(string $curve): string
164167
{
165168
switch ($curve) {
@@ -170,114 +173,120 @@ private static function getOpensslCurveName(string $curve): string
170173
case 'P-521':
171174
return 'secp521r1';
172175
default:
173-
throw new InvalidArgumentException(\sprintf('The curve "%s" is not supported.', $curve));
176+
throw new InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve));
174177
}
175178
}
176179

177180
private static function p256PublicKey(): string
178181
{
179-
return \pack('H*',
182+
return pack(
183+
'H*',
180184
'3059' // SEQUENCE, length 89
181-
.'3013' // SEQUENCE, length 19
182-
.'0607' // OID, length 7
183-
.'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key
184-
.'0608' // OID, length 8
185-
.'2a8648ce3d030107' // 1.2.840.10045.3.1.7 = P-256 Curve
186-
.'0342' // BIT STRING, length 66
187-
.'00' // prepend with NUL - pubkey will follow
185+
.'3013' // SEQUENCE, length 19
186+
.'0607' // OID, length 7
187+
.'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key
188+
.'0608' // OID, length 8
189+
.'2a8648ce3d030107' // 1.2.840.10045.3.1.7 = P-256 Curve
190+
.'0342' // BIT STRING, length 66
191+
.'00' // prepend with NUL - pubkey will follow
188192
);
189193
}
190194

191195
private static function p384PublicKey(): string
192196
{
193-
return \pack('H*',
197+
return pack(
198+
'H*',
194199
'3076' // SEQUENCE, length 118
195-
.'3010' // SEQUENCE, length 16
196-
.'0607' // OID, length 7
197-
.'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key
198-
.'0605' // OID, length 5
199-
.'2b81040022' // 1.3.132.0.34 = P-384 Curve
200-
.'0362' // BIT STRING, length 98
201-
.'00' // prepend with NUL - pubkey will follow
200+
.'3010' // SEQUENCE, length 16
201+
.'0607' // OID, length 7
202+
.'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key
203+
.'0605' // OID, length 5
204+
.'2b81040022' // 1.3.132.0.34 = P-384 Curve
205+
.'0362' // BIT STRING, length 98
206+
.'00' // prepend with NUL - pubkey will follow
202207
);
203208
}
204209

205210
private static function p521PublicKey(): string
206211
{
207-
return \pack('H*',
212+
return pack(
213+
'H*',
208214
'30819b' // SEQUENCE, length 154
209-
.'3010' // SEQUENCE, length 16
210-
.'0607' // OID, length 7
211-
.'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key
212-
.'0605' // OID, length 5
213-
.'2b81040023' // 1.3.132.0.35 = P-521 Curve
214-
.'038186' // BIT STRING, length 134
215-
.'00' // prepend with NUL - pubkey will follow
215+
.'3010' // SEQUENCE, length 16
216+
.'0607' // OID, length 7
217+
.'2a8648ce3d0201' // 1.2.840.10045.2.1 = EC Public Key
218+
.'0605' // OID, length 5
219+
.'2b81040023' // 1.3.132.0.35 = P-521 Curve
220+
.'038186' // BIT STRING, length 134
221+
.'00' // prepend with NUL - pubkey will follow
216222
);
217223
}
218224

219225
private static function p256PrivateKey(JWK $jwk): string
220226
{
221-
$d = \unpack('H*', Base64Url::decode($jwk->get('d')))[1];
222-
$dl = \mb_strlen($d, '8bit') / 2;
223-
224-
return \pack('H*',
225-
'30'.\dechex(87 + $dl) // SEQUENCE, length 87+length($d)
226-
.'020101' // INTEGER, 1
227-
.'04'.\dechex($dl) // OCTET STRING, length($d)
228-
.$d
229-
.'a00a' // TAGGED OBJECT #0, length 10
230-
.'0608' // OID, length 8
231-
.'2a8648ce3d030107' // 1.3.132.0.34 = P-384 Curve
232-
.'a144' // TAGGED OBJECT #1, length 68
233-
.'0342' // BIT STRING, length 66
234-
.'00' // prepend with NUL - pubkey will follow
227+
$d = unpack('H*', str_pad(Base64Url::decode($jwk->get('d')), 32, "\0", STR_PAD_LEFT))[1];
228+
229+
return pack(
230+
'H*',
231+
'3077' // SEQUENCE, length 87+length($d)=32
232+
.'020101' // INTEGER, 1
233+
.'0420' // OCTET STRING, length($d) = 32
234+
.$d
235+
.'a00a' // TAGGED OBJECT #0, length 10
236+
.'0608' // OID, length 8
237+
.'2a8648ce3d030107' // 1.3.132.0.34 = P-384 Curve
238+
.'a144' // TAGGED OBJECT #1, length 68
239+
.'0342' // BIT STRING, length 66
240+
.'00' // prepend with NUL - pubkey will follow
235241
);
236242
}
237243

238244
private static function p384PrivateKey(JWK $jwk): string
239245
{
240-
$d = \unpack('H*', Base64Url::decode($jwk->get('d')))[1];
241-
$dl = \mb_strlen($d, '8bit') / 2;
242-
243-
return \pack('H*',
244-
'3081'.\dechex(116 + $dl) // SEQUENCE, length 116 + length($d)
245-
.'020101' // INTEGER, 1
246-
.'04'.\dechex($dl) // OCTET STRING, length($d)
247-
.$d
248-
.'a007' // TAGGED OBJECT #0, length 7
249-
.'0605' // OID, length 5
250-
.'2b81040022' // 1.3.132.0.34 = P-384 Curve
251-
.'a164' // TAGGED OBJECT #1, length 100
252-
.'0362' // BIT STRING, length 98
253-
.'00' // prepend with NUL - pubkey will follow
246+
$d = unpack('H*', str_pad(Base64Url::decode($jwk->get('d')), 48, "\0", STR_PAD_LEFT))[1];
247+
248+
return pack(
249+
'H*',
250+
'3081a4' // SEQUENCE, length 116 + length($d)=48
251+
.'020101' // INTEGER, 1
252+
.'0430' // OCTET STRING, length($d) = 30
253+
.$d
254+
.'a007' // TAGGED OBJECT #0, length 7
255+
.'0605' // OID, length 5
256+
.'2b81040022' // 1.3.132.0.34 = P-384 Curve
257+
.'a164' // TAGGED OBJECT #1, length 100
258+
.'0362' // BIT STRING, length 98
259+
.'00' // prepend with NUL - pubkey will follow
254260
);
255261
}
256262

257263
private static function p521PrivateKey(JWK $jwk): string
258264
{
259-
$d = \unpack('H*', Base64Url::decode($jwk->get('d')))[1];
260-
$dl = \mb_strlen($d, '8bit') / 2;
261-
262-
return \pack('H*',
263-
'3081'.\dechex(154 + $dl) // SEQUENCE, length 154+length(d)
264-
.'020101' // INTEGER, 1
265-
.'04'.\dechex($dl) // OCTET STRING, length(d)
266-
.$d
267-
.'a007' // TAGGED OBJECT #0, length 7
268-
.'0605' // OID, length 5
269-
.'2b81040023' // 1.3.132.0.35 = P-521 Curve
270-
.'a18189' // TAGGED OBJECT #1, length 137
271-
.'038186' // BIT STRING, length 134
272-
.'00' // prepend with NUL - pubkey will follow
265+
$d = unpack('H*', str_pad(Base64Url::decode($jwk->get('d')), 66, "\0", STR_PAD_LEFT))[1];
266+
267+
return pack(
268+
'H*',
269+
'3081dc' // SEQUENCE, length 154 + length($d)=66
270+
.'020101' // INTEGER, 1
271+
.'0442' // OCTET STRING, length(d) = 66
272+
.$d
273+
.'a007' // TAGGED OBJECT #0, length 7
274+
.'0605' // OID, length 5
275+
.'2b81040023' // 1.3.132.0.35 = P-521 Curve
276+
.'a18189' // TAGGED OBJECT #1, length 137
277+
.'038186' // BIT STRING, length 134
278+
.'00' // prepend with NUL - pubkey will follow
273279
);
274280
}
275281

276282
private static function getKey(JWK $jwk): string
277283
{
284+
$nistCurve = self::getNistCurve($jwk->get('crv'));
285+
$length = (int) ceil($nistCurve->getSize() / 8);
286+
278287
return
279-
\pack('H*', '04')
280-
.Base64Url::decode($jwk->get('x'))
281-
.Base64Url::decode($jwk->get('y'));
288+
"\04"
289+
.str_pad(Base64Url::decode($jwk->get('x')), $length, "\0", STR_PAD_LEFT)
290+
.str_pad(Base64Url::decode($jwk->get('y')), $length, "\0", STR_PAD_LEFT);
282291
}
283292
}

src/EncryptionAlgorithm/KeyEncryption/ECDHES/ECDHES.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,16 @@ public function calculateAgreementKey(JWK $private_key, JWK $public_key): string
9494
case 'P-384':
9595
case 'P-521':
9696
$curve = $this->getCurve($public_key->get('crv'));
97+
if (\function_exists('openssl_pkey_derive')) {
98+
try {
99+
$publicPem = ECKey::convertPublicKeyToPEM($public_key);
100+
$privatePem = ECKey::convertPrivateKeyToPEM($private_key);
101+
102+
return openssl_pkey_derive($publicPem, $privatePem, $curve->getSize());
103+
} catch (\Throwable $throwable) {
104+
//Does nothing. Will fallback to the pure PHP function
105+
}
106+
}
97107

98108
$rec_x = $this->convertBase64ToGmp($public_key->get('x'));
99109
$rec_y = $this->convertBase64ToGmp($public_key->get('y'));

0 commit comments

Comments
 (0)