2 * Copyright (c) 2010-2024 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.gree.internal;
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;
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;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.openhab.binding.gree.internal.gson.GreeBaseDTO;
34 * The CryptoUtil class provides functionality for encrypting and decrypting
35 * messages sent to and from the Air Conditioner
37 * @author John Cunha - Initial contribution
38 * @author Markus Michels - Refactoring, adapted to OH 2.5x
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;
48 public enum EncryptionTypes {
53 public static byte[] getAESGeneralKeyByteArray() {
54 return AES_KEY.getBytes(StandardCharsets.UTF_8);
57 public static byte[] getGCMGeneralKeyByteArray() {
58 return GCM_KEY.getBytes(StandardCharsets.UTF_8);
61 public static byte[] getGeneralKeyByteArray(EncryptionTypes encType) {
62 if (encType == EncryptionTypes.GCM) {
63 return getGCMGeneralKeyByteArray();
65 return getAESGeneralKeyByteArray();
68 public static byte[] getGCMIVByteArray() {
69 return HexFormat.of().parseHex(GCM_IV);
72 public static byte[] getGCMADDByteArray() {
73 return GCM_ADD.getBytes(StandardCharsets.UTF_8);
76 public static <T extends GreeBaseDTO> EncryptionTypes getEncryptionType(T response) {
77 return response.tag != null ? EncryptionTypes.GCM : EncryptionTypes.ECB;
80 public static <T extends GreeBaseDTO> String decrypt(T response) throws GreeException {
81 return decrypt(response, getEncryptionType(response));
84 public static <T extends GreeBaseDTO> String decrypt(byte[] keyarray, T response) throws GreeException {
85 return decrypt(keyarray, response, getEncryptionType(response));
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);
92 return decrypt(getAESGeneralKeyByteArray(), response, encType);
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);
101 return decryptPack(keyarray, response.pack);
105 public static String decryptPack(byte[] keyarray, String message) throws GreeException {
107 Key key = new SecretKeySpec(keyarray, "AES");
108 Base64.Decoder decoder = Base64.getDecoder();
109 byte[] imageByte = decoder.decode(message);
111 Cipher aesCipher = Cipher.getInstance("AES");
112 aesCipher.init(Cipher.DECRYPT_MODE, key);
113 byte[] bytePlainText = aesCipher.doFinal(imageByte);
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);
122 public static String decryptGCMPack(byte[] keyBytes, String pack, String tag) throws GreeException {
124 Key key = new SecretKeySpec(keyBytes, "AES");
125 Base64.Decoder decoder = Base64.getDecoder();
127 byte[] packBytes = decoder.decode(pack);
128 byte[] tagBytes = decoder.decode(tag);
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);
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());
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);
147 public static String[] encrypt(byte[] keyarray, String message, EncryptionTypes encType) throws GreeException {
148 if (encType == EncryptionTypes.GCM) {
149 return encryptGCMPack(keyarray, message);
151 String[] res = new String[1];
152 res[0] = encryptPack(keyarray, message);
157 public static String encryptPack(byte[] keyarray, String message) throws GreeException {
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());
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);
172 public static String[] encryptGCMPack(byte[] keyarray, String message) throws GreeException {
174 Key key = new SecretKeySpec(keyarray, "AES");
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());
181 byte[] encrypted = gcmCipher.doFinal(message.getBytes(StandardCharsets.UTF_8));
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);
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);