using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ExtensionMethods;
using System.Text.RegularExpressions;
namespace CryptanalysisCore
{
public class Transposition : Cipher
{
private Random random = new Random();
public const int MaxKeyLength = 12;
public const int MinKeyLength = 4;
public const int topWordsCount = 200;
public const int TestKeyLengthAttack = 0;
public Transposition()
: base()
{
//ExceptionText = "Klíčem musí být všechny malá písmena anglické abecedy, libovolně rozpřeházená.";
CipherType = Storage.Ciphers.trans;
}
public override string Encrypt(string opentext, string key)
{
base.Encrypt(opentext, key);
opentext = Analyse.NormalizeText(opentext, Analyse.TextTypes.WithoutSpacesLower);
string[] columns = GetEncryptColumns(opentext, key);
var orderedColumns = ColumnEncryptOrder(columns, key);
var result = orderedColumns.Values.Implode();
return result.ToUpper();
}
public override string Decrypt(string ciphertext, string key)
{
base.Decrypt(ciphertext, key);
ciphertext = Analyse.NormalizeText(ciphertext, Analyse.TextTypes.WithoutSpacesLower);
if (!IsKeylengthValid(ciphertext, key))
throw new Exceptions.InvalidCipherKey("Délka klíče nesouhlasí s délkou šifrového textu");
var columns = GetDecryptColumns(ciphertext, key);
var orderedColumns = ColumnDecryptOrder(columns, key);
var result = GetOpentext(orderedColumns);
return result.ToLower();
}
private bool IsKeylengthValid(string ciphertext, string key)
{
return ciphertext.Length % key.Length == 0;
}
///
/// Ze seřazených sloupců vrátí otevřený text
///
///
///
private string GetOpentext(string[] orderedColumns)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < orderedColumns[0].Length; i++)
{
for (int j = 0; j < orderedColumns.Length; j++)
{
sb.Append(orderedColumns[j][i].ToString());
}
}
return sb.ToString();
}
///
/// Vrátí sloupečky v zašifrovaném textu
///
///
///
///
private string[] GetDecryptColumns(string ciphertext, string key)
{
return GetDecryptColumns(ciphertext, key.Length);
}
private string[] GetDecryptColumns(string ciphertext, int keyLength)
{
int columnsLength = (int)((double)ciphertext.Length / (double)keyLength);
string[] columns = new string[keyLength];
for (int i = 0; i < keyLength; i++)
{
columns[i] = ciphertext.Substring(i * columnsLength, columnsLength);
}
return columns;
}
///
/// Vrátí pořadí sloupců při dešifrování
///
///
///
///
private string[] ColumnDecryptOrder(string[] columns, string key)
{
Dictionary indexesColumns = new Dictionary();
for (int i = 0; i < key.Length; i++)
{
indexesColumns[GetColumnKey(indexesColumns.Keys.ToArray(), key[i])] = i;
}
indexesColumns = indexesColumns.OrderBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value);
string[] orderColumns = new string[key.Length];
int counter = 0;
foreach (var pair in indexesColumns)
{
orderColumns[pair.Value] = columns[counter++];
}
return orderColumns;
}
///
/// Vrátí pořadí sloupců při šifrování
///
///
///
///
private Dictionary ColumnEncryptOrder(string[] columns, string key)
{
Dictionary indexesColumns = new Dictionary();
for (int i = 0; i < key.Length; i++)
{
indexesColumns[GetColumnKey(indexesColumns.Keys.ToArray(), key[i])] = columns[i];
}
var result = indexesColumns.OrderBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value);
return result;
}
///
/// Najde první možný validní klíč sloepce. Pokud existuje "x" a "xx", vrátí "xxx" apod.
///
///
///
///
private string GetColumnKey(string[] indexesColumns, char currKey)
{
string modifKey = currKey.ToString();
while (indexesColumns.Contains(modifKey))
modifKey += currKey.ToString();
return modifKey;
}
///
/// Vrátí sloupečky v textu podle délky klíče při šifrování
///
///
///
///
private string[] GetEncryptColumns(string opentext, string key)
{
return GetEncryptColumns(opentext, key.Length);
}
private string[] GetEncryptColumns(string opentext, int keyLength)
{
int columnLength = (int)Math.Ceiling((double)opentext.Length / (double)keyLength);
string[] columns = new string[keyLength];
for (int i = 0; i < keyLength; i++)
{
StringBuilder sb = new StringBuilder();
for (int j = 0; j < columnLength; j++)
{
int index = j * keyLength + i;
if (index < opentext.Length)
sb.Append(opentext[index]);
else
sb.Append(Text.RandomString(1));
}
columns[i] = sb.ToString();
}
return columns;
}
public override bool IsKeyValid(string key)
{
return key.Length >= 2 && Regex.IsMatch(key, @"^[a-zA-Z\?]+$");
}
protected override void SetCrackMethods()
{
CrackMethods = new CrackMethod[] { BigWordAttack };
}
/*********************************************/
/*********** One big word attack *************/
/*********************************************/
private List BigWordAttack(string ciphertext, Storage.Languages language)
{
ciphertext = Analyse.NormalizeText(ciphertext, Analyse.TextTypes.WithoutSpacesLower);
var keyLengths = GetKeyLengths(ciphertext);
LangCharacteristic lang = Storage.GetLangChar(language);
List possKeys = new List();
foreach (int keyLength in keyLengths)
{
string[] columns = GetDecryptColumns(ciphertext, keyLength);
string[] rows = GetRows(columns);
DictionaryAttack attack = new DictionaryAttack();
string[] keys = attack.GetKeys(rows, lang.TopWords);
string[] orderedKeys = OrderKeys(keys, ciphertext, lang);
if (orderedKeys.Length > 0)
possKeys.Add(orderedKeys[0]);
}
if (possKeys.Count > 0)
{
string[] finalKeys = OrderKeys(possKeys.ToArray(), ciphertext, lang);
return finalKeys.ToList();
}
throw new Exceptions.MatchNotFound();
}
///
/// Seřadí klíče od nejpravděpodobnějších po nejméně pravděpodobné
///
///
///
///
///
private string[] OrderKeys(string[] keys, string ciphertext, LangCharacteristic lang)
{
var opentexts = GetOpentexts(keys, ciphertext);
var probabilities = GetProbabilities(opentexts, keys, lang);
var orderedKeys = probabilities.Keys.ToArray();
return orderedKeys;
}
///
/// Zjistí pravděpodobnost jednotlivých klíčů
///
///
///
///
///
private Dictionary GetProbabilities(string[] opentexts, string[] keys, LangCharacteristic lang)
{
string[] topTopWords = lang.TopWords.Take(topWordsCount).ToArray();
Dictionary probabilities = new Dictionary();
for (int i = 0; i < keys.Length; i++)
probabilities[keys[i]] = Analyse.WordsContains(opentexts[i], topTopWords);
return probabilities.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value);
}
///
/// Vrátí všechny dešifrované texty
///
///
///
///
private string[] GetOpentexts(string[] keys, string ciphertext)
{
var opentexts = new string[keys.Length];
for (int i = 0; i < keys.Length; i++)
opentexts[i] = Decrypt(ciphertext, keys[i]);
return opentexts;
}
/*********** First Letters Attack ************/
// TODO: ostatní jazyky nemají TopWords!!!
private List FirstLettersAttack(string ciphertext, Storage.Languages language)
{
var keyLengths = GetKeyLengths(ciphertext);
int minWordLength = 3;
int testStringLength = 20;
double minSuccess = (double)20 / (double)1300;
foreach (int keyLength in keyLengths)
{
if (keyLength < 10)
continue;
string[] columns = GetDecryptColumns(ciphertext, keyLength);
string[] rows = GetRows(columns);
string[] sentence = rows.Take((int)Math.Ceiling((double)testStringLength / (double)keyLength)).ToArray();
var lang = Storage.GetLangChar(language);
DictionaryAttack firstAttack = new DictionaryAttack();
string[] topWords = lang.TopWords.Where(x => x.Length >= minWordLength).ToArray();
string[] topTopWords = topWords.Take(100).ToArray();
var keys = firstAttack.CrossFilter(sentence, topWords);
var opentexts = new string[keys.Length];
for (int i = 0; i < keys.Length; i++)
opentexts[i] = Decrypt(ciphertext, keys[i]);
Dictionary probabilities = new Dictionary();
for (int i = 0; i < keys.Length; i++)
probabilities[keys[i]] = Analyse.WordsContains(opentexts[i], topTopWords);
var resultKeys = probabilities.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value);
var isthere = opentexts.Where(x => x.Contains("oznamenipodala")).Count() == 1;
if(resultKeys.Count > 0 && ((double)resultKeys.First().Value / (double)1300) > minSuccess)
return resultKeys.Take(10).Select(x => x.Key).ToList();
}
throw new Exceptions.MatchNotFound();
}
private string[] GetRows(string[] columns)
{
string[] rows = new string[columns[0].Length];
for (int i = 0; i < columns[0].Length; i++)
{
StringBuilder sb = new StringBuilder();
for (int j = 0; j < columns.Length; j++)
{
sb.Append(columns[j][i].ToString());
}
rows[i] = sb.ToString();
}
return rows;
}
private int[] GetKeyLengths(string ciphertext)
{
int averageKeyLength = ((MinKeyLength + MaxKeyLength) / 2);
var keyLengths = Maths.AllDivisors(ciphertext.Length).Where(x => x >= MinKeyLength && x <= MaxKeyLength).ToArray();
var orderedKeyLengths = keyLengths.OrderBy(x => Math.Abs(x - averageKeyLength)).ToArray();
return orderedKeyLengths;
}
/*********** Brute force attack **************/
private List BruteForceAttack(string ciphertext, Storage.Languages language)
{
int keyLength = 7;
string[] columns = GetDecryptColumns(ciphertext, keyLength);
string[] rows = GetRows(columns);
string firstLine = rows[0];
var variations = Combinatorics.Permutation(firstLine.ToCharArray().ToList());
int res = variations.Count(x => x.Implode() == "namstar");
List canonicalKey = Storage.Letters.Take(keyLength).ToList();
var permKeys = Combinatorics.Permutation(canonicalKey);
Dictionary possTexts = new Dictionary();
foreach (var key in permKeys)
{
string k = key.Implode();
possTexts[k] = Decrypt(ciphertext, k);
}
var langChar = Storage.GetLangChar(language);
var result = possTexts.OrderBy(x => Analyse.SimilarityIndex(x.Value, langChar)).Take(10).ToDictionary(x => x.Key, x => x.Value);
throw new Exceptions.MatchNotFound();
}
/********************** konec ************************/
public override string RandomKey()
{
int length = random.Next(MinKeyLength, MaxKeyLength);
return Text.RandomString(length);
}
public override string ToString()
{
return Storage.Ciphers.trans.ToString();
}
}
}