decrypt 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. #!/usr/bin/php
  2. <?php
  3. /*
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. const SNAME='decrypt';
  16. const SVERS='0.2';
  17. mb_internal_encoding('UTF-8');
  18. $conf=[
  19. 'phrase'=>null,
  20. 'encrypt'=>false,
  21. 'method'=>0,
  22. 'write'=>false,
  23. 'norm'=>false,
  24. 'progress'=>0,
  25. 'substabfp'=>'substable.txt',
  26. 'solsfp'=>'solutions.txt',
  27. 'dictfps'=>[],
  28. 'debug'=>false
  29. ];
  30. $help=
  31. 'SYNOPSIS
  32. '.SNAME.' -d <dictionary file> <phrase>
  33. DESCRIPTION
  34. This is '.SNAME.' v'.SVERS.', a CLI PHP script that can help decrypting
  35. phrases encrypted with any algorithm producing an encrypted phrase where each
  36. symbol always replaces the same, different symbol from the clear text one.
  37. COMMAND LINE OPTIONS
  38. -d, --dictfp <dictionary file>
  39. Load a dictionary file. It has to be a simple text file with one word on each
  40. line. You have to specify at least one, and you can specify more than one
  41. prepending «-d» or «--dictfp» to each file path.
  42. Currently '.SNAME.' comes with two xz compressed dictionary files:
  43. «english.txt.xz» and «italiano.txt.xz». To use any of them, you have to
  44. decompress it with the xz program. They both are dumps from the corresponding
  45. aspell dictionaries, with each lemma declined in all its forms. You can
  46. get dumps for other languages with something like this:
  47. aspell -d <lang code> dump master | aspell -l <lang code> expand 4
  48. (You may need to further process the above command output with tr, grep, sed
  49. and the likes, in order to have a functional dictionary file).
  50. -n, --normalize
  51. “Normalize” each dictionar(y|ies) entry replacing every accented or otherwise
  52. “signed” character with its “plain” form, and removing possible duplicated
  53. entries. For example, this will substitute every “è” character with “e”.
  54. -e, --encrypt
  55. Encrypt the given phrase. Useful for testing purposes. By default, or when
  56. «-m» or «--method» is set to «0», it encrypts the phrase by simply switching
  57. every source character with its subsequent character in the UTF-8 table. With
  58. «-m» or «--method» set to «1», it encrypts the phrase using a random
  59. substition table.
  60. -m, --method <0|1>
  61. Choose a method for encryption when «-e» or «--encrypt» is specified (see
  62. previous option description). It defaults to “'.$conf['method'].'”.
  63. -p, --progress <0|1|2>
  64. With «0», show no progress during script execution. With «1», show bare
  65. minimum progress. With «2», show detailed progress (this consumes more CPU
  66. and RAM). Defaults to «'.$conf['progress'].'».
  67. -w, --write
  68. Write any substition table '.SNAME.' may find into a «'.$conf['substabfp'].'»
  69. file and any solution '.SNAME.' may find into a «'.$conf['solsfp'].'» file.
  70. Both files will be written in current working directory. Note that possible
  71. existing files with the same name will be overwritten.
  72. -h, --help
  73. Show this help text and exit.
  74. EXAMPLES
  75. decrypt "ipx nvdi xppe xpvme b xppedivdl divdl?" -d english.txt -p 1 -w
  76. With the bundled «english.txt» dictionary, this will produce 68 possible
  77. solutions, with «how much wood would a woodchuck chuck?» being one of them,
  78. showing bare minimum progress during the execution, writing substitution
  79. tables into «'.$conf['substabfp'].'» and solutions into «'.$conf['solsfp'].'» within
  80. current working directory.
  81. DISCLAIMER AND LICENSE
  82. This program comes with ABSOLUTELY NO WARRANTY; for details see the source.
  83. This is free software, and you are welcome to redistribute it under certain
  84. conditions; see <http://www.gnu.org/licenses/> for details.';
  85. // https://www.codiceedizioni.it/lccc-la-cura-cripto-contest/
  86. // 4E E1V VXVTHM VP SRR 0AY DA23I5VO L 2W4T WL 9ONA1Z K5 HIDUFJ 88LC NZD4 S50B.
  87. // this is almost certainly not encrypted by just replacing each source symbol
  88. // occurrence with the same different symbol
  89. // sempre caro mi fu quest'ermo colle
  90. // tfnqsf dbsp nj gv rvftu'fsnp dpmmf
  91. // e il naufragar m'è dolce in questo mare
  92. // f jm obvgsbhbs n'é epmdf jo rvftup nbsf
  93. // 6 v0 7òôõíò3òí x'à êú0ø6 v7 ûô6hwú xòí6 (random substitution table)
  94. // how much wood would a woodchuck chuck?
  95. // ipx nvdi xppe xpvme b xppedivdl divdl?
  96. for ($i=1; $i<$argc; $i++) {
  97. if ($argv[$i]=='-h' || $argv[$i]=='--help') {
  98. echo "{$help}\n";
  99. exit(0);
  100. } elseif ($argv[$i]=='--makereadme') {
  101. file_put_contents(__DIR__.'/README.md',"```text\n{$help}\n```\n");
  102. exit(0);
  103. } elseif ($argv[$i]=='-e' || $argv[$i]=='--encrypt') {
  104. $conf['encrypt']=true;
  105. } elseif ($argv[$i]=='-d' || $argv[$i]=='--dictfp') {
  106. if ($i<$argc-1) {
  107. $i++;
  108. $conf['dictfps'][]=$argv[$i];
  109. } else {
  110. edie("«{$argv[$i]}» requires an argument (use «-h» or «--help» for help); shutting down.\n",1);
  111. }
  112. } elseif ($argv[$i]=='-m' || $argv[$i]=='--method') {
  113. if ($i<$argc-1) {
  114. if (in_array($argv[$i+1],['0','1']))
  115. $conf['method']=$argv[$i+1]+0;
  116. else
  117. edie("«{$argv[$i+1]}» is not a valid argument for «{$argv[$i]}» (use «-h» or «--help» for help); shutting down.\n",1);
  118. $i++;
  119. } else {
  120. edie("«{$argv[$i]}» requires an argument (use «-h» or «--help» for help); shutting down.\n",1);
  121. }
  122. } elseif ($argv[$i]=='-w' || $argv[$i]=='--write') {
  123. $conf['write']=true;
  124. } elseif ($argv[$i]=='-n' || $argv[$i]=='--norm') {
  125. $conf['norm']=true;
  126. } elseif ($argv[$i]=='-p' || $argv[$i]=='--progress') {
  127. if ($i<$argc-1) {
  128. if (in_array($argv[$i+1],['0','1','2']))
  129. $conf['progress']=$argv[$i+1]+0;
  130. else
  131. edie("«{$argv[$i+1]}» is not a valid argument for «{$argv[$i]}» (use «-h» or «--help» for help); shutting down.\n",1);
  132. $i++;
  133. } else {
  134. edie("«{$argv[$i]}» requires an argument (use «-h» or «--help» for help); shutting down.\n",1);
  135. }
  136. } elseif (is_null($conf['phrase'])) {
  137. $conf['phrase']=trim($argv[$i]);
  138. } else {
  139. edie("a phrase has already been specified and «{$argv[$i]}» is not a known option (use «-h» or «--help» for help); shutting down.\n",1);
  140. }
  141. }
  142. //print_r($conf); pause();
  143. if (is_null($conf['phrase']))
  144. edie("you have not specified a phrase (use «-h» or «--help» for help); shutting down.\n",1);
  145. elseif ($conf['phrase']=='')
  146. edie("you can’t specify an empty string as phrase (use «-h» or «--help» for help); shutting down.\n",1);
  147. if ($conf['encrypt']) {
  148. $ichars='aàáâãäåbcdeèéêëfghiìíîïjklmnoòóôõöøpqrstuùúûüvwxyz0123456789';
  149. $ichars=mb_str_split($ichars,1,'UTF-8');
  150. $phrase=mb_strtolower($conf['phrase'],'UTF-8');
  151. $phrase=mb_str_split($phrase,1,'UTF-8');
  152. if ($conf['method']==1) {
  153. $buff=$ichars;
  154. shuffle($buff);
  155. $len=count($ichars);
  156. for ($i=0; $i<$len; $i++)
  157. $ochars[$ichars[$i]]=$buff[$i];
  158. $len=count($phrase);
  159. $buff='';
  160. for ($i=0; $i<$len; $i++) {
  161. $c=$phrase[$i];
  162. if (array_key_exists($c,$ochars))
  163. $buff.=$ochars[$c];
  164. else
  165. $buff.=$c;
  166. }
  167. } elseif ($conf['method']==0) {
  168. $buff='';
  169. $len=count($phrase);
  170. for ($i=0; $i<$len; $i++) {
  171. $c=$phrase[$i];
  172. if (!in_array($c,$ichars))
  173. $buff.=$c;
  174. else
  175. $buff.=mb_chr(mb_ord($c,'UTF-8')+1,'UTF-8');
  176. }
  177. }
  178. echo "{$buff}\n";
  179. exit(0);
  180. }
  181. if (count($conf['dictfps'])==0)
  182. edie("you have specified no dictionaries (use «-h» or «--help» for help); shutting down.\n",1);
  183. $norma=[
  184. 'a'=>['à','á','â','ã','ä','å'],
  185. 'e'=>['è','é','ê','ë'],
  186. 'i'=>['ì','í','î','ï'],
  187. 'o'=>['ò','ó','ô','õ','ö','ø'],
  188. 'u'=>['ù','ú','û','ü']
  189. ];
  190. $dict=[];
  191. foreach ($conf['dictfps'] as $fp) {
  192. $buff=@file($fp,FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES);
  193. if ($buff===false)
  194. edie("could not open dictionary file «{$fp}» (use «-h» or «--help» for help); shutting down.\n",1);
  195. foreach ($buff as $k=>$v)
  196. $conf['norm'] ? $dict[]=norm(mb_strtolower($v,'UTF-8')) : $dict[]=mb_strtolower($v,'UTF-8');
  197. }
  198. $dict=array_unique($dict);
  199. //shuffle($dict);
  200. foreach ($dict as $k=>$v) {
  201. $buff=mb_str_split($v,1,'UTF-8');
  202. $len=count($buff);
  203. if ($conf['method']==1)
  204. for ($i=0; $i<$len; $i++)
  205. $buff[$i]=mb_chr(mb_ord($buff[$i],'UTF-8')+$i,'UTF-8');
  206. $dict[$k]=['len'=>$len,'splitword'=>$buff,'word'=>$v];
  207. }
  208. if (count($dict)==0)
  209. edie("the dictionar(y|ies) you have specified contain(s) no words; shutting down.\n",1);
  210. //print_r($dict); pause();
  211. //foreach ($dict as $k=>$v) echo "{$v['word']}\n"; pause();
  212. $phrase=preg_replace('#\s+#u',' ',$conf['phrase']);
  213. $phrase=mb_strtolower($phrase,'UTF-8');
  214. $cphrase=$phrase;
  215. $phrase=preg_split('#\W+#u',$phrase,0,PREG_SPLIT_NO_EMPTY);
  216. //$phrase=explode(' ',$phrase);
  217. //print_r($phrase); pause();
  218. $buff=[];
  219. foreach ($phrase as $k=>$v)
  220. $buff[$k]=mb_strlen($v,'UTF-8');
  221. arsort($buff,SORT_NUMERIC);
  222. foreach ($phrase as $k=>$v) {
  223. $split=mb_str_split($v,1,'UTF-8');
  224. $len=count($split);
  225. $buff[$k]=['len'=>$len,'splitword'=>$split,'word'=>$v,'sols'=>[]];
  226. }
  227. $phrase=$buff;
  228. //print_r($phrase); pause();
  229. $c=count($phrase);
  230. $d=0;
  231. $cns=0;
  232. $substa=[[]];
  233. foreach ($phrase as $pindex=>$pword) {
  234. $d++;
  235. $ib=$pindex+1;
  236. eprog(1,"Working on phrase word {$ib}: «{$pword['word']}» ({$d}/{$c})\n");
  237. $newsubsta=ckdict($dict,$pword,$pindex,$substa);
  238. /*foreach ($newsubsta as $k=>$v)
  239. echo strsubsta($v)."\n";*/
  240. $cns=count($newsubsta);
  241. eprog(1,"Finished working on phrase word {$ib}: «{$pword['word']}» ({$d}/{$c}); found {$cns} compatible keys.\n");
  242. //pause();
  243. if ($cns==0) break;
  244. $substa=$newsubsta;
  245. unset($newsubsta);
  246. }
  247. if (count($substa)>0) {
  248. if ($conf['write'])
  249. if (false===file_put_contents($conf['substabfp'],print_r($substa,true)))
  250. eerr("Warning: could not open «{$conf['substabfp']}» for writing.\n");
  251. //ksort($phrase,SORT_NUMERIC);
  252. $i=0;
  253. if ($conf['write']) {
  254. $f=@fopen($conf['solsfp'],'w');
  255. if ($f===false)
  256. eerr("Warning: could not open «{$conf['solsfp']}» for writing.\n");
  257. }
  258. foreach ($substa as $subst) {
  259. $i++;
  260. $oline=$i.': '.getsubst($subst,$cphrase)."\n";
  261. echo $oline;
  262. if ($conf['write'] && $f!==false) fwrite($f,$oline);
  263. }
  264. if ($conf['write'] && $f!==false) fclose($f);
  265. if ($cns==0) echo "Warning: no complete solutions found.\n";
  266. } else {
  267. echo "No solutions found.\n";
  268. }
  269. exit(0);
  270. function ckdict(&$dict,$pword,$pindex,$substa) {
  271. global $phrase, $conf;
  272. $newsubsta=[];
  273. foreach ($dict as $dword) {
  274. if ($dword['len']==$pword['len']) {
  275. //echo "«{$pword['word']}» has same length as «{$dword['word']}»\n";
  276. foreach ($substa as $subst) {
  277. $newsubst=$subst;
  278. //echo '-> '.strsubsta($newsubst)."\n";
  279. $ok=true;
  280. for ($i=0; $i<$pword['len']; $i++) {
  281. $pc=$pword['splitword'][$i];
  282. $dc=$dword['splitword'][$i];
  283. //$dc=mb_chr(mb_ord($dword['splitword'][$i],'UTF-8')+$i,'UTF-8');
  284. $x=array_search($dc,$newsubst,true);
  285. if (($x!==false && $x!=$pc) || (array_key_exists($pc,$newsubst) && $newsubst[$pc]!=$dc)) {
  286. $ok=false;
  287. break;
  288. }
  289. $newsubst[$pc]=$dc;
  290. }
  291. if ($ok) {
  292. if ($conf['progress']>1 && !in_array($dword['word'],$phrase[$pindex]['sols'])) {
  293. $phrase[$pindex]['sols'][]=$dword['word'];
  294. echo "«{$pword['word']}» could be «{$dword['word']}»\n";
  295. }
  296. $newsubsta[]=$newsubst;
  297. }
  298. }
  299. }
  300. }
  301. return $newsubsta;
  302. }
  303. function eprog($lev,$str) {
  304. global $conf;
  305. if ($lev<=$conf['progress'])
  306. echo $str;
  307. }
  308. function eerr($str) {
  309. fwrite(STDERR,$str);
  310. }
  311. function pause() {
  312. echo "Press return to continue ";
  313. fgets(STDIN);
  314. }
  315. function getsubst($subst,$str) {
  316. $out='';
  317. $str=mb_str_split($str,1,'UTF-8');
  318. $len=count($str);
  319. for ($i=0; $i<$len; $i++)
  320. if (array_key_exists($str[$i],$subst))
  321. $out.="\33[0;7m{$subst[$str[$i]]}\33[0;0m";
  322. else
  323. $out.=$str[$i];
  324. str_replace("\33[0;0m\33[0;7m",'',$out);
  325. return $out;
  326. }
  327. function norm($str) {
  328. global $norma;
  329. $out='';
  330. $str=mb_str_split($str,1,'UTF-8');
  331. $len=count($str);
  332. for ($i=0; $i<$len; $i++) {
  333. $nc=$str[$i];
  334. foreach ($norma as $k=>$nnca)
  335. if (in_array($str[$i],$nnca)) {
  336. $nc=$k;
  337. break;
  338. }
  339. $out.=$nc;
  340. }
  341. return $out;
  342. }
  343. function strsubsta($arr) {
  344. $out='';
  345. foreach ($arr as $key=>$val)
  346. $out.="{$key}={$val}; ";
  347. if (strlen($out)>0)
  348. $out=substr($out,0,-2);
  349. return $out;
  350. }
  351. function edie($msg,$ec) {
  352. fwrite(STDERR,'Error: '.$msg);
  353. exit($ec);
  354. }
  355. ?>