-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
FeistelCipher.cs
176 lines (149 loc) · 6.71 KB
/
FeistelCipher.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
using System;
using System.Collections.Generic;
using System.Text;
namespace Algorithms.Encoders;
/// <summary>
/// Encodes using Feistel cipher.
/// https://en.wikipedia.org/wiki/Feistel_cipher
/// In cryptography, a Feistel cipher (also known as Luby–Rackoff block cipher)
/// is a symmetric structure used in the construction of block ciphers,
/// named after the German-born physicist and cryptographer Horst Feistel
/// who did pioneering research while working for IBM (USA)
/// A large proportion of block ciphers use the scheme, including the US DES,
/// the Soviet/Russian GOST and the more recent Blowfish and Twofish ciphers.
/// </summary>
public class FeistelCipher : IEncoder<uint>
{
// number of rounds to transform data block, each round a new "round" key is generated.
private const int Rounds = 32;
/// <summary>
/// Encodes text using specified key,
/// where n - text length.
/// </summary>
/// <param name="text">Text to be encoded.</param>
/// <param name="key">Key that will be used to encode the text.</param>
/// <exception cref="ArgumentException">Error: key should be more than 0x00001111 for better encoding, key=0 will throw DivideByZero exception.</exception>
/// <returns>Encoded text.</returns>
public string Encode(string text, uint key)
{
List<ulong> blocksListPlain = SplitTextToBlocks(text);
StringBuilder encodedText = new();
foreach (ulong block in blocksListPlain)
{
uint temp = 0;
// decompose a block to two subblocks 0x0123456789ABCDEF => 0x01234567 & 0x89ABCDEF
uint rightSubblock = (uint)(block & 0x00000000FFFFFFFF);
uint leftSubblock = (uint)(block >> 32);
uint roundKey;
// Feistel "network" itself
for (int round = 0; round < Rounds; round++)
{
roundKey = GetRoundKey(key, round);
temp = rightSubblock ^ BlockModification(leftSubblock, roundKey);
rightSubblock = leftSubblock;
leftSubblock = temp;
}
// compile text string formating the block value to text (hex based), length of the output = 16 byte always
ulong encodedBlock = leftSubblock;
encodedBlock = (encodedBlock << 32) | rightSubblock;
encodedText.Append(string.Format("{0:X16}", encodedBlock));
}
return encodedText.ToString();
}
/// <summary>
/// Decodes text that was encoded using specified key.
/// </summary>
/// <param name="text">Text to be decoded.</param>
/// <param name="key">Key that was used to encode the text.</param>
/// <exception cref="ArgumentException">Error: key should be more than 0x00001111 for better encoding, key=0 will throw DivideByZero exception.</exception>
/// <exception cref="ArgumentException">Error: The length of text should be divisible by 16 as it the block lenght is 16 bytes.</exception>
/// <returns>Decoded text.</returns>
public string Decode(string text, uint key)
{
// The plain text will be padded to fill the size of block (16 bytes)
if (text.Length % 16 != 0)
{
throw new ArgumentException($"The length of {nameof(key)} should be divisible by 16");
}
List<ulong> blocksListEncoded = GetBlocksFromEncodedText(text);
StringBuilder decodedTextHex = new();
foreach (ulong block in blocksListEncoded)
{
uint temp = 0;
// decompose a block to two subblocks 0x0123456789ABCDEF => 0x01234567 & 0x89ABCDEF
uint rightSubblock = (uint)(block & 0x00000000FFFFFFFF);
uint leftSubblock = (uint)(block >> 32);
// Feistel "network" - decoding, the order of rounds and operations on the blocks is reverted
uint roundKey;
for (int round = Rounds - 1; round >= 0; round--)
{
roundKey = GetRoundKey(key, round);
temp = leftSubblock ^ BlockModification(rightSubblock, roundKey);
leftSubblock = rightSubblock;
rightSubblock = temp;
}
// compose decoded block
ulong decodedBlock = leftSubblock;
decodedBlock = (decodedBlock << 32) | rightSubblock;
for(int i = 0; i < 8; i++)
{
ulong a = (decodedBlock & 0xFF00000000000000) >> 56;
// it's a trick, the code works with non zero characters, if your text has ASCII code 0x00 it will be skipped.
if (a != 0)
{
decodedTextHex.Append((char)a);
}
decodedBlock = decodedBlock << 8;
}
}
return decodedTextHex.ToString();
}
// Using the size of block = 8 bytes this function splts the text and returns set of 8 bytes (ulong) blocks
// the last block is extended up to 8 bytes if the tail of the text is smaller than 8 bytes
private static List<ulong> SplitTextToBlocks(string text)
{
List<ulong> blocksListPlain = new();
byte[] textArray = Encoding.ASCII.GetBytes(text);
int offset = 8;
for(int i = 0; i < text.Length; i += 8)
{
// text not always has len%16 == 0, that's why the offset should be adjusted for the last part of the text
if (i > text.Length - 8)
{
offset = text.Length - i;
}
string block = Convert.ToHexString(textArray, i, offset);
blocksListPlain.Add(Convert.ToUInt64(block, 16));
}
return blocksListPlain;
}
// convert the encoded text to the set of ulong values (blocks for decoding)
private static List<ulong> GetBlocksFromEncodedText(string text)
{
List<ulong> blocksListPlain = new();
for(int i = 0; i < text.Length; i += 16)
{
ulong block = Convert.ToUInt64(text.Substring(i, 16), 16);
blocksListPlain.Add(block);
}
return blocksListPlain;
}
// here might be any deterministic math formula
private static uint BlockModification(uint block, uint key)
{
for (int i = 0; i < 32; i++)
{
// 0x55555555 for the better distribution 0 an 1 in the block
block = ((block ^ 0x55555555) * block) % key;
block = block ^ key;
}
return block;
}
// There are many ways to generate a round key, any deterministic math formula does work
private static uint GetRoundKey(uint key, int round)
{
// "round + 2" - to avoid a situation when pow(key,1) ^ key = key ^ key = 0
uint a = (uint)Math.Pow((double)key, round + 2);
return a ^ key;
}
}