748 lines
30 KiB
C#
748 lines
30 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.Diagnostics;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Security.Cryptography;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using BitVectorLib;
|
||
|
||
namespace StegoHackathonConsole
|
||
{
|
||
class Program
|
||
{
|
||
// Настройки по умолчанию
|
||
private static int _threads = Environment.ProcessorCount;
|
||
private static bool _verbose = false;
|
||
|
||
static async Task Main(string[] args)
|
||
{
|
||
// Настройка кодировки для корректного вывода кириллицы
|
||
Console.OutputEncoding = Encoding.UTF8;
|
||
|
||
PrintBanner();
|
||
|
||
if (args.Length == 0)
|
||
{
|
||
PrintHelp();
|
||
return;
|
||
}
|
||
|
||
var arguments = ParseArguments(args);
|
||
string mode = arguments.GetValueOrDefault("mode") ?? string.Empty;
|
||
mode = mode.ToLower();
|
||
|
||
// Глобальные настройки
|
||
if (arguments.ContainsKey("threads"))
|
||
int.TryParse(arguments["threads"], out _threads);
|
||
if (arguments.ContainsKey("verbose"))
|
||
_verbose = true;
|
||
|
||
try
|
||
{
|
||
switch (mode)
|
||
{
|
||
case "hack":
|
||
await RunHackModeFast(arguments);
|
||
break;
|
||
case "encrypt":
|
||
await RunEncryptMode(arguments);
|
||
break;
|
||
case "verify":
|
||
await RunVerifyMode(arguments);
|
||
break;
|
||
default:
|
||
Console.ForegroundColor = ConsoleColor.Red;
|
||
Console.WriteLine("ОШИБКА: Неизвестный режим работы.");
|
||
Console.ResetColor();
|
||
PrintHelp();
|
||
break;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.ForegroundColor = ConsoleColor.Red;
|
||
Console.WriteLine($"\nКРИТИЧЕСКАЯ ОШИБКА: {ex.Message}");
|
||
if (_verbose) Console.WriteLine(ex.StackTrace);
|
||
Console.ResetColor();
|
||
}
|
||
}
|
||
|
||
static void PrintBanner()
|
||
{
|
||
var process = Process.GetCurrentProcess();
|
||
var gcInfo = GC.GetGCMemoryInfo();
|
||
|
||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||
Console.WriteLine("================================================================================");
|
||
Console.WriteLine(" STEGOHACKATHON CLI v1.0 ");
|
||
Console.WriteLine("================================================================================");
|
||
Console.ResetColor();
|
||
|
||
Console.WriteLine($" [SYSTEM] OS Description : {System.Runtime.InteropServices.RuntimeInformation.OSDescription}");
|
||
Console.WriteLine($" [SYSTEM] Architecture : {System.Runtime.InteropServices.RuntimeInformation.OSArchitecture}");
|
||
|
||
string cpuName = GetCpuModelName();
|
||
Console.WriteLine($" [CPU] Model : {cpuName}");
|
||
Console.WriteLine($" [CPU] Logical Cores : {Environment.ProcessorCount}");
|
||
|
||
long totalRamMb = gcInfo.TotalAvailableMemoryBytes / 1024 / 1024;
|
||
long appMemMb = process.WorkingSet64 / 1024 / 1024;
|
||
Console.WriteLine($" [RAM] System Total : {totalRamMb:N0} MB");
|
||
Console.WriteLine($" [RAM] App Current : {appMemMb:N0} MB");
|
||
Console.WriteLine($" [PROC] Threads Limit : {_threads}");
|
||
Console.WriteLine($" [PROC] GC Mode : {(System.Runtime.GCSettings.IsServerGC ? "Server" : "Workstation")}");
|
||
|
||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||
Console.WriteLine("================================================================================\n");
|
||
Console.ResetColor();
|
||
}
|
||
|
||
static string GetCpuModelName()
|
||
{
|
||
try
|
||
{
|
||
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux))
|
||
{
|
||
if (File.Exists("/proc/cpuinfo"))
|
||
{
|
||
var lines = File.ReadAllLines("/proc/cpuinfo");
|
||
foreach (var line in lines)
|
||
{
|
||
if (line.StartsWith("model name", StringComparison.OrdinalIgnoreCase) ||
|
||
line.StartsWith("Model", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
var parts = line.Split(':');
|
||
if (parts.Length > 1) return parts[1].Trim();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
|
||
{
|
||
string? name = Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER");
|
||
if (!string.IsNullOrEmpty(name)) return name;
|
||
}
|
||
}
|
||
catch { }
|
||
return "Unknown / Generic";
|
||
}
|
||
|
||
static void PrintHelp()
|
||
{
|
||
Console.WriteLine("Использование:");
|
||
Console.WriteLine(" --mode hack --data <файл.bin> --book <книга.txt> [--threads <N>]");
|
||
Console.WriteLine(" --mode encrypt --book <книга.txt> --out <вывод.bin>");
|
||
Console.WriteLine(" --mode verify --data <файл.bin> --key <ключ.key.bin>");
|
||
Console.WriteLine("\nПример:");
|
||
Console.WriteLine(" dotnet run -- --mode hack --data task1.bin --book WarAndPeace.txt --threads 8");
|
||
}
|
||
|
||
static Dictionary<string, string> ParseArguments(string[] args)
|
||
{
|
||
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||
for (int i = 0; i < args.Length; i++)
|
||
{
|
||
if (args[i].StartsWith("--") || args[i].StartsWith("-"))
|
||
{
|
||
string key = args[i].TrimStart('-');
|
||
string val = "true";
|
||
if (i + 1 < args.Length && !args[i + 1].StartsWith("-"))
|
||
{
|
||
val = args[i + 1];
|
||
i++;
|
||
}
|
||
dict[key] = val;
|
||
}
|
||
}
|
||
return dict;
|
||
}
|
||
|
||
// --- РЕЖИМ 1: ОПТИМИЗИРОВАННЫЙ ВЗЛОМ ---
|
||
static async Task RunHackModeFast(Dictionary<string, string> args)
|
||
{
|
||
string stegoPath = args.GetValueOrDefault("data") ?? string.Empty;
|
||
string bookPath = args.GetValueOrDefault("book") ?? string.Empty;
|
||
|
||
if (string.IsNullOrEmpty(stegoPath) || !File.Exists(stegoPath))
|
||
{
|
||
Console.WriteLine("[ERR] Файл данных не найден.");
|
||
return;
|
||
}
|
||
if (string.IsNullOrEmpty(bookPath) || !File.Exists(bookPath))
|
||
{
|
||
Console.WriteLine("[ERR] Файл книги не найден.");
|
||
return;
|
||
}
|
||
|
||
Console.WriteLine($"[INIT] Чтение файлов...");
|
||
byte[] stegoBytes = await File.ReadAllBytesAsync(stegoPath);
|
||
string bookText = await File.ReadAllTextAsync(bookPath, Encoding.UTF8);
|
||
byte[] bookBytes = Encoding.UTF8.GetBytes(bookText);
|
||
|
||
Console.WriteLine($"[DATA] Стего-контейнер: {stegoBytes.Length} байт");
|
||
Console.WriteLine($"[BOOK] Книга: {bookBytes.Length} байт");
|
||
|
||
string[] etalonStrings = AssocStego.GetDefaultEtalons10();
|
||
int etalonLen = etalonStrings[0].Length;
|
||
int blockByteSize = (etalonLen + 7) / 8;
|
||
|
||
byte[][] etalonsRaw = new byte[10][];
|
||
for (int i = 0; i < 10; i++)
|
||
{
|
||
etalonsRaw[i] = new BitVector(etalonStrings[i]).ToByteArray();
|
||
}
|
||
|
||
int bytesEncrypted = stegoBytes.Length / (3 * blockByteSize);
|
||
Console.WriteLine($"[INFO] Длина блока: {blockByteSize} байт. Скрыто символов: {bytesEncrypted}");
|
||
|
||
List<byte[]> stegoChunks = new List<byte[]>(bytesEncrypted * 3);
|
||
for (int i = 0; i < stegoBytes.Length; i += blockByteSize)
|
||
{
|
||
byte[] chunk = new byte[blockByteSize];
|
||
Array.Copy(stegoBytes, i, chunk, 0, blockByteSize);
|
||
stegoChunks.Add(chunk);
|
||
}
|
||
|
||
int maxOffset = bookBytes.Length - bytesEncrypted;
|
||
if (maxOffset <= 0)
|
||
{
|
||
Console.WriteLine("[FAIL] Размер книги меньше данных.");
|
||
return;
|
||
}
|
||
|
||
Stopwatch sw = Stopwatch.StartNew();
|
||
long processed = 0;
|
||
bool found = false;
|
||
object lockObj = new object();
|
||
|
||
Console.WriteLine($"[RUN ] СТАРТ ПЕРЕБОРА (TURBO MODE) на {_threads} потоках...");
|
||
|
||
using var timer = new Timer(_ =>
|
||
{
|
||
long current = Interlocked.Read(ref processed);
|
||
double progress = (double)current / maxOffset * 100.0;
|
||
double speed = current / sw.Elapsed.TotalSeconds;
|
||
long ram = Process.GetCurrentProcess().WorkingSet64 / 1024 / 1024;
|
||
Console.Write($"\r[PROG] {progress:F2}% | Speed: {speed:N0} ops/s | RAM: {ram} MB | Time: {sw.Elapsed:mm\\:ss} ");
|
||
}, null, 1000, 1000);
|
||
|
||
var pOptions = new ParallelOptions { MaxDegreeOfParallelism = _threads };
|
||
|
||
Parallel.For(0, maxOffset + 1, pOptions, (i, state) =>
|
||
{
|
||
if (found) { state.Stop(); return; }
|
||
|
||
if ((i & 4095) == 0) Interlocked.Add(ref processed, 4096);
|
||
|
||
byte[][] currentMasks = new byte[10][];
|
||
for (int k = 0; k < 10; k++)
|
||
{
|
||
currentMasks[k] = new byte[blockByteSize];
|
||
for (int b = 0; b < blockByteSize; b++) currentMasks[k][b] = 0xFF;
|
||
}
|
||
|
||
bool[] digitUsed = new bool[10];
|
||
bool possible = true;
|
||
int quickCheckLen = (bytesEncrypted < 15) ? bytesEncrypted : 15;
|
||
|
||
for (int j = 0; j < quickCheckLen; j++)
|
||
{
|
||
byte b = bookBytes[i + j];
|
||
int d2 = b / 100;
|
||
int d1 = (b % 100) / 10;
|
||
int d0 = b % 10;
|
||
int idx = j * 3;
|
||
|
||
FastAndNotXor(currentMasks[d2], stegoChunks[idx], etalonsRaw[d2]);
|
||
FastAndNotXor(currentMasks[d1], stegoChunks[idx + 1], etalonsRaw[d1]);
|
||
FastAndNotXor(currentMasks[d0], stegoChunks[idx + 2], etalonsRaw[d0]);
|
||
|
||
digitUsed[d2] = true;
|
||
digitUsed[d1] = true;
|
||
digitUsed[d0] = true;
|
||
}
|
||
|
||
for (int k = 0; k < 10; k++)
|
||
{
|
||
if (digitUsed[k])
|
||
{
|
||
if (FastPopCount(currentMasks[k]) < 2)
|
||
{
|
||
possible = false;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!possible) return;
|
||
|
||
for (int k = 0; k < 10; k++)
|
||
{
|
||
for (int b = 0; b < blockByteSize; b++) currentMasks[k][b] = 0xFF;
|
||
digitUsed[k] = false;
|
||
}
|
||
|
||
for (int j = 0; j < bytesEncrypted; j++)
|
||
{
|
||
byte b = bookBytes[i + j];
|
||
int d2 = b / 100;
|
||
int d1 = (b % 100) / 10;
|
||
int d0 = b % 10;
|
||
int idx = j * 3;
|
||
|
||
FastAndNotXor(currentMasks[d2], stegoChunks[idx], etalonsRaw[d2]);
|
||
FastAndNotXor(currentMasks[d1], stegoChunks[idx + 1], etalonsRaw[d1]);
|
||
FastAndNotXor(currentMasks[d0], stegoChunks[idx + 2], etalonsRaw[d0]);
|
||
|
||
digitUsed[d2] = true; digitUsed[d1] = true; digitUsed[d0] = true;
|
||
}
|
||
|
||
int totalBits = 0;
|
||
for (int k = 0; k < 10; k++)
|
||
{
|
||
if (digitUsed[k])
|
||
totalBits += FastPopCount(currentMasks[k]);
|
||
}
|
||
|
||
if (totalBits > 5)
|
||
{
|
||
lock (lockObj)
|
||
{
|
||
if (!found)
|
||
{
|
||
found = true;
|
||
sw.Stop();
|
||
Console.WriteLine($"\n\n[SUCCESS] Взлом успешен! Offset: {i}");
|
||
Console.WriteLine($"[TIME] Время: {sw.ElapsedMilliseconds} мс");
|
||
SaveFoundKey(stegoPath, bookBytes, i, bytesEncrypted, currentMasks, etalonStrings, etalonLen);
|
||
state.Stop();
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
if (!found) Console.WriteLine("\n[FAIL] Совпадений не найдено.");
|
||
}
|
||
|
||
static void FastAndNotXor(byte[] target, byte[] stego, byte[] etalon)
|
||
{
|
||
for (int i = 0; i < target.Length; i++)
|
||
{
|
||
target[i] = (byte)(target[i] & ~(stego[i] ^ etalon[i]));
|
||
}
|
||
}
|
||
|
||
static int FastPopCount(byte[] data)
|
||
{
|
||
int count = 0;
|
||
for (int i = 0; i < data.Length; i++)
|
||
{
|
||
int n = data[i];
|
||
while (n != 0)
|
||
{
|
||
count++;
|
||
n &= (n - 1);
|
||
}
|
||
}
|
||
return count;
|
||
}
|
||
|
||
static void SaveFoundKey(string stegoPath, byte[] bookBytes, int offset, int len, byte[][] masksRaw, string[] etalonsStr, int etalonLen)
|
||
{
|
||
byte[] decrypted = new byte[len];
|
||
Array.Copy(bookBytes, offset, decrypted, 0, len);
|
||
string text = Encoding.UTF8.GetString(decrypted);
|
||
|
||
Console.WriteLine("--- НАЧАЛО ТЕКСТА ---");
|
||
Console.WriteLine(text.Substring(0, Math.Min(100, text.Length)) + "...");
|
||
Console.WriteLine("---------------------");
|
||
|
||
BitVector[] keyVectors = new BitVector[10];
|
||
for (int k = 0; k < 10; k++)
|
||
{
|
||
keyVectors[k] = new BitVector(masksRaw[k], etalonLen);
|
||
}
|
||
|
||
var sys = new AssocStego(etalonsStr);
|
||
string keyPath = Path.ChangeExtension(stegoPath, ".recovered.key.bin");
|
||
string txtPath = Path.ChangeExtension(stegoPath, ".txt");
|
||
|
||
sys.SaveKeyToFile(keyPath, keyVectors);
|
||
File.WriteAllText(txtPath, text);
|
||
|
||
Console.WriteLine($"[SAVE] Ключ: {keyPath}");
|
||
Console.WriteLine($"[SAVE] Текст: {txtPath}");
|
||
}
|
||
|
||
// --- РЕЖИМ 2: ШИФРОВАНИЕ ---
|
||
static async Task RunEncryptMode(Dictionary<string, string> args)
|
||
{
|
||
string bookPath = args.GetValueOrDefault("book") ?? string.Empty;
|
||
string outFile = args.GetValueOrDefault("out") ?? "output.bin";
|
||
|
||
if (string.IsNullOrEmpty(bookPath) || !File.Exists(bookPath)) throw new FileNotFoundException("Книга не найдена.");
|
||
|
||
string text = await File.ReadAllTextAsync(bookPath);
|
||
Random rnd = new Random();
|
||
int len = rnd.Next(200, 600);
|
||
int start = rnd.Next(0, text.Length - len);
|
||
string secretText = text.Substring(start, len);
|
||
|
||
Console.WriteLine($"[ENCRYPT] Фрагмент: \"{secretText.Substring(0, Math.Min(30, secretText.Length))}...\"");
|
||
|
||
var etalons = AssocStego.GetDefaultEtalons10();
|
||
var system = new AssocStego(etalons);
|
||
|
||
Console.WriteLine("[KEY] Генерация масок...");
|
||
var keys = system.CreateKey();
|
||
|
||
string keyFile = Path.ChangeExtension(outFile, ".key.bin");
|
||
system.SaveKeyToFile(keyFile, keys);
|
||
|
||
Console.WriteLine($"[WRITE] Запись в {outFile}...");
|
||
byte[] data = Encoding.UTF8.GetBytes(secretText);
|
||
|
||
using (var fs = new FileStream(outFile, FileMode.Create))
|
||
using (var ss = new AssocStegoStream(fs, system))
|
||
{
|
||
ss.Write(data, 0, data.Length);
|
||
}
|
||
Console.WriteLine("[DONE] Успешно.");
|
||
}
|
||
|
||
// --- РЕЖИМ 3: ПРОВЕРКА ---
|
||
static async Task RunVerifyMode(Dictionary<string, string> args)
|
||
{
|
||
string dataPath = args.GetValueOrDefault("data") ?? string.Empty;
|
||
string keyPath = args.GetValueOrDefault("key") ?? string.Empty;
|
||
|
||
if (!File.Exists(dataPath) || !File.Exists(keyPath)) throw new FileNotFoundException("Файлы не найдены.");
|
||
|
||
var etalons = AssocStego.GetDefaultEtalons10();
|
||
var system = new AssocStego(etalons);
|
||
|
||
Console.WriteLine($"[LOAD] Ключ: {keyPath}");
|
||
system.Key = system.LoadKeyFromFile(keyPath);
|
||
|
||
byte[] encrypted = await File.ReadAllBytesAsync(dataPath);
|
||
int containerByteLen = (system.etalonLength + 7) / 8;
|
||
int count = encrypted.Length / (containerByteLen * 3);
|
||
byte[] buffer = new byte[count];
|
||
|
||
Console.WriteLine($"[DECRYPT] Расшифровка...");
|
||
using (var ms = new MemoryStream(encrypted))
|
||
using (var ss = new AssocStegoStream(ms, system))
|
||
{
|
||
ss.Read(buffer, 0, count);
|
||
}
|
||
Console.WriteLine($"[TEXT] {Encoding.UTF8.GetString(buffer)}");
|
||
}
|
||
}
|
||
|
||
// --- КЛАССЫ ЛОГИКИ (ИСПОЛЬЗУЮТ BitVectorLib) ---
|
||
|
||
public class AssocStegoStream : Stream
|
||
{
|
||
private readonly Stream _stream;
|
||
private readonly AssocStego _stegoAlg;
|
||
private readonly int _containerByteLen;
|
||
private readonly int _hiddenByteLen;
|
||
|
||
public AssocStegoStream(Stream stream, AssocStego stegoAlg)
|
||
{
|
||
_stream = stream;
|
||
_stegoAlg = stegoAlg;
|
||
_containerByteLen = (_stegoAlg.etalonLength + 7) / 8;
|
||
_hiddenByteLen = 3 * _containerByteLen;
|
||
}
|
||
|
||
public override bool CanRead => _stream.CanRead;
|
||
public override bool CanSeek => false;
|
||
public override bool CanWrite => _stream.CanWrite;
|
||
public override long Length => _stream.Length;
|
||
public override long Position { get => _stream.Position; set => throw new NotSupportedException(); }
|
||
public override void Flush() => _stream.Flush();
|
||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||
public override void SetLength(long value) => throw new NotSupportedException();
|
||
|
||
public override void Write(byte[] buffer, int offset, int count)
|
||
{
|
||
for (int i = offset; i < offset + count; i++)
|
||
{
|
||
byte[] hidden = HideByte(buffer[i]);
|
||
_stream.Write(hidden, 0, hidden.Length);
|
||
}
|
||
}
|
||
|
||
public override int Read(byte[] buffer, int offset, int count)
|
||
{
|
||
int readCount = 0;
|
||
for (int i = offset; i < offset + count; i++)
|
||
{
|
||
var hidden = new byte[_hiddenByteLen];
|
||
int totalRead = 0;
|
||
while (totalRead < _hiddenByteLen)
|
||
{
|
||
int r = _stream.Read(hidden, totalRead, _hiddenByteLen - totalRead);
|
||
if (r == 0) return readCount;
|
||
totalRead += r;
|
||
}
|
||
buffer[i] = DiscloseByte(hidden);
|
||
readCount++;
|
||
}
|
||
return readCount;
|
||
}
|
||
|
||
private byte[] HideByte(byte value)
|
||
{
|
||
int v = value;
|
||
int d2 = v / 100;
|
||
int rem = v % 100;
|
||
int d1 = rem / 10;
|
||
int d0 = rem % 10;
|
||
|
||
BitVector c2 = _stegoAlg.HideEtalon(d2);
|
||
BitVector c1 = _stegoAlg.HideEtalon(d1);
|
||
BitVector c0 = _stegoAlg.HideEtalon(d0);
|
||
|
||
byte[] b2 = c2.ToByteArray();
|
||
byte[] b1 = c1.ToByteArray();
|
||
byte[] b0 = c0.ToByteArray();
|
||
|
||
byte[] result = new byte[b2.Length + b1.Length + b0.Length];
|
||
int pos = 0;
|
||
Buffer.BlockCopy(b2, 0, result, pos, b2.Length); pos += b2.Length;
|
||
Buffer.BlockCopy(b1, 0, result, pos, b1.Length); pos += b1.Length;
|
||
Buffer.BlockCopy(b0, 0, result, pos, b0.Length);
|
||
|
||
return result;
|
||
}
|
||
|
||
public byte DiscloseByte(byte[] hidden)
|
||
{
|
||
byte[] b2 = new byte[_containerByteLen];
|
||
byte[] b1 = new byte[_containerByteLen];
|
||
byte[] b0 = new byte[_containerByteLen];
|
||
|
||
int pos = 0;
|
||
Buffer.BlockCopy(hidden, pos, b2, 0, _containerByteLen); pos += _containerByteLen;
|
||
Buffer.BlockCopy(hidden, pos, b1, 0, _containerByteLen); pos += _containerByteLen;
|
||
Buffer.BlockCopy(hidden, pos, b0, 0, _containerByteLen);
|
||
|
||
int d2 = _stegoAlg.DiscloseEtalon(new BitVector(b2, _stegoAlg.etalonLength));
|
||
int d1 = _stegoAlg.DiscloseEtalon(new BitVector(b1, _stegoAlg.etalonLength));
|
||
int d0 = _stegoAlg.DiscloseEtalon(new BitVector(b0, _stegoAlg.etalonLength));
|
||
|
||
if (d2 < 0 || d1 < 0 || d0 < 0) return 63;
|
||
return (byte)(d2 * 100 + d1 * 10 + d0);
|
||
}
|
||
}
|
||
|
||
public class AssocStego
|
||
{
|
||
public readonly BitVector[] etalons;
|
||
public readonly int etalonLength;
|
||
private readonly List<int>[] maskUnitPositions;
|
||
|
||
private BitVector[]? key;
|
||
private readonly BitVector zeroVector;
|
||
|
||
// Свойство выбрасывает исключение, если ключ не задан
|
||
public BitVector[] Key
|
||
{
|
||
get => key ?? throw new InvalidOperationException("Ключ не сгенерирован!");
|
||
set => key = value;
|
||
}
|
||
|
||
public AssocStego(params string[] e)
|
||
{
|
||
etalonLength = e[0].Length;
|
||
etalons = new BitVector[e.Length];
|
||
maskUnitPositions = new List<int>[e.Length];
|
||
zeroVector = new BitVector(new byte[(etalonLength + 7) / 8], etalonLength);
|
||
|
||
for (int i = 0; i < e.Length; i++)
|
||
{
|
||
etalons[i] = new BitVector(e[i]);
|
||
maskUnitPositions[i] = new List<int>();
|
||
}
|
||
}
|
||
|
||
public BitVector[] CreateKey()
|
||
{
|
||
var eIndexes = Enumerable.Range(0, etalons.Length).ToList();
|
||
ResetMaskUnitPositions();
|
||
CreateKeyRec(eIndexes, true);
|
||
var masks = new BitVector[etalons.Length];
|
||
for (int i = 0; i < etalons.Length; i++)
|
||
masks[i] = CreateVector(etalonLength, maskUnitPositions[i].ToArray());
|
||
Key = masks;
|
||
return masks;
|
||
}
|
||
|
||
private void CreateKeyRec(List<int> eIndexes, bool firstPerm)
|
||
{
|
||
if (eIndexes.Count < 2) return;
|
||
var c = firstPerm ? Shuffle(eIndexes) : eIndexes;
|
||
var d1 = new List<int> { c[0] };
|
||
BitVector a0 = etalons[c[0]] ^ etalons[c[1]];
|
||
|
||
for (int i = 0; i < c.Count - 2; i++)
|
||
{
|
||
BitVector a1 = etalons[c[0]] ^ etalons[c[i + 2]];
|
||
BitVector a3 = a0 & a1;
|
||
if (IsZero(a3)) d1.Add(c[i + 2]);
|
||
else a0 = a3;
|
||
}
|
||
|
||
int rndBit = GetRandomOneBitIndex(a0);
|
||
foreach (int idx in c) maskUnitPositions[idx].Add(rndBit);
|
||
var d2 = c.Except(d1).ToList();
|
||
|
||
Parallel.Invoke(() => CreateKeyRec(d1, false), () => CreateKeyRec(d2, false));
|
||
}
|
||
|
||
private bool IsZero(BitVector v)
|
||
{
|
||
foreach (byte b in v.ToByteArray()) if (b != 0) return false;
|
||
return true;
|
||
}
|
||
|
||
private List<int> Shuffle(List<int> list)
|
||
{
|
||
var res = new List<int>(list);
|
||
var rng = new Random();
|
||
int n = res.Count;
|
||
while (n > 1) { n--; int k = rng.Next(n + 1); (res[k], res[n]) = (res[n], res[k]); }
|
||
return res;
|
||
}
|
||
|
||
private void ResetMaskUnitPositions() { foreach (var l in maskUnitPositions) l.Clear(); }
|
||
|
||
private int GetRandomOneBitIndex(BitVector v)
|
||
{
|
||
var bytes = v.ToByteArray();
|
||
var ones = new List<int>();
|
||
for (int i = 0; i < bytes.Length * 8; i++)
|
||
{
|
||
int byteIdx = i / 8;
|
||
if (byteIdx >= bytes.Length) break;
|
||
int bitIdx = 7 - (i % 8);
|
||
if ((bytes[byteIdx] & (1 << bitIdx)) != 0) ones.Add(i);
|
||
}
|
||
if (ones.Count == 0) return 0;
|
||
return ones[new Random().Next(ones.Count)];
|
||
}
|
||
|
||
private BitVector CreateVector(int len, int[] indexes)
|
||
{
|
||
int byteLen = (len + 7) / 8;
|
||
byte[] arr = new byte[byteLen];
|
||
foreach (int idx in indexes)
|
||
{
|
||
int byteIndex = idx / 8;
|
||
int bitIndex = 7 - (idx % 8);
|
||
if (byteIndex < arr.Length) arr[byteIndex] |= (byte)(1 << bitIndex);
|
||
}
|
||
return new BitVector(arr, len);
|
||
}
|
||
|
||
public BitVector HideEtalon(int idx)
|
||
{
|
||
byte[] randomBytes = new byte[(etalonLength + 7) / 8];
|
||
RandomNumberGenerator.Fill(randomBytes);
|
||
BitVector container = new BitVector(randomBytes, etalonLength);
|
||
return container ^ ((container ^ etalons[idx]) & Key[idx]);
|
||
}
|
||
|
||
public int DiscloseEtalon(BitVector stego)
|
||
{
|
||
for (int i = 0; i < etalons.Length; i++)
|
||
{
|
||
BitVector a = stego & Key[i];
|
||
BitVector b = etalons[i] & Key[i];
|
||
if (a.ToByteArray().SequenceEqual(b.ToByteArray())) return i;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
public void SaveKeyToFile(string path, BitVector[] k)
|
||
{
|
||
using var bw = new BinaryWriter(File.Create(path));
|
||
bw.Write(0x41534B59);
|
||
bw.Write(k.Length);
|
||
bw.Write(etalonLength);
|
||
foreach (var vec in k)
|
||
{
|
||
byte[] b = vec.ToByteArray();
|
||
bw.Write(b.Length);
|
||
bw.Write(b);
|
||
}
|
||
}
|
||
|
||
public BitVector[] LoadKeyFromFile(string path)
|
||
{
|
||
using var br = new BinaryReader(File.OpenRead(path));
|
||
if (br.ReadInt32() != 0x41534B59) throw new Exception("Bad magic");
|
||
int count = br.ReadInt32();
|
||
int len = br.ReadInt32();
|
||
var res = new BitVector[count];
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
int blen = br.ReadInt32();
|
||
byte[] b = br.ReadBytes(blen);
|
||
res[i] = new BitVector(b, len);
|
||
}
|
||
return res;
|
||
}
|
||
|
||
public static string[] GetDefaultEtalons10() => new[] {
|
||
"111111111111111111111111111111111111111111111111111111000000000000000000000000",
|
||
"000000000100000000000000000111111111111111111100000000000000000000000011111111",
|
||
"100000000000000000111111111111111111100000000111111111111111110000000000000000",
|
||
"100000000100000000111111111100000000100000000000000000111111111111111111111111",
|
||
"000000000111111111100000000111111111111111111100000000000000001111111100000000",
|
||
"100000000111111111111111111100000000111111111111111111000000001111111100000000",
|
||
"111111111100000000000000000100000000111111111111111111000000001111111111111111",
|
||
"111111111100000000111111111100000000000000000000000000000000000000000011111111",
|
||
"111111111111111111111111111111111111111111111111111111000000001111111100000000",
|
||
"100000000111111111111111111111111111100000000000000000111111111111111100000000"
|
||
}.Select(s => new string(s.Reverse().ToArray())).ToArray();
|
||
}
|
||
|
||
public static class BitVectorHelpers
|
||
{
|
||
public static BitVector CreateFullMask(int bitLength)
|
||
{
|
||
int byteLen = (bitLength + 7) / 8;
|
||
byte[] b = new byte[byteLen];
|
||
for (int i = 0; i < byteLen; i++) b[i] = 0xFF;
|
||
return new BitVector(b, bitLength);
|
||
}
|
||
public static BitVector CreateZeroMask(int bitLength) => new BitVector(new byte[(bitLength + 7) / 8], bitLength);
|
||
public static BitVector AndNot(BitVector a, BitVector b)
|
||
{
|
||
byte[] ba = a.ToByteArray();
|
||
byte[] bb = b.ToByteArray();
|
||
int len = Math.Min(ba.Length, bb.Length);
|
||
byte[] result = new byte[ba.Length];
|
||
for (int i = 0; i < len; i++) result[i] = (byte)(ba[i] & ~bb[i]);
|
||
return new BitVector(result, a.ToByteArray().Length * 8);
|
||
}
|
||
public static int PopCount(BitVector v)
|
||
{
|
||
byte[] bytes = v.ToByteArray();
|
||
int count = 0;
|
||
foreach (byte b in bytes)
|
||
{
|
||
int n = b;
|
||
while (n > 0) { if ((n & 1) == 1) count++; n >>= 1; }
|
||
}
|
||
return count;
|
||
}
|
||
}
|
||
} |