1 /**
2  * Provides de-/encryption methods for Gravity's Mixcrypt
3  * algorithm.
4  * It uses a combination of a custom routine and their
5  * broken DES.
6  */
7 module zgrf.crypto.mixcrypt;
8 
9 import std.bitmanip;
10 
11 import zgrf.crypto.desbroken;
12 import zgrf.bits;
13 
14 /**
15  * Encrypts data using the key 0x00. Key parameter is ignored.
16  *
17  * Params:
18  *     data = The data array to be encrypted
19  *     key = Ignored. Should be empty
20  *
21  * Returns: An array of bytes containing the encrypted data
22  */
23 ubyte[] encrypt(const(ubyte)[] data, const(ubyte)[] key = []) pure
24 {
25     size_t length = data.length;
26     const ubyte extraLength = length % 8;
27     const size_t paddedLength = (extraLength > 0) ? length + 8 - extraLength : length;
28     length -= extraLength;
29 
30     auto subkeys = createSubkeys(key);
31 
32     const auto cycle = getCycle(length);
33 
34     ubyte[] encryptedData = new ubyte[paddedLength];
35 
36     auto j = 0;
37     for (auto i = 0, step = 0; i < length; i += 8)
38     {
39         step = i / 8;
40         if (step < 20 || step % cycle == 0)
41         {
42             const processedBlock = processBlock(data[i .. i + 8], subkeys);
43             encryptedData[i .. i + 8] = processedBlock;
44         }
45         else
46         {
47             if (j == 7)
48             {
49                 const permBlock = permutateByteBlock(data[i .. i + 8], false);
50                 encryptedData[i .. i + 8] = permBlock;
51                 j = 0;
52             }
53             else
54             {
55                 encryptedData[i .. i + 8] = data[i .. i + 8];
56             }
57             j++;
58         }
59     }
60 
61     if (extraLength > 0)
62     {
63         const ubyte padding = 8 - extraLength;
64         ubyte[] lastBlock = new ubyte[8];
65         lastBlock[0 .. extraLength] = data[length .. length + extraLength];
66         foreach (i; 0 .. padding)
67         {
68             lastBlock[extraLength + i] = padding;
69         }
70         const auto step = length / 8;
71         if (step < 20 || step % cycle == 0)
72         {
73             const processedBlock = processBlock(lastBlock, subkeys);
74             encryptedData[length .. length + 8] = processedBlock;
75         }
76         else
77         {
78             if (j == 7)
79             {
80                 const permBlock = permutateByteBlock(lastBlock, false);
81                 encryptedData[length .. length + 8] = permBlock;
82             }
83             else
84             {
85                 encryptedData[length .. length + 8] = lastBlock;
86             }
87         }
88     }
89 
90     return encryptedData;
91 }
92 
93 /**
94  * Decrypts data using the key 0x00. Key parameter is ignored.
95  *
96  * The input data must be a multiple of 8 bytes long.
97  *
98  * Params:
99  *  data = The data array to be decrypted
100  *  key = Ignored. Should be empty
101  *  unencryptedSize = The size/length of the unencrypted data.
102  *                    The returning data will be truncated to this size if it has padding.
103  *
104  * Returns: An array of bytes containing the decrypted data
105  */
106 ubyte[] decrypt(const(ubyte)[] data, const(ubyte)[] key, const size_t unencryptedSize) pure
107 in (data.length % 8 == 0, "Data must be a multiple of 64 bits (8 bytes)")
108 {
109     const size_t length = data.length;
110 
111     auto subkeys = createSubkeys(key);
112 
113     const auto cycle = getCycle(unencryptedSize);
114 
115     ubyte[] decryptedData = new ubyte[length];
116 
117     for (auto i = 0, j = 0, step = 0; i < length; i += 8)
118     {
119         step = i / 8;
120         if (step < 20 || step % cycle == 0)
121         {
122             const processedBlock = processBlock(data[i .. i + 8], subkeys);
123             decryptedData[i .. i + 8] = processedBlock;
124         }
125         else
126         {
127             if (j == 7)
128             {
129                 const permBlock = permutateByteBlock(data[i .. i + 8]);
130                 decryptedData[i .. i + 8] = permBlock;
131                 j = 0;
132             }
133             else
134             {
135                 decryptedData[i .. i + 8] = data[i .. i + 8];
136             }
137             j++;
138         }
139     }
140 
141     const long padding = decryptedData.length - unencryptedSize;
142 
143     if (padding > 0)
144     {
145         return decryptedData[0 .. $ - padding];
146     }
147     else
148     {
149         return decryptedData;
150     }
151 }
152 
153 private int getCycle(const size_t unencryptedSize) pure
154 {
155     ubyte digits = 0;
156     size_t step = unencryptedSize;
157     while (step > 0)
158     {
159         step /= 10;
160         digits++;
161     }
162     if (digits == 0)
163     {
164         digits = 1;
165     }
166 
167     int cycle;
168     if (digits < 3)
169     {
170         cycle = 1;
171     }
172     else if (digits < 5)
173     {
174         cycle = digits + 1;
175     }
176     else if (digits < 7)
177     {
178         cycle = digits + 9;
179     }
180     else
181     {
182         cycle = digits + 15;
183     }
184 
185     return cycle;
186 }
187 
188 private ubyte substituteLastByte(const ubyte lastByte) pure
189 {
190     ubyte result;
191     switch (lastByte)
192     {
193     case 0x77:
194         result = 0x48;
195         break;
196     case 0x48:
197         result = 0x77;
198         break;
199     case 0x00:
200         result = 0x2B;
201         break;
202     case 0x2B:
203         result = 0x00;
204         break;
205     case 0x01:
206         result = 0x68;
207         break;
208     case 0x68:
209         result = 0x01;
210         break;
211     case 0x60:
212         result = 0xFF;
213         break;
214     case 0xFF:
215         result = 0x60;
216         break;
217     case 0x6C:
218         result = 0x80;
219         break;
220     case 0x80:
221         result = 0x6C;
222         break;
223     case 0xB9:
224         result = 0xC0;
225         break;
226     case 0xC0:
227         result = 0xB9;
228         break;
229     case 0xEB:
230         result = 0xFE;
231         break;
232     case 0xFE:
233         result = 0xEB;
234         break;
235     default:
236         result = lastByte;
237         break;
238     }
239     return result;
240 }
241 
242 private ubyte[8] permutateByteBlock(const ubyte[] data, bool isDecryption = true) pure
243 {
244     ubyte[8] block;
245     if (isDecryption)
246     {
247         block[0] = data[3];
248         block[1] = data[4];
249         block[2] = data[6];
250         block[3] = data[0];
251         block[4] = data[1];
252         block[5] = data[2];
253         block[6] = data[5];
254     }
255     else
256     {
257         block[0] = data[3];
258         block[1] = data[4];
259         block[2] = data[5];
260         block[3] = data[0];
261         block[4] = data[1];
262         block[5] = data[6];
263         block[6] = data[2];
264     }
265 
266     const ubyte lastByte = substituteLastByte(data[7]);
267     block[7] = lastByte;
268 
269     return block;
270 }
271 
272 ///
273 unittest
274 {
275     const ubyte[] key = [0x0e, 0x32, 0x92, 0x32, 0xea, 0x6d, 0x0d, 0x73];
276     const ubyte[] message = [0x64, 0x61, 0x74, 0x61, 0x5C, 0x61, 0x6C, 0x64, 0x65,
277         0x5F, 0x61, 0x6C, 0x63, 0x68, 0x65, 0x2E, 0x67, 0x61,
278         0x74, 0x00, 0x00, 0x00, 0x00, 0x00];
279     const ubyte[] encoded = [0x30, 0x65, 0x25, 0x25, 0x08, 0x24, 0x39, 0x30, 0x64,
280         0x5B, 0x21, 0x6D, 0x37, 0x6D, 0x31, 0x7A, 0x63, 0x71,
281         0x65, 0x11, 0x51, 0x00, 0x55, 0x04];
282 
283     const ubyte[] actualDecoded = decrypt(encoded, key, 24);
284     const ubyte[] actualEncoded = encrypt(message, key);
285 
286     assert(actualEncoded == encoded);
287     assert(actualDecoded == message);
288 }
289