Skip to content

Commit e7a6c02

Browse files
authored
Add support for Ed448/EdDSA. (jpadilla#675)
* Add support for Ed448/EdDSA. * Add test for verification using EdDSA private key.
1 parent 19ce9c5 commit e7a6c02

File tree

7 files changed

+198
-55
lines changed

7 files changed

+198
-55
lines changed

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Fixed
2323
Added
2424
~~~~~
2525

26+
- Add support for Ed448/EdDSA. `#675 <https://github.com/jpadilla/pyjwt/pull/675>`__
27+
2628
`v2.1.0 <https://github.com/jpadilla/pyjwt/compare/2.0.1...2.1.0>`__
2729
--------------------------------------------------------------------
2830

docs/algorithms.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ This library currently supports:
1717
* PS256 - RSASSA-PSS signature using SHA-256 and MGF1 padding with SHA-256
1818
* PS384 - RSASSA-PSS signature using SHA-384 and MGF1 padding with SHA-384
1919
* PS512 - RSASSA-PSS signature using SHA-512 and MGF1 padding with SHA-512
20-
* EdDSA - Ed25519 signature using SHA-512. Provides 128-bit security
20+
* EdDSA - Both Ed25519 signature using SHA-512 and Ed448 signature using SHA-3 are supported. Ed25519 and Ed448 provide 128-bit and 224-bit security respectively.
2121

2222
Asymmetric (Public-key) Algorithms
2323
----------------------------------

jwt/algorithms.py

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
EllipticCurvePrivateKey,
2323
EllipticCurvePublicKey,
2424
)
25+
from cryptography.hazmat.primitives.asymmetric.ed448 import (
26+
Ed448PrivateKey,
27+
Ed448PublicKey,
28+
)
2529
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
2630
Ed25519PrivateKey,
2731
Ed25519PublicKey,
@@ -93,7 +97,7 @@ def get_default_algorithms():
9397
"PS256": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256),
9498
"PS384": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384),
9599
"PS512": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA512),
96-
"EdDSA": Ed25519Algorithm(),
100+
"EdDSA": OKPAlgorithm(),
97101
}
98102
)
99103

@@ -534,9 +538,9 @@ def verify(self, msg, key, sig):
534538
except InvalidSignature:
535539
return False
536540

537-
class Ed25519Algorithm(Algorithm):
541+
class OKPAlgorithm(Algorithm):
538542
"""
539-
Performs signing and verification operations using Ed25519
543+
Performs signing and verification operations using EdDSA
540544
541545
This class requires ``cryptography>=2.6`` to be installed.
542546
"""
@@ -546,7 +550,10 @@ def __init__(self, **kwargs):
546550

547551
def prepare_key(self, key):
548552

549-
if isinstance(key, (Ed25519PrivateKey, Ed25519PublicKey)):
553+
if isinstance(
554+
key,
555+
(Ed25519PrivateKey, Ed25519PublicKey, Ed448PrivateKey, Ed448PublicKey),
556+
):
550557
return key
551558

552559
if isinstance(key, (bytes, str)):
@@ -565,28 +572,30 @@ def prepare_key(self, key):
565572

566573
def sign(self, msg, key):
567574
"""
568-
Sign a message ``msg`` using the Ed25519 private key ``key``
575+
Sign a message ``msg`` using the EdDSA private key ``key``
569576
:param str|bytes msg: Message to sign
570-
:param Ed25519PrivateKey key: A :class:`.Ed25519PrivateKey` instance
577+
:param Ed25519PrivateKey}Ed448PrivateKey key: A :class:`.Ed25519PrivateKey`
578+
or :class:`.Ed448PrivateKey` iinstance
571579
:return bytes signature: The signature, as bytes
572580
"""
573581
msg = bytes(msg, "utf-8") if type(msg) is not bytes else msg
574582
return key.sign(msg)
575583

576584
def verify(self, msg, key, sig):
577585
"""
578-
Verify a given ``msg`` against a signature ``sig`` using the Ed25519 key ``key``
586+
Verify a given ``msg`` against a signature ``sig`` using the EdDSA key ``key``
579587
580-
:param str|bytes sig: Ed25519 signature to check ``msg`` against
588+
:param str|bytes sig: EdDSA signature to check ``msg`` against
581589
:param str|bytes msg: Message to sign
582-
:param Ed25519PrivateKey|Ed25519PublicKey key: A private or public Ed25519 key instance
590+
:param Ed25519PrivateKey|Ed25519PublicKey|Ed448PrivateKey|Ed448PublicKey key:
591+
A private or public EdDSA key instance
583592
:return bool verified: True if signature is valid, False if not.
584593
"""
585594
try:
586595
msg = bytes(msg, "utf-8") if type(msg) is not bytes else msg
587596
sig = bytes(sig, "utf-8") if type(sig) is not bytes else sig
588597

589-
if isinstance(key, Ed25519PrivateKey):
598+
if isinstance(key, (Ed25519PrivateKey, Ed448PrivateKey)):
590599
key = key.public_key()
591600
key.verify(sig, msg)
592601
return True # If no exception was raised, the signature is valid.
@@ -595,21 +604,21 @@ def verify(self, msg, key, sig):
595604

596605
@staticmethod
597606
def to_jwk(key):
598-
if isinstance(key, Ed25519PublicKey):
607+
if isinstance(key, (Ed25519PublicKey, Ed448PublicKey)):
599608
x = key.public_bytes(
600609
encoding=Encoding.Raw,
601610
format=PublicFormat.Raw,
602611
)
603-
612+
crv = "Ed25519" if isinstance(key, Ed25519PublicKey) else "Ed448"
604613
return json.dumps(
605614
{
606615
"x": base64url_encode(force_bytes(x)).decode(),
607616
"kty": "OKP",
608-
"crv": "Ed25519",
617+
"crv": crv,
609618
}
610619
)
611620

612-
if isinstance(key, Ed25519PrivateKey):
621+
if isinstance(key, (Ed25519PrivateKey, Ed448PrivateKey)):
613622
d = key.private_bytes(
614623
encoding=Encoding.Raw,
615624
format=PrivateFormat.Raw,
@@ -621,12 +630,13 @@ def to_jwk(key):
621630
format=PublicFormat.Raw,
622631
)
623632

633+
crv = "Ed25519" if isinstance(key, Ed25519PrivateKey) else "Ed448"
624634
return json.dumps(
625635
{
626636
"x": base64url_encode(force_bytes(x)).decode(),
627637
"d": base64url_encode(force_bytes(d)).decode(),
628638
"kty": "OKP",
629-
"crv": "Ed25519",
639+
"crv": crv,
630640
}
631641
)
632642

@@ -648,7 +658,7 @@ def from_jwk(jwk):
648658
raise InvalidKeyError("Not an Octet Key Pair")
649659

650660
curve = obj.get("crv")
651-
if curve != "Ed25519":
661+
if curve != "Ed25519" and curve != "Ed448":
652662
raise InvalidKeyError(f"Invalid curve: {curve}")
653663

654664
if "x" not in obj:
@@ -657,8 +667,12 @@ def from_jwk(jwk):
657667

658668
try:
659669
if "d" not in obj:
660-
return Ed25519PublicKey.from_public_bytes(x)
670+
if curve == "Ed25519":
671+
return Ed25519PublicKey.from_public_bytes(x)
672+
return Ed448PublicKey.from_public_bytes(x)
661673
d = base64url_decode(obj.get("d"))
662-
return Ed25519PrivateKey.from_private_bytes(d)
674+
if curve == "Ed25519":
675+
return Ed25519PrivateKey.from_private_bytes(d)
676+
return Ed448PrivateKey.from_private_bytes(d)
663677
except ValueError as err:
664678
raise InvalidKeyError("Invalid key parameter") from err

tests/keys/jwk_okp_key_Ed448.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"kty": "OKP",
3+
"kid": "sig_ed448_01",
4+
"crv": "Ed448",
5+
"use": "sig",
6+
"x": "kvqP7TzMosCQCpNcW8qY2HmVmpPYUEIGn-sQWQgoWlAZbWpnXpXqAT6yMoYA08pkJm7P_HKZoHwA",
7+
"d": "Zh5xx0r_0tq39xj-8jGuCwAA6wsDim2ME7cX_iXzqDRgPN8lsZZHu60AO7m31Fa4NtHO07eU63q8",
8+
"alg": "EdDSA"
9+
}

tests/keys/jwk_okp_pub_Ed448.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"kty": "OKP",
3+
"kid": "sig_ed448_01",
4+
"crv": "Ed448",
5+
"use": "sig",
6+
"x": "kvqP7TzMosCQCpNcW8qY2HmVmpPYUEIGn-sQWQgoWlAZbWpnXpXqAT6yMoYA08pkJm7P_HKZoHwA",
7+
"alg": "EdDSA"
8+
}

0 commit comments

Comments
 (0)