2 * Copyright (c) 2010-2021 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.mihome.internal;
15 import java.io.UnsupportedEncodingException;
16 import java.security.InvalidAlgorithmParameterException;
17 import java.security.InvalidKeyException;
18 import java.security.NoSuchAlgorithmException;
20 import javax.crypto.BadPaddingException;
21 import javax.crypto.Cipher;
22 import javax.crypto.IllegalBlockSizeException;
23 import javax.crypto.NoSuchPaddingException;
24 import javax.crypto.spec.IvParameterSpec;
25 import javax.crypto.spec.SecretKeySpec;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
31 * Encrypts communication between openhab & xiaomi bridge (required by xiaomi).
33 * @author Ondřej Pečta - Initial contribution to Xiaomi MiHome Binding for OH 1.x
34 * @author Dieter Schmidt - Refactor logger
36 public class EncryptionHelper {
38 protected static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
40 // AES‐CBC 128 initial vector, taken from protocol description
41 protected static final byte[] IV = parseHexBinary("17996D093D28DDB3BA695A2E6F58562E");
43 private final Logger logger = LoggerFactory.getLogger(EncryptionHelper.class);
45 public String encrypt(String text, String key) {
46 return encrypt(text, key, IV);
49 public String encrypt(String text, String key, byte[] iv) {
50 IvParameterSpec vector = new IvParameterSpec(iv);
53 cipher = Cipher.getInstance("AES/CBC/NoPadding");
54 } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
55 logger.warn("Failed to construct Cipher");
58 SecretKeySpec keySpec;
60 keySpec = new SecretKeySpec(key.getBytes("UTF8"), "AES");
61 } catch (UnsupportedEncodingException e) {
62 logger.warn("Failed to construct SecretKeySpec");
66 cipher.init(Cipher.ENCRYPT_MODE, keySpec, vector);
67 } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
68 logger.warn("Failed to init Cipher");
73 encrypted = cipher.doFinal(text.getBytes());
74 return bytesToHex(encrypted);
75 } catch (IllegalBlockSizeException | BadPaddingException e) {
76 logger.warn("Failed to finally encrypt");
81 private static byte[] parseHexBinary(String s) {
82 final int len = s.length();
84 // "111" is not a valid hex encoding.
86 throw new IllegalArgumentException("hexBinary needs to be even-length: " + s);
89 byte[] out = new byte[len / 2];
91 for (int i = 0; i < len; i += 2) {
92 int h = hexToBin(s.charAt(i));
93 int l = hexToBin(s.charAt(i + 1));
94 if (h == -1 || l == -1) {
95 throw new IllegalArgumentException("contains illegal character for hexBinary: " + s);
98 out[i / 2] = (byte) (h * 16 + l);
104 private static int hexToBin(char ch) {
105 if ('0' <= ch && ch <= '9') {
108 if ('A' <= ch && ch <= 'F') {
109 return ch - 'A' + 10;
111 if ('a' <= ch && ch <= 'f') {
112 return ch - 'a' + 10;
117 private String bytesToHex(byte[] bytes) {
118 char[] hexChars = new char[bytes.length * 2];
119 for (int j = 0; j < bytes.length; j++) {
120 int v = bytes[j] & 0xFF;
121 hexChars[j * 2] = HEX_ARRAY[v >>> 4];
122 hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
124 return new String(hexChars);