Untitled

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

Raw Paste

Comments 0
Login to join the discussion
  • No comments yet — be the first!
Login to post a comment. Login
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), and — with your consent — to measure usage and show ads. See Privacy.