]> git.basschouten.com Git - openhab-addons.git/blob
f51678788bcb057521bbcfba2721c7db372d0eaf
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.gree.internal;
14
15 import java.nio.charset.StandardCharsets;
16 import java.security.InvalidAlgorithmParameterException;
17 import java.security.InvalidKeyException;
18 import java.security.Key;
19 import java.security.NoSuchAlgorithmException;
20 import java.util.Base64;
21 import java.util.HexFormat;
22
23 import javax.crypto.BadPaddingException;
24 import javax.crypto.Cipher;
25 import javax.crypto.IllegalBlockSizeException;
26 import javax.crypto.NoSuchPaddingException;
27 import javax.crypto.spec.GCMParameterSpec;
28 import javax.crypto.spec.SecretKeySpec;
29
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.openhab.binding.gree.internal.gson.GreeBaseDTO;
32
33 /**
34  * The CryptoUtil class provides functionality for encrypting and decrypting
35  * messages sent to and from the Air Conditioner
36  *
37  * @author John Cunha - Initial contribution
38  * @author Markus Michels - Refactoring, adapted to OH 2.5x
39  */
40 @NonNullByDefault
41 public class GreeCryptoUtil {
42     private static final String AES_KEY = "a3K8Bx%2r8Y7#xDh";
43     private static final String GCM_KEY = "{yxAHAY_Lm6pbC/<";
44     private static final String GCM_IV = "5440784449675a516c5e6313";
45     private static final String GCM_ADD = "qualcomm-test";
46     private static final int TAG_LENGTH = 16;
47
48     public enum EncryptionTypes {
49         ECB,
50         GCM
51     };
52
53     public static byte[] getAESGeneralKeyByteArray() {
54         return AES_KEY.getBytes(StandardCharsets.UTF_8);
55     }
56
57     public static byte[] getGCMGeneralKeyByteArray() {
58         return GCM_KEY.getBytes(StandardCharsets.UTF_8);
59     }
60
61     public static byte[] getGeneralKeyByteArray(EncryptionTypes encType) {
62         if (encType == EncryptionTypes.GCM) {
63             return getGCMGeneralKeyByteArray();
64         }
65         return getAESGeneralKeyByteArray();
66     }
67
68     public static byte[] getGCMIVByteArray() {
69         return HexFormat.of().parseHex(GCM_IV);
70     }
71
72     public static byte[] getGCMADDByteArray() {
73         return GCM_ADD.getBytes(StandardCharsets.UTF_8);
74     }
75
76     public static <T extends GreeBaseDTO> EncryptionTypes getEncryptionType(T response) {
77         return response.tag != null ? EncryptionTypes.GCM : EncryptionTypes.ECB;
78     }
79
80     public static <T extends GreeBaseDTO> String decrypt(T response) throws GreeException {
81         return decrypt(response, getEncryptionType(response));
82     }
83
84     public static <T extends GreeBaseDTO> String decrypt(byte[] keyarray, T response) throws GreeException {
85         return decrypt(keyarray, response, getEncryptionType(response));
86     }
87
88     public static <T extends GreeBaseDTO> String decrypt(T response, EncryptionTypes encType) throws GreeException {
89         if (encType == EncryptionTypes.GCM) {
90             return decrypt(getGCMGeneralKeyByteArray(), response, encType);
91         } else {
92             return decrypt(getAESGeneralKeyByteArray(), response, encType);
93         }
94     }
95
96     public static <T extends GreeBaseDTO> String decrypt(byte[] keyarray, T response, EncryptionTypes encType)
97             throws GreeException {
98         if (encType == EncryptionTypes.GCM) {
99             return decryptGCMPack(keyarray, response.pack, response.tag);
100         } else {
101             return decryptPack(keyarray, response.pack);
102         }
103     }
104
105     public static String decryptPack(byte[] keyarray, String message) throws GreeException {
106         try {
107             Key key = new SecretKeySpec(keyarray, "AES");
108             Base64.Decoder decoder = Base64.getDecoder();
109             byte[] imageByte = decoder.decode(message);
110
111             Cipher aesCipher = Cipher.getInstance("AES");
112             aesCipher.init(Cipher.DECRYPT_MODE, key);
113             byte[] bytePlainText = aesCipher.doFinal(imageByte);
114
115             return new String(bytePlainText, StandardCharsets.UTF_8);
116         } catch (NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | InvalidKeyException
117                 | IllegalBlockSizeException ex) {
118             throw new GreeException("Decryption of recieved data failed", ex);
119         }
120     }
121
122     public static String decryptGCMPack(byte[] keyBytes, String pack, String tag) throws GreeException {
123         try {
124             Key key = new SecretKeySpec(keyBytes, "AES");
125             Base64.Decoder decoder = Base64.getDecoder();
126
127             byte[] packBytes = decoder.decode(pack);
128             byte[] tagBytes = decoder.decode(tag);
129
130             byte[] messageBytes = new byte[packBytes.length + tagBytes.length];
131             System.arraycopy(packBytes, 0, messageBytes, 0, packBytes.length);
132             System.arraycopy(tagBytes, 0, messageBytes, packBytes.length, tagBytes.length);
133
134             Cipher gcmCipher = Cipher.getInstance("AES/GCM/NoPadding");
135             GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, getGCMIVByteArray());
136             gcmCipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec);
137             gcmCipher.updateAAD(getGCMADDByteArray());
138
139             byte[] bytePlainText = gcmCipher.doFinal(messageBytes);
140             return new String(bytePlainText, StandardCharsets.UTF_8);
141         } catch (NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | InvalidKeyException
142                 | IllegalBlockSizeException | InvalidAlgorithmParameterException ex) {
143             throw new GreeException("GCM decryption of recieved data failed", ex);
144         }
145     }
146
147     public static String[] encrypt(byte[] keyarray, String message, EncryptionTypes encType) throws GreeException {
148         if (encType == EncryptionTypes.GCM) {
149             return encryptGCMPack(keyarray, message);
150         } else {
151             String[] res = new String[1];
152             res[0] = encryptPack(keyarray, message);
153             return res;
154         }
155     }
156
157     public static String encryptPack(byte[] keyarray, String message) throws GreeException {
158         try {
159             Key key = new SecretKeySpec(keyarray, "AES");
160             Cipher aesCipher = Cipher.getInstance("AES");
161             aesCipher.init(Cipher.ENCRYPT_MODE, key);
162             byte[] bytePlainText = aesCipher.doFinal(message.getBytes());
163
164             Base64.Encoder newencoder = Base64.getEncoder();
165             return new String(newencoder.encode(bytePlainText), StandardCharsets.UTF_8);
166         } catch (NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | InvalidKeyException
167                 | IllegalBlockSizeException ex) {
168             throw new GreeException("Unable to encrypt outbound data", ex);
169         }
170     }
171
172     public static String[] encryptGCMPack(byte[] keyarray, String message) throws GreeException {
173         try {
174             Key key = new SecretKeySpec(keyarray, "AES");
175
176             Cipher gcmCipher = Cipher.getInstance("AES/GCM/NoPadding");
177             GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, getGCMIVByteArray());
178             gcmCipher.init(Cipher.ENCRYPT_MODE, key, gcmParameterSpec);
179             gcmCipher.updateAAD(getGCMADDByteArray());
180
181             byte[] encrypted = gcmCipher.doFinal(message.getBytes(StandardCharsets.UTF_8));
182
183             int packLength = encrypted.length - TAG_LENGTH;
184             byte[] pack = new byte[packLength];
185             byte[] tag = new byte[TAG_LENGTH];
186             System.arraycopy(encrypted, 0, pack, 0, packLength);
187             System.arraycopy(encrypted, packLength, tag, 0, TAG_LENGTH);
188
189             Base64.Encoder encoder = Base64.getEncoder();
190             String[] encryptedData = new String[2];
191             encryptedData[0] = new String(encoder.encode(pack), StandardCharsets.UTF_8);
192             encryptedData[1] = new String(encoder.encode(tag), StandardCharsets.UTF_8);
193             return encryptedData;
194         } catch (NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | InvalidKeyException
195                 | IllegalBlockSizeException | InvalidAlgorithmParameterException ex) {
196             throw new GreeException("Unable to encrypt (gcm) outbound data", ex);
197         }
198     }
199 }