Last active
July 19, 2022 10:12
-
-
Save sblmnl/222c786ddebd00e2dbae7ab361fb2618 to your computer and use it in GitHub Desktop.
Known plaintext attack against the Chug encryption algorithm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using ChugSharp; | |
using ChugSharp.PaddingAlgorithms; | |
using System; | |
using System.Collections.Generic; | |
using System.Security.Cryptography; | |
struct Permutation | |
{ | |
public byte Key { get; set; } | |
public byte[] Data { get; set; } | |
public static Permutation Create(byte[] ciphertext, byte[] plaintext, byte initialN) | |
{ | |
int key = (ciphertext[0] - initialN) % 256; | |
return new Permutation() | |
{ | |
Key = key < 0 | |
? (byte)(256 + key) | |
: (byte)key, | |
Data = plaintext | |
}; | |
} | |
} | |
static class Extensions | |
{ | |
public static IEnumerable<Permutation> DetectKnownPlaintext(this IEnumerable<Permutation> permutations, byte[] knownPlaintext) | |
{ | |
bool Contains(byte[] haystack, byte[] needle) | |
{ | |
for (int i = 0; i < haystack.Length; i++) | |
{ | |
if (haystack[i] == needle[0]) | |
{ | |
bool match = true; | |
for (int j = 1; j < needle.Length; j++) | |
{ | |
if (i + j >= haystack.Length || haystack[i + j] != needle[j]) | |
{ | |
match = false; | |
break; | |
} | |
} | |
if (match) | |
return true; | |
} | |
} | |
return false; | |
} | |
if (knownPlaintext is null) | |
throw new ArgumentNullException(nameof(knownPlaintext)); | |
foreach (var permutation in permutations) | |
{ | |
if (Contains(permutation.Data, knownPlaintext)) | |
{ | |
yield return permutation; | |
} | |
} | |
} | |
public static IEnumerable<Permutation> TryUnpadPermutations(this IEnumerable<Permutation> permutations, IChugPaddingAlgorithm padding) | |
{ | |
foreach (var permutation in permutations) | |
{ | |
var unpadded = new Permutation() { Key = permutation.Key }; | |
try | |
{ | |
unpadded.Data = padding.Unpad(permutation.Data); | |
} | |
catch | |
{ | |
continue; | |
} | |
yield return unpadded; | |
} | |
} | |
} | |
class Program | |
{ | |
static IEnumerable<Permutation> GetPermutations(byte[] ciphertext, int keySize) | |
{ | |
byte[] Decrypt(int initialN) | |
{ | |
if (ciphertext is null) | |
throw new ArgumentNullException(nameof(ciphertext)); | |
if (ciphertext.Length == 0) | |
throw new ArgumentException("The ciphertext cannot be empty!"); | |
if (keySize < 0) | |
throw new ArgumentOutOfRangeException(nameof(keySize)); | |
if (initialN < 0 || initialN > 255) | |
throw new ArgumentOutOfRangeException(nameof(initialN)); | |
byte[] plaintext = new byte[ciphertext.Length]; | |
int n = initialN; | |
for (int i = 0; i < ciphertext.Length; i++) | |
{ | |
if (i != 0 && i % keySize == 0) | |
n += initialN; | |
switch (i % 2) | |
{ | |
case 0: | |
plaintext[i] = (byte)((ciphertext[i] + n) % 256); | |
break; | |
case 1: | |
plaintext[i] = (byte)((ciphertext[i] - n) % 256); | |
break; | |
} | |
} | |
return plaintext; | |
} | |
for (int i = 0; i < 256; i++) | |
{ | |
yield return Permutation.Create(ciphertext, Decrypt(i), (byte)i); | |
} | |
} | |
static void Main() | |
{ | |
// Create a random key and plaintext | |
byte[] key = new byte[2]; | |
byte[] plaintext = new byte[16]; | |
byte[] knownPlaintext = new byte[plaintext.Length / 4]; | |
RandomNumberGenerator.Fill(key); | |
RandomNumberGenerator.Fill(plaintext); | |
for (int i = 0; i < knownPlaintext.Length; i++) | |
knownPlaintext[i] = plaintext[i]; | |
// Encrypt w/ length prefixed random padding using a block size of 32 bytes | |
byte[] ciphertext = new Chug(true).Encrypt(plaintext, key); | |
Console.WriteLine("[info]"); | |
Console.WriteLine(); | |
Console.WriteLine("key\t: {0}", BitConverter.ToString(key)); | |
Console.WriteLine("pt\t: {0}", BitConverter.ToString(plaintext)); | |
Console.WriteLine("kpt\t: {0}", BitConverter.ToString(knownPlaintext)); | |
Console.WriteLine("ct\t: {0}", BitConverter.ToString(ciphertext)); | |
Console.WriteLine(); | |
Console.WriteLine("[messages]"); | |
Console.WriteLine(); | |
// Decrypt all permutations of the ciphertext w/ the key length | |
// then detect the permutations that contain the known plaintext | |
// and attempt to unpad them. | |
foreach (var permutation in GetPermutations(ciphertext, key.Length) | |
.DetectKnownPlaintext(knownPlaintext) | |
.TryUnpadPermutations(new LengthPrefixedRandomPadding(32))) | |
{ | |
Console.WriteLine("key: {0} - msg: {1}", | |
permutation.Key.ToString("X2"), | |
BitConverter.ToString(permutation.Data)); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is what a successful decryption looks like: