Last active
October 6, 2023 12:58
-
-
Save Semdevmaster/08330ae877ee90f0324606ea558f010d to your computer and use it in GitHub Desktop.
Simple example of encrypting traffic between client and server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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