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.tapocontrol.internal.helpers;
15 import java.security.KeyFactory;
16 import java.security.PrivateKey;
17 import java.security.spec.PKCS8EncodedKeySpec;
19 import javax.crypto.Cipher;
20 import javax.crypto.spec.IvParameterSpec;
21 import javax.crypto.spec.SecretKeySpec;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
29 * Based on K4CZP3R's p100-java-poc
31 * @author Christian Wild - Initial Initial contribution
34 public class TapoCipher {
35 private final Logger logger = LoggerFactory.getLogger(TapoCipher.class);
36 protected static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";
37 protected static final String CIPHER_ALGORITHM = "AES";
38 protected static final String CIPHER_CHARSET = "UTF-8";
39 protected static final String HANDSHAKE_TRANSFORMATION = "RSA/ECB/PKCS1Padding";
40 protected static final String HANDSHAKE_ALGORITHM = "RSA";
41 protected static final String HANDSHAKE_CHARSET = "UTF-8";
44 private Cipher encodeCipher;
46 private Cipher decodeCipher;
48 private MimeEncode mimeEncode;
51 * CREATE NEW EMPTY CIPHER
57 * CREATE NEW CIPHER WITH KEY AND CREDENTIALS
59 * @param handshakeKey Key from Handshake-Request
60 * @param credentials TapoCredentials
63 public TapoCipher(String handshakeKey, TapoCredentials credentials) {
64 setKey(handshakeKey, credentials);
68 * SET NEW KEY AND CREDENTIALS
73 public void setKey(String handshakeKey, TapoCredentials credentials) {
74 logger.trace("Init TapoCipher with key: {} ", handshakeKey);
75 MimeEncode mimeEncode = new MimeEncode();
77 byte[] decode = mimeEncode.decode(handshakeKey.getBytes(HANDSHAKE_CHARSET));
78 byte[] decode2 = mimeEncode.decode(credentials.getPrivateKeyBytes());
79 Cipher instance = Cipher.getInstance(HANDSHAKE_TRANSFORMATION);
80 KeyFactory kf = KeyFactory.getInstance(HANDSHAKE_ALGORITHM);
81 PrivateKey p = kf.generatePrivate(new PKCS8EncodedKeySpec(decode2));
82 instance.init(Cipher.DECRYPT_MODE, p);
83 byte[] doFinal = instance.doFinal(decode);
84 byte[] bArr = new byte[16];
85 byte[] bArr2 = new byte[16];
86 System.arraycopy(doFinal, 0, bArr, 0, 16);
87 System.arraycopy(doFinal, 16, bArr2, 0, 16);
88 initCipher(bArr, bArr2);
89 } catch (Exception ex) {
90 logger.warn("Something went wrong: {}", ex.getMessage());
95 * INIT ENCODE/DECDE-CIPHERS
101 protected void initCipher(byte[] bArr, byte[] bArr2) throws Exception {
103 mimeEncode = new MimeEncode();
104 SecretKeySpec secretKeySpec = new SecretKeySpec(bArr, CIPHER_ALGORITHM);
105 IvParameterSpec ivParameterSpec = new IvParameterSpec(bArr2);
106 this.encodeCipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
107 this.decodeCipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
108 this.encodeCipher.init(1, secretKeySpec, ivParameterSpec);
109 this.decodeCipher.init(2, secretKeySpec, ivParameterSpec);
110 } catch (Exception e) {
111 logger.warn("initChiper failed: {}", e.getMessage());
112 this.encodeCipher = null;
113 this.decodeCipher = null;
120 * @param str source string to encode
121 * @return encoded string
124 public String encode(String str) throws Exception {
126 doFinal = this.encodeCipher.doFinal(str.getBytes(CIPHER_CHARSET));
127 String encrypted = mimeEncode.encodeToString(doFinal);
128 return encrypted.replace("\r\n", "");
134 * @param str source string to decode
135 * @return decoded string
138 public String decode(String str) throws Exception {
139 byte[] data = mimeEncode.decode(str.getBytes(CIPHER_CHARSET));
141 doFinal = this.decodeCipher.doFinal(data);
142 return new String(doFinal);