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[] lTable = new int[TABLE_SIZE];
54 private static final int[] aTable = 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 aTable[index] = a & 0xFF;
66 /* Multiply by three */
67 d = (a & 0x80) & 0xFF;
75 /* Set the log table value */
76 lTable[aTable[index]] = index & 0xFF;
78 aTable[255] = aTable[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 byte[] expandedArray = ParadoxUtil.extendArray(byteArray, KEY_ARRAY_LENGTH);
167 return expandedArray;
170 private void expandKey(byte[] input) {
171 // fill array to 32th byte with 0xEE
172 byte[] filledArray = fillArray(input);
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;
179 for (int j = 0; j < 4; j++) {
180 expandedKey[j * 4 + i + 16] = filledArray[i * 4 + j + 16] & 0xFF;
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];
191 for (int j = 0; j < 4; j++) {
192 temp[j] = s[temp[j]];
199 for (int j = 1; j < 4; j++) {
200 temp[j - 1] = temp[j];
204 temp[0] ^= EncryptionHandlerConstants.RCON[(i / 8 - 1)];
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];
214 private int gmul(int c, int b) {
215 int s = lTable[c] + lTable[b];
218 if (b == 0 || c == 0) {
224 private void sBox(int[] a, int[] box) {
225 for (int i = 0; i < 16; i++) {
230 private void mixColumn(int[] a) {
231 final int[] xtimetbl = EncryptionHandlerConstants.XTIMETABLE;
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++) {
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;
244 for (int i = 0; i < 4; i++) {
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]);
259 for (int j = 0; j < 4; j++) {
260 for (int i = 0; i < 4; i++) {
261 a[i * 4 + j] = b[i][j];
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];
274 for (int j = 0; j < 4; j++) {
275 a[i * 4 + j] = tmpArray[j];
280 private void keyAddition(int[] result, int startIndex) {
281 for (int i = 0; i < 16; i++) {
282 result[i] ^= expandedKey[i + startIndex];