parent
1f34b7bbfb
commit
3aa76d8054
@ -0,0 +1,6 @@ |
||||
Zephir/aes-loader/hydraterbootloader/ext |
||||
Zephir/aes-loader/hydraterbootloader/.zephir |
||||
Zephir/aes-license/hydraterlicense/ext |
||||
Zephir/aes-license/hydraterlicense/.zephir |
||||
*.log |
||||
*.swp |
||||
@ -0,0 +1,69 @@ |
||||
{ |
||||
"stubs": { |
||||
"path": "ide\/%version%\/%namespace%\/", |
||||
"stubs-run-after-generate": false, |
||||
"banner": "" |
||||
}, |
||||
"api": { |
||||
"path": "doc\/%version%", |
||||
"theme": { |
||||
"name": "zephir", |
||||
"options": { |
||||
"github": null, |
||||
"analytics": null, |
||||
"main_color": "#3E6496", |
||||
"link_color": "#3E6496", |
||||
"link_hover_color": "#5F9AE7" |
||||
} |
||||
} |
||||
}, |
||||
"warnings": { |
||||
"unused-variable": true, |
||||
"unused-variable-external": false, |
||||
"possible-wrong-parameter": true, |
||||
"possible-wrong-parameter-undefined": false, |
||||
"nonexistent-function": true, |
||||
"nonexistent-class": true, |
||||
"non-valid-isset": true, |
||||
"non-array-update": true, |
||||
"non-valid-objectupdate": true, |
||||
"non-valid-fetch": true, |
||||
"invalid-array-index": true, |
||||
"non-array-append": true, |
||||
"invalid-return-type": true, |
||||
"unreachable-code": true, |
||||
"nonexistent-constant": true, |
||||
"not-supported-magic-constant": true, |
||||
"non-valid-decrement": true, |
||||
"non-valid-increment": true, |
||||
"non-valid-clone": true, |
||||
"non-valid-new": true, |
||||
"non-array-access": true, |
||||
"invalid-reference": true, |
||||
"invalid-typeof-comparison": true, |
||||
"conditional-initialization": true |
||||
}, |
||||
"optimizations": { |
||||
"static-type-inference": true, |
||||
"static-type-inference-second-pass": true, |
||||
"local-context-pass": true, |
||||
"constant-folding": true, |
||||
"static-constant-class-folding": true, |
||||
"call-gatherer-pass": true, |
||||
"check-invalid-reads": false, |
||||
"internal-call-transformation": false |
||||
}, |
||||
"extra": { |
||||
"indent": "spaces", |
||||
"export-classes": false |
||||
}, |
||||
"namespace": "hydraterlicense", |
||||
"name": "hydraterlicense", |
||||
"description": "", |
||||
"author": "Phalcon Team", |
||||
"version": "0.0.1", |
||||
"verbose": false, |
||||
"requires": { |
||||
"extensions": ["openssl"] |
||||
} |
||||
} |
||||
@ -0,0 +1,71 @@ |
||||
namespace HydraterLicense; |
||||
|
||||
class KeyGenerator |
||||
{ |
||||
/** |
||||
* Generates an RSA private key and saves it to a file. |
||||
* |
||||
* @param string path Output path for private key (e.g., "private.pem") |
||||
* @param int bits RSA key size (default: 2048) |
||||
* @return bool |
||||
*/ |
||||
public function generatePrivateKey(string path, int bits = 2048) -> bool |
||||
{ |
||||
|
||||
if unlikely !extension_loaded("openssl") { |
||||
echo "OpenSSL extension not loaded"; |
||||
return false; |
||||
} |
||||
|
||||
var config, privateKey, exported, result; |
||||
|
||||
let config = [ |
||||
"private_key_type": OPENSSL_KEYTYPE_RSA, |
||||
"private_key_bits": bits, |
||||
"digest_alg": "sha256" |
||||
]; |
||||
|
||||
let privateKey = openssl_pkey_new(config); |
||||
|
||||
let exported = ""; |
||||
if unlikely !openssl_pkey_export(privateKey, exported) { |
||||
echo "Failed to export pkey"; |
||||
return false; |
||||
} |
||||
|
||||
let result = file_put_contents(path, exported); |
||||
if !result { |
||||
echo "Unable to save"; |
||||
} |
||||
return result !== false; |
||||
} |
||||
|
||||
/** |
||||
* Extracts public key from private key and saves it to file. |
||||
* |
||||
* @param string privatePath Path to private.pem |
||||
* @param string publicPath Output path for public.pem |
||||
* @return bool |
||||
*/ |
||||
public function generatePublicKey(string privatePath, string publicPath) -> bool |
||||
{ |
||||
var privatePem, privateKey, pubKeyDetails, pubKey, written; |
||||
|
||||
let privatePem = file_get_contents(privatePath); |
||||
if privatePem === false { |
||||
return false; |
||||
} |
||||
|
||||
let privateKey = openssl_pkey_get_private(privatePem); |
||||
|
||||
let pubKeyDetails = openssl_pkey_get_details(privateKey); |
||||
if typeof pubKeyDetails !== "array" || !isset pubKeyDetails["key"] { |
||||
return false; |
||||
} |
||||
|
||||
let pubKey = pubKeyDetails["key"]; |
||||
let written = file_put_contents(publicPath, pubKey); |
||||
return written !== false; |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,95 @@ |
||||
namespace HydraterLicense; |
||||
|
||||
class LicenseWriter |
||||
{ |
||||
/** |
||||
* Create a digitally signed license.json file |
||||
* |
||||
* @param array fileSettings Array of [filename => ["enabled": bool, "expires": string ISO 8601 expiration date, "password": string]] |
||||
* @param array domains Allowed domain names |
||||
* @param string privateKeyPem Private key in PEM format |
||||
* @param string aesKey 32-byte encryption key |
||||
* @param string aesIV 16-byte initialization vector |
||||
*/ |
||||
public function createLicenseJson(array fileSettings, array domains, string privateKeyPem, string aesKey, string aesIV, string licenseFile) |
||||
{ |
||||
var features = [], filename, setting, feature, plainPassword; |
||||
var license, licenseJson, signature, finalPayload; |
||||
var encrypted, encryptedB64, finalJson, enabled, expires; |
||||
var fileHandle, myfeature; |
||||
|
||||
// Build feature list |
||||
for filename, setting in fileSettings { |
||||
if typeof setting !== "array" { |
||||
continue; |
||||
} |
||||
if !isset setting["feature"] || !isset setting["enabled"] || !isset setting["password"] { |
||||
continue; |
||||
} |
||||
let myfeature = setting["feature"]; |
||||
|
||||
if !isset setting["expires"] { |
||||
let expires = "*"; // Never Expires |
||||
} else { |
||||
let expires = (string) setting["expires"]; |
||||
} |
||||
|
||||
if ends_with(filename, ".aes") { |
||||
let plainPassword = (string) setting["password"]; |
||||
|
||||
// Encrypt password with AES-256-CBC |
||||
let encrypted = openssl_encrypt( |
||||
plainPassword, |
||||
"aes-256-cbc", |
||||
aesKey, |
||||
1, // OPENSSL_RAW_DATA |
||||
aesIV |
||||
); |
||||
|
||||
// Base64 encode encrypted output |
||||
let encryptedB64 = base64_encode(encrypted); |
||||
if setting["enabled"] == true || setting["enabled"] == 1 { |
||||
let enabled = true; |
||||
} else { |
||||
let enabled = false; |
||||
} |
||||
|
||||
let feature = [ |
||||
"file": filename, |
||||
"feature": myfeature, |
||||
"enabled": enabled, |
||||
"expires": expires, |
||||
"password": encryptedB64 |
||||
]; |
||||
let features[] = feature; |
||||
} |
||||
} |
||||
|
||||
let license = [ |
||||
"features": features, |
||||
"domains": domains |
||||
]; |
||||
|
||||
// JSON encode license (pretty format) |
||||
let licenseJson = json_encode(license, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); |
||||
|
||||
// Sign using openssl_sign via PHP |
||||
let signature = ""; |
||||
openssl_sign(licenseJson, signature, privateKeyPem, "sha256"); |
||||
|
||||
// Wrap license + signature into final JSON |
||||
let finalPayload = [ |
||||
"license": json_decode(licenseJson), |
||||
"signature": base64_encode(signature) |
||||
]; |
||||
|
||||
let finalJson = json_encode(finalPayload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); |
||||
|
||||
// Save to license.json |
||||
let fileHandle = fopen(licenseFile, "w"); |
||||
if fileHandle !== false { |
||||
fwrite(fileHandle, finalJson); |
||||
fclose(fileHandle); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,148 @@ |
||||
use HydraterLicense\KeyGenerator; |
||||
use HydraterLicense\LicenseWriter; |
||||
|
||||
namespace HydraterLicense; |
||||
|
||||
class MakeLicense |
||||
{ |
||||
|
||||
public function makePassword(int size = 16)->string { |
||||
var r; |
||||
let r = this->generateCryptoKey(size); |
||||
return bin2hex(r); |
||||
} |
||||
|
||||
public function generateLicense( |
||||
array Array_For_Files, |
||||
array AllowedDomains, |
||||
string PrivatePEM, |
||||
string PublicPEM, |
||||
string AESKeysFile, |
||||
string LicenseFile |
||||
) { |
||||
var Key, KeyHex, iv, ivHex, aes, privateKey, password; |
||||
var secret_php_file, writer, file; |
||||
var aesKey, aesIV, gen, v, e, r; |
||||
try { |
||||
if !file_exists(AESKeysFile) { |
||||
// Generate a 32-byte (256-bit) AES key |
||||
let Key = this->generateCryptoKey(32); |
||||
let KeyHex = bin2hex(Key); |
||||
|
||||
// Generate a 16-byte (128-bit) IV |
||||
let iv = this->generateCryptoKey(16); |
||||
let ivHex = bin2hex(iv); |
||||
|
||||
// Output the results |
||||
let aes = "<?php \n"; |
||||
let aes .= "function getAES_master_keys() { \n"; |
||||
let aes .= "\t // Generated AES-256 Key (32 bytes): \n"; |
||||
let aes .= "\t $aesKey = hex2bin(\"". KeyHex ."\"); \n"; |
||||
let aes .= "\t // Generated IV (16 bytes): \n"; |
||||
let aes .= "\t $aesIV = hex2bin(\"". ivHex ."\"); \n"; |
||||
let aes .= "\t return [\$aesKey, \$aesIV]; \n"; |
||||
let aes .= "}"; |
||||
file_put_contents(AESKeysFile, aes); |
||||
|
||||
let aesKey = Key; |
||||
let aesIV = iv; |
||||
} else { |
||||
require AESKeysFile; |
||||
let r = getAES_master_keys(); |
||||
let aesKey = r[0]; |
||||
let aesIV = r[1]; |
||||
} |
||||
|
||||
if file_exists(PrivatePEM) { |
||||
let privateKey = file_get_contents(PrivatePEM); |
||||
} else { |
||||
let gen = new KeyGenerator(); |
||||
|
||||
if gen->generatePrivateKey(PrivatePEM) { |
||||
echo "Private key generated.\n"; |
||||
} else { |
||||
echo "Private key generation failed.\n"; |
||||
exit(1); |
||||
} |
||||
|
||||
if gen->generatePublicKey(PrivatePEM, PublicPEM) { |
||||
echo "Public key extracted.\n"; |
||||
} else { |
||||
echo "Public key extraction failed.\n"; |
||||
exit(1); |
||||
} |
||||
let privateKey = file_get_contents(PrivatePEM); |
||||
} |
||||
|
||||
// Go ahead and make encypted AES files from PHP files |
||||
for file, v in Array_For_Files { |
||||
let password = v["password"]; |
||||
let secret_php_file = str_replace(".aes", ".php", file); |
||||
let secret_php_file = str_replace("/aes/", "/secret_php_files/", secret_php_file); |
||||
this->encryptPhpFile(secret_php_file, file, password); |
||||
} |
||||
|
||||
// Make new License File with new AES files |
||||
let writer = new LicenseWriter(); |
||||
writer->createLicenseJson( |
||||
Array_For_Files, |
||||
AllowedDomains, |
||||
privateKey, |
||||
aesKey, |
||||
aesIV, |
||||
LicenseFile |
||||
); |
||||
echo "Encrypted and signed license.json created.\n"; |
||||
return true; |
||||
} catch RuntimeException, e { |
||||
echo "Runtime error occurred: ", e->getMessage(), "\n"; |
||||
} catch Exception, e { |
||||
echo "Generic error: ", e->getMessage(), "\n"; |
||||
} |
||||
} |
||||
|
||||
protected function generateCryptoKey(int length) { |
||||
if (function_exists("random_bytes")) { |
||||
return random_bytes(length); |
||||
} elseif (function_exists("openssl_random_pseudo_bytes")) { |
||||
return openssl_random_pseudo_bytes(length); |
||||
} else { |
||||
throw new \RuntimeException("No cryptographically secure random function available"); |
||||
} |
||||
} |
||||
|
||||
protected function evpBytesToKey(string password, string salt, int keyLen = 32, int ivLen = 16) { |
||||
var dtot, d, key, iv; |
||||
let dtot = ""; |
||||
let d = ""; |
||||
while (strlen(dtot) < (keyLen + ivLen)) { |
||||
let d = md5(d . password . salt, true); |
||||
let dtot .= d; |
||||
} |
||||
let key = substr(dtot, 0, keyLen); |
||||
let iv = substr(dtot, keyLen, ivLen); |
||||
return [key, iv]; |
||||
} |
||||
|
||||
protected function encryptPhpFile(string inFile, string outFile, string password) { |
||||
var data, salt, key, iv, ciphertext, prefix, r; |
||||
let data = file_get_contents(inFile); |
||||
if data === false { |
||||
die("Cannot read input file.\n"); |
||||
} |
||||
|
||||
let salt = random_bytes(8); |
||||
let r = this->evpBytesToKey(password, salt); |
||||
let key = r[0]; |
||||
let iv = r[1]; |
||||
|
||||
let ciphertext = openssl_encrypt(data, "AES-256-CBC", key, OPENSSL_RAW_DATA, iv); |
||||
if ciphertext === false { |
||||
die("Encryption failed.\n"); |
||||
} |
||||
|
||||
let prefix = "Salted__" . salt; |
||||
file_put_contents(outFile, prefix . ciphertext); |
||||
echo "Encrypted file written to: " . outFile ."\n"; |
||||
} |
||||
} |
||||
@ -0,0 +1,69 @@ |
||||
{ |
||||
"stubs": { |
||||
"path": "ide\/%version%\/%namespace%\/", |
||||
"stubs-run-after-generate": false, |
||||
"banner": "" |
||||
}, |
||||
"api": { |
||||
"path": "doc\/%version%", |
||||
"theme": { |
||||
"name": "zephir", |
||||
"options": { |
||||
"github": null, |
||||
"analytics": null, |
||||
"main_color": "#3E6496", |
||||
"link_color": "#3E6496", |
||||
"link_hover_color": "#5F9AE7" |
||||
} |
||||
} |
||||
}, |
||||
"warnings": { |
||||
"unused-variable": true, |
||||
"unused-variable-external": false, |
||||
"possible-wrong-parameter": true, |
||||
"possible-wrong-parameter-undefined": false, |
||||
"nonexistent-function": true, |
||||
"nonexistent-class": true, |
||||
"non-valid-isset": true, |
||||
"non-array-update": true, |
||||
"non-valid-objectupdate": true, |
||||
"non-valid-fetch": true, |
||||
"invalid-array-index": true, |
||||
"non-array-append": true, |
||||
"invalid-return-type": true, |
||||
"unreachable-code": true, |
||||
"nonexistent-constant": true, |
||||
"not-supported-magic-constant": true, |
||||
"non-valid-decrement": true, |
||||
"non-valid-increment": true, |
||||
"non-valid-clone": true, |
||||
"non-valid-new": true, |
||||
"non-array-access": true, |
||||
"invalid-reference": true, |
||||
"invalid-typeof-comparison": true, |
||||
"conditional-initialization": true |
||||
}, |
||||
"optimizations": { |
||||
"static-type-inference": true, |
||||
"static-type-inference-second-pass": true, |
||||
"local-context-pass": true, |
||||
"constant-folding": true, |
||||
"static-constant-class-folding": true, |
||||
"call-gatherer-pass": true, |
||||
"check-invalid-reads": false, |
||||
"internal-call-transformation": false |
||||
}, |
||||
"extra": { |
||||
"indent": "spaces", |
||||
"export-classes": false |
||||
}, |
||||
"namespace": "hydraterbootloader", |
||||
"name": "hydraterbootloader", |
||||
"description": "Hydrater bootloader for encrypted PHP files", |
||||
"author": "Robert Strutts", |
||||
"version": "0.0.1", |
||||
"verbose": false, |
||||
"requires": { |
||||
"extensions": ["openssl"] |
||||
} |
||||
} |
||||
@ -0,0 +1,204 @@ |
||||
namespace HydraterBootloader; |
||||
|
||||
use HydraterBootloader\Loader; |
||||
|
||||
class LicenseVerifier |
||||
{ |
||||
public function tryEnabledItem(string licenseFile, string keyFile, string featureName, string publicKeyPem, bool useInclude = true, bool forceRefresh = false) { |
||||
var l, f, found, password, filePath, r, aesKey, aesIV, data; |
||||
if featureName == "" || licenseFile == "" || publicKeyPem == "" { |
||||
return false; |
||||
} |
||||
if ! this->verifySignature(licenseFile, publicKeyPem) { |
||||
return false; |
||||
} |
||||
|
||||
let l = new Loader(); |
||||
|
||||
if ! file_exists(keyFile) { |
||||
return false; |
||||
} |
||||
require keyFile; |
||||
|
||||
let r = getAES_master_keys(); |
||||
let aesKey = r[0]; |
||||
let aesIV = r[1]; |
||||
|
||||
let data = this->getEnabledFeatures(licenseFile, aesKey, aesIV); |
||||
let found = false; |
||||
for f in data { |
||||
if ! isset f["feature"] || f["feature"] != featureName { |
||||
continue; |
||||
} |
||||
let filePath = f["file"]; |
||||
if isset f["enabled"] && f["enabled"] == true { |
||||
if isset f["password"] { |
||||
let password = f["password"]; |
||||
let found = true; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
if found { |
||||
return l->run(filePath, password, useInclude, forceRefresh); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Verify digital signature using public key |
||||
*/ |
||||
protected function verifySignature(string licensePath, string publicKeyPem) -> bool |
||||
{ |
||||
var contents, parsed, licensePart, licenseJson, signatureB64, signature, result, PEMContent; |
||||
|
||||
if licensePath == "" || publicKeyPem == "" { |
||||
return false; |
||||
} |
||||
let PEMContent = file_get_contents(publicKeyPem); |
||||
if PEMContent === false || PEMContent == "" { |
||||
return false; |
||||
} |
||||
|
||||
let contents = file_get_contents(licensePath); |
||||
if contents === false { |
||||
throw new \Exception("No License Data!"); |
||||
} |
||||
|
||||
let parsed = json_decode(contents, true); |
||||
if typeof parsed !== "array" || !isset parsed["license"] || !isset parsed["signature"] { |
||||
throw new \Exception("No Signature!"); |
||||
} |
||||
|
||||
let licensePart = parsed["license"]; |
||||
let licenseJson = json_encode(licensePart, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); |
||||
let signatureB64 = parsed["signature"]; |
||||
let signature = base64_decode(signatureB64); |
||||
|
||||
let result = openssl_verify(licenseJson, signature, PEMContent, "sha256"); |
||||
return result === 1; |
||||
} |
||||
|
||||
/** |
||||
* Return enabled features with decrypted passwords |
||||
*/ |
||||
protected function getEnabledFeatures(string licensePath, string aesKey, string aesIV) -> array |
||||
{ |
||||
var contents, parsed, features, feature, result = [], encryptedB64, encryptedRaw, decrypted, expires; |
||||
var i, total; |
||||
|
||||
let contents = file_get_contents(licensePath); |
||||
if contents === false { |
||||
return []; |
||||
} |
||||
|
||||
let parsed = json_decode(contents, true); |
||||
if typeof parsed !== "array" || !isset parsed["license"]["features"] { |
||||
return []; |
||||
} |
||||
|
||||
let features = parsed["license"]["features"]; |
||||
let total = count(features); |
||||
|
||||
for i in range(0, total - 1) { |
||||
let feature = features[i]; |
||||
if isset feature["enabled"] && feature["enabled"] == true { |
||||
if isset feature["file"] && isset feature["feature"] && isset feature["password"] { |
||||
|
||||
if ! isset feature["expires"] { |
||||
let expires = "*"; |
||||
} else { |
||||
let expires = feature["expires"]; |
||||
} |
||||
|
||||
if this->isExpired(expires) { |
||||
|
||||
let result[] = [ |
||||
"file": feature["file"], |
||||
"feature": feature["feature"], |
||||
"enabled": false, |
||||
"status": "⏳ License expired" |
||||
]; |
||||
|
||||
continue; // Skip granting Feature as its expired! |
||||
} |
||||
|
||||
let encryptedB64 = feature["password"]; |
||||
let encryptedRaw = base64_decode(encryptedB64); |
||||
let decrypted = openssl_decrypt(encryptedRaw, "aes-256-cbc", aesKey, 1, aesIV); |
||||
|
||||
let result[] = [ |
||||
"file": feature["file"], |
||||
"feature": feature["feature"], |
||||
"password": decrypted, |
||||
"enabled": true, |
||||
"status": "License valid" |
||||
]; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Check if license is expired |
||||
*/ |
||||
protected function isExpired(string expires) -> bool |
||||
{ |
||||
var now, licenseTime; |
||||
|
||||
// Grant unlimited access time on a star! |
||||
if expires == "*" { |
||||
return false; |
||||
} |
||||
|
||||
// Compare current timestamp with expiration |
||||
let now = time(); |
||||
let licenseTime = strtotime(expires); |
||||
|
||||
if unlikely licenseTime === false { |
||||
throw new \Exception("Invalid date format: " . expires); |
||||
} |
||||
|
||||
return now > licenseTime; |
||||
} |
||||
|
||||
/** |
||||
* Check if a domain is allowed (exact or wildcard match) |
||||
*/ |
||||
public function isDomainAllowed(string licensePath, string domain) -> bool |
||||
{ |
||||
var contents, parsed, allowed, i, rule, suffix; |
||||
|
||||
let contents = file_get_contents(licensePath); |
||||
if contents === false { |
||||
return false; |
||||
} |
||||
|
||||
let parsed = json_decode(contents, true); |
||||
if typeof parsed !== "array" || !isset parsed["license"]["domains"] { |
||||
return false; |
||||
} |
||||
|
||||
let allowed = parsed["license"]["domains"]; |
||||
for i in range(0, count(allowed) - 1) { |
||||
let rule = allowed[i]; |
||||
|
||||
// Wildcard match: *.example.com |
||||
if starts_with(rule, "*.") { |
||||
let suffix = substr(rule, 1); // remove * |
||||
if ends_with(domain, suffix) { |
||||
return true; |
||||
} |
||||
} else { |
||||
// Exact match |
||||
if domain == rule { |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,125 @@ |
||||
namespace HydraterBootloader; |
||||
|
||||
class Loader |
||||
{ |
||||
protected function evpBytesToKey(string password, string salt) -> array |
||||
{ |
||||
var key, iv, d, d_i, result; |
||||
|
||||
let d = "", |
||||
result = ""; |
||||
|
||||
while strlen(result) < 48 { |
||||
let d_i = md5(d . password . salt, true); |
||||
let d = d_i; |
||||
let result .= d_i; |
||||
} |
||||
|
||||
let key = substr(result, 0, 32); |
||||
let iv = substr(result, 32, 16); |
||||
|
||||
return [key, iv]; |
||||
} |
||||
|
||||
protected function getCacheDir() -> string |
||||
{ |
||||
var path; |
||||
|
||||
if is_writable("/dev/shm") { |
||||
let path = "/dev/shm/tank"; |
||||
} else { |
||||
let path = "/tmp/tank"; // fallback |
||||
} |
||||
|
||||
if !is_dir(path) { |
||||
mkdir(path, 0700, true); |
||||
} |
||||
|
||||
return path; |
||||
} |
||||
|
||||
protected function isExpired(string path, int maxAgeSeconds) -> bool |
||||
{ |
||||
var mtime, now; |
||||
|
||||
let mtime = filemtime(path); |
||||
if mtime === false { |
||||
return true; |
||||
} |
||||
|
||||
let now = time(); |
||||
return (now - mtime) > maxAgeSeconds; |
||||
} |
||||
|
||||
protected function decrypt(string ciphertext, string password) -> string |
||||
{ |
||||
var salt, key, iv, body, decrypted, args, result; |
||||
|
||||
if substr(ciphertext, 0, 8) != "Salted__" { |
||||
throw new \Exception("Missing OpenSSL salt header."); |
||||
} |
||||
|
||||
let salt = substr(ciphertext, 8, 8); |
||||
let body = substr(ciphertext, 16); |
||||
|
||||
let result = this->evpBytesToKey(password, salt); |
||||
let key = result[0]; |
||||
let iv = result[1]; |
||||
|
||||
let args = [ |
||||
body, |
||||
"AES-256-CBC", |
||||
key, |
||||
1, |
||||
iv |
||||
]; |
||||
|
||||
let decrypted = call_user_func_array("openssl_decrypt", args); |
||||
|
||||
if typeof decrypted != "string" { |
||||
throw new \Exception("Decryption failed."); |
||||
} |
||||
|
||||
return decrypted; |
||||
} |
||||
|
||||
public function run(string filePath, string password, bool useInclude = true, bool forceRefresh = false) |
||||
{ |
||||
var ciphertext, plaintext, tmpPath, hash, cacheDir, expired; |
||||
|
||||
if !file_exists(filePath) { |
||||
throw new \Exception("File does not exist: " . filePath); |
||||
} |
||||
|
||||
let cacheDir = this->getCacheDir(); |
||||
let hash = hash("sha256", filePath); |
||||
let tmpPath = cacheDir . "/aesphp_" . hash . ".php"; |
||||
|
||||
if useInclude && file_exists(tmpPath) && !forceRefresh { |
||||
let expired = this->isExpired(tmpPath, 28800); // 8 hours = 28800 seconds |
||||
if !expired { |
||||
require tmpPath; |
||||
return; |
||||
} else { |
||||
unlink(tmpPath); // Remove expired cache |
||||
} |
||||
} |
||||
|
||||
let ciphertext = file_get_contents(filePath); |
||||
if !ciphertext { |
||||
throw new \Exception("Unable to read file: " . filePath); |
||||
} |
||||
|
||||
let plaintext = this->decrypt(ciphertext, password); |
||||
|
||||
if useInclude { |
||||
file_put_contents(tmpPath, plaintext); |
||||
if forceRefresh { |
||||
register_shutdown_function("unlink", tmpPath); |
||||
} |
||||
return require tmpPath; |
||||
} else { |
||||
return eval(plaintext); |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue