Untitled

PHP boxlabs 109 Views Size: 5.79 KB Posted on: Sep 4, 25 @ 7:55 PM
  1. function old_key_candidates(string $k): array {
  2.     // Try raw, trimmed, without wrapping quotes, and hex->bin
  3.     $cands = [];
  4.     $adds  = [];
  5.  
  6.     $adds[] = $k;
  7.     $adds[] = trim($k);
  8.     $adds[] = trim($k, " \t\n\r\0\x0B'\"");
  9.  
  10.     // If it looks like hex, also try binary form
  11.     foreach ($adds as $a) {
  12.         $cands[] = $a;
  13.         if (ctype_xdigit($a) && (strlen($a) % 2 === 0)) {
  14.             $bin = @hex2bin($a);
  15.             if ($bin !== false) $cands[] = $bin;
  16.         }
  17.     }
  18.  
  19.     // For each, also try md5(key) in both binary+hex (some v2 forks used SECRET)
  20.     $all = [];
  21.     foreach (array_unique($cands, SORT_STRING) as $cand) {
  22.         $all[] = $cand;                       // raw
  23.         $all[] = md5($cand, true);            // md5 bin
  24.         $all[] = md5($cand, false);           // md5 hex
  25.     }
  26.  
  27.     // de-duplicate by string identity
  28.     return array_values(array_unique($all, SORT_STRING));
  29. }
  30.  
  31. // --- v2.x decrypt that tries many realistic permutations --------------------
  32. function try_decrypt_v22(string $value, array $keys): ?string {
  33.     $cipher = 'AES-256-CBC';
  34.     $ivlen  = openssl_cipher_iv_length($cipher);
  35.     $zeroIv = str_repeat("\0", $ivlen);
  36.  
  37.     $decoded = base64_decode($value, true); // default v2 produced base64
  38.  
  39.     foreach ($keys as $key) {
  40.         // 1) Exact legacy style — PHP auto-base64, empty IV param (omitted)
  41.         $pt = @openssl_decrypt($value, $cipher, $key);
  42.         if ($pt !== false) return $pt;
  43.  
  44.         // 2) base64-decoded + RAW_DATA + zero IV (some stacks behaved like this)
  45.         if ($decoded !== false) {
  46.             $pt = @openssl_decrypt($decoded, $cipher, $key, OPENSSL_RAW_DATA, $zeroIv);
  47.             if ($pt !== false) return $pt;
  48.         }
  49.  
  50.         // 3) explicit empty IV with base64 input (older PHP tolerated empty IV)
  51.         $pt = @openssl_decrypt($value, $cipher, $key, 0, '');
  52.         if ($pt !== false) return $pt;
  53.  
  54.         if ($decoded !== false) {
  55.             // 4) raw + empty IV, no RAW_DATA
  56.             $pt = @openssl_decrypt($decoded, $cipher, $key, 0, '');
  57.             if ($pt !== false) return $pt;
  58.         }
  59.     }
  60.     return null;
  61. }
  62.  
  63. function decrypt_v31_with_key(string $value, string $keyBin): ?string {
  64.     $decoded = base64_decode($value, true);
  65.     if ($decoded === false) return null;
  66.     $cipher = 'AES-256-CBC';
  67.     $ivlen  = openssl_cipher_iv_length($cipher);
  68.     $hmacLen = 32;
  69.     if (strlen($decoded) < $ivlen + $hmacLen) return null;
  70.     $iv  = substr($decoded, 0, $ivlen);
  71.     $hmc = substr($decoded, $ivlen, $hmacLen);
  72.     $ct  = substr($decoded, $ivlen + $hmacLen);
  73.     $calc = hash_hmac('sha256', $ct, $keyBin, true);
  74.     if (!hash_equals($hmc, $calc)) return null;
  75.     $pt = openssl_decrypt($ct, $cipher, $keyBin, OPENSSL_RAW_DATA, $iv);
  76.     return ($pt !== false) ? $pt : null;
  77. }
  78.  
  79. function encrypt_v31_with_key(string $plaintext, string $keyBin): string {
  80.     $cipher = 'AES-256-CBC';
  81.     $ivlen  = openssl_cipher_iv_length($cipher);
  82.     $iv     = random_bytes($ivlen);
  83.     $ct     = openssl_encrypt($plaintext, $cipher, $keyBin, OPENSSL_RAW_DATA, $iv);
  84.     $h      = hash_hmac('sha256', $ct, $keyBin, true);
  85.     return base64_encode($iv . $h . $ct);
  86. }
  87.  
  88. // Re-key all rows with encrypt='1'
  89. function migrate_encrypted_pastes(PDO $pdo, string $oldKeyInput, string $newKeyHex, bool $dryRun=false): array {
  90.     $res = ['checked'=>0,'converted'=>0,'skipped'=>0,'failed'=>0,'errors'=>[]];
  91.  
  92.     if (!extension_loaded('openssl')) {
  93.         $res['errors'][] = 'OpenSSL extension not available.';
  94.         return $res;
  95.     }
  96.     $oldKeyCandidates = old_key_candidates($oldKeyInput);
  97.     if (empty($oldKeyCandidates)) {
  98.         $res['errors'][] = 'Old $sec_key not provided.';
  99.         return $res;
  100.     }
  101.     $newKeyBin = @hex2bin(trim($newKeyHex));
  102.     if ($newKeyBin === false || strlen($newKeyBin)!==32) {
  103.         $res['errors'][] = 'New $sec_key is not valid 64-hex (32-byte) key.';
  104.         return $res;
  105.     }
  106.  
  107.     $total = (int)$pdo->query("SELECT COUNT(*) FROM pastes WHERE encrypt='1'")->fetchColumn();
  108.     if ($total === 0) return $res;
  109.  
  110.     $batch = 500;
  111.     for ($offset=0; $offset<$total; $offset+=$batch) {
  112.         $q = $pdo->prepare("SELECT id, content FROM pastes WHERE encrypt='1' ORDER BY id ASC LIMIT :lim OFFSET :off");
  113.         $q->bindValue(':lim', $batch, PDO::PARAM_INT);
  114.         $q->bindValue(':off', $offset, PDO::PARAM_INT);
  115.         $q->execute();
  116.         $rows = $q->fetchAll(PDO::FETCH_ASSOC);
  117.         if (!$rows) break;
  118.  
  119.         if (!$dryRun) $pdo->beginTransaction();
  120.         foreach ($rows as $r) {
  121.             $res['checked']++;
  122.             $id  = (int)$r['id'];
  123.             $enc = (string)$r['content'];
  124.  
  125.             // Already new-format under current key? Skip.
  126.             $already = decrypt_v31_with_key($enc, $newKeyBin);
  127.             if ($already !== null) { $res['skipped']++; continue; }
  128.  
  129.             // Try legacy decrypt with multiple key interpretations
  130.             $plain = try_decrypt_v22($enc, $oldKeyCandidates);
  131.             if ($plain === null) {
  132.                 $res['failed']++;
  133.                 $res['errors'][] = "ID $id: could not decrypt with any old key variant.";
  134.                 continue;
  135.             }
  136.  
  137.             // Re-encrypt and verify
  138.             $reb = encrypt_v31_with_key($plain, $newKeyBin);
  139.             if (decrypt_v31_with_key($reb, $newKeyBin) !== $plain) {
  140.                 $res['failed']++;
  141.                 $res['errors'][] = "ID $id: verification after re-encrypt failed.";
  142.                 continue;
  143.             }
  144.  
  145.             if (!$dryRun) {
  146.                 $u = $pdo->prepare("UPDATE pastes SET content=:c WHERE id=:id");
  147.                 $u->execute([':c'=>$reb, ':id'=>$id]);
  148.             }
  149.             $res['converted']++;
  150.         }
  151.         if (!$dryRun) $pdo->commit();
  152.     }
  153.  
  154.     return $res;
  155. }

Raw Paste

Comments 0
Login to post a comment.
  • No comments yet. Be the first.
Login to post a comment. Login or Register
We use cookies. To comply with GDPR in the EU and the UK we have to show you these.

We use cookies and similar technologies to keep this website functional (including spam protection via Google reCAPTCHA or Cloudflare Turnstile), and — with your consent — to measure usage and show ads. See Privacy.