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