1 module crypto.tea;
2 
3 import std.bitmanip;
4 import std.exception;
5 
6 public import crypto.padding;
7 
8 package class TEA
9 {
10     private enum int DELTA = cast(int) 0x9E3779B9;
11     private int[4] m_key;
12     private int    m_rounds;
13 
14     public this(const int[4] key)
15     {
16         m_key    = key.dup;
17         m_rounds = 32;
18     }
19 
20     ~this()
21     {
22         import crypto.utils : secureZeroMemory;
23         secureZeroMemory(cast(void[]) m_key);
24     }
25 
26     /// Encrypt given ubyte array (length to be crypted must be 8 ubyte aligned)
27     public alias encrypt = crypt!(encryptBlock);
28     /// Decrypt given ubyte array (length to be crypted must be 8 ubyte aligned)
29     public alias decrypt = crypt!(decryptBlock);
30 
31     private void crypt(alias T)(ubyte[] _ubytes, size_t _offset = 0, long _count = -1)
32     {
33         if (_count == -1)
34         {
35             _count = cast(long)(_ubytes.length - _offset);
36         }
37 
38         enforce(_count % 8 == 0);
39 
40         for (size_t i = _offset; i < (_offset + _count); i += 8)
41         {
42             T(_ubytes, i);
43         }
44     }
45 
46     /// Encrypt given block of 8 ubytes
47     private void encryptBlock(ubyte[] _ubytes, size_t _offset)
48     {
49         auto v0 = _ubytes.peek!(int, Endian.littleEndian)(_offset);
50         auto v1 = _ubytes.peek!(int, Endian.littleEndian)(_offset + 4);
51 
52         int sum = 0;
53 
54         foreach (i; 0 .. m_rounds)
55         {
56             sum += DELTA;
57             v0 += ((v1 << 4) + m_key[0]) ^ (v1 + sum) ^ ((v1 >> 5) + m_key[1]);
58             v1 += ((v0 << 4) + m_key[2]) ^ (v0 + sum) ^ ((v0 >> 5) + m_key[3]);
59         }
60 
61         _ubytes.write!(int, Endian.littleEndian)(v0, _offset);
62         _ubytes.write!(int, Endian.littleEndian)(v1, _offset + 4);
63     }
64 
65     /// Decrypt given block of 8 ubytes
66     private void decryptBlock(ubyte[] _ubytes, size_t _offset)
67     {
68         auto v0 = _ubytes.peek!(int, Endian.littleEndian)(_offset);
69         auto v1 = _ubytes.peek!(int, Endian.littleEndian)(_offset + 4);
70 
71         auto sum = cast(int)(cast(uint) DELTA * cast(uint) m_rounds);  //0xC6EF3720
72 
73         foreach (i; 0 .. m_rounds)
74         {
75             v1 -= ((v0 << 4) + m_key[2]) ^ (v0 + sum) ^ ((v0 >> 5) + m_key[3]);
76             v0 -= ((v1 << 4) + m_key[0]) ^ (v1 + sum) ^ ((v1 >> 5) + m_key[1]);
77             sum -= DELTA;
78         }
79             
80         _ubytes.write!(int, Endian.littleEndian)(v0, _offset);
81         _ubytes.write!(int, Endian.littleEndian)(v1, _offset + 4);
82     }
83 }
84 
85 ///
86 class Tea
87 {
88     public static ubyte[] encrypt(const ubyte[] input, const char[] key, PaddingMode paddingMode = PaddingMode.NoPadding)
89     {
90         ubyte[] buf = cast(ubyte[])key;
91         int[4] bkey = [buf[0], buf[1], buf[2], buf[3]];
92 
93         return encrypt(input, bkey, paddingMode);
94     }
95 
96     public static ubyte[] encrypt(const ubyte[] input, const int[4] key, PaddingMode paddingMode = PaddingMode.NoPadding)
97     {
98         ubyte[] data = Padding.padding(input, 8, paddingMode);
99 
100         TEA tea = new TEA(key);
101         tea.encrypt(data);
102 
103         return data;
104     }
105 
106     public static ubyte[] decrypt(const ubyte[] input, const char[] key, PaddingMode paddingMode = PaddingMode.NoPadding)
107     {
108         ubyte[] buf = cast(ubyte[])key;
109         int[4] bkey = [buf[0], buf[1], buf[2], buf[3]];
110 
111         return decrypt(input, bkey, paddingMode);
112     }
113 
114     public static ubyte[] decrypt(const ubyte[] input, const int[4] key, PaddingMode paddingMode = PaddingMode.NoPadding)
115     {
116         auto data = input.dup;
117         TEA tea = new TEA(key);
118         tea.decrypt(data);
119 
120         return Padding.unpadding(data, 8, paddingMode);
121     }
122 }
123 
124 unittest
125 {
126     ubyte[] data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
127     int[4] key = [1, 2, 3, 4];
128 
129     ubyte[] buf = Tea.encrypt(data, key, PaddingMode.PKCS5);
130     buf = Tea.decrypt(buf, key, PaddingMode.PKCS5);
131     assert(data == buf);
132 }
133 
134 package class XTEA
135 {
136     private enum int DELTA = cast(int) 0x9E3779B9;
137     private int[4] m_key;
138     private int m_rounds;
139 
140     public this(const int[4] key, const int rounds)
141     {
142         m_key    = key.dup;
143         m_rounds = rounds;
144     }
145 
146     ~this()
147     {
148         import crypto.utils : secureZeroMemory;
149         secureZeroMemory(cast(void[]) m_key);
150     }
151 
152     /// Encrypt given ubyte array (length to be crypted must be 8 ubyte aligned)
153     public alias encrypt = crypt!(encryptBlock);
154     /// Decrypt given ubyte array (length to be crypted must be 8 ubyte aligned)
155     public alias decrypt = crypt!(decryptBlock);
156 
157     private void crypt(alias T)(ubyte[] _ubytes, size_t _offset = 0, long _count = -1)
158     {
159         if (_count == -1)
160         {
161             _count = cast(long)(_ubytes.length - _offset);
162         }
163 
164         enforce(_count % 8 == 0);
165 
166         for (size_t i = _offset; i < (_offset + _count); i += 8)
167         {
168             T(_ubytes, i);
169         }
170     }
171 
172     /// Encrypt given block of 8 ubytes
173     private void encryptBlock(ubyte[] _ubytes, size_t _offset)
174     {
175         auto v0 = _ubytes.peek!(int, Endian.littleEndian)(_offset);
176         auto v1 = _ubytes.peek!(int, Endian.littleEndian)(_offset + 4);
177 
178         int sum = 0;
179 
180         foreach (i; 0 .. m_rounds)
181         {
182             v0 += ((v1 << 4 ^ cast(int)(cast(uint) v1 >> 5)) + v1) ^ (sum + m_key[sum & 3]);
183             sum += DELTA;
184             v1 += ((v0 << 4 ^ cast(int)(cast(uint) v0 >> 5)) + v0) ^ (sum + m_key[cast(int)(cast(uint) sum >> 11) & 3]);
185         }
186 
187         _ubytes.write!(int, Endian.littleEndian)(v0, _offset);
188         _ubytes.write!(int, Endian.littleEndian)(v1, _offset + 4);
189     }
190 
191     /// Decrypt given block of 8 ubytes
192     private void decryptBlock(ubyte[] _ubytes, size_t _offset)
193     {
194         auto v0 = _ubytes.peek!(int, Endian.littleEndian)(_offset);
195         auto v1 = _ubytes.peek!(int, Endian.littleEndian)(_offset + 4);
196 
197         auto sum = cast(int)(cast(uint) DELTA * cast(uint) m_rounds);
198 
199         foreach (i; 0 .. m_rounds)
200         {
201             v1 -= ((v0 << 4 ^ cast(int)(cast(uint) v0 >> 5)) + v0) ^ (sum + m_key[cast(int)(cast(uint) sum >> 11) & 3]);
202             sum -= DELTA;
203             v0 -= ((v1 << 4 ^ cast(int)(cast(uint) v1 >> 5)) + v1) ^ (sum + m_key[sum & 3]);
204         }
205 
206         _ubytes.write!(int, Endian.littleEndian)(v0, _offset);
207         _ubytes.write!(int, Endian.littleEndian)(v1, _offset + 4);
208     }
209 }
210 
211 ///
212 class Xtea
213 {
214     public static ubyte[] encrypt(const ubyte[] input, const char[] key, const int rounds = 64, PaddingMode paddingMode = PaddingMode.NoPadding)
215     {
216         ubyte[] buf = cast(ubyte[])key;
217         int[4] bkey = [buf[0], buf[1], buf[2], buf[3]];
218 
219         return encrypt(input, bkey, rounds, paddingMode);
220     }
221     
222     public static ubyte[] encrypt(const ubyte[] input, const int[4] key, const int rounds = 64, PaddingMode paddingMode = PaddingMode.NoPadding)
223     {
224         ubyte[] data = Padding.padding(input, 8, paddingMode);
225 
226         XTEA xtea = new XTEA(key, rounds);
227         xtea.encrypt(data);
228 
229         return data;
230     }
231 
232     public static ubyte[] decrypt(const ubyte[] input, const char[] key, const int rounds = 64, PaddingMode paddingMode = PaddingMode.NoPadding)
233     {
234         ubyte[] buf = cast(ubyte[])key;
235         int[4] bkey = [buf[0], buf[1], buf[2], buf[3]];
236 
237         return decrypt(input, bkey, rounds, paddingMode);
238     }
239 
240     public static ubyte[] decrypt(const ubyte[] input, const int[4] key, const int rounds = 64, PaddingMode paddingMode = PaddingMode.NoPadding)
241     {
242         auto data = input.dup;
243         XTEA xtea = new XTEA(key, rounds);
244         xtea.decrypt(data);
245 
246         return Padding.unpadding(data, 8, paddingMode);
247     }
248 }
249 
250 unittest
251 {
252     ubyte[] data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
253     int[4] key = [1, 2, 3, 4];
254     immutable int rounds = 64;
255 
256     ubyte[] buf = Xtea.encrypt(data, key, rounds, PaddingMode.PKCS5);
257     buf = Xtea.decrypt(buf, key, rounds, PaddingMode.PKCS5);
258     assert(data == buf);
259 }