Skip to content

Commit 109b5f0

Browse files
authored
Key factory (#106)
EC key creation is now faster when OpenSSL supports EC keys.
1 parent f995b68 commit 109b5f0

File tree

4 files changed

+246
-39
lines changed

4 files changed

+246
-39
lines changed

performance/KeyFactory/KeyFactory.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* The MIT License (MIT)
7+
*
8+
* Copyright (c) 2014-2018 Spomky-Labs
9+
*
10+
* This software may be modified and distributed under the terms
11+
* of the MIT license. See the LICENSE file for details.
12+
*/
13+
14+
namespace Jose\Performance\KeyFactory;
15+
16+
use Base64Url\Base64Url;
17+
use Jose\Component\Core\JWK;
18+
use Jose\Component\Core\Util\Ecc\NistCurve;
19+
20+
/**
21+
* @Revs(1000)
22+
* @Groups({"KeyFactory"})
23+
*/
24+
final class KeyFactory
25+
{
26+
/**
27+
* @Subject()
28+
*/
29+
public function usingThePurePhpMethod()
30+
{
31+
$curve = NistCurve::curve256();
32+
$privateKey = $curve->createPrivateKey();
33+
$publicKey = $curve->createPublicKey($privateKey);
34+
35+
JWK::create([
36+
'kty' => 'EC',
37+
'crv' => $curve,
38+
'd' => Base64Url::encode(gmp_export($privateKey->getSecret())),
39+
'x' => Base64Url::encode(gmp_export($publicKey->getPoint()->getX())),
40+
'y' => Base64Url::encode(gmp_export($publicKey->getPoint()->getY())),
41+
]);
42+
}
43+
44+
/**
45+
* @Subject()
46+
*/
47+
public function usingOpenSSL()
48+
{
49+
$key = openssl_pkey_new([
50+
'curve_name' => 'prime256v1',
51+
'private_key_type' => OPENSSL_KEYTYPE_EC,
52+
]);
53+
$res = openssl_pkey_export($key, $out);
54+
if (false === $res) {
55+
throw new \RuntimeException('Unable to create the key');
56+
}
57+
$res = openssl_pkey_get_private($out);
58+
59+
$details = openssl_pkey_get_details($res);
60+
61+
JWK::create([
62+
'kty' => 'EC',
63+
'crv' => 'P-256',
64+
'x' => Base64Url::encode($details['ec']['x']),
65+
'y' => Base64Url::encode($details['ec']['y']),
66+
'd' => Base64Url::encode($details['ec']['d']),
67+
]);
68+
}
69+
}

src/Component/Core/Util/Ecc/NistCurve.php

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ class NistCurve
4949
*/
5050
public static function curve256(): Curve
5151
{
52-
$p = gmp_init('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff', 16);
53-
$a = gmp_init('0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc', 16);
54-
$b = gmp_init('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b', 16);
55-
$x = gmp_init('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296', 16);
56-
$y = gmp_init('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5', 16);
57-
$n = gmp_init('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551', 16);
52+
$p = gmp_init('ffffffff00000001000000000000000000000000ffffffffffffffffffffffff', 16);
53+
$a = gmp_init('ffffffff00000001000000000000000000000000fffffffffffffffffffffffc', 16);
54+
$b = gmp_init('5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b', 16);
55+
$x = gmp_init('6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296', 16);
56+
$y = gmp_init('4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5', 16);
57+
$n = gmp_init('ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551', 16);
5858
$generator = Point::create($x, $y, $n);
5959

6060
return new Curve(256, $p, $a, $b, $generator);
@@ -67,12 +67,12 @@ public static function curve256(): Curve
6767
*/
6868
public static function curve384(): Curve
6969
{
70-
$p = gmp_init('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff', 16);
71-
$a = gmp_init('0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc', 16);
72-
$b = gmp_init('0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef', 16);
73-
$x = gmp_init('0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7', 16);
74-
$y = gmp_init('0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f', 16);
75-
$n = gmp_init('0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973', 16);
70+
$p = gmp_init('fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff', 16);
71+
$a = gmp_init('fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc', 16);
72+
$b = gmp_init('b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef', 16);
73+
$x = gmp_init('aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7', 16);
74+
$y = gmp_init('3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f', 16);
75+
$n = gmp_init('ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973', 16);
7676
$generator = Point::create($x, $y, $n);
7777

7878
return new Curve(384, $p, $a, $b, $generator);
@@ -85,12 +85,12 @@ public static function curve384(): Curve
8585
*/
8686
public static function curve521(): Curve
8787
{
88-
$p = gmp_init('0x000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16);
89-
$a = gmp_init('0x000001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc', 16);
90-
$b = gmp_init('0x00000051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00', 16);
91-
$x = gmp_init('0x000000c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66', 16);
92-
$y = gmp_init('0x0000011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650', 16);
93-
$n = gmp_init('0x000001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409', 16);
88+
$p = gmp_init('000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16);
89+
$a = gmp_init('000001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc', 16);
90+
$b = gmp_init('00000051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00', 16);
91+
$x = gmp_init('000000c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66', 16);
92+
$y = gmp_init('0000011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650', 16);
93+
$n = gmp_init('000001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409', 16);
9494
$generator = Point::create($x, $y, $n);
9595

9696
return new Curve(521, $p, $a, $b, $generator);

src/Component/Encryption/Algorithm/KeyEncryption/ECDHES.php

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -264,17 +264,13 @@ private function convertDecToBin(\GMP $dec): string
264264
*/
265265
public function createECKey(string $crv): JWK
266266
{
267-
$curve = $this->getCurve($crv);
268-
$privateKey = $curve->createPrivateKey();
269-
$point = $curve->createPublicKey($privateKey)->getPoint();
267+
try {
268+
$jwk = self::createECKeyUsingOpenSSL($crv);
269+
} catch (\Exception $e) {
270+
$jwk = self::createECKeyUsingPurePhp($crv);
271+
}
270272

271-
return JWK::create([
272-
'kty' => 'EC',
273-
'crv' => $crv,
274-
'x' => Base64Url::encode($this->convertDecToBin($point->getX())),
275-
'y' => Base64Url::encode($this->convertDecToBin($point->getY())),
276-
'd' => Base64Url::encode($this->convertDecToBin($privateKey->getSecret())),
277-
]);
273+
return JWK::create($jwk);
278274
}
279275

280276
/**
@@ -308,4 +304,87 @@ public static function createOKPKey(string $curve): JWK
308304
'd' => Base64Url::encode($d),
309305
]);
310306
}
307+
308+
/**
309+
* @param string $curve
310+
*
311+
* @return array
312+
*/
313+
private static function createECKeyUsingPurePhp(string $curve): array
314+
{
315+
switch ($curve) {
316+
case 'P-256':
317+
$nistCurve = NistCurve::curve256();
318+
319+
break;
320+
case 'P-384':
321+
$nistCurve = NistCurve::curve384();
322+
323+
break;
324+
case 'P-521':
325+
$nistCurve = NistCurve::curve521();
326+
327+
break;
328+
default:
329+
throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve));
330+
}
331+
332+
$privateKey = $nistCurve->createPrivateKey();
333+
$publicKey = $nistCurve->createPublicKey($privateKey);
334+
335+
return [
336+
'kty' => 'EC',
337+
'crv' => $curve,
338+
'd' => Base64Url::encode(gmp_export($privateKey->getSecret())),
339+
'x' => Base64Url::encode(gmp_export($publicKey->getPoint()->getX())),
340+
'y' => Base64Url::encode(gmp_export($publicKey->getPoint()->getY())),
341+
];
342+
}
343+
344+
/**
345+
* @param string $curve
346+
*
347+
* @return array
348+
*/
349+
private static function createECKeyUsingOpenSSL(string $curve): array
350+
{
351+
$key = openssl_pkey_new([
352+
'curve_name' => self::getOpensslCurveName($curve),
353+
'private_key_type' => OPENSSL_KEYTYPE_EC,
354+
]);
355+
$res = openssl_pkey_export($key, $out);
356+
if (false === $res) {
357+
throw new \RuntimeException('Unable to create the key');
358+
}
359+
$res = openssl_pkey_get_private($out);
360+
361+
$details = openssl_pkey_get_details($res);
362+
363+
return [
364+
'kty' => 'EC',
365+
'crv' => $curve,
366+
'd' => Base64Url::encode($details['ec']['d']),
367+
'x' => Base64Url::encode($details['ec']['x']),
368+
'y' => Base64Url::encode($details['ec']['y']),
369+
];
370+
}
371+
372+
/**
373+
* @param string $curve
374+
*
375+
* @return string
376+
*/
377+
private static function getOpensslCurveName(string $curve): string
378+
{
379+
switch ($curve) {
380+
case 'P-256':
381+
return 'prime256v1';
382+
case 'P-384':
383+
return 'secp384r1';
384+
case 'P-521':
385+
return 'secp521r1';
386+
default:
387+
throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve));
388+
}
389+
}
311390
}

src/Component/KeyManagement/JWKFactory.php

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,23 @@ public static function createRSAKey(int $size, array $values = []): JWK
6363
* @return JWK
6464
*/
6565
public static function createECKey(string $curve, array $values = []): JWK
66+
{
67+
try {
68+
$jwk = self::createECKeyUsingOpenSSL($curve);
69+
} catch (\Exception $e) {
70+
$jwk = self::createECKeyUsingPurePhp($curve);
71+
}
72+
$values = array_merge($values, $jwk);
73+
74+
return JWK::create($values);
75+
}
76+
77+
/**
78+
* @param string $curve
79+
*
80+
* @return array
81+
*/
82+
private static function createECKeyUsingPurePhp(string $curve): array
6683
{
6784
switch ($curve) {
6885
case 'P-256':
@@ -84,18 +101,60 @@ public static function createECKey(string $curve, array $values = []): JWK
84101
$privateKey = $nistCurve->createPrivateKey();
85102
$publicKey = $nistCurve->createPublicKey($privateKey);
86103

87-
$values = array_merge(
88-
$values,
89-
[
90-
'kty' => 'EC',
91-
'crv' => $curve,
92-
'd' => Base64Url::encode(gmp_export($privateKey->getSecret())),
93-
'x' => Base64Url::encode(gmp_export($publicKey->getPoint()->getX())),
94-
'y' => Base64Url::encode(gmp_export($publicKey->getPoint()->getY())),
95-
]
96-
);
104+
return [
105+
'kty' => 'EC',
106+
'crv' => $curve,
107+
'd' => Base64Url::encode(gmp_export($privateKey->getSecret())),
108+
'x' => Base64Url::encode(gmp_export($publicKey->getPoint()->getX())),
109+
'y' => Base64Url::encode(gmp_export($publicKey->getPoint()->getY())),
110+
];
111+
}
97112

98-
return JWK::create($values);
113+
/**
114+
* @param string $curve
115+
*
116+
* @return array
117+
*/
118+
private static function createECKeyUsingOpenSSL(string $curve): array
119+
{
120+
$key = openssl_pkey_new([
121+
'curve_name' => self::getOpensslCurveName($curve),
122+
'private_key_type' => OPENSSL_KEYTYPE_EC,
123+
]);
124+
$res = openssl_pkey_export($key, $out);
125+
if (false === $res) {
126+
throw new \RuntimeException('Unable to create the key');
127+
}
128+
$res = openssl_pkey_get_private($out);
129+
130+
$details = openssl_pkey_get_details($res);
131+
132+
return [
133+
'kty' => 'EC',
134+
'crv' => $curve,
135+
'x' => Base64Url::encode(bin2hex($details['ec']['x'])),
136+
'y' => Base64Url::encode(bin2hex($details['ec']['y'])),
137+
'd' => Base64Url::encode(bin2hex($details['ec']['d'])),
138+
];
139+
}
140+
141+
/**
142+
* @param string $curve
143+
*
144+
* @return string
145+
*/
146+
private static function getOpensslCurveName(string $curve): string
147+
{
148+
switch ($curve) {
149+
case 'P-256':
150+
return 'prime256v1';
151+
case 'P-384':
152+
return 'secp384r1';
153+
case 'P-521':
154+
return 'secp521r1';
155+
default:
156+
throw new \InvalidArgumentException(sprintf('The curve "%s" is not supported.', $curve));
157+
}
99158
}
100159

101160
/**

0 commit comments

Comments
 (0)