Repository: paragonie/paseto (language: php)

Issue:
Invalid MAC for given ciphertext
I have generated the secret local key using PASETO V3 on Node.js, and then converted it from KeyObject to hex. However the secret key is not compatible with PASETO PHP.

Generated secret key: _feed3e081af0fdfdfbdaac45421cbdc1139e06cd52ec15bd4c7c4522cecfb314_
Generated token: _v3.local.SwguoFUJpfByqaQiEhEhe9sM63XhvEkY_mXOkdDcIt7-Gi1D8CrnkaSEO-EXPgGTobUF5Gyvq51pLRDUBu-aAShnjRkaRlulID4JMvoatxieQ7k9qAIDX9nQ_dt-rCx_2Y1eV-cahtK6ovrAFlxMthQf4mwWFyJDl_ggLBd3wzMfDjV-ci78uIRcQdpKvL37bWeUon2t8OOC0sKImZGoSAr6uNpSUNFG_

I can decrypt it in Node, but in PHP an error is returned: **ParagonIE\Paseto\Exception\SecurityException:** Invalid MAC for given ciphertext**

Am I doing something wrong or the secret keys are incompatible with other platforms?

PHP code:
```php
$sharedKey = new SymmetricKey("feed3e081af0fdfdfbdaac45421cbdc1139e06cd52ec15bd4c7c4522cecfb314", new Version3);
$providedToken = "v3.local.SwguoFUJpfByqaQiEhEhe9sM63XhvEkY_mXOkdDcIt7-Gi1D8CrnkaSEO-EXPgGTobUF5Gyvq51pLRDUBu-aAShnjRkaRlulID4JMvoatxieQ7k9qAIDX9nQ_dt-rCx_2Y1eV-cahtK6ovrAFlxMthQf4mwWFyJDl_ggLBd3wzMfDjV-ci78uIRcQdpKvL37bWeUon2t8OOC0sKImZGoSAr6uNpSUNFG";
$parser = (new Parser())
    ->setKey($sharedKey)
    //->addRule(new ValidAt(new DateTime()))
    ->addRule(new IssuedBy('connect-ccas'))
    ->setPurpose(Purpose::local())
    ->setAllowedVersions(ProtocolCollection::v3());

try {
    $token = $parser->parse($providedToken);
    echo "Provided token is valid. Authorization succeded!";
} catch (PasetoException $ex) {
    echo "Provided token is invalid. Authorization failed!";
    echo "DEV_ERR: ".$ex;
}
```

Node:
```js
const key = await V3.generateKey('local', {format: "keyobject"});
console.log((key.export()).toString('hex'));
//Outputs: feed3e081af0fdfdfbdaac45421cbdc1139e06cd52ec15bd4c7c4522cecfb314
```


Discussion / hints:
The `new SymmetricKey` constructor expects you to pass in the raw key material as bytes. If you have the key exported as hex, you'd need to replace your first line with something like:

```php
$sharedKey = new SymmetricKey(hex2bin("feed3e081af0fdfdfbdaac45421cbdc1139e06cd52ec15bd4c7c4522cecfb314"), new Version3);
```

Fix patch:
diff --git a/src/Keys/SymmetricKey.php b/src/Keys/SymmetricKey.php
index 1d4c63c..d11cb7c 100644
--- a/src/Keys/SymmetricKey.php
+++ b/src/Keys/SymmetricKey.php
@@ -39,13 +39,31 @@ class SymmetricKey implements ReceivingKey, SendingKey
      *
      * @param string $keyMaterial
      * @param ProtocolInterface|null $protocol
+     *
+     * @throws PasetoException
      */
     public function __construct(
         string $keyMaterial,
         ProtocolInterface $protocol = null
     ) {
-        $this->key = $keyMaterial;
         $this->protocol = $protocol ?? new Version4;
+
+        switch ($this->protocol::class) {
+            case Version3::class:
+                if (strlen($keyMaterial) !== Version3::SYMMETRIC_KEY_BYTES) {
+                    throw new PasetoException("Invalid key length");
+                }
+                break;
+            case Version4::class:
+                if (strlen($keyMaterial) !== Version4::SYMMETRIC_KEY_BYTES) {
+                    throw new PasetoException("Invalid key length");
+                }
+                break;
+            default:
+                throw new InvalidVersionException("Unsupported version", ExceptionCode::BAD_VERSION);
+        }
+
+        $this->key = $keyMaterial;
     }
 
     /**


Test patch:
diff --git a/tests/KeyTest.php b/tests/KeyTest.php
index 4710bb8..92f24e5 100644
--- a/tests/KeyTest.php
+++ b/tests/KeyTest.php
@@ -7,7 +7,11 @@
     AsymmetricSecretKey
 };
 use ParagonIE\ConstantTime\Binary;
-use ParagonIE\Paseto\Exception\SecurityException;
+use ParagonIE\Paseto\Exception\{
+    PasetoException,
+    SecurityException,
+};
+use ParagonIE\Paseto\Keys\Version4\SymmetricKey;
 use ParagonIE\Paseto\Util;
 use ParagonIE\Paseto\Protocol\{
     Version3,
@@ -137,4 +141,28 @@ public function testInvalidEdDSAKey()
         $this->expectException(SecurityException::class);
         (new AsymmetricSecretKey($bad, new Version4()))->assertSecretKeyValid();
     }
+
+    public function testShortV3SymmetricKey()
+    {
+        $this->expectException(PasetoException::class);
+        new SymmetricKey(random_bytes(31), new Version3());
+    }
+
+    public function testShortV4SymmetricKey()
+    {
+        $this->expectException(PasetoException::class);
+        new SymmetricKey(random_bytes(31), new Version4());
+    }
+
+    public function testLongV3SymmetricKey()
+    {
+        $this->expectException(PasetoException::class);
+        new SymmetricKey(random_bytes(33), new Version3());
+    }
+
+    public function testLongV4SymmetricKey()
+    {
+        $this->expectException(PasetoException::class);
+        new SymmetricKey(random_bytes(33), new Version4());
+    }
 }