2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.paradoxalarm.internal.communication.crypto;
15 import java.util.Arrays;
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;
23 * This class is used to encrypt and decrypt communication from/to Paradox system. Singleton pattern.
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.
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.
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
36 * @author Konstantin Polihronov - Initial contribution
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
43 public class EncryptionHandler {
45 private final Logger logger = LoggerFactory.getLogger(EncryptionHandler.class);
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;
53 private static final int[] L_TABLE = new int[TABLE_SIZE];
54 private static final int[] A_TABLE = new int[TABLE_SIZE];
56 private static EncryptionHandler instance = new EncryptionHandler(new byte[] {});
61 private static void generateTables() {
64 for (int index = 0; index < 255; index++) {
65 A_TABLE[index] = a & 0xFF;
66 /* Multiply by three */
67 d = (a & 0x80) & 0xFF;
75 /* Set the log table value */
76 L_TABLE[A_TABLE[index]] = index & 0xFF;
78 A_TABLE[255] = A_TABLE[0];
82 private final int[] expandedKey = new int[KEY_LENGTH];
84 private EncryptionHandler(byte[] newKey) {
85 if (newKey.length > 0) {
90 public static EncryptionHandler getInstance() {
94 public synchronized EncryptionHandler updateKey(byte[] newKey) {
95 instance = new EncryptionHandler(newKey);
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);
106 int[] payloadAsIntArray = ParadoxUtil.toIntArray(payload);
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);
114 for (int r = 1; r <= ROUNDS; r++) {
116 shiftRow(tempArray, 0);
118 mixColumn(tempArray);
120 keyAddition(tempArray, r * 16);
123 result = ParadoxUtil.mergeByteArrays(result, ParadoxUtil.toByteArray(tempArray));
126 printArray("Encrypted array", result);
130 public byte[] decrypt(byte[] payload) {
131 int[] payloadAsIntArray = ParadoxUtil.toIntArray(payload);
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);
138 for (int r = ROUNDS; r > 0; r--) {
139 keyAddition(tempArray, r * 16);
141 invMixColumn(tempArray);
144 shiftRow(tempArray, 1);
147 keyAddition(tempArray, 0);
149 result = ParadoxUtil.mergeByteArrays(result, ParadoxUtil.toByteArray(tempArray));
152 printArray("Decrypted array", result);
156 private void printArray(String description, byte[] array) {
157 ParadoxUtil.printByteArray(description, array, array.length);
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);
166 return ParadoxUtil.extendArray(byteArray, KEY_ARRAY_LENGTH);
169 private void expandKey(byte[] input) {
170 // fill array to 32th byte with 0xEE
171 byte[] filledArray = fillArray(input);
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;
178 for (int j = 0; j < 4; j++) {
179 expandedKey[j * 4 + i + 16] = filledArray[i * 4 + j + 16] & 0xFF;
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];
190 for (int j = 0; j < 4; j++) {
191 temp[j] = s[temp[j]];
198 System.arraycopy(temp, 1, temp, 0, temp.length - 1);
201 temp[0] ^= EncryptionHandlerConstants.RCON[(i / 8 - 1)];
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];
211 private int gmul(int c, int b) {
212 int s = L_TABLE[c] + L_TABLE[b];
215 if (b == 0 || c == 0) {
221 private void sBox(int[] a, int[] box) {
222 for (int i = 0; i < 16; i++) {
227 private void mixColumn(int[] a) {
228 final int[] xtimetbl = EncryptionHandlerConstants.XTIMETABLE;
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++) {
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;
241 for (int i = 0; i < 4; i++) {
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]);
256 for (int j = 0; j < 4; j++) {
257 for (int i = 0; i < 4; i++) {
258 a[i * 4 + j] = b[i][j];
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];
270 for (int j = 0; j < 4; j++) {
271 a[i * 4 + j] = tmpArray[j];
276 private void keyAddition(int[] result, int startIndex) {
277 for (int i = 0; i < 16; i++) {
278 result[i] ^= expandedKey[i + startIndex];