]> git.basschouten.com Git - openhab-addons.git/blob
078ef7adfe5fc32e6ec1767908bb9da26a2e3c5d
[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[] L_TABLE = new int[TABLE_SIZE];
54     private static final int[] A_TABLE = 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             A_TABLE[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 ^= A_TABLE[index];
74             a &= 0xFF;
75             /* Set the log table value */
76             L_TABLE[A_TABLE[index]] = index & 0xFF;
77         }
78         A_TABLE[255] = A_TABLE[0];
79         L_TABLE[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         return ParadoxUtil.extendArray(byteArray, KEY_ARRAY_LENGTH);
167     }
168
169     private void expandKey(byte[] input) {
170         // fill array to 32th byte with 0xEE
171         byte[] filledArray = fillArray(input);
172
173         int[] temp = { 0, 0, 0, 0 };
174         for (int i = 0; i < 4; i++) {
175             for (int j = 0; j < 4; j++) {
176                 expandedKey[j * 4 + i] = filledArray[i * 4 + j] & 0xFF;
177             }
178             for (int j = 0; j < 4; j++) {
179                 expandedKey[j * 4 + i + 16] = filledArray[i * 4 + j + 16] & 0xFF;
180             }
181         }
182
183         final int[] s = EncryptionHandlerConstants.S;
184         for (int i = 8; i < 60; i++) {
185             for (int j = 0; j < 4; j++) {
186                 temp[j] = expandedKey[(((i - 1) & 0xfc) << 2) + ((i - 1) & 0x03) + j * 4];
187             }
188
189             if (i % 4 == 0) {
190                 for (int j = 0; j < 4; j++) {
191                     temp[j] = s[temp[j]];
192                 }
193             }
194
195             if (i % 8 == 0) {
196                 int tmp = temp[0];
197
198                 System.arraycopy(temp, 1, temp, 0, temp.length - 1);
199
200                 temp[3] = tmp;
201                 temp[0] ^= EncryptionHandlerConstants.RCON[(i / 8 - 1)];
202             }
203
204             for (int j = 0; j < 4; j++) {
205                 expandedKey[((i & 0xfc) << 2) + (i & 0x03)
206                         + j * 4] = expandedKey[(((i - 8) & 0xfc) << 2) + ((i - 8) & 0x03) + j * 4] ^ temp[j];
207             }
208         }
209     }
210
211     private int gmul(int c, int b) {
212         int s = L_TABLE[c] + L_TABLE[b];
213         s %= 255;
214         s = A_TABLE[s];
215         if (b == 0 || c == 0) {
216             s = 0;
217         }
218         return s;
219     }
220
221     private void sBox(int[] a, int[] box) {
222         for (int i = 0; i < 16; i++) {
223             a[i] = box[a[i]];
224         }
225     }
226
227     private void mixColumn(int[] a) {
228         final int[] xtimetbl = EncryptionHandlerConstants.XTIMETABLE;
229
230         int[] b = new int[] { 0, 0, 0, 0 };
231         for (int j = 0; j < 4; j++) {
232             int tmp = a[j] ^ a[j + 4] ^ a[j + 8] ^ a[j + 12];
233             for (int i = 0; i < 4; i++) {
234                 b[i] = a[i * 4 + j];
235             }
236             b[0] ^= xtimetbl[a[j] ^ a[j + 4]] ^ tmp;
237             b[1] ^= xtimetbl[a[j + 4] ^ a[j + 8]] ^ tmp;
238             b[2] ^= xtimetbl[a[j + 8] ^ a[j + 12]] ^ tmp;
239             b[3] ^= xtimetbl[a[j + 12] ^ a[j]] ^ tmp;
240
241             for (int i = 0; i < 4; i++) {
242                 a[i * 4 + j] = b[i];
243             }
244         }
245     }
246
247     private void invMixColumn(int[] a) {
248         int[][] b = { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } };
249         for (int j = 0; j < 4; j++) {
250             for (int i = 0; i < 4; i++) {
251                 b[i][j] = gmul(0xe, a[i * 4 + j]) ^ gmul(0xb, a[((i + 1) % 4) * 4 + j])
252                         ^ gmul(0xd, a[((i + 2) % 4) * 4 + j]) ^ gmul(0x9, a[((i + 3) % 4) * 4 + j]);
253             }
254         }
255
256         for (int j = 0; j < 4; j++) {
257             for (int i = 0; i < 4; i++) {
258                 a[i * 4 + j] = b[i][j];
259             }
260         }
261     }
262
263     private void shiftRow(int[] a, int d) {
264         int[] tmpArray = new int[] { 0, 0, 0, 0 };
265         for (int i = 1; i < 4; i++) {
266             for (int j = 0; j < 4; j++) {
267                 int index = i * 4 + (j + EncryptionHandlerConstants.SHIFTS[0][i][d]) % 4;
268                 tmpArray[j] = a[index];
269             }
270             for (int j = 0; j < 4; j++) {
271                 a[i * 4 + j] = tmpArray[j];
272             }
273         }
274     }
275
276     private void keyAddition(int[] result, int startIndex) {
277         for (int i = 0; i < 16; i++) {
278             result[i] ^= expandedKey[i + startIndex];
279         }
280     }
281 }