Gabriel Tofvesson 48673e37da Added lots of comments (with just a pinch of personality added to them)
Added more helper methods (they just never stop, do they?)
Moved some stuff around because I want my chaos to be sorted
Actually made the server and clients interactive in the way they're supposed to be
Added CBC to NetClient to prevent ciphertexts from leaking info to malicious third parties
Removed the old (built in) AES implementation in favour of my own
Added a static salt to the AES implementation to heavily discrourage rainbowtables
2018-02-24 01:48:13 +01:00

228 lines
8.0 KiB
C#

using System;
using System.Linq;
using System.Reflection;
namespace Tofvesson.Crypto
{
// Specifies the basic structure of a block cipher
public abstract class BlockCipher
{
public Int32 BlockSize { get; private set; }
public BlockCipher(int blockSize) { this.BlockSize = blockSize; }
public abstract byte[] Encrypt(byte[] message);
public abstract byte[] Decrypt(byte[] ciphertext);
}
// Base structure of a cipher block chaining algorithm
// For an in-depth explanation of what this is (as well as visuals of the implementations that are a bit futher down), visit https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
public abstract class GenericCBC : BlockCipher
{
private static readonly byte[] splitter = ":".ToUTF8Bytes();
private readonly byte[] iv_e;
public byte[] IV { get => (byte[])iv_e.Clone(); }
protected readonly byte[] currentIV_e;
protected readonly byte[] currentIV_d;
protected readonly BlockCipher cipher;
public GenericCBC(BlockCipher cipher, RandomProvider provider) : base(cipher.BlockSize)
{
this.cipher = cipher;
// Generate initialization vector and set it as the current iv
iv_e = provider.GetBytes(new byte[cipher.BlockSize]);
currentIV_e = new byte[cipher.BlockSize];
currentIV_d = new byte[cipher.BlockSize];
Array.Copy(iv_e, currentIV_e, iv_e.Length);
Array.Copy(iv_e, currentIV_d, iv_e.Length);
}
public GenericCBC(BlockCipher cipher, byte[] iv_e) : base(cipher.BlockSize)
{
this.iv_e = iv_e;
this.cipher = cipher;
currentIV_e = new byte[cipher.BlockSize];
currentIV_d = new byte[cipher.BlockSize];
Array.Copy(iv_e, currentIV_e, iv_e.Length);
Array.Copy(iv_e, currentIV_d, iv_e.Length);
}
// Separate a given messae into blocks for processing
protected byte[][] SplitBlocks(byte[] message)
{
byte[][] blocks = new byte[(message.Length / cipher.BlockSize) + (message.Length % cipher.BlockSize == 0 ? 0 : 1)][];
for (int i = 0; i < blocks.Length; ++i)
{
blocks[i] = message.SubArray(i * cipher.BlockSize, Math.Min((i + 1) * cipher.BlockSize, message.Length));
if (blocks[i].Length != cipher.BlockSize)
{
byte[] res = new byte[cipher.BlockSize];
Array.Copy(blocks[i], res, blocks[i].Length);
blocks[i] = res;
}
}
return blocks;
}
// Recombine blocks that have been split back into a single string of bytes
protected byte[] CollectBlocks(byte[][] result)
{
byte[] collected = new byte[result.Length * cipher.BlockSize];
for (int i = 0; i < result.Length; ++i) Array.Copy(result[i], 0, collected, cipher.BlockSize * i, cipher.BlockSize);
return collected;
}
// Resets the state of this CBC instance
public virtual void Reset()
{
Array.Copy(iv_e, currentIV_e, iv_e.Length);
Array.Copy(iv_e, currentIV_d, iv_e.Length);
}
}
/// <summary>
/// Standard cipher block chaining implementation (not recommended, but available nonetheless)
/// </summary>
public sealed class CBC : GenericCBC
{
public CBC(BlockCipher cipher, RandomProvider provider) : base(cipher, provider)
{ }
public CBC(BlockCipher cipher, byte[] iv_e) : base(cipher, iv_e)
{ }
// This entire method is pretty self-explanatory. All you need to know is: currentIV_e represents the IV currently being used for encryption
public override byte[] Encrypt(byte[] message)
{
byte[][] blocks = SplitBlocks(message);
for (int i = 0; i < blocks.Length; ++i)
{
byte[] enc_result = cipher.Encrypt(blocks[i].XOR(currentIV_e));
Array.Copy(enc_result, currentIV_e, enc_result.Length);
Array.Copy(enc_result, blocks[i], cipher.BlockSize);
}
return CollectBlocks(blocks);
}
public override byte[] Decrypt(byte[] ciphertext)
{
// Split ciphertext into encrypted blocks
byte[][] blocks = SplitBlocks(ciphertext);
for(int i = 0; i<blocks.Length; ++i)
{
// Decrypt block
Array.Copy(cipher.Decrypt(blocks[i]).XOR(currentIV_d), blocks[i], cipher.BlockSize);
// Set the next iv to be this iteration's decrypted block
Array.Copy(blocks[i], currentIV_d, cipher.BlockSize);
}
return CollectBlocks(blocks);
}
}
// Propogating CBC
public sealed class PCBC : GenericCBC
{
public PCBC(BlockCipher cipher, RandomProvider provider) : base(cipher, provider)
{ }
public PCBC(BlockCipher cipher, byte[] iv_e) : base(cipher, iv_e)
{ }
public override byte[] Encrypt(byte[] message)
{
byte[][] blocks = SplitBlocks(message);
for (int i = 0; i < blocks.Length; ++i)
{
// Store the unmodified input text block
byte[] before = (byte[])blocks[i].Clone();
// Compute the encrypted value for this block
byte[] enc_result = cipher.Encrypt(blocks[i].XOR(currentIV_e));
// Store/compute new IV
Array.Copy(enc_result, currentIV_e, enc_result.Length);
currentIV_e.XOR(before);
// Store encryption result
Array.Copy(enc_result, 0, blocks[i], i * 16, 16);
}
return CollectBlocks(blocks);
}
public override byte[] Decrypt(byte[] ciphertext)
{
// Split ciphertext into blocks (ciphertext should ahve a length that is a multiple of cipher.BlockSize)
byte[][] blocks = SplitBlocks(ciphertext);
// Decrypt each block
for (int i = 0; i < blocks.Length; ++i)
{
// Temporarily store a copy of the encrypted block
byte[] before = (byte[])blocks[i].Clone();
// Decrypt the block
Array.Copy(cipher.Decrypt(before).XOR(currentIV_d), blocks[i], cipher.BlockSize);
// Compute the next IV
Array.Copy(currentIV_d, before.XOR(blocks[i]), cipher.BlockSize);
}
return CollectBlocks(blocks);
}
}
public sealed class CFB : GenericCBC
{
public CFB(BlockCipher cipher, RandomProvider provider) : base(cipher, provider)
{ }
public CFB(BlockCipher cipher, byte[] iv_e) : base(cipher, iv_e)
{ }
public override byte[] Encrypt(byte[] message)
{
byte[][] blocks = SplitBlocks(message);
for (int i = 0; i < blocks.Length; ++i)
// Encrypt IV and compute the XOR of the result with the plaintext. Finally, set the ciphertext as the IV for the next iteration
Array.Copy(blocks[i] = blocks[i].XOR(cipher.Encrypt(currentIV_e)), currentIV_e, cipher.BlockSize);
return CollectBlocks(blocks);
}
public override byte[] Decrypt(byte[] ciphertext)
{
// Split ciphertext into blocks (ciphertext should ahve a length that is a multiple of cipher.BlockSize)
byte[][] blocks = SplitBlocks(ciphertext);
for (int i = 0; i < blocks.Length; ++i)
{
// Store unmodified copy
byte[] before = (byte[])blocks[i].Clone();
// Decrypt
blocks[i] = blocks[i].XOR(cipher.Encrypt(currentIV_d));
// Set the ciphertext a the iv for the next iteration
Array.Copy(before, currentIV_d, cipher.BlockSize);
}
return CollectBlocks(blocks);
}
}
}