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.tapocontrol.internal.api.protocol.aes;
15 import static java.util.Base64.*;
16 import static org.openhab.binding.tapocontrol.internal.constants.TapoErrorCode.*;
18 import java.security.KeyFactory;
19 import java.security.PrivateKey;
20 import java.security.spec.PKCS8EncodedKeySpec;
22 import javax.crypto.Cipher;
23 import javax.crypto.spec.IvParameterSpec;
24 import javax.crypto.spec.SecretKeySpec;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.openhab.binding.tapocontrol.internal.helpers.TapoErrorHandler;
28 import org.openhab.binding.tapocontrol.internal.helpers.TapoKeyPair;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
34 * Based on K4CZP3R's p100-java-poc
36 * @author Christian Wild - Initial Initial contribution
39 public class SecurePasstroughCipher {
40 private final Logger logger = LoggerFactory.getLogger(SecurePasstroughCipher.class);
41 protected static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";
42 protected static final String CIPHER_ALGORITHM = "AES";
43 protected static final String CIPHER_CHARSET = "UTF-8";
44 protected static final String HANDSHAKE_TRANSFORMATION = "RSA/ECB/PKCS1Padding";
45 protected static final String HANDSHAKE_ALGORITHM = "RSA";
46 protected static final String HANDSHAKE_CHARSET = "UTF-8";
49 private Cipher encodeCipher;
51 private Cipher decodeCipher;
54 * CREATE NEW EMPTY CIPHER
56 public SecurePasstroughCipher() {
60 * CREATE NEW CIPHER WITH KEY AND CREDENTIALS
62 * @param handshakeKey key from Handshake-Request
63 * @param keyPair keyPair
64 * @throws TapoErrorHandler
66 public SecurePasstroughCipher(String handshakeKey, TapoKeyPair keyPair) throws TapoErrorHandler {
67 setKey(handshakeKey, keyPair);
71 * SET NEW KEY AND CREDENTIALS
73 * @param handshakeKey key from Handshake-Request
74 * @param keyPair keyPair
76 public void setKey(String handshakeKey, TapoKeyPair keyPair) throws TapoErrorHandler {
77 logger.trace("Init passtroughCipher with key: {} ", handshakeKey);
79 byte[] decode = getMimeDecoder().decode(handshakeKey.getBytes(HANDSHAKE_CHARSET));
80 byte[] decode2 = getMimeDecoder().decode(keyPair.getPrivateKeyBytes());
81 Cipher instance = Cipher.getInstance(HANDSHAKE_TRANSFORMATION);
82 KeyFactory kf = KeyFactory.getInstance(HANDSHAKE_ALGORITHM);
83 PrivateKey p = kf.generatePrivate(new PKCS8EncodedKeySpec(decode2));
84 instance.init(Cipher.DECRYPT_MODE, p);
85 byte[] doFinal = instance.doFinal(decode);
86 byte[] bArr = new byte[16];
87 byte[] bArr2 = new byte[16];
88 System.arraycopy(doFinal, 0, bArr, 0, 16);
89 System.arraycopy(doFinal, 16, bArr2, 0, 16);
90 initCipher(bArr, bArr2);
91 } catch (Exception e) {
92 logger.warn("handshake Failed: {}", e.getMessage());
93 throw new TapoErrorHandler(ERR_API_HAND_SHAKE_FAILED, e.getMessage());
98 * INIT ENCODE/DECDE-CIPHERS
104 protected void initCipher(byte[] bArr, byte[] bArr2) throws Exception {
106 SecretKeySpec secretKeySpec = new SecretKeySpec(bArr, CIPHER_ALGORITHM);
107 IvParameterSpec ivParameterSpec = new IvParameterSpec(bArr2);
108 encodeCipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
109 decodeCipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
110 encodeCipher.init(1, secretKeySpec, ivParameterSpec);
111 decodeCipher.init(2, secretKeySpec, ivParameterSpec);
112 } catch (Exception e) {
113 logger.warn("initChiper failed: {}", e.getMessage());
122 * @param str source string to encode
123 * @return encoded string
126 public String encode(String str) throws Exception {
128 doFinal = encodeCipher.doFinal(str.getBytes(CIPHER_CHARSET));
129 String encrypted = getMimeEncoder().encodeToString(doFinal);
130 return encrypted.replace("\r\n", "");
136 * @param str source string to decode
137 * @return decoded string
140 public String decode(String str) throws Exception {
141 byte[] data = getMimeDecoder().decode(str.getBytes(CIPHER_CHARSET));
143 doFinal = decodeCipher.doFinal(data);
144 return new String(doFinal);