ImageLSB_Encoder.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. using System.Text;
  2. using System.Text.RegularExpressions;
  3. using SixLabors.ImageSharp;
  4. using SixLabors.ImageSharp.Formats.Png;
  5. using SixLabors.ImageSharp.PixelFormats;
  6. namespace VberZero.Tools;
  7. public static class ImageLSB_Encoder
  8. {
  9. #region 隐藏、提取 消息
  10. #region Base64
  11. /// <summary>
  12. /// 将给定的消息(文件)隐藏在给定的载体(图像)中
  13. /// </summary>
  14. /// <param name="path"></param>
  15. /// <param name="message"></param>
  16. /// <returns></returns>
  17. public static string HideMessageBackBase64(string path, string message)
  18. {
  19. var payload = Encoding.UTF8.GetBytes(message);
  20. return HideBytesBackBase64(path, payload);
  21. }
  22. /// <summary>
  23. /// 将给定的消息(文件)隐藏在给定的载体(图像)中
  24. /// </summary>
  25. /// <param name="carrierImage"></param>
  26. /// <param name="message"></param>
  27. /// <returns></returns>
  28. public static string HideMessageBackBase64(this Image<Rgba32> carrierImage, string message)
  29. {
  30. var payload = Encoding.UTF8.GetBytes(message);
  31. return HideBytesBackBase64(carrierImage, payload);
  32. }
  33. /// <summary>
  34. /// 将给定的消息(文件)隐藏在给定的载体(图像)中
  35. /// </summary>
  36. /// <param name="path"></param>
  37. /// <param name="payload"></param>
  38. /// <returns></returns>
  39. public static string HideBytesBackBase64(string path, byte[] payload)
  40. {
  41. using var carrierImage = (Image<Rgba32>)Image.Load(path);
  42. return HideBytesBackBase64(carrierImage, payload);
  43. }
  44. /// <summary>
  45. /// 将给定的消息(文件)隐藏在给定的载体(图像)中
  46. /// </summary>
  47. /// <param name="carrierImage"></param>
  48. /// <param name="payload"></param>
  49. /// <returns></returns>
  50. public static string HideBytesBackBase64(this Image<Rgba32> carrierImage, byte[] payload)
  51. {
  52. var image = HideBytes(carrierImage, payload);
  53. var base64Str = image.ToBase64String(PngFormat.Instance);
  54. return base64Str;
  55. }
  56. /// <summary>
  57. /// 从stego图像(base64String)中提取字符串消息
  58. /// </summary>
  59. /// <param name="base64String"></param>
  60. /// <returns></returns>
  61. public static string ExtractMessageFromBase64(string base64String)
  62. {
  63. base64String = base64String.StartsWith("data") ? base64String.Split(',')[1] : base64String;
  64. byte[] data = Convert.FromBase64String(base64String);
  65. using var image = (Image<Rgba32>)Image.Load(data);
  66. var payload = ExtractBytes(image);
  67. var message = Encoding.UTF8.GetString(payload);
  68. return message;
  69. }
  70. /// <summary>
  71. /// 从stego图像(base64String)中提取消息并将其作为字节数组返回
  72. /// </summary>
  73. /// <param name="base64String"></param>
  74. /// <returns></returns>
  75. public static byte[] ExtractBytesFromBase64(string base64String)
  76. {
  77. base64String = base64String.StartsWith("data") ? base64String.Split(',')[1] : base64String;
  78. byte[] data = Convert.FromBase64String(base64String);
  79. using var image = (Image<Rgba32>)Image.Load(data);
  80. var payload = ExtractBytes(image);
  81. return payload;
  82. }
  83. #endregion Base64
  84. #region Hide
  85. /// <summary>
  86. /// 将给定的消息隐藏在给定的载体(图像)中
  87. /// </summary>
  88. /// <param name="path"></param>
  89. /// <param name="message"></param>
  90. /// <returns></returns>
  91. public static Image<Rgba32> HideMessage(string path, string message)
  92. {
  93. using var carrierImage = (Image<Rgba32>)Image.Load(path);
  94. return HideMessage(carrierImage, message);
  95. }
  96. /// <summary>
  97. /// 将给定的消息隐藏在给定的载体(图像)中
  98. /// </summary>
  99. /// <param name="carrierImage"></param>
  100. /// <param name="message"></param>
  101. /// <returns></returns>
  102. public static Image<Rgba32> HideMessage(this Image<Rgba32> carrierImage, string message)
  103. {
  104. var payload = Encoding.UTF8.GetBytes(message);
  105. return HideBytes(carrierImage, payload);
  106. }
  107. /// <summary>
  108. /// 将给定的消息(文件)隐藏在给定的载体(图像)中
  109. /// </summary>
  110. /// <param name="path"></param>
  111. /// <param name="payload"></param>
  112. /// <returns></returns>
  113. public static Image<Rgba32> HideBytes(string path, byte[] payload)
  114. {
  115. using var carrierImage = (Image<Rgba32>)Image.Load(path);
  116. return HideBytes(carrierImage, payload);
  117. }
  118. /// <summary>
  119. /// 将给定的消息(文件)隐藏在给定的载体(图像)中
  120. /// </summary>
  121. /// <param name="carrierImage"></param>
  122. /// <param name="payload"></param>
  123. /// <returns></returns>
  124. public static Image<Rgba32> HideBytes(this Image<Rgba32> carrierImage, byte[] payload)
  125. {
  126. //var stegoImage = carrierImage.Clone();
  127. var stegoImage = carrierImage.Clone();
  128. //获取消息对象的二进制字符串及其长度
  129. var completeMessage = GenerateMessageBitPattern(payload);
  130. var completeMessageLength = (uint)completeMessage.Length;
  131. //如果消息太大,则引发异常
  132. var carrierCapacity = CalculateCapacity(carrierImage, "bits");
  133. if (completeMessageLength > carrierCapacity) throw new Exception("MessageTooBigException");
  134. var pixelX = 0;
  135. var pixelY = 0;
  136. var messageBitCounter = 0;
  137. while (messageBitCounter < completeMessageLength)
  138. {
  139. if (pixelY >= stegoImage.Height)
  140. {
  141. break;
  142. }
  143. // 获取当前像素
  144. var pixel = stegoImage[pixelX, pixelY];
  145. //选择使用像素的三个LSB中的R、G、B
  146. byte color;
  147. switch (messageBitCounter % 3)
  148. {
  149. case 0:
  150. color = InsertMessageBit(pixel.R, completeMessage[messageBitCounter].ToString());
  151. stegoImage[pixelX, pixelY] = new Rgba32(color, pixel.G, pixel.B, pixel.A);
  152. break;
  153. case 1:
  154. color = InsertMessageBit(pixel.G, completeMessage[messageBitCounter].ToString());
  155. stegoImage[pixelX, pixelY] = new Rgba32(pixel.R, color, pixel.B, pixel.A);
  156. break;
  157. case 2:
  158. color = InsertMessageBit(pixel.B, completeMessage[messageBitCounter].ToString());
  159. stegoImage[pixelX, pixelY] = new Rgba32(pixel.R, pixel.G, color, pixel.A);
  160. // 获取下一个像素
  161. pixelX++;
  162. if (pixelX >= stegoImage.Width)
  163. {
  164. pixelX = 0;
  165. pixelY++;
  166. }
  167. break;
  168. }
  169. messageBitCounter++;
  170. }
  171. return stegoImage;
  172. }
  173. #endregion Hide
  174. #region Extract
  175. /// <summary>
  176. /// 从stego图像中提取字符串消息
  177. /// </summary>
  178. /// <param name="path"></param>
  179. /// <returns></returns>
  180. public static string ExtractMessage(string path)
  181. {
  182. using var image = (Image<Rgba32>)Image.Load(path);
  183. var payload = ExtractBytes(image);
  184. var message = Encoding.UTF8.GetString(payload);
  185. return message;
  186. }
  187. /// <summary>
  188. /// 从stego图像中提取字符串消息
  189. /// </summary>
  190. /// <param name="carrierImage"></param>
  191. /// <returns></returns>
  192. public static string ExtractMessage(this Image<Rgba32> carrierImage)
  193. {
  194. var payload = ExtractBytes(carrierImage);
  195. var message = Encoding.UTF8.GetString(payload);
  196. return message;
  197. }
  198. /// <summary>
  199. /// 从stego图像中提取消息并将其作为字节数组返回
  200. /// </summary>
  201. /// <param name="path"></param>
  202. /// <returns></returns>
  203. public static byte[] ExtractBytes(string path)
  204. {
  205. using var image = (Image<Rgba32>)Image.Load(path);
  206. var payload = ExtractBytes(image);
  207. return payload;
  208. }
  209. /// <summary>
  210. /// 从stego图像中提取消息并将其作为字节数组返回
  211. /// </summary>
  212. /// <param name="stegoImage"></param>
  213. /// <returns></returns>
  214. public static byte[] ExtractBytes(this Image<Rgba32> stegoImage)
  215. {
  216. //StringBuilder messageNameBuilder = new StringBuilder();
  217. var payloadSizeBuilder = new StringBuilder();
  218. var payloadBuilder = new StringBuilder();
  219. Rgba32 pixel;
  220. var pixelX = 0;
  221. var pixelY = 0;
  222. var messageBitCounter = 0;
  223. // 提取有效载荷的大小
  224. while (messageBitCounter < 24)
  225. {
  226. if (pixelY >= stegoImage.Height)
  227. {
  228. break;
  229. }
  230. pixel = stegoImage[pixelX, pixelY];
  231. switch (messageBitCounter % 3)
  232. {
  233. case 0:
  234. payloadSizeBuilder.Append(ExtractLsbAsString(pixel.R));
  235. break;
  236. case 1:
  237. payloadSizeBuilder.Append(ExtractLsbAsString(pixel.G));
  238. break;
  239. case 2:
  240. payloadSizeBuilder.Append(ExtractLsbAsString(pixel.B));
  241. pixelX++;
  242. if (pixelX >= stegoImage.Width)
  243. {
  244. pixelX = 0;
  245. pixelY++;
  246. }
  247. break;
  248. }
  249. messageBitCounter++;
  250. }
  251. // 组成有效载荷的大小
  252. var payloadSizeString = payloadSizeBuilder.ToString();
  253. var payloadSize = BinaryToUint(payloadSizeString);
  254. // 提取有效载荷
  255. while (messageBitCounter < payloadSize + 24)
  256. {
  257. if (pixelY >= stegoImage.Height)
  258. {
  259. break;
  260. }
  261. pixel = stegoImage[pixelX, pixelY];
  262. switch (messageBitCounter % 3)
  263. {
  264. case 0:
  265. payloadBuilder.Append(ExtractLsbAsString(pixel.R));
  266. break;
  267. case 1:
  268. payloadBuilder.Append(ExtractLsbAsString(pixel.G));
  269. break;
  270. case 2:
  271. payloadBuilder.Append(ExtractLsbAsString(pixel.B));
  272. pixelX++;
  273. if (pixelX >= stegoImage.Width)
  274. {
  275. pixelX = 0;
  276. pixelY++;
  277. }
  278. break;
  279. }
  280. messageBitCounter++;
  281. }
  282. var payloadString = payloadBuilder.ToString();
  283. var payload = ConvertBitStringToByteArray(payloadString);
  284. return payload;
  285. }
  286. #endregion Extract
  287. #endregion 隐藏、提取 消息
  288. #region Private
  289. /// <summary>
  290. /// 计算完整消息(不仅是有效负载)和图像的LSB之间的汉明距离,并将它们作为整数返回(所有8字节最后一位置为偶数)
  291. /// </summary>
  292. /// <param name="carrierImage"></param>
  293. /// <param name="message"></param>
  294. /// <exception cref="ArgumentException"></exception>
  295. /// <exception cref="ArgumentOutOfRangeException"></exception>
  296. /// <returns></returns>
  297. private static int CalculateHammingDistance(Image<Rgba32> carrierImage, string message)
  298. {
  299. // 获取有关载体的所有必要信息
  300. uint carrierWidth = (uint)carrierImage.Width;
  301. uint carrierHeight = (uint)carrierImage.Height;
  302. uint maxCarrierPixels = (carrierWidth * carrierHeight);
  303. uint capacity = (3 * maxCarrierPixels);
  304. if (message.Length > capacity)
  305. {
  306. throw new Exception("MessageTooBigException");
  307. }
  308. // 收集加扰载体的LSB
  309. string carrierLsb = CollectCarrierLsb(carrierImage);
  310. // 计算Hamming距离
  311. int hammingDistance = 0;
  312. int lsbCounter = 0;
  313. foreach (char bit in message)
  314. {
  315. // 如果数组的索引达到2 ^ 31,则需要重新设置。这是因为数组只能通过int值访问
  316. if (lsbCounter == int.MaxValue)
  317. {
  318. carrierLsb = carrierLsb.Substring(lsbCounter);
  319. lsbCounter = 0;
  320. }
  321. // 如果消息位和LSB不匹配,则增加汉明距离
  322. if (!Equals(bit, carrierLsb[lsbCounter]))
  323. {
  324. hammingDistance++;
  325. }
  326. lsbCounter++;
  327. }
  328. return hammingDistance;
  329. }
  330. /// <summary>
  331. /// 收集从像素排序的给定载体图像的所有LSB
  332. /// (0,0)到(xMax,yMax)以及颜色R,G,B,并将它们作为字符串返回
  333. /// </summary>
  334. /// <param name="carrierImage"></param>
  335. private static string CollectCarrierLsb(Image<Rgba32> carrierImage)
  336. {
  337. StringBuilder sb = new StringBuilder();
  338. int width = carrierImage.Width;
  339. int height = carrierImage.Height;
  340. // 在整个图像上循环
  341. for (int y = 0; y < height; y++)
  342. {
  343. for (int x = 0; x < width; x++)
  344. {
  345. var pixel = carrierImage[x, y];
  346. sb.Append(ExtractLsbAsString(pixel.R));
  347. sb.Append(ExtractLsbAsString(pixel.G));
  348. sb.Append(ExtractLsbAsString(pixel.B));
  349. }
  350. }
  351. return sb.ToString();
  352. }
  353. /// <summary>
  354. /// 从消息对象生成二进制位字符串
  355. /// </summary>
  356. /// <param name="payload"></param>
  357. /// <exception cref="ArgumentException"></exception>
  358. /// <exception cref="ArgumentOutOfRangeException"></exception>
  359. /// <returns></returns>
  360. private static string GenerateMessageBitPattern(byte[] payload)
  361. {
  362. //将数据转换为二进制字符串
  363. var payloadSize = (uint)payload.Length * 8;
  364. var payloadBinary = ByteArrayToBinary(payload);
  365. var payloadSizeBinary = DecimalToBinary(payloadSize, 24);
  366. var sb = new StringBuilder();
  367. sb.Append(payloadSizeBinary);
  368. sb.Append(payloadBinary);
  369. return sb.ToString();
  370. }
  371. /// <summary>
  372. /// 在任意字节值中插入新的LSB
  373. /// </summary>
  374. /// <param name="carrierByte"></param>
  375. /// <param name="msgBit"></param>
  376. /// <returns></returns>
  377. private static byte InsertMessageBit(byte carrierByte, string msgBit)
  378. {
  379. var sb = new StringBuilder(DecimalToBinary(carrierByte, 8));
  380. sb.Remove(sb.Length - 1, 1);
  381. sb.Append(msgBit);
  382. var stegoByte = BinaryToByte(sb.ToString());
  383. return stegoByte;
  384. }
  385. /// <summary>
  386. /// 从字节中提取LSB并将其作为字符串返回
  387. /// </summary>
  388. /// <param name="inputByte"></param>
  389. private static string ExtractLsbAsString(byte inputByte)
  390. {
  391. return inputByte % 2 == 0 ? "0" : "1";
  392. }
  393. private static uint CalculateCapacity(Image carrier, string unit)
  394. {
  395. var width = (uint)carrier.Width;
  396. var height = (uint)carrier.Height;
  397. switch (unit)
  398. {
  399. case "bit":
  400. return 3 * width * height;
  401. case "byte":
  402. return 3 * width * height / 8;
  403. case "kB":
  404. return 3 * width * height / 8192;
  405. case "mB":
  406. return 3 * width * height / 8192 / 1024;
  407. case null:
  408. return 3 * width * height;
  409. default:
  410. return 3 * width * height;
  411. }
  412. }
  413. #endregion Private
  414. #region Converter
  415. /// <summary>
  416. /// 将文本字符串转换为二进制字符串模式
  417. /// </summary>
  418. /// <param name="str"></param>
  419. /// <param name="zeroPadding"></param>
  420. /// <returns></returns>
  421. private static string StringToBinary(string str, int zeroPadding)
  422. {
  423. var encoding = new UTF8Encoding();
  424. var buffer = encoding.GetBytes(str);
  425. if (buffer.Length > 64) throw new Exception("MessageNameTooBigException");
  426. if (buffer.Length < 64) Array.Resize(ref buffer, zeroPadding);
  427. var sb = new StringBuilder();
  428. foreach (var element in buffer) sb.Append(Convert.ToString(element, 2).PadLeft(8, '0'));
  429. return sb.ToString();
  430. }
  431. /// <summary>
  432. /// 将 int 对象转换为其二进制字符串表示形式,并用左侧指定数量的零
  433. /// </summary>
  434. /// <param name="decimalNumber"></param>
  435. /// <param name="zeroPadding"></param>
  436. /// <returns></returns>
  437. private static string DecimalToBinary(int decimalNumber, int zeroPadding = 0)
  438. {
  439. if (zeroPadding != 0) return Convert.ToString(decimalNumber, 2).PadLeft(zeroPadding, '0');
  440. return Convert.ToString(decimalNumber, 2);
  441. }
  442. /// <summary>
  443. /// 将 uint 对象转换为其二进制字符串表示形式,并用左侧指定数量的零
  444. /// </summary>
  445. /// <param name="decimalNumber"></param>
  446. /// <param name="zeroPadding"></param>
  447. /// <returns></returns>
  448. private static string DecimalToBinary(uint decimalNumber, int zeroPadding = 0)
  449. {
  450. if (zeroPadding != 0) return Convert.ToString(decimalNumber, 2).PadLeft(zeroPadding, '0');
  451. return Convert.ToString(decimalNumber, 2);
  452. }
  453. /// <summary>
  454. /// 将 byte 对象转换为其二进制字符串表示形式,并用左侧指定数量的零
  455. /// </summary>
  456. /// <param name="decimalNumber"></param>
  457. /// <param name="zeroPadding"></param>
  458. /// <returns></returns>
  459. private static string DecimalToBinary(byte decimalNumber, int zeroPadding = 0)
  460. {
  461. if (zeroPadding != 0) return Convert.ToString(decimalNumber, 2).PadLeft(zeroPadding, '0');
  462. return Convert.ToString(decimalNumber, 2);
  463. }
  464. /// <summary>
  465. /// 将字节数组转换为其二进制字符串表示形式
  466. /// </summary>
  467. /// <param name="array"></param>
  468. /// <returns></returns>
  469. private static string ByteArrayToBinary(byte[] array)
  470. {
  471. var sb = new StringBuilder();
  472. foreach (var by in array) sb.Append(DecimalToBinary(by, 8));
  473. return sb.ToString();
  474. }
  475. /// <summary>
  476. /// 将二进制字符串模式转换为其各自的UTF8文本表示
  477. /// </summary>
  478. /// <param name="str"></param>
  479. /// <returns></returns>
  480. private static string BinaryToString(string str)
  481. {
  482. var temp = BinaryToByteArray(str);
  483. var val = Encoding.UTF8.GetString(temp);
  484. return val;
  485. }
  486. /// <summary>
  487. /// 将二进制字符串模式转换为字节变量
  488. /// </summary>
  489. /// <param name="binary"></param>
  490. /// <returns></returns>
  491. private static byte BinaryToByte(string binary)
  492. {
  493. var by = Convert.ToByte(binary, 2);
  494. return by;
  495. }
  496. /// <summary>
  497. /// 将二进制字符串模式转换为字节数组
  498. /// </summary>
  499. /// <param name="binary"></param>
  500. /// <returns></returns>
  501. private static byte[] BinaryToByteArray(string binary)
  502. {
  503. return ConvertBitStringToByteArray(binary);
  504. }
  505. /// <summary>
  506. /// 将二进制字符串模式转换为 uint 变量
  507. /// </summary>
  508. /// <param name="binary"></param>
  509. /// <returns></returns>
  510. private static uint BinaryToUint(string binary)
  511. {
  512. var unsignedInt = Convert.ToUInt32(binary, 2);
  513. return unsignedInt;
  514. }
  515. /// <summary>
  516. /// 将存储位值的uint变量转换为存储kB值的双变量
  517. /// </summary>
  518. /// <param name="bits"></param>
  519. /// <returns></returns>
  520. private static double BitsToKiloBytes(uint bits)
  521. {
  522. // ReSharper disable once PossibleLossOfFraction
  523. float bytes = bits / 8;
  524. var kiloBytes = bytes / 1024;
  525. return Math.Round(kiloBytes, 5);
  526. }
  527. private static byte[] ConvertBitStringToByteArray(string source)
  528. {
  529. var data =
  530. Regex.Matches(source, ".{8}")
  531. .Select(m => Convert.ToByte(m.Groups[0].Value, 2))
  532. .ToArray();
  533. return data;
  534. }
  535. #endregion Converter
  536. }