]> git.basschouten.com Git - openhab-addons.git/blob
a2c000b50648297e5a4ff1ebe9de224f78163fde
[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.km200.internal;
14
15 import java.io.UnsupportedEncodingException;
16 import java.nio.charset.StandardCharsets;
17 import java.security.GeneralSecurityException;
18 import java.security.MessageDigest;
19 import java.security.NoSuchAlgorithmException;
20 import java.util.Arrays;
21 import java.util.Base64;
22
23 import javax.crypto.Cipher;
24 import javax.crypto.spec.SecretKeySpec;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 /**
32  * The KM200Cryption is managing the en- and decription of the communication to the device
33  *
34  * @author Markus Eckhardt - Initial contribution
35  */
36 @NonNullByDefault
37 public class KM200Cryption {
38
39     private final Logger logger = LoggerFactory.getLogger(KM200Cryption.class);
40
41     private final KM200Device remoteDevice;
42
43     public KM200Cryption(KM200Device remoteDevice) {
44         this.remoteDevice = remoteDevice;
45     }
46
47     /**
48      * This function removes zero padding from a byte array.
49      *
50      */
51     private byte[] removeZeroPadding(byte[] bytes) {
52         int i = bytes.length - 1;
53         while (i >= 0 && bytes[i] == 0) {
54             --i;
55         }
56         return Arrays.copyOf(bytes, i + 1);
57     }
58
59     /**
60      * This function adds zero padding to a byte array.
61      *
62      */
63     private byte[] addZeroPadding(byte[] bdata, int bSize, String cSet) throws UnsupportedEncodingException {
64         int encryptPadchar = bSize - (bdata.length % bSize);
65         byte[] padchars = new String(new char[encryptPadchar]).getBytes(cSet);
66         byte[] paddedData = new byte[bdata.length + padchars.length];
67         System.arraycopy(bdata, 0, paddedData, 0, bdata.length);
68         System.arraycopy(padchars, 0, paddedData, bdata.length, padchars.length);
69         return paddedData;
70     }
71
72     /**
73      * This function does the decoding for a new message from the device
74      *
75      */
76     public @Nullable String decodeMessage(byte[] encoded) {
77         String retString = null;
78         byte[] decodedB64 = null;
79
80         // MimeDecoder was the only working decoder.
81         decodedB64 = Base64.getMimeDecoder().decode(encoded);
82
83         try {
84             /* Check whether the length of the decryptData is NOT multiplies of 16 */
85             if ((decodedB64.length & 0xF) != 0) {
86                 logger.debug("Length of message is {}.", decodedB64.length);
87                 /* Return the data */
88                 retString = new String(decodedB64, remoteDevice.getCharSet());
89                 logger.debug("Did NOT decrypt message, returning {}.", retString);
90                 return retString;
91             }
92             // --- create cipher
93             final Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
94             cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(remoteDevice.getCryptKeyPriv(), "AES"));
95             byte[] decryptedData = cipher.doFinal(decodedB64);
96             byte[] decryptedDataWOZP = removeZeroPadding(decryptedData);
97             return (new String(decryptedDataWOZP, remoteDevice.getCharSet()));
98         } catch (UnsupportedEncodingException | GeneralSecurityException e) {
99             logger.warn("Exception on encoding ({})", e.getMessage());
100             return null;
101         }
102     }
103
104     /**
105      * This function does the encoding for a new message to the device
106      *
107      */
108     public byte @Nullable [] encodeMessage(String data) {
109         try {
110             // --- create cipher
111             byte[] bdata = data.getBytes(remoteDevice.getCharSet());
112             final Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
113             cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(remoteDevice.getCryptKeyPriv(), "AES"));
114             int bsize = cipher.getBlockSize();
115             /* Add Padding, encrypt AES and B64 */
116             byte[] encryptedData = cipher.doFinal(addZeroPadding(bdata, bsize, remoteDevice.getCharSet()));
117             try {
118                 return (Base64.getMimeEncoder().encode(encryptedData));
119             } catch (IllegalArgumentException e) {
120                 logger.debug("Base64encoding not possible: {}", e.getMessage());
121             }
122         } catch (UnsupportedEncodingException | GeneralSecurityException e) {
123             logger.warn("Exception on encoding ({})", e.getMessage());
124         }
125         return null;
126     }
127
128     /**
129      * This function creates the private key from the MD5Salt, the device and the private password
130      *
131      * @author Markus Eckhardt
132      */
133     public void recreateKeys() {
134         if (!remoteDevice.getGatewayPassword().isBlank() && !remoteDevice.getPrivatePassword().isBlank()
135                 && remoteDevice.getMD5Salt().length > 0) {
136             byte[] md5K1 = null;
137             byte[] md5K2Init = null;
138             byte[] md5K2Private = null;
139             byte[] bytesOfGatewayPassword = null;
140             byte[] bytesOfPrivatePassword = null;
141
142             /* Needed keys for the communication */
143             byte[] cryptKeyInit;
144             byte[] cryptKeyPriv;
145             MessageDigest md = null;
146             try {
147                 md = MessageDigest.getInstance("MD5");
148             } catch (NoSuchAlgorithmException e) {
149                 logger.warn("No such algorithm, MD5: {}", e.getMessage());
150                 return;
151             }
152
153             /* First half of the key: MD5 of (GatewayPassword . Salt) */
154             bytesOfGatewayPassword = remoteDevice.getGatewayPassword().getBytes(StandardCharsets.UTF_8);
155             byte[] combParts1 = new byte[bytesOfGatewayPassword.length + remoteDevice.getMD5Salt().length];
156             System.arraycopy(bytesOfGatewayPassword, 0, combParts1, 0, bytesOfGatewayPassword.length);
157             System.arraycopy(remoteDevice.getMD5Salt(), 0, combParts1, bytesOfGatewayPassword.length,
158                     remoteDevice.getMD5Salt().length);
159             md5K1 = md.digest(combParts1);
160
161             /* Second half of the key: - Initial: MD5 of ( Salt) */
162             md5K2Init = md.digest(remoteDevice.getMD5Salt());
163
164             /* Second half of the key: - private: MD5 of ( Salt . PrivatePassword) */
165             bytesOfPrivatePassword = remoteDevice.getPrivatePassword().getBytes(StandardCharsets.UTF_8);
166             byte[] combParts2 = new byte[bytesOfPrivatePassword.length + remoteDevice.getMD5Salt().length];
167             System.arraycopy(remoteDevice.getMD5Salt(), 0, combParts2, 0, remoteDevice.getMD5Salt().length);
168             System.arraycopy(bytesOfPrivatePassword, 0, combParts2, remoteDevice.getMD5Salt().length,
169                     bytesOfPrivatePassword.length);
170             md5K2Private = md.digest(combParts2);
171
172             /* Create Keys */
173             cryptKeyInit = new byte[md5K1.length + md5K2Init.length];
174             System.arraycopy(md5K1, 0, cryptKeyInit, 0, md5K1.length);
175             System.arraycopy(md5K2Init, 0, cryptKeyInit, md5K1.length, md5K2Init.length);
176             remoteDevice.setCryptKeyInit(cryptKeyInit);
177
178             cryptKeyPriv = new byte[md5K1.length + md5K2Private.length];
179             System.arraycopy(md5K1, 0, cryptKeyPriv, 0, md5K1.length);
180             System.arraycopy(md5K2Private, 0, cryptKeyPriv, md5K1.length, md5K2Private.length);
181             remoteDevice.setCryptKeyPriv(cryptKeyPriv);
182         }
183     }
184 }