bsum.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. /*
  2. *******************************************************************************
  3. \file bsum.c
  4. \brief Hash files using belt-hash / bash-hash
  5. \project bee2/cmd
  6. \created 2014.10.28
  7. \version 2024.06.14
  8. \copyright The Bee2 authors
  9. \license Licensed under the Apache License, Version 2.0 (see LICENSE.txt).
  10. *******************************************************************************
  11. */
  12. #include "../cmd.h"
  13. #include <bee2/core/dec.h>
  14. #include <bee2/core/hex.h>
  15. #include <bee2/core/mem.h>
  16. #include <bee2/core/str.h>
  17. #include <bee2/core/util.h>
  18. #include <bee2/crypto/bash.h>
  19. #include <bee2/crypto/belt.h>
  20. #include <stdio.h>
  21. #ifdef OS_WIN
  22. #include <locale.h>
  23. #endif
  24. /*
  25. *******************************************************************************
  26. Утилита bsum
  27. Функционал:
  28. - хэширование файлов с помощью алгоритмов СТБ 34.101.31 и СТБ 34.101.77;
  29. - проверка хэш-значений.
  30. Поддержаны следующие алгоритмы хэширования:
  31. - belt-hash (СТБ 34.101.31);
  32. - bash32, bash64, ..., bash512 (СТБ 34.101.77);
  33. - bash-prg-hashNNND (СТБ 34.101.77), где NNN in {256, 384, 512}, D in {1, 2}.
  34. \remark В алгоритмах bash-prg-hashNNND используется пустой анонс (annonce, фр.).
  35. Хэш-значения выводятся в формате
  36. ```
  37. hex(хэш_значение_файла) имя_файла
  38. ```
  39. Файл такого формата используется при проверке хэш-значений.
  40. \remark Такой же формат файла хэш-значений используется в утилитах
  41. {md5|sha1|sha256}sum. В bsum частично повторен интерфейс командной строки
  42. этих утилит.
  43. Примеры:
  44. bee2cmd bsum file1 file2 file3
  45. bee2cmd bsum -belt-hash file1 file2 file3 > checksum
  46. bee2cmd bsum -c checksum
  47. bee2cmd bsum -- -c
  48. Обратим внимание на последнюю команду. В ней лексема "--" означает окончание
  49. опций командной строки. Следующий за лексемой параметр "-с" будет
  50. интерпретироваться как имя файла, а не как опция.
  51. \warning В Windows имена файлов на русском языке будут записаны в checksum_file
  52. в кодировке cp1251. В Linux -- в кодировке UTF8.
  53. \todo Поддержка UTF8 в Windows.
  54. *******************************************************************************
  55. */
  56. static const char _name[] = "bsum";
  57. static const char _descr[] = "hash files using {belt|bash} algorithms";
  58. static int bsumUsage()
  59. {
  60. printf(
  61. "bee2cmd/%s: %s\n"
  62. "Usage:\n"
  63. " bsum [hash_alg] <file_to_hash> <file_to_hash> ...\n"
  64. " bsum [hash_alg] -c <checksum_file>\n"
  65. " hash_alg:\n"
  66. " -belt-hash (STB 34.101.31), by default\n"
  67. " -bash32, -bash64, ..., -bash512 (STB 34.101.77)\n"
  68. " -bash-prg-hashNNND (STB 34.101.77)\n"
  69. " with NNN in {256, 384, 512}, D in {1, 2}\n"
  70. " \\note annonce = NULL\n"
  71. " \\remark use \"--\" to stop parsing options"
  72. ,
  73. _name, _descr
  74. );
  75. return -1;
  76. }
  77. /*
  78. *******************************************************************************
  79. Разбор параметров
  80. Идентификатор хэш-алгоритма (hid), заданного в командной строке:
  81. * 0 -- belt-hash;
  82. * 32, 64, ..., 512 -- bash32, bash64, ..., bash512;
  83. * NNND -- bash-prg-hashNNND (NNN in {256, 384, 512}, D in {1, 2}).
  84. *******************************************************************************
  85. */
  86. static bool_t bsumHidIsValid(size_t hid)
  87. {
  88. return hid == 0 ||
  89. (hid <= 512 && hid % 32 == 0) ||
  90. (hid % 10 != 0 && hid % 10 <= 2 &&
  91. (hid / 10) % 128 == 0 && 2 <= hid / 1280 && hid / 1280 <= 4);
  92. }
  93. static size_t bsumHidHashLen(size_t hid)
  94. {
  95. ASSERT(bsumHidIsValid(hid));
  96. return hid == 0 ? 32 : (hid <= 512 ? hid / 8 : hid / 80);
  97. };
  98. /*
  99. *******************************************************************************
  100. Хэширование файла
  101. \remark Если в функции bsumHash() переместить переменную buf в кучу,
  102. то скорость обработки больших файлов (несколько Gb) существенно упадет.
  103. Возможные объяснения:
  104. ```
  105. https://stackoverflow.com/questions/24057331/
  106. is-accessing-data-in-the-heap-faster-than-from-the-stack
  107. ```
  108. *******************************************************************************
  109. */
  110. static int bsumHash(octet hash[], size_t hid, const char* filename)
  111. {
  112. octet buf[32768];
  113. octet state[4096];
  114. size_t hash_len;
  115. void (*step_hash)(const void*, size_t, void*);
  116. FILE* fp;
  117. size_t count;
  118. // pre
  119. ASSERT(beltHash_keep() <= sizeof(state));
  120. ASSERT(bashHash_keep() <= sizeof(state));
  121. ASSERT(bashPrg_keep() <= sizeof(state));
  122. // обработать hid
  123. hash_len = bsumHidHashLen(hid);
  124. if (hid == 0)
  125. {
  126. beltHashStart(state);
  127. step_hash = beltHashStepH;
  128. }
  129. else if (hid <= 512)
  130. {
  131. bashHashStart(state, hid / 2);
  132. step_hash = bashHashStepH;
  133. }
  134. else
  135. {
  136. bashPrgStart(state, hid / 20, hid % 10, 0, 0, 0, 0);
  137. bashPrgAbsorbStart(state);
  138. step_hash = bashPrgAbsorbStep;
  139. }
  140. ASSERT(memIsValid(hash, hash_len));
  141. // открыть файл
  142. fp = fopen(filename, "rb");
  143. if (!fp)
  144. {
  145. printf("%s: FAILED [open]\n", filename);
  146. return -1;
  147. }
  148. // читать и хэшировать файл
  149. do
  150. {
  151. count = fread(buf, 1, sizeof(buf), fp);
  152. step_hash(buf, count, state);
  153. }
  154. while (count == sizeof(buf));
  155. // ошибка чтения?
  156. if (ferror(fp))
  157. {
  158. fclose(fp);
  159. memWipe(buf, sizeof(buf));
  160. memWipe(state, sizeof(state));
  161. printf("%s: FAILED [read]\n", filename);
  162. return -1;
  163. }
  164. // закрыть файл
  165. if (fclose(fp) != 0)
  166. {
  167. memWipe(buf, sizeof(buf));
  168. memWipe(state, sizeof(state));
  169. printf("%s: FAILED [close]\n", filename);
  170. return -1;
  171. }
  172. // возвратить хэш-значение
  173. if (hid == 0)
  174. beltHashStepG(hash, state);
  175. else if (hid <= 512)
  176. bashHashStepG(hash, hash_len, state);
  177. else
  178. bashPrgSqueeze(hash, hash_len, state);
  179. // завершить
  180. memWipe(buf, sizeof(buf));
  181. memWipe(state, sizeof(state));
  182. return 0;
  183. }
  184. static int bsumPrint(size_t hid, int argc, char* argv[])
  185. {
  186. octet hash[64];
  187. char str[64 * 2 + 8];
  188. int ret = 0;
  189. for (; argc--; argv++)
  190. {
  191. if (bsumHash(hash, hid, argv[0]) != 0)
  192. {
  193. ret = -1;
  194. continue;
  195. }
  196. hexFrom(str, hash, bsumHidHashLen(hid));
  197. hexLower(str);
  198. printf("%s %s\n", str, argv[0]);
  199. }
  200. return ret;
  201. }
  202. static int bsumCheck(size_t hid, const char* filename)
  203. {
  204. octet hash[64];
  205. size_t hash_len;
  206. char str[1024];
  207. size_t str_len;
  208. FILE* fp;
  209. size_t all_lines = 0;
  210. size_t bad_lines = 0;
  211. size_t bad_files = 0;
  212. size_t bad_hashes = 0;
  213. // длина хэш-значения в байтах
  214. hash_len = bsumHidHashLen(hid);
  215. // открыть файл контрольных сумм
  216. fp = fopen(filename, "rb");
  217. if (!fp)
  218. {
  219. printf("%s: No such file\n", filename);
  220. return -1;
  221. }
  222. for (; fgets(str, sizeof(str), fp); ++all_lines)
  223. {
  224. // проверить строку
  225. str_len = strLen(str);
  226. if (str_len < hash_len * 2 + 2 ||
  227. str[2 * hash_len] != ' ' ||
  228. str[2 * hash_len + 1] != ' ' ||
  229. (str[hash_len * 2] = 0, !hexIsValid(str)))
  230. {
  231. bad_lines++;
  232. continue;
  233. }
  234. // выделить имя файла
  235. if(str[str_len - 1] == '\n')
  236. str[--str_len] = 0;
  237. if(str[str_len - 1] == '\r')
  238. str[--str_len] = 0;
  239. // хэшировать
  240. if (bsumHash(hash, hid, str + 2 * hash_len + 2) == -1)
  241. {
  242. bad_files++;
  243. continue;
  244. }
  245. if (!hexEq(hash, str))
  246. {
  247. bad_hashes++;
  248. printf("%s: FAILED [checksum]\n", str + 2 * hash_len + 2);
  249. continue;
  250. }
  251. printf("%s: OK\n", str + 2 * hash_len + 2);
  252. }
  253. // закрыть файл контрольных сумм
  254. if (fclose(fp) != 0)
  255. {
  256. printf("%s: FAILED [close]\n", filename);
  257. return -1;
  258. }
  259. // печать предупреждений
  260. if (bad_lines)
  261. fprintf(stderr, bad_lines == 1 ?
  262. "WARNING: %lu input line (out of %lu) is improperly formatted\n" :
  263. "WARNING: %lu input lines (out of %lu) are improperly formatted\n",
  264. (unsigned long)bad_lines, (unsigned long)all_lines);
  265. if (bad_files)
  266. fprintf(stderr, bad_files == 1 ?
  267. "WARNING: %lu listed file could not be opened or read\n" :
  268. "WARNING: %lu listed files could not be opened or read\n",
  269. (unsigned long)bad_files);
  270. if (bad_hashes)
  271. fprintf(stderr, bad_hashes == 1 ?
  272. "WARNING: %lu computed checksum did not match\n":
  273. "WARNING: %lu computed checksums did not match\n",
  274. (unsigned long)bad_hashes);
  275. return (bad_lines || bad_files || bad_hashes) ? -1 : 0;
  276. }
  277. /*
  278. *******************************************************************************
  279. Главная функция
  280. *******************************************************************************
  281. */
  282. int bsumMain(int argc, char* argv[])
  283. {
  284. err_t code = ERR_OK;
  285. size_t hid = SIZE_MAX;
  286. bool_t check = FALSE;
  287. #ifdef OS_WIN
  288. setlocale(LC_ALL, "russian_belarus.1251");
  289. #endif
  290. // справка
  291. if (argc < 2)
  292. return bsumUsage();
  293. // разбор опций
  294. ++argv, --argc;
  295. while (argc && strStartsWith(argv[0], "-"))
  296. {
  297. // belt-hash
  298. if (strStartsWith(argv[0], "-belt-hash"))
  299. {
  300. if (hid != SIZE_MAX)
  301. {
  302. code = ERR_CMD_PARAMS;
  303. break;
  304. }
  305. hid = 0;
  306. --argc, ++argv;
  307. }
  308. // bash-prg-hash
  309. else if (strStartsWith(argv[0], "-bash-prg-hash"))
  310. {
  311. char* alg_name = argv[0] + strLen("-bash-prg-hash");
  312. if (hid != SIZE_MAX || !decIsValid(alg_name) ||
  313. strLen(alg_name) != 4 || decCLZ(alg_name) ||
  314. !bsumHidIsValid(hid = (size_t)decToU32(alg_name)))
  315. {
  316. code = ERR_CMD_PARAMS;
  317. break;
  318. }
  319. --argc, ++argv;
  320. }
  321. // bash
  322. else if (strStartsWith(argv[0], "-bash"))
  323. {
  324. char* alg_name = argv[0] + strLen("-bash");
  325. if (hid != SIZE_MAX || !decIsValid(alg_name) ||
  326. 2 > strLen(alg_name) || strLen(alg_name) > 4 ||
  327. decCLZ(alg_name) ||
  328. !bsumHidIsValid(hid = (size_t)decToU32(alg_name)))
  329. {
  330. code = ERR_CMD_PARAMS;
  331. break;
  332. }
  333. --argc, ++argv;
  334. }
  335. // check
  336. else if (strEq(argv[0], "-c"))
  337. {
  338. if (check)
  339. {
  340. code = ERR_CMD_PARAMS;
  341. break;
  342. }
  343. check = TRUE;
  344. --argc, ++argv;
  345. }
  346. // --
  347. else if (strEq(argv[0], "--"))
  348. {
  349. --argc, ++argv;
  350. break;
  351. }
  352. else
  353. {
  354. code = ERR_CMD_PARAMS;
  355. break;
  356. }
  357. }
  358. // дополнительные проверки и обработка ошибок
  359. if (code == ERR_OK && (argc < 1 || check && argc != 1))
  360. code = ERR_CMD_PARAMS;
  361. if (code != ERR_OK)
  362. {
  363. fprintf(stderr, "bee2cmd/%s: %s\n", _name, errMsg(code));
  364. return -1;
  365. }
  366. // belt-hash по умолчанию
  367. if (hid == SIZE_MAX)
  368. hid = 0;
  369. // вычисление/проверка хэш-значениий
  370. ASSERT(bsumHidIsValid(hid));
  371. return check ? bsumCheck(hid, argv[0]) : bsumPrint(hid, argc, argv);
  372. }
  373. /*
  374. *******************************************************************************
  375. Инициализация
  376. *******************************************************************************
  377. */
  378. err_t bsumInit()
  379. {
  380. return cmdReg(_name, _descr, bsumMain);
  381. }