2 * Copyright (c) 2010-2023 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.nio.charset.StandardCharsets;
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 and 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 = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
60 cipher.init(Cipher.ENCRYPT_MODE, keySpec, vector);
61 } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
62 logger.warn("Failed to init Cipher");
67 encrypted = cipher.doFinal(text.getBytes());
68 return bytesToHex(encrypted);
69 } catch (IllegalBlockSizeException | BadPaddingException e) {
70 logger.warn("Failed to finally encrypt");
75 private static byte[] parseHexBinary(String s) {
76 final int len = s.length();
78 // "111" is not a valid hex encoding.
80 throw new IllegalArgumentException("hexBinary needs to be even-length: " + s);
83 byte[] out = new byte[len / 2];
85 for (int i = 0; i < len; i += 2) {
86 int h = hexToBin(s.charAt(i));
87 int l = hexToBin(s.charAt(i + 1));
88 if (h == -1 || l == -1) {
89 throw new IllegalArgumentException("contains illegal character for hexBinary: " + s);
92 out[i / 2] = (byte) (h * 16 + l);
98 private static int hexToBin(char ch) {
99 if ('0' <= ch && ch <= '9') {
102 if ('A' <= ch && ch <= 'F') {
103 return ch - 'A' + 10;
105 if ('a' <= ch && ch <= 'f') {
106 return ch - 'a' + 10;
111 private String bytesToHex(byte[] bytes) {
112 char[] hexChars = new char[bytes.length * 2];
113 for (int j = 0; j < bytes.length; j++) {
114 int v = bytes[j] & 0xFF;
115 hexChars[j * 2] = HEX_ARRAY[v >>> 4];
116 hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
118 return new String(hexChars);