├── README.md └── openai_proxy.php /README.md: -------------------------------------------------------------------------------- 1 | # OpenAI-Proxy-PHP 2 | 3 | A simple PHP script designed to protect your OpenAI API key and ensure secure communication. 4 | 5 | ## Overview 6 | 7 | OpenAI-Proxy-PHP is a PHP script that secures your OpenAI API key, preventing it from being exposed in your application code or transmitted in plaintext over the network. This script is utilized in the AIWrapper-SwiftUI repository to enhance security. 8 | 9 | ## Features 10 | 11 | * API Key Protection: Safeguard your OpenAI API key from exposure. 12 | * Secure Communication: Prevents the API key from being transmitted in plaintext. 13 | * Customizable Prompts: Easily configure custom prompts for your AI. 14 | * Shared Secret Key: Optional shared secret key for an additional layer of security. 15 | 16 | This script is used in the AIWrapper-SwiftUI repo located at 17 | [https://github.com/adamlyttleapps/OpenAI-Wrapper-SwiftUI](https://github.com/adamlyttleapps/OpenAI-Wrapper-SwiftUI) 18 | 19 | ## Usage: 20 | 21 | To use OpenAI-Proxy-PHP, follow these steps: 22 | 23 | Copy openai_proxy.php onto your server and then follow the instructions withint the source code: 24 | 25 | 1. **Copy the Script:** Copy openai_proxy.php to your server. 26 | 2. **Configure the Script:** Open the openai_proxy.php file and follow these instructions: 27 | 28 | ``` 29 | // STEP 1: ADD YOUR OPENAI KEY 30 | For Apache (.htaccess): 31 | SetEnv OPENAI_API_KEY sk-xxxxxx... 32 | 33 | For Nginx + PHP-FPM (conf): 34 | fastcgi_param OPENAI_API_KEY sk-xxxxxx...; 35 | 36 | Or in php.ini: 37 | env[OPENAI_API_KEY] = "sk-xxxxxx..." 38 | 39 | // STEP 2: SPECIFY THE LOCATION WHERE THIS SCRIPT IS STORED 40 | $script_location = "https://adamlyttleapps.com/demo/OpenAIProxy-PHP"; 41 | 42 | // STEP 3: CONFIGURE YOUR CUSTOM PROMPT 43 | $custom_prompt = "You are a friendly chatbot called 'Test Identifier: AI Wrapper Test Agent' whose only purpose is to assist the user with identifying what they have taken a photo of, tips, and other information which may be helpful"; 44 | 45 | // STEP 4: SETUP THE SHARED SECRET KEY 46 | // (this is the secret key in the client and server, leave blank if you want to bypass this check) 47 | $shared_secret_key = ""; 48 | ``` 49 | 50 | ## Shared Secret Key 51 | 52 | When the $shared_secret_key is set, the script expects each client request to include a secure HMAC hash. The server verifies the incoming request by computing: 53 | ``` 54 | hash_hmac('sha256', $_POST['messages'], $shared_secret_key) 55 | ``` 56 | It then compares the result against the client-provided $_POST['hash'] using hash_equals() to prevent timing attacks. 57 | 58 | This allows you to control access by rotating or updating the shared secret. If the hash is missing or incorrect, the request will be rejected with a 403 response. 59 | 60 | ## Special Thanks 61 | 62 | Special thanks to [quinncomendant](https://github.com/quinncomendant) for providing important security recommendations 63 | 64 | ## Contributions 65 | 66 | Contributions are welcome! Feel free to open an issue or submit a pull request on the [GitHub repository](https://github.com/adamlyttleapps/OpenAI-Proxy-PHP). 67 | 68 | ## MIT License 69 | 70 | This project is licensed under the MIT License. See the LICENSE file for more details. 71 | 72 | This README provides a clear overview of the project, detailed usage instructions, and additional sections like examples, contributions, and licensing, making it more comprehensive and user-friendly. 73 | -------------------------------------------------------------------------------- /openai_proxy.php: -------------------------------------------------------------------------------- 1 | x.com/adamlyttleapps 10 | // --> github.com/adamlyttleapps 11 | 12 | // === CONFIGURATION === 13 | 14 | // STEP 1: ADD YOUR OPENAI KEY (use environment variable for safety) 15 | $openai_key = getenv("OPENAI_API_KEY"); 16 | 17 | // STEP 2: SPECIFY THE LOCATION WHERE THIS SCRIPT IS HOSTED 18 | $script_location = "https://yourdomain.com/demo/OpenAIProxy-PHP"; // <-- update this 19 | 20 | // STEP 3: CUSTOM SYSTEM PROMPT 21 | $custom_prompt = "You are a friendly chatbot called 'Test Identifier: AI Wrapper Test Agent' whose only purpose is to assist the user with identifying what they have taken a photo of, tips, and other helpful information."; 22 | 23 | // STEP 4: SHARED SECRET KEY (optional) 24 | $shared_secret_key = ""; // leave blank to disable 25 | 26 | // === MAIN LOGIC STARTS HERE === 27 | 28 | header('Content-Type: text/plain; charset=utf-8'); 29 | 30 | // Check if messages were sent 31 | if (!isset($_POST['messages']) || empty($_POST['messages'])) { 32 | http_response_code(400); 33 | echo "Missing 'messages' parameter."; 34 | exit(); 35 | } 36 | 37 | // Shared secret check (optional) 38 | if (!empty($shared_secret_key)) { 39 | $client_hash = $_POST['hash'] ?? ''; 40 | $expected_hash = hash_hmac('sha256', $_POST['messages'], $shared_secret_key); 41 | if (!hash_equals($expected_hash, $client_hash)) { 42 | http_response_code(403); 43 | echo "Invalid shared secret."; 44 | exit(); 45 | } 46 | } 47 | 48 | class OpenAI { 49 | private $api_key; 50 | 51 | public function __construct($key) { 52 | $this->api_key = $key; 53 | } 54 | 55 | private function secret_key() { 56 | return "Bearer {$this->api_key}"; 57 | } 58 | 59 | public function request($messages, $max_tokens = 1500) { 60 | $body = [ 61 | "model" => "gpt-4o", 62 | "messages" => $messages, 63 | "max_tokens" => $max_tokens, 64 | "temperature" => 0.7, 65 | "top_p" => 1, 66 | "presence_penalty" => 0.75, 67 | "frequency_penalty" => 0.75, 68 | "stream" => false, 69 | ]; 70 | 71 | $curl = curl_init(); 72 | curl_setopt_array($curl, [ 73 | CURLOPT_URL => "https://api.openai.com/v1/chat/completions", 74 | CURLOPT_RETURNTRANSFER => true, 75 | CURLOPT_TIMEOUT => 60, 76 | CURLOPT_CUSTOMREQUEST => "POST", 77 | CURLOPT_POSTFIELDS => json_encode($body), 78 | CURLOPT_HTTPHEADER => [ 79 | 'Content-Type: application/json', 80 | 'Authorization: ' . $this->secret_key() 81 | ] 82 | ]); 83 | 84 | $response = curl_exec($curl); 85 | $error = curl_error($curl); 86 | curl_close($curl); 87 | 88 | return $error ? ['error' => ['message' => $error]] : json_decode($response, true); 89 | } 90 | } 91 | 92 | // Helpers for formatting messages 93 | 94 | function add_message($role, $text) { 95 | return [ 96 | 'role' => $role, 97 | 'content' => [['type' => 'text', 'text' => $text]] 98 | ]; 99 | } 100 | 101 | function add_message_image_data($role, $base64_image) { 102 | global $script_location; 103 | 104 | $decoded = base64_decode(urldecode($base64_image), true); 105 | if ($decoded === false) return null; 106 | 107 | $hash = md5($base64_image); 108 | $dir = __DIR__ . "/tmp"; 109 | if (!is_dir($dir)) mkdir($dir, 0755, true); 110 | 111 | $file_path = "$dir/{$hash}.jpg"; 112 | file_put_contents($file_path, $decoded); 113 | 114 | // Basic validation 115 | if (!getimagesize($file_path)) { 116 | unlink($file_path); 117 | return null; 118 | } 119 | 120 | return [ 121 | 'role' => $role, 122 | 'content' => [[ 123 | 'type' => 'image_url', 124 | 'image_url' => ['url' => "$script_location/tmp/{$hash}.jpg"] 125 | ]] 126 | ]; 127 | } 128 | 129 | function parse_messages($raw_messages) { 130 | global $custom_prompt; 131 | 132 | $parsed = [add_message("system", $custom_prompt)]; 133 | 134 | foreach ($raw_messages as $m) { 135 | if (!empty($m['message'])) { 136 | $parsed[] = add_message($m['role'], $m['message']); 137 | } 138 | if (!empty($m['image'])) { 139 | $img_message = add_message_image_data($m['role'], $m['image']); 140 | if ($img_message) $parsed[] = $img_message; 141 | } 142 | } 143 | 144 | return $parsed; 145 | } 146 | 147 | // Process the messages 148 | $input = json_decode($_POST['messages'], true); 149 | $messages = parse_messages($input); 150 | 151 | // Query OpenAI 152 | $openai = new OpenAI($openai_key); 153 | $response = $openai->request($messages); 154 | 155 | // Output response 156 | if (isset($response['choices'][0]['message']['content'])) { 157 | echo $response['choices'][0]['message']['content']; 158 | } elseif (isset($response['error']['message'])) { 159 | echo "OpenAI Error: " . $response['error']['message']; 160 | } else { 161 | echo "An unknown error occurred."; 162 | } 163 | ?> 164 | --------------------------------------------------------------------------------