Skip to content

Instantly share code, notes, and snippets.

@Semdevmaster
Last active October 6, 2023 12:58

Revisions

  1. Semdevmaster revised this gist Oct 6, 2023. 2 changed files with 66 additions and 0 deletions.
    63 changes: 63 additions & 0 deletions EncryptionService.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,63 @@
    <?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);
    }
    }
    3 changes: 3 additions & 0 deletions secureTransportController.php
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,6 @@
    <?php
    declare(strict_types=1);

    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Route;
    use Modules\ApiV1\Services\EncryptionService;
  2. Semdevmaster revised this gist Oct 6, 2023. 1 changed file with 6 additions and 0 deletions.
    6 changes: 6 additions & 0 deletions secureTransportController.php
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,9 @@
    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')));
  3. Semdevmaster created this gist Oct 6, 2023.
    132 changes: 132 additions & 0 deletions secureTransport.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,132 @@
    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)
    */
    31 changes: 31 additions & 0 deletions secureTransportController.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,31 @@
    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),
    ];
    });