]> git.basschouten.com Git - openhab-addons.git/blob
e43ee2dbaa089474bf9e526496edd058cc0777fa
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.paradoxalarm.internal.communication.crypto;
14
15 import java.util.Arrays;
16
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.openhab.binding.paradoxalarm.internal.util.ParadoxUtil;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
21
22 /**
23  * This class is used to encrypt and decrypt communication from/to Paradox system. Singleton pattern.
24  *
25  * Paradox encryption is using Rijndael 256-key expansion alghoritm.
26  * When key is changed the updateKey(byte[]) method needs to be called in front.
27  * Encrypt and Decrypt methods use the expandedKey field to do their job.
28  *
29  * The first packet sent to Paradox is the IP150 password as bytes, extended to 32 bytes with 0xEE.
30  * The first response contains the key that will be used for the rest of communication.
31  *
32  * Most of the coding is copy from Python / rewrite in Java from second link of jpbaracca's PAI repository. Probably
33  * some of the variables can be named better but I don't understand this code in it's full scope so I preferred to keep
34  * it as it is.
35  *
36  * @author Konstantin Polihronov - Initial contribution
37  *
38  * @see <a href=https://www.samiam.org/key-schedule.html>Sam Trendholme's page about AES</a>
39  * @see <a href=https://github.com/ParadoxAlarmInterface/pai>Github of jpbaracca's work - ParadoxAlarmInterface in
40  *      python</a>
41  */
42 @NonNullByDefault
43 public class EncryptionHandler {
44
45     private final Logger logger = LoggerFactory.getLogger(EncryptionHandler.class);
46
47     private static final int KEY_ARRAY_LENGTH = 32;
48     private static final int TABLE_SIZE = 256;
49     private static final int KEY_LENGTH = 240;
50     private static final int PAYLOAD_RATE_LENGTH = 16;
51     private static final int ROUNDS = 14;
52
53     private static final int[] lTable = new int[TABLE_SIZE];
54     private static final int[] aTable = new int[TABLE_SIZE];
55
56     private static EncryptionHandler instance = new EncryptionHandler(new byte[] {});
57     static {
58         generateTables();
59     }
60
61     private static void generateTables() {
62         int a = 1;
63         int d;
64         for (int index = 0; index < 255; index++) {
65             aTable[index] = a & 0xFF;
66             /* Multiply by three */
67             d = (a & 0x80) & 0xFF;
68             a <<= 1;
69             if (d == 0x80) {
70                 a ^= 0x1b;
71                 a &= 0xFF;
72             }
73             a ^= aTable[index];
74             a &= 0xFF;
75             /* Set the log table value */
76             lTable[aTable[index]] = index & 0xFF;
77         }
78         aTable[255] = aTable[0];
79         lTable[0] = 0;
80     }
81
82     private final int[] expandedKey = new int[KEY_LENGTH];
83
84     private EncryptionHandler(byte[] newKey) {
85         if (newKey.length > 0) {
86             expandKey(newKey);
87         }
88     }
89
90     public static EncryptionHandler getInstance() {
91         return instance;
92     }
93
94     public synchronized EncryptionHandler updateKey(byte[] newKey) {
95         instance = new EncryptionHandler(newKey);
96         return instance;
97     }
98
99     public byte[] encrypt(byte[] payload) {
100         if (payload.length % 16 != 0) {
101             payload = ParadoxUtil.extendArray(payload, PAYLOAD_RATE_LENGTH);
102             printArray("Array had to be extended:", payload);
103             logger.trace("New payload length={}", payload.length);
104         }
105
106         int[] payloadAsIntArray = ParadoxUtil.toIntArray(payload);
107
108         final int[] s = EncryptionHandlerConstants.S;
109         byte[] result = new byte[0];
110         for (int i = 0; i < payloadAsIntArray.length / 16; i++) {
111             int[] tempArray = Arrays.copyOfRange(payloadAsIntArray, i * 16, (i + 1) * 16);
112             keyAddition(tempArray, 0);
113
114             for (int r = 1; r <= ROUNDS; r++) {
115                 sBox(tempArray, s);
116                 shiftRow(tempArray, 0);
117                 if (r != ROUNDS) {
118                     mixColumn(tempArray);
119                 }
120                 keyAddition(tempArray, r * 16);
121             }
122
123             result = ParadoxUtil.mergeByteArrays(result, ParadoxUtil.toByteArray(tempArray));
124         }
125
126         printArray("Encrypted array", result);
127         return result;
128     }
129
130     public byte[] decrypt(byte[] payload) {
131         int[] payloadAsIntArray = ParadoxUtil.toIntArray(payload);
132
133         final int[] si = EncryptionHandlerConstants.Si;
134         byte[] result = new byte[0];
135         for (int i = 0; i < payloadAsIntArray.length / 16; i++) {
136             int[] tempArray = Arrays.copyOfRange(payloadAsIntArray, i * 16, (i + 1) * 16);
137
138             for (int r = ROUNDS; r > 0; r--) {
139                 keyAddition(tempArray, r * 16);
140                 if (r != ROUNDS) {
141                     invMixColumn(tempArray);
142                 }
143                 sBox(tempArray, si);
144                 shiftRow(tempArray, 1);
145             }
146
147             keyAddition(tempArray, 0);
148
149             result = ParadoxUtil.mergeByteArrays(result, ParadoxUtil.toByteArray(tempArray));
150         }
151
152         printArray("Decrypted array", result);
153         return result;
154     }
155
156     private void printArray(String description, byte[] array) {
157         ParadoxUtil.printByteArray(description, array, array.length);
158     }
159
160     private byte[] fillArray(byte[] keyBytes) {
161         byte[] byteArray = new byte[keyBytes.length];
162         for (int i = 0; i < keyBytes.length; i++) {
163             byteArray[i] = (byte) (keyBytes[i] & 0xFF);
164         }
165
166         byte[] expandedArray = ParadoxUtil.extendArray(byteArray, KEY_ARRAY_LENGTH);
167         return expandedArray;
168     }
169
170     private void expandKey(byte[] input) {
171         // fill array to 32th byte with 0xEE
172         byte[] filledArray = fillArray(input);
173
174         int[] temp = { 0, 0, 0, 0 };
175         for (int i = 0; i < 4; i++) {
176             for (int j = 0; j < 4; j++) {
177                 expandedKey[j * 4 + i] = filledArray[i * 4 + j] & 0xFF;
178             }
179             for (int j = 0; j < 4; j++) {
180                 expandedKey[j * 4 + i + 16] = filledArray[i * 4 + j + 16] & 0xFF;
181             }
182         }
183
184         final int[] s = EncryptionHandlerConstants.S;
185         for (int i = 8; i < 60; i++) {
186             for (int j = 0; j < 4; j++) {
187                 temp[j] = expandedKey[(((i - 1) & 0xfc) << 2) + ((i - 1) & 0x03) + j * 4];
188             }
189
190             if (i % 4 == 0) {
191                 for (int j = 0; j < 4; j++) {
192                     temp[j] = s[temp[j]];
193                 }
194             }
195
196             if (i % 8 == 0) {
197                 int tmp = temp[0];
198
199                 for (int j = 1; j < 4; j++) {
200                     temp[j - 1] = temp[j];
201                 }
202
203                 temp[3] = tmp;
204                 temp[0] ^= EncryptionHandlerConstants.RCON[(i / 8 - 1)];
205             }
206
207             for (int j = 0; j < 4; j++) {
208                 expandedKey[((i & 0xfc) << 2) + (i & 0x03)
209                         + j * 4] = expandedKey[(((i - 8) & 0xfc) << 2) + ((i - 8) & 0x03) + j * 4] ^ temp[j];
210             }
211         }
212     }
213
214     private int gmul(int c, int b) {
215         int s = lTable[c] + lTable[b];
216         s %= 255;
217         s = aTable[s];
218         if (b == 0 || c == 0) {
219             s = 0;
220         }
221         return s;
222     }
223
224     private void sBox(int[] a, int[] box) {
225         for (int i = 0; i < 16; i++) {
226             a[i] = box[a[i]];
227         }
228     }
229
230     private void mixColumn(int[] a) {
231         final int[] xtimetbl = EncryptionHandlerConstants.XTIMETABLE;
232
233         int[] b = new int[] { 0, 0, 0, 0 };
234         for (int j = 0; j < 4; j++) {
235             int tmp = a[j] ^ a[j + 4] ^ a[j + 8] ^ a[j + 12];
236             for (int i = 0; i < 4; i++) {
237                 b[i] = a[i * 4 + j];
238             }
239             b[0] ^= xtimetbl[a[j] ^ a[j + 4]] ^ tmp;
240             b[1] ^= xtimetbl[a[j + 4] ^ a[j + 8]] ^ tmp;
241             b[2] ^= xtimetbl[a[j + 8] ^ a[j + 12]] ^ tmp;
242             b[3] ^= xtimetbl[a[j + 12] ^ a[j]] ^ tmp;
243
244             for (int i = 0; i < 4; i++) {
245                 a[i * 4 + j] = b[i];
246             }
247         }
248     }
249
250     private void invMixColumn(int[] a) {
251         int[][] b = { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } };
252         for (int j = 0; j < 4; j++) {
253             for (int i = 0; i < 4; i++) {
254                 b[i][j] = gmul(0xe, a[i * 4 + j]) ^ gmul(0xb, a[((i + 1) % 4) * 4 + j])
255                         ^ gmul(0xd, a[((i + 2) % 4) * 4 + j]) ^ gmul(0x9, a[((i + 3) % 4) * 4 + j]);
256             }
257         }
258
259         for (int j = 0; j < 4; j++) {
260             for (int i = 0; i < 4; i++) {
261                 a[i * 4 + j] = b[i][j];
262             }
263         }
264     }
265
266     private void shiftRow(int[] a, int d) {
267         int[] tmpArray = new int[] { 0, 0, 0, 0 };
268         for (int i = 1; i < 4; i++) {
269             for (int j = 0; j < 4; j++) {
270                 int[][][] shifts = EncryptionHandlerConstants.SHIFTS;
271                 int index = i * 4 + (j + shifts[0][i][d]) % 4;
272                 tmpArray[j] = a[index];
273             }
274             for (int j = 0; j < 4; j++) {
275                 a[i * 4 + j] = tmpArray[j];
276             }
277         }
278     }
279
280     private void keyAddition(int[] result, int startIndex) {
281         for (int i = 0; i < 16; i++) {
282             result[i] ^= expandedKey[i + startIndex];
283         }
284     }
285 }