Skip to content

Commit 5cef809

Browse files
author
Greg Bowler
authored
Sodium implementation (#8)
* wip: working on sodium implementation * feature: initial sodium library implementation in object-oriented code for #7 * test: message classes * refactor: remove unused class * refactor: remove unused uri classes * test: cipher & init vector * test: key pair - 100% coverage * feature: encrypted uri * tweak: don't pass unused public key * tweak: remove unused import * test: fix tests after refactor * refactor: only use psr standard functionality * refactor: automatically generate key's bytes * refactor: secretbox sodium.php * refactor: secretbox sodium.php * refactor: secretbox sodium-lib-uri.php * refactor: secretbox remove unused references * refactor: no need to pass shared key as it's already in the uri * test: EncryptedMessage * test: tests passing * feature: do not pass key in uri * test: cipher text geturi * test: key * test: EncryptedUri - 100% coverage * tweak: output shared key with uri * tweak: use cipher test uri * stan: remove unused key
1 parent 376ccab commit 5cef809

29 files changed

+493
-445
lines changed

Diff for: README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,17 @@ The `URIAdapter` class can be used to convert from a `Message` to a URI query st
3535
$message = "Hello, PHP.Gt!";
3636
$privateKey = "This can be any string, but a long random string is best.";
3737

38-
$message = new \Gt\Cipher\Message($message, $privateKey);
38+
$message = new \Gt\Cipher\Message\PlainTextMessage($message, $privateKey);
3939
// Redirect to receiver.php, possibly on another server:
40-
header("Location: " . new \Gt\Cipher\UriAdapter($message, "/receiver.php"));
40+
header("Location: " . new \Gt\Cipher\CipherUri($message, "/receiver.php"));
4141
```
4242

4343
`receiver.php`:
4444

4545
```php
4646
// This key must be the same on the sender and receiver!
4747
$privateKey = "This can be any string, but a long random string is best.";
48-
$cipher = new \Gt\Cipher\EncryptedMessage($_GET["cipher"], $_GET["iv"], $privateKey);
48+
$cipher = new \Gt\Cipher\Message\EncryptedMessage($_GET["cipher"], $_GET["iv"], $privateKey);
4949
echo $cipher->getMessage();
5050
// Output: Hello, PHP.Gt!
5151
```

Diff for: composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"type": "library",
55
"require": {
66
"php": ">=8.0",
7-
"ext-openssl": "*",
7+
"ext-sodium": "*",
88
"phpgt/http": "v1.*"
99
},
1010
"require-dev": {

Diff for: composer.lock

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: sodium-lib-uri.php

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
use Gt\Cipher\EncryptedUri;
3+
use Gt\Cipher\Key;
4+
use Gt\Cipher\Message\PlainTextMessage;
5+
6+
require("vendor/autoload.php");
7+
8+
$sharedKey = new Key();
9+
$message = new PlainTextMessage("Cipher test!");
10+
echo "Message to send: $message", PHP_EOL;
11+
12+
$cipherText = $message->encrypt($sharedKey);
13+
$uri = $cipherText->getUri("https://cipher-test.g105b.com/");
14+
echo "Key: $sharedKey", PHP_EOL;
15+
echo "URI: $uri", PHP_EOL;
16+
17+
// At this point, the remote code at example.com has access to the encrypted
18+
// message from the URI's query string parameters.
19+
20+
// The following code represents the receiving side of the platform:
21+
$incomingUri = (string)$uri;
22+
$encryptedUri = new EncryptedUri($uri);
23+
$plainTextMessage = $encryptedUri->decryptMessage($sharedKey);
24+
25+
echo "Decrypted: $plainTextMessage", PHP_EOL;

Diff for: sodium-lib.php

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
use Gt\Cipher\Key;
3+
use Gt\Cipher\Message\EncryptedMessage;
4+
use Gt\Cipher\Message\PlainTextMessage;
5+
6+
require("vendor/autoload.php");
7+
8+
$sharedKey = new Key();
9+
$message = new PlainTextMessage("Cipher test!");
10+
echo "Message to send: $message", PHP_EOL;
11+
12+
$cipherText = $message->encrypt($sharedKey);
13+
14+
echo "Shared key: $sharedKey", PHP_EOL;
15+
echo "IV: ", $message->getIv(), PHP_EOL;
16+
echo "Cipher: $cipherText", PHP_EOL;
17+
18+
$encryptedMessage = new EncryptedMessage($cipherText, $message->getIv());
19+
$decrypted = $encryptedMessage->decrypt($sharedKey);
20+
21+
echo "Decrypted: $decrypted", PHP_EOL;

Diff for: sodium.php

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
$messageToTransmit = "Cipher test!";
3+
$sharedKey = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
4+
$iv = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
5+
6+
$encryptedBytes = sodium_crypto_secretbox(
7+
$messageToTransmit,
8+
$iv,
9+
$sharedKey
10+
);
11+
$cipher = base64_encode($encryptedBytes);
12+
13+
echo "Shared key: ", base64_encode($sharedKey), PHP_EOL;
14+
echo "IV: ", base64_encode($iv), PHP_EOL;
15+
echo "Cipher: ", base64_encode($cipher), PHP_EOL;
16+
17+
$decrypted = sodium_crypto_secretbox_open(
18+
$encryptedBytes,
19+
$iv,
20+
$sharedKey,
21+
);
22+
23+
echo "Decrypted: $decrypted", PHP_EOL;

Diff for: src/AbstractMessage.php

-38
This file was deleted.

Diff for: src/CipherText.php

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
namespace Gt\Cipher;
3+
4+
use Gt\Http\Uri;
5+
use Psr\Http\Message\UriInterface;
6+
use Stringable;
7+
8+
class CipherText implements Stringable {
9+
private string $bytes;
10+
11+
public function __construct(
12+
string $data,
13+
private InitVector $iv,
14+
private Key $key,
15+
) {
16+
17+
$this->bytes = sodium_crypto_secretbox(
18+
$data,
19+
$this->iv->getBytes(),
20+
$this->key->getBytes(),
21+
);
22+
}
23+
24+
public function __toString():string {
25+
return base64_encode($this->getBytes());
26+
}
27+
28+
public function getBytes():string {
29+
return $this->bytes;
30+
}
31+
32+
public function getUri(string|UriInterface $baseUri = ""):UriInterface {
33+
if(!$baseUri instanceof UriInterface) {
34+
$baseUri = new Uri($baseUri);
35+
}
36+
37+
$currentQuery = $baseUri->getQuery();
38+
parse_str($currentQuery, $queryParams);
39+
$queryParams["cipher"] = (string)$this;
40+
$queryParams["iv"] = (string)$this->iv;
41+
42+
return $baseUri->withQuery(http_build_query($queryParams));
43+
}
44+
}

Diff for: src/EncryptedMessage.php

-25
This file was deleted.

Diff for: src/EncryptedUri.php

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
namespace Gt\Cipher;
3+
4+
use Gt\Cipher\Message\DecryptionFailureException;
5+
use Gt\Cipher\Message\PlainTextMessage;
6+
use Gt\Http\Uri;
7+
use Psr\Http\Message\UriInterface;
8+
9+
class EncryptedUri {
10+
private string $encryptedBytes;
11+
private InitVector $iv;
12+
13+
public function __construct(
14+
string|UriInterface $uri,
15+
) {
16+
if(!$uri instanceof UriInterface) {
17+
$uri = new Uri($uri);
18+
}
19+
20+
parse_str($uri->getQuery(), $queryParams);
21+
$cipher = $queryParams["cipher"] ?? null;
22+
$iv = $queryParams["iv"] ?? null;
23+
if(!$cipher || !is_string($cipher)) {
24+
throw new MissingQueryStringException("cipher");
25+
}
26+
if(!$iv || !is_string($iv)) {
27+
throw new MissingQueryStringException("iv");
28+
}
29+
30+
$this->encryptedBytes = base64_decode(str_replace(" ", "+", $cipher));
31+
$this->iv = (new InitVector())->withBytes(base64_decode(str_replace(" ", "+", $iv)));
32+
}
33+
34+
public function decryptMessage(Key $key):PlainTextMessage {
35+
$decrypted = sodium_crypto_secretbox_open(
36+
$this->encryptedBytes,
37+
$this->iv->getBytes(),
38+
$key->getBytes(),
39+
);
40+
if($decrypted === false) {
41+
throw new DecryptionFailureException("Error decrypting cipher message");
42+
}
43+
return new PlainTextMessage(
44+
$decrypted,
45+
$this->iv,
46+
);
47+
}
48+
}

Diff for: src/InitVector.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class InitVector implements Stringable {
77
private string $bytes;
88

99
public function __construct(
10-
int $byteLength = 16
10+
int $byteLength = SODIUM_CRYPTO_BOX_NONCEBYTES
1111
) {
1212
if($byteLength < 1) {
1313
throw new CipherException("IV byte length must be greater than 1");
@@ -26,6 +26,6 @@ public function withBytes(string $bytes):self {
2626
}
2727

2828
public function __toString():string {
29-
return bin2hex($this->bytes);
29+
return base64_encode($this->bytes);
3030
}
3131
}

Diff for: src/InitVectorFactory.php

-9
This file was deleted.

Diff for: src/Key.php

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
namespace Gt\Cipher;
3+
4+
use Stringable;
5+
6+
class Key implements Stringable {
7+
protected string $bytes;
8+
9+
public function __construct(
10+
?string $bytes = null
11+
) {
12+
$this->bytes = $bytes ?? random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
13+
}
14+
15+
public function __toString():string {
16+
return base64_encode($this->bytes);
17+
}
18+
19+
public function getBytes():string {
20+
return $this->bytes;
21+
}
22+
}

Diff for: src/Message.php

-25
This file was deleted.

Diff for: src/Message/AbstractMessage.php

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
namespace Gt\Cipher\Message;
3+
4+
use Gt\Cipher\InitVector;
5+
use Stringable;
6+
7+
abstract class AbstractMessage implements Stringable {
8+
protected InitVector $iv;
9+
10+
public function __construct(
11+
protected string $data,
12+
?InitVector $iv = null,
13+
) {
14+
$this->iv = $iv ?? new InitVector();
15+
}
16+
17+
public function __toString():string {
18+
return $this->data;
19+
}
20+
21+
public function getIv():InitVector {
22+
return $this->iv;
23+
}
24+
}

Diff for: src/Message/DecryptionFailureException.php

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
namespace Gt\Cipher\Message;
3+
4+
use Gt\Cipher\CipherException;
5+
6+
class DecryptionFailureException extends CipherException {}

0 commit comments

Comments
 (0)