]> git.basschouten.com Git - openhab-addons.git/blob
6490c0c428286530714f5618afebdbed4ed9b6cd
[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                 /* Return the data */
87                 retString = new String(decodedB64, remoteDevice.getCharSet());
88                 logger.debug("Did NOT decrypt message");
89                 return retString;
90             }
91             // --- create cipher
92             final Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
93             cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(remoteDevice.getCryptKeyPriv(), "AES"));
94             byte[] decryptedData = cipher.doFinal(decodedB64);
95             byte[] decryptedDataWOZP = removeZeroPadding(decryptedData);
96             return (new String(decryptedDataWOZP, remoteDevice.getCharSet()));
97         } catch (UnsupportedEncodingException | GeneralSecurityException e) {
98             logger.warn("Exception on encoding", e);
99             return null;
100         }
101     }
102
103     /**
104      * This function does the encoding for a new message to the device
105      *
106      */
107     public byte @Nullable [] encodeMessage(String data) {
108         try {
109             // --- create cipher
110             byte[] bdata = data.getBytes(remoteDevice.getCharSet());
111             final Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
112             cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(remoteDevice.getCryptKeyPriv(), "AES"));
113             int bsize = cipher.getBlockSize();
114             /* Add Padding, encrypt AES and B64 */
115             byte[] encryptedData = cipher.doFinal(addZeroPadding(bdata, bsize, remoteDevice.getCharSet()));
116             try {
117                 return (Base64.getMimeEncoder().encode(encryptedData));
118             } catch (IllegalArgumentException e) {
119                 logger.debug("Base64encoding not possible: {}", e.getMessage());
120             }
121         } catch (UnsupportedEncodingException | GeneralSecurityException e) {
122             logger.warn("Exception on encoding", e);
123         }
124         return null;
125     }
126
127     /**
128      * This function creates the private key from the MD5Salt, the device and the private password
129      *
130      * @author Markus Eckhardt
131      */
132     public void recreateKeys() {
133         if (!remoteDevice.getGatewayPassword().isBlank() && !remoteDevice.getPrivatePassword().isBlank()
134                 && remoteDevice.getMD5Salt().length > 0) {
135             byte[] md5K1 = null;
136             byte[] md5K2Init = null;
137             byte[] md5K2Private = null;
138             byte[] bytesOfGatewayPassword = null;
139             byte[] bytesOfPrivatePassword = null;
140
141             /* Needed keys for the communication */
142             byte[] cryptKeyInit;
143             byte[] cryptKeyPriv;
144             MessageDigest md = null;
145             try {
146                 md = MessageDigest.getInstance("MD5");
147             } catch (NoSuchAlgorithmException e) {
148                 logger.warn("No such algorithm, MD5: {}", e.getMessage());
149                 return;
150             }
151
152             /* First half of the key: MD5 of (GatewayPassword . Salt) */
153             bytesOfGatewayPassword = remoteDevice.getGatewayPassword().getBytes(StandardCharsets.UTF_8);
154             byte[] combParts1 = new byte[bytesOfGatewayPassword.length + remoteDevice.getMD5Salt().length];
155             System.arraycopy(bytesOfGatewayPassword, 0, combParts1, 0, bytesOfGatewayPassword.length);
156             System.arraycopy(remoteDevice.getMD5Salt(), 0, combParts1, bytesOfGatewayPassword.length,
157                     remoteDevice.getMD5Salt().length);
158             md5K1 = md.digest(combParts1);
159
160             /* Second half of the key: - Initial: MD5 of ( Salt) */
161             md5K2Init = md.digest(remoteDevice.getMD5Salt());
162
163             /* Second half of the key: - private: MD5 of ( Salt . PrivatePassword) */
164             bytesOfPrivatePassword = remoteDevice.getPrivatePassword().getBytes(StandardCharsets.UTF_8);
165             byte[] combParts2 = new byte[bytesOfPrivatePassword.length + remoteDevice.getMD5Salt().length];
166             System.arraycopy(remoteDevice.getMD5Salt(), 0, combParts2, 0, remoteDevice.getMD5Salt().length);
167             System.arraycopy(bytesOfPrivatePassword, 0, combParts2, remoteDevice.getMD5Salt().length,
168                     bytesOfPrivatePassword.length);
169             md5K2Private = md.digest(combParts2);
170
171             /* Create Keys */
172             cryptKeyInit = new byte[md5K1.length + md5K2Init.length];
173             System.arraycopy(md5K1, 0, cryptKeyInit, 0, md5K1.length);
174             System.arraycopy(md5K2Init, 0, cryptKeyInit, md5K1.length, md5K2Init.length);
175             remoteDevice.setCryptKeyInit(cryptKeyInit);
176
177             cryptKeyPriv = new byte[md5K1.length + md5K2Private.length];
178             System.arraycopy(md5K1, 0, cryptKeyPriv, 0, md5K1.length);
179             System.arraycopy(md5K2Private, 0, cryptKeyPriv, md5K1.length, md5K2Private.length);
180             remoteDevice.setCryptKeyPriv(cryptKeyPriv);
181         }
182     }
183 }