Skip to content

Instantly share code, notes, and snippets.

@Semdevmaster
Last active October 6, 2023 12:58
Show Gist options
  • Save Semdevmaster/08330ae877ee90f0324606ea558f010d to your computer and use it in GitHub Desktop.
Save Semdevmaster/08330ae877ee90f0324606ea558f010d to your computer and use it in GitHub Desktop.
Simple example of encrypting traffic between client and server
<?php
declare(strict_types=1);
namespace Modules\ApiV1\Services;
use phpseclib3\Crypt\Common\PrivateKey;
use phpseclib3\Crypt\Common\PublicKey;
use phpseclib3\Crypt\RSA;
use phpseclib3\Crypt\PublicKeyLoader;
class EncryptionService
{
public static function generateKeys(): void
{
$privateKey = RSA::createKey();
self::saveKeysToEnv($privateKey->toString('PKCS8'), $privateKey->getPublicKey()->toString('PKCS8'));
}
public static function getPublicKey(): PublicKey
{
return PublicKeyLoader::loadPublicKey(config('encryption.public_key_path'));
}
public static function getPrivateKey(): PrivateKey
{
return PublicKeyLoader::loadPrivateKey(config('encryption.private_key_path'));
}
public static function encrypt($plaintext): bool|string
{
return self::getPublicKey()
->encrypt($plaintext);
}
public static function decrypt($ciphertext): bool|string
{
return self::getPrivateKey()
->decrypt($ciphertext);
}
private static function saveKeysToEnv($privateKeyString, $publicKeyString): void
{
$envFile = app()->environmentFilePath();
$contents = file_get_contents($envFile);
$privateKeyExists = str_contains($contents, 'APP_PRIVATE_KEY=');
$publicKeyExists = str_contains($contents, 'APP_PUBLIC_KEY=');
if ($privateKeyExists) {
$contents = preg_replace('/APP_PRIVATE_KEY=.*/', "APP_PRIVATE_KEY=\"$privateKeyString\"", $contents);
} else {
$contents .= "\nAPP_PRIVATE_KEY=\"$privateKeyString\"";
}
if ($publicKeyExists) {
$contents = preg_replace('/APP_PUBLIC_KEY=.*/', "APP_PUBLIC_KEY=\"$publicKeyString\"", $contents);
} else {
$contents .= "\nAPP_PUBLIC_KEY=\"$publicKeyString\"";
}
file_put_contents($envFile, $contents);
}
}
class SecureTransport {
constructor (algorithm = 'AES-GCM') {
this.algorithm = algorithm
}
async encrypt (targetString) {
const rsaKey = await this.loadRsaKey()
const aesKey = await this.generateAesKey()
const encryptedAesKey = await this.encryptAesKeyWithPublicRSAKey(aesKey, rsaKey)
const { ciphertext, iv } = await this.aesEncrypt(targetString, aesKey)
const base64EncryptedData = this.arrayBufferToBase64(ciphertext)
const base64Iv = this.arrayBufferToBase64(iv)
const base64EncryptedAesKey = this.arrayBufferToBase64(encryptedAesKey)
return {
data: base64EncryptedData,
iv: base64Iv,
aesKeyEncrypted: base64EncryptedAesKey,
aesKey
}
}
async decrypt (ciphertext, iv, aesKey) {
const ciphertextBuffer = this.base64ToArrayBuffer(ciphertext)
const nonceBuffer = this.base64ToArrayBuffer(iv)
return await this.aesDecrypt(ciphertextBuffer, aesKey, nonceBuffer)
}
async loadRsaKey () {
const rsa_key = localStorage.getItem('rsa_key')
if (!rsa_key) {
console.log('В хранилище нет ключа для загрузки')
}
return await this.importRsaKey(rsa_key)
}
importRsaKey (pem) {
const pemHeader = '-----BEGIN PUBLIC KEY-----'
const pemFooter = '-----END PUBLIC KEY-----'
const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length - 1)
return crypto.subtle.importKey(
'spki',
this.base64ToArrayBuffer(pemContents),
{
name: 'RSA-OAEP',
hash: 'SHA-256',
},
true,
['encrypt', 'wrapKey']
)
}
async generateAesKey (algorithm = 'AES-GCM', length = 256, extractable = true) {
return crypto.subtle.generateKey(
{
name: algorithm,
length: length,
},
extractable,
['encrypt', 'decrypt']
)
}
async encryptAesKeyWithPublicRSAKey (aesKey, rsaKey) {
return await crypto.subtle.wrapKey(
'raw',
aesKey,
rsaKey,
{
name: 'RSA-OAEP'
}
)
}
async aesEncrypt (plaintext, aesKey, ivLength = 12) {
const iv = crypto.getRandomValues(new Uint8Array(ivLength))
const ciphertext = await crypto.subtle.encrypt(
{ name: this.algorithm, iv },
aesKey,
new TextEncoder().encode(plaintext)
)
return { ciphertext, aesKey, iv }
}
async aesDecrypt (ciphertext, key, iv) {
const plaintext = await crypto.subtle.decrypt(
{ name: this.algorithm, iv },
key,
ciphertext
)
return new TextDecoder().decode(plaintext)
}
arrayBufferToBase64 (buffer) {
let binary = ''
const bytes = new Uint8Array(buffer)
const len = bytes.byteLength
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i])
}
return btoa(binary)
}
base64ToArrayBuffer (base64) {
const binaryString = atob(base64)
const len = binaryString.length
const bytes = new Uint8Array(len)
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i)
}
return bytes.buffer
}
}
export default new SecureTransport()
/* Example of using this class
const targetString = 'Привет сервер'
const { data, iv, aesKeyEncrypted, aesKey } = await secureTransport.encrypt(targetString)
const { ciphertext, iv: response_iv } = await fetch(
'https://my-site.ru/api/v1/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ data, iv, aesKeyEncrypted }),
}
).then(response => response.json())
const decrypted_data = await secureTransport.decrypt(ciphertext, response_iv, aesKey)
console.log(decrypted_data)
*/
<?php
declare(strict_types=1);
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Modules\ApiV1\Services\EncryptionService;
use phpseclib3\Crypt\AES;
use phpseclib3\Crypt\Random;
Route::post('test', static function (Request $request) {
$data = base64_decode($request->input('data'));
$decrypted_aes_key = EncryptionService::decrypt(base64_decode($request->input('aesKeyEncrypted')));
$iv = base64_decode($request->input('iv'));
$tagLength = 16;
$tag = substr($data, -$tagLength);
$ciphertext = substr($data, 0, -$tagLength);
$aes = new AES('gcm');
$aes->setKeyLength(256);
$aes->setKey($decrypted_aes_key);
$aes->setNonce($iv);
$aes->setTag($tag);
$plaintext = $aes->decrypt($ciphertext);
dump($plaintext);
//===================================================
$target_string = 'Привет клиент';
$iv = Random::string(12);
$aes->setNonce($iv);
$result = $aes->encrypt($target_string);
$tag = $aes->getTag();
return [
'ciphertext' => base64_encode($result.$tag),
'iv' => base64_encode($iv),
];
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment