From 1494ff97e0e1817574ed320f9b80217cc2d7056f Mon Sep 17 00:00:00 2001 From: tim-kos Date: Mon, 27 Apr 2026 13:01:13 +0200 Subject: [PATCH 1/2] Default PHP SDK request signatures to sha384 --- CHANGELOG.md | 4 ++++ README.md | 12 ++++++++++++ lib/transloadit/Transloadit.php | 2 ++ lib/transloadit/TransloaditRequest.php | 15 +++++++++++++-- test/simple/TransloaditRequestTest.php | 20 +++++++++++++++----- test/simple/TransloaditTest.php | 3 +++ 6 files changed, 49 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f60b153..ed20224 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ diff: https://github.com/transloadit/php-sdk/compare/3.3.0...main +- Default Assembly signatures to `sha384` and include the algorithm prefix (`sha384:...`) +- Add request/client-level `signatureAlgorithm` configuration for legacy `sha1` compatibility +- Extend tests to cover default `sha384` signing, legacy override, and Node CLI parity + ## [3.3.0](https://github.com/transloadit/php-sdk/tree/3.3.0) - Replace the custom Node parity helper with the official `transloadit` CLI for Smart CDN signatures diff --git a/README.md b/README.md index fda87a7..2bebeca 100644 --- a/README.md +++ b/README.md @@ -358,6 +358,12 @@ The auth key of your Transloadit account. The auth secret of your Transloadit account. +#### $Transloadit->signatureAlgorithm = 'sha384'; + +Controls which HMAC algorithm is used for Assembly request signing. +The default is `'sha384'` (recommended). If you need compatibility with a legacy key, +you can set this to `'sha1'`. + #### $Transloadit->request($options = [], $execute = true); Creates a new `TransloaditRequest` using the `$Transloadit->key` and @@ -444,6 +450,12 @@ The auth key of your Transloadit account. The auth secret of your Transloadit account. +#### $TransloaditRequest->signatureAlgorithm = 'sha384'; + +Controls which HMAC algorithm is used when preparing the Assembly request signature. +The generated `signature` field uses the `:` format, for example: +`sha384:...`. + #### $TransloaditRequest->method = 'GET'; Inherited from `CurlRequest`. Can be used to set the type of request to be diff --git a/lib/transloadit/Transloadit.php b/lib/transloadit/Transloadit.php index 41675b1..d4e65f8 100644 --- a/lib/transloadit/Transloadit.php +++ b/lib/transloadit/Transloadit.php @@ -5,6 +5,7 @@ class Transloadit { public $key = null; public $secret = null; + public $signatureAlgorithm = 'sha384'; public $endpoint = 'https://api2.transloadit.com'; public function __construct($attributes = []) { @@ -17,6 +18,7 @@ public function request($options = [], $execute = true) { $options = $options + [ 'key' => $this->key, 'secret' => $this->secret, + 'signatureAlgorithm' => $this->signatureAlgorithm, 'endpoint' => $this->endpoint, 'waitForCompletion' => false, ]; diff --git a/lib/transloadit/TransloaditRequest.php b/lib/transloadit/TransloaditRequest.php index ec6c7e5..b00d177 100644 --- a/lib/transloadit/TransloaditRequest.php +++ b/lib/transloadit/TransloaditRequest.php @@ -14,6 +14,7 @@ class TransloaditRequest extends CurlRequest { public $params = []; public $expires = '+2 hours'; + public $signatureAlgorithm = 'sha384'; public $headers = [ 'Expect:', @@ -42,12 +43,22 @@ public function getParamsString() { return json_encode($params); } - public function signString($string) { + public function signString($string, $algorithm = null) { if (empty($this->secret)) { return null; } - return hash_hmac('sha1', $string, $this->secret); + $effectiveAlgorithm = strtolower((string) ($algorithm ?? $this->signatureAlgorithm ?? 'sha384')); + if ($effectiveAlgorithm === '') { + $effectiveAlgorithm = 'sha384'; + } + + $signature = hash_hmac($effectiveAlgorithm, $string, $this->secret); + if ($signature === false) { + throw new \InvalidArgumentException('Unsupported signature algorithm: ' . $effectiveAlgorithm); + } + + return $effectiveAlgorithm . ':' . $signature; } public function prepare() { diff --git a/test/simple/TransloaditRequestTest.php b/test/simple/TransloaditRequestTest.php index 42ac32f..05223ab 100644 --- a/test/simple/TransloaditRequestTest.php +++ b/test/simple/TransloaditRequestTest.php @@ -23,6 +23,7 @@ public function testAttributes() { $this->assertEquals($this->request->secret, null); $this->assertEquals($this->request->params, []); $this->assertEquals($this->request->expires, '+2 hours'); + $this->assertEquals($this->request->signatureAlgorithm, 'sha384'); $this->assertEquals('Expect:', $this->request->headers[0]); $this->assertContains('Transloadit-Client: php-sdk:%s', $this->request->headers); } @@ -99,13 +100,21 @@ public function testSignString() { // No secret, no signature $this->assertEquals(null, $this->request->signString('foo')); - // Verify the test vector given in the documentation, see: http://transloadit.com/docs/authentication + // Verify the test vector (default sha384 + algorithm prefix) $this->request->secret = 'd805593620e689465d7da6b8caf2ac7384fdb7e9'; - $expectedSignature = 'fec703ccbe36b942c90d17f64b71268ed4f5f512'; + $expectedSignature = 'sha384:69b74f954488cbb571cace210ae9039d18d84ec57edc784d19fd364f4295c99c93c14f0fed7f245b480d5856f12effc2'; $params = '{"auth":{"expires":"2010\/10\/19 09:01:20+00:00","key":"2b0c45611f6440dfb64611e872ec3211"},"steps":{"encode":{"robot":"\/video\/encode"}}}'; $signature = $this->request->signString($params); $this->assertEquals($expectedSignature, $signature); + + // Explicit algorithm override for legacy keys + $legacySignature = $this->request->signString($params, 'sha1'); + $this->assertEquals('sha1:fec703ccbe36b942c90d17f64b71268ed4f5f512', $legacySignature); + + // Request-level override should affect default signing behavior + $this->request->signatureAlgorithm = 'sha1'; + $this->assertEquals('sha1:fec703ccbe36b942c90d17f64b71268ed4f5f512', $this->request->signString($params)); } public function testGetParamsString() { @@ -146,7 +155,7 @@ public function testSignatureParityWithNodeCli(): void { 'width' => 320, ], ], - ], 'cli-key', 'cli-secret', 'sha1'); + ], 'cli-key', 'cli-secret', 'sha384'); $this->assertNotNull($cliResult); $this->assertArrayHasKey('signature', $cliResult); @@ -166,8 +175,9 @@ public function testSignatureParityWithNodeCli(): void { $cliParams['steps']['resize']['width'] ); - $expectedSignature = hash_hmac('sha1', $cliResult['params'], 'cli-secret'); - $this->assertEquals('sha1:' . $expectedSignature, $cliResult['signature']); + $expectedSignature = hash_hmac('sha384', $cliResult['params'], 'cli-secret'); + $this->assertEquals('sha384:' . $expectedSignature, $cliResult['signature']); + $this->assertEquals($cliResult['signature'], $request->signString($cliResult['params'])); } public function testExecute() { diff --git a/test/simple/TransloaditTest.php b/test/simple/TransloaditTest.php index ef8f1ea..7d48d4e 100644 --- a/test/simple/TransloaditTest.php +++ b/test/simple/TransloaditTest.php @@ -21,6 +21,7 @@ public function testConstructor() { public function testAttributes() { $this->assertEquals($this->transloadit->key, null); $this->assertEquals($this->transloadit->secret, null); + $this->assertEquals($this->transloadit->signatureAlgorithm, 'sha384'); } public function testCreateAssembly() { @@ -78,10 +79,12 @@ public function testCancelAssembly() { public function testRequest() { $this->transloadit->key = 'my-key'; $this->transloadit->secret = 'my-secret'; + $this->transloadit->signatureAlgorithm = 'sha1'; $request = $this->transloadit->request(['url' => 'foobar'], false); $this->assertEquals($this->transloadit->key, $request->key); $this->assertEquals($this->transloadit->secret, $request->secret); + $this->assertEquals($this->transloadit->signatureAlgorithm, $request->signatureAlgorithm); $this->assertEquals('foobar', $request->url); // Unfortunately we can't test the $execute parameter because PHP From 0173ecb7daf3b0f3bc5bb2eac723cd90492cf2d9 Mon Sep 17 00:00:00 2001 From: tim-kos Date: Mon, 27 Apr 2026 13:15:09 +0200 Subject: [PATCH 2/2] Normalize invalid signature algorithm errors --- lib/transloadit/TransloaditRequest.php | 7 ++++++- test/simple/TransloaditRequestTest.php | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/transloadit/TransloaditRequest.php b/lib/transloadit/TransloaditRequest.php index b00d177..05b2437 100644 --- a/lib/transloadit/TransloaditRequest.php +++ b/lib/transloadit/TransloaditRequest.php @@ -53,7 +53,12 @@ public function signString($string, $algorithm = null) { $effectiveAlgorithm = 'sha384'; } - $signature = hash_hmac($effectiveAlgorithm, $string, $this->secret); + try { + $signature = hash_hmac($effectiveAlgorithm, $string, $this->secret); + } catch (\ValueError $e) { + throw new \InvalidArgumentException('Unsupported signature algorithm: ' . $effectiveAlgorithm, 0, $e); + } + if ($signature === false) { throw new \InvalidArgumentException('Unsupported signature algorithm: ' . $effectiveAlgorithm); } diff --git a/test/simple/TransloaditRequestTest.php b/test/simple/TransloaditRequestTest.php index 05223ab..5dc8d21 100644 --- a/test/simple/TransloaditRequestTest.php +++ b/test/simple/TransloaditRequestTest.php @@ -117,6 +117,13 @@ public function testSignString() { $this->assertEquals('sha1:fec703ccbe36b942c90d17f64b71268ed4f5f512', $this->request->signString($params)); } + public function testSignStringWithInvalidAlgorithmThrowsInvalidArgumentException() { + $this->request->secret = 'secret'; + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Unsupported signature algorithm: definitely-not-a-real-algorithm'); + $this->request->signString('payload', 'definitely-not-a-real-algorithm'); + } + public function testGetParamsString() { $this->request->key = 'dskjadjk2j42jkh4'; $PARAMS = $this->request->params = ['foo' => 'bar'];