PHPackages                             studio-design/ecdsa-signature - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. studio-design/ecdsa-signature

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

studio-design/ecdsa-signature
=============================

ECDSA signature value object with DER/JWS raw format conversion and curve-order validation. For Cloud KMS/HSM-based JWT signing where OpenSSL/KMS returns DER but JWT (RFC 7518) requires raw format.

v1.0.0(2mo ago)0542MITPHPPHP ^8.2CI passing

Since Apr 9Pushed 2mo agoCompare

[ Source](https://github.com/studio-design/ecdsa-signature)[ Packagist](https://packagist.org/packages/studio-design/ecdsa-signature)[ RSS](/packages/studio-design-ecdsa-signature/feed)WikiDiscussions main Synced 2w ago

READMEChangelog (1)Dependencies (3)Versions (3)Used By (0)

ECDSA Signature
===============

[](#ecdsa-signature)

[![License: MIT](https://camo.githubusercontent.com/08cef40a9105b6526ca22088bc514fbfdbc9aac1ddbf8d4e6c750e3a88a44dca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d626c75652e737667)](LICENSE)

A lightweight PHP value object for ECDSA signatures with DER/JWS raw format conversion and mathematical validation.

Why?
----

[](#why)

OpenSSL and Cloud KMS (Google Cloud KMS, AWS KMS, Azure Key Vault) return ECDSA signatures in ASN.1 DER format. However, JWT/JWS ([RFC 7518 Section 3.4](https://www.rfc-editor.org/rfc/rfc7518#section-3.4)) requires raw concatenated `R || S` format with fixed-length components.

Major PHP JWT libraries (`firebase/php-jwt`, `lcobucci/jwt`, `web-token/jwt-library`) all handle this conversion internally but expose it only as **private** or **@internal** methods. If you're signing JWTs via Cloud KMS or an HSM — where the private key never leaves the remote service — you need this conversion as a standalone utility.

This library provides an immutable value object that guarantees both format correctness and mathematical validity (`0 < r, s < n`).

Non-Goals
---------

[](#non-goals)

This library **does not**:

- Verify ECDSA signatures (use `openssl_verify()` or your KMS provider for that)
- Generate or manage keys
- Build or parse JWTs
- Support non-NIST curves (e.g. secp256k1, Ed25519)

It is a **signature format converter + validated value object**, not a cryptography library.

Installation
------------

[](#installation)

```
composer require studio-design/ecdsa-signature
```

Requirements
------------

[](#requirements)

- PHP 8.2+

No extensions required. No external dependencies.

Quick Start
-----------

[](#quick-start)

### DER to Raw (for JWT signing)

[](#der-to-raw-for-jwt-signing)

Convert a DER-encoded ECDSA signature (from OpenSSL or Cloud KMS) to JWS raw format:

```
use StudioDesign\EcdsaSignature\Curve;
use StudioDesign\EcdsaSignature\EcdsaSignature;

// Sign with OpenSSL (returns DER format)
openssl_sign($payload, $derSignature, $privateKey, OPENSSL_ALGO_SHA256);

// Parse DER and convert to JWS raw format (R||S)
$sig = EcdsaSignature::fromDer($derSignature, Curve::P256);
$rawSignature = $sig->toRaw();
// $rawSignature is now 64 bytes (32-byte R + 32-byte S)
```

### Raw to DER (for signature verification)

[](#raw-to-der-for-signature-verification)

Convert a JWS raw signature back to DER format for OpenSSL verification:

```
$sig = EcdsaSignature::fromRaw($rawSignature, Curve::P256);
$derSignature = $sig->toDer();

// Verify with OpenSSL (expects DER format)
$result = openssl_verify($payload, $derSignature, $publicKey, OPENSSL_ALGO_SHA256);
```

Choosing a Curve
----------------

[](#choosing-a-curve)

Multiple ways to select a curve, depending on your context:

```
use StudioDesign\EcdsaSignature\Curve;

// Direct enum usage
$curve = Curve::P256;

// From a JOSE algorithm name (JWT header "alg" field)
$curve = Curve::fromJoseAlg('ES256');   // → Curve::P256
$curve = Curve::fromJoseAlg('ES384');   // → Curve::P384
$curve = Curve::fromJoseAlg('ES512');   // → Curve::P521

// From an OpenSSL curve name
$curve = Curve::fromOpenSslCurveName('prime256v1');  // → Curve::P256
$curve = Curve::fromOpenSslCurveName('secp384r1');   // → Curve::P384
$curve = Curve::fromOpenSslCurveName('secp521r1');   // → Curve::P521

// From the JOSE key-size integer (256, 384, 512)
// Note: Curve::from() is PHP's built-in enum method and throws ValueError,
// not EcdsaSignatureException. Prefer fromJoseAlg() or fromOpenSslCurveName()
// for consistent error handling.
$curve = Curve::from(256);  // → Curve::P256

// Reverse lookups
$curve->joseAlg();          // "ES256"
$curve->openSslCurveName(); // "prime256v1"
```

Usage Examples
--------------

[](#usage-examples)

### JWT with Cloud KMS

[](#jwt-with-cloud-kms)

```
use StudioDesign\EcdsaSignature\Curve;
use StudioDesign\EcdsaSignature\EcdsaSignature;

// 1. Determine curve from JWT algorithm
$alg = 'ES256';
$curve = Curve::fromJoseAlg($alg);

// 2. Build JWT header and payload
$header  = base64url_encode(json_encode(['alg' => $alg, 'typ' => 'JWT', 'kid' => $kid]));
$payload = base64url_encode(json_encode($claims));
$signingInput = "{$header}.{$payload}";

// 3. Send digest to Cloud KMS for signing (returns DER)
$digest = hash('sha256', $signingInput, binary: true);
$derSignature = $kmsClient->asymmetricSign($keyName, $digest);

// 4. Convert DER signature to JWS raw format
$sig = EcdsaSignature::fromDer($derSignature, $curve);

// 5. Assemble JWT
$jwt = "{$signingInput}." . base64url_encode($sig->toRaw());
```

### OpenSSL Sign and Verify

[](#openssl-sign-and-verify)

```
use StudioDesign\EcdsaSignature\Curve;
use StudioDesign\EcdsaSignature\EcdsaSignature;

$curve = Curve::fromOpenSslCurveName('prime256v1');

// Sign (OpenSSL produces DER)
$key = openssl_pkey_new([
    'ec' => ['curve_name' => $curve->openSslCurveName()],
    'private_key_type' => OPENSSL_KEYTYPE_EC,
]);
openssl_sign($payload, $der, $key, OPENSSL_ALGO_SHA256);

// Convert to raw for JWT
$raw = EcdsaSignature::fromDer($der, $curve)->toRaw();

// Later: convert raw back to DER for verification
$derAgain = EcdsaSignature::fromRaw($raw, $curve)->toDer();
openssl_verify($payload, $derAgain, $publicKey, OPENSSL_ALGO_SHA256);
```

### Accessing Components

[](#accessing-components)

```
$sig = EcdsaSignature::fromRaw($rawSignature, Curve::P256);

$sig->r();      // R component (32 bytes, fixed-length big-endian binary)
$sig->s();      // S component (32 bytes, fixed-length big-endian binary)
$sig->curve();  // Curve::P256
```

Error Handling
--------------

[](#error-handling)

The library provides a structured exception hierarchy. All exceptions extend both `EcdsaSignatureException` and PHP's built-in `InvalidArgumentException`, so existing `catch (InvalidArgumentException)` blocks continue to work.

```
use StudioDesign\EcdsaSignature\Exception\EcdsaSignatureException;
use StudioDesign\EcdsaSignature\Exception\InvalidDerSignature;
use StudioDesign\EcdsaSignature\Exception\InvalidRawSignature;
use StudioDesign\EcdsaSignature\Exception\InvalidSignatureComponent;
use StudioDesign\EcdsaSignature\Exception\UnsupportedCurve;

try {
    $curve = Curve::fromJoseAlg($alg);
    $sig = EcdsaSignature::fromDer($input, $curve);
} catch (UnsupportedCurve $e) {
    // Algorithm name or curve name is not supported
} catch (InvalidDerSignature $e) {
    // DER structure is malformed (bad tag, truncated, non-minimal encoding, etc.)
} catch (InvalidSignatureComponent $e) {
    // R or S is mathematically out of range (zero, >= curve order, oversized)
}

try {
    $sig = EcdsaSignature::fromRaw($input, $curve);
} catch (InvalidRawSignature $e) {
    // Wrong byte length for the given curve
} catch (InvalidSignatureComponent $e) {
    // R or S is mathematically out of range
}

// Or catch everything from this library at once:
try {
    $curve = Curve::fromJoseAlg($alg);
    $sig = EcdsaSignature::fromDer($input, $curve);
} catch (EcdsaSignatureException $e) {
    // Any error from this library — curve resolution, DER parsing, or range validation
}
```

ExceptionThrown byMeaning`UnsupportedCurve``Curve::fromJoseAlg()`, `Curve::fromOpenSslCurveName()`Curve identifier is not supported`InvalidDerSignature``EcdsaSignature::fromDer()`DER structure is malformed`InvalidRawSignature``EcdsaSignature::fromRaw()`Raw signature has wrong byte length`InvalidSignatureComponent``EcdsaSignature::fromDer()`, `EcdsaSignature::fromRaw()`R or S fails `0 < value < n` check`EcdsaSignatureException`(base class)Any of the aboveSupported Curves
----------------

[](#supported-curves)

JOSE AlgorithmCurve EnumOpenSSL NameCurveRaw Signature LengthES256`Curve::P256``prime256v1`P-25664 bytesES384`Curve::P384``secp384r1`P-38496 bytesES512`Curve::P521``secp521r1`P-521132 bytesValidation
----------

[](#validation)

Both `fromDer()` and `fromRaw()` validate that signature components satisfy `0 < r, s < n` (where `n` is the curve order). Signatures with zero-valued or out-of-range components are rejected.

`fromDer()` additionally enforces strict DER encoding rules per X.690: minimal integer encoding, proper tag/length structure, no trailing data.

This ensures that every `EcdsaSignature` instance represents a mathematically plausible ECDSA signature.

API Reference
-------------

[](#api-reference)

### `EcdsaSignature::fromDer(string $der, Curve $curve): self`

[](#ecdsasignaturefromderstring-der-curve-curve-self)

Parse a DER-encoded ECDSA signature.

- **Throws** `InvalidDerSignature` if the DER data is structurally malformed
- **Throws** `InvalidSignatureComponent` if R or S is out of range

### `EcdsaSignature::fromRaw(string $raw, Curve $curve): self`

[](#ecdsasignaturefromrawstring-raw-curve-curve-self)

Parse a JWS raw (R||S) ECDSA signature.

- **Throws** `InvalidRawSignature` if the byte length is wrong for the given curve
- **Throws** `InvalidSignatureComponent` if R or S is out of range

### `EcdsaSignature::toDer(): string`

[](#ecdsasignaturetoder-string)

Encode this signature as ASN.1 DER.

### `EcdsaSignature::toRaw(): string`

[](#ecdsasignaturetoraw-string)

Encode this signature as JWS raw (R||S) format.

### `EcdsaSignature::r(): string` / `EcdsaSignature::s(): string`

[](#ecdsasignaturer-string--ecdsasignatures-string)

Fixed-length R/S components as big-endian binary strings.

### `EcdsaSignature::curve(): Curve`

[](#ecdsasignaturecurve-curve)

The elliptic curve this signature belongs to.

### `Curve` enum

[](#curve-enum)

```
Curve::P256  // ES256, backing value 256
Curve::P384  // ES384, backing value 384
Curve::P521  // ES512, backing value 512

// Factory methods (recommended)
Curve::fromJoseAlg('ES256');                // From JOSE algorithm name
Curve::fromOpenSslCurveName('prime256v1');  // From OpenSSL curve name
Curve::from(256);                           // PHP built-in enum factory (throws ValueError, not EcdsaSignatureException)

// Properties
$curve->componentLength();   // Per-component byte length (32, 48, 66)
$curve->order();             // Curve order as fixed-length binary string
$curve->joseAlg();           // JOSE algorithm name ("ES256", "ES384", "ES512")
$curve->openSslCurveName();  // OpenSSL curve name ("prime256v1", "secp384r1", "secp521r1")
```

Background
----------

[](#background)

ECDSA produces two integer values (r, s). These can be encoded in two ways:

- **ASN.1 DER** — Variable-length encoding: `SEQUENCE { INTEGER r, INTEGER s }`. This is what OpenSSL and Cloud KMS return.
- **JWS Raw (IEEE P1363)** — Fixed-length concatenation: `R || S`, each padded to the key size. This is what JWT/JWS requires per [RFC 7518](https://www.rfc-editor.org/rfc/rfc7518#section-3.4).

The conversion handles:

- Stripping/adding DER sign-padding bytes (leading `0x00` for positive integers with high bit set)
- Padding/trimming R and S to fixed-length components
- Validating DER structure integrity (SEQUENCE tag, length fields, trailing data detection)
- Validating mathematical range (`0 < r, s < n`)

License
-------

[](#license)

MIT License. See [LICENSE](LICENSE) for details.

###  Health Score

42

—

FairBetter than 89% of packages

Maintenance85

Actively maintained with recent releases

Popularity18

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

79d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/6fc976536b6b21ad39ca38269c6c59eceabc5fd57d6627e6742279cff53c387a?d=identicon)[wadakatu](/maintainers/wadakatu)

---

Top Contributors

[![wadakatu](https://avatars.githubusercontent.com/u/72595463?v=4)](https://github.com/wadakatu "wadakatu (26 commits)")

---

Tags

jwtvalidationsignatureJWSRFC7518DERkmsECDSAhsm

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/studio-design-ecdsa-signature/health.svg)

```
[![Health](https://phpackages.com/badges/studio-design-ecdsa-signature/health.svg)](https://phpackages.com/packages/studio-design-ecdsa-signature)
```

###  Alternatives

[web-token/jwt-framework

JSON Object Signing and Encryption library for PHP and Symfony Bundle.

95220.1M98](/packages/web-token-jwt-framework)[web-token/jwt-library

JWT library

2013.9M116](/packages/web-token-jwt-library)[web-token/jwt-bundle

JWT Bundle of the JWT Framework.

132.6M8](/packages/web-token-jwt-bundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
