It looks as if there is the following scenario:
- a 32 bytes symmetric key, generated on server side in base64 format
- this 32 bytes key was encrypted on server side with the public key from the client
- to decrypt on client side one needs to use the private key of the client, which seems to be base64 encoded in PKCS8 format
On client side we could:
- construct an EncryptedMessage with the key
- create a PrivateKey instance
- afterwards decrypt the message with the private key
- the result is then split into two data instances with 16 bytes each, as shown in the question example
The source code for this function could look like this:
static func decodeTapoKey(key: String, keyPair: KspKeyPair) throws -> (Data, Data) {
let keyWithoutWhiteSpaces = String(key.compactMap { $0.isWhitespace ? nil : $0 })
let encrypted = try EncryptedMessage(base64Encoded: keyWithoutWhiteSpaces)
let privateKey = try PrivateKey(base64Encoded: keyPair.privateKey)
let decryptedKey = try encrypted.decrypted(with: privateKey, padding: SecPadding.PKCS1)
let b_arr = decryptedKey.data[0..<16]
let b_arr2 = decryptedKey.data[16..<32]
return (b_arr, b_arr2)
}
Test
One can generate a private and public test key as well as an AES key with 256 bit (= 32 bytes). With this data we should get the same binary output for the key as in the Python script.
For the output of hex data we can use the function hexDescription
from this fine answer: https://stackoverflow.com/a/39075044.
A completely self-contained example:
Decrypt.swift
import Foundation
import SwiftyRSA
struct KspKeyPair {
let privateKey: String
let publicKey: String
}
final class Decrypt {
static func decodeTapoKey(key: String, keyPair: KspKeyPair) throws -> (Data, Data) {
let keyWithoutWhiteSpaces = String(key.compactMap { $0.isWhitespace ? nil : $0 })
let encrypted = try EncryptedMessage(base64Encoded: keyWithoutWhiteSpaces)
let privateKey = try PrivateKey(base64Encoded: keyPair.privateKey)
let decryptedKey = try encrypted.decrypted(with: privateKey, padding: SecPadding.PKCS1)
let b_arr = decryptedKey.data[0..<16]
let b_arr2 = decryptedKey.data[16..<32]
return (b_arr, b_arr2)
}
}
extension Data {
var hexDescription: String {
return reduce("") {$0 + String(format: "%02x", $1)}
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController {
let privateKey =
"""
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMY+gDb9hwm9gmYB+iDYxKPngQ3X
Rm3U4tzea9NLtmHgWipM44tE4zWkEeaBawWbFjrnT0lPriTXPn6OvvDRCY7eRejB9wP/aezoNMMr
A2XQ6Du8gcopM1ylUJCWjr9nftTb6wn75wY5IMPUKM2N0uZ+dvfQH4qEQwE05z6Iz1szAgMBAAEC
gYAW5UcHktZSwKlbwKSzwHVNfMJB5/gBXVHqMmH/oEHrIe8n7YNmJUmce1t55L6IgjXaDbbxf5tc
M+PK2A+jXnEc8+2snDbI1PpBepEwg5vzZ8RYnhGZT6P0/PsIqInkTIZnfXDnQ6wBapO1m9xJDe/B
fyWMVRa6JqJtgQ0XpfG76QJBAOgKPSF5wJKC69lCbvyh38fQoeJxEKrD03FGXGBDLLYOHqfijdMT
vcnE415994MI3fTy2dWm/yB8wZ7DvYVwVX8CQQDatuZyi6LhLhU47l5vpFsOnRWGhGrZaIN0o7N/
1v5Vwieujrwy/yW2uCvUeXVnmohJa+sFSr29HO4PEQwWtFxNAkBXSxrKUDJ5K9Wsa0izs/YrBrsQ
JDb/9yHBmJXCBSN57f/sateuE9wvXummr78AxcIyl3YJ4YRTZXu1za+r1qHjAkEAzjkavPKQ18W9
2PpZLOdJvFO9EiMVJH15Rad8/pNXKMFy7RJEvcj6ZHjvSt5jJxb8Xk5VQZ4hnYkDpk0qmtXhGQJB
AIO6TEXkHU3Sn0qTZsPmpu+EfCADLNKG43kiv74+cRzS7Th2A6E1yq5Y/lmbdpYaHqm0mKgvMHb2
ls7DtRTYoXo=
"""
let publicKey =
"""
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGPoA2/YcJvYJmAfog2MSj54EN10Zt1OLc3mvT
S7Zh4FoqTOOLROM1pBHmgWsFmxY6509JT64k1z5+jr7w0QmO3kXowfcD/2ns6DTDKwNl0Og7vIHK
KTNcpVCQlo6/Z37U2+sJ++cGOSDD1CjNjdLmfnb30B+KhEMBNOc+iM9bMwIDAQAB
"""
let secretKey =
"""
YyqsaPrgmVKcjS6UQY5aV0KtOdovqbuwGQMkZMoHtwygZQVy3fiLp0/03B4LKkYtIm0SBPYJw/cV
w2DyZYsPPvX71RbuU9Pp+AJkWTfReEWhSW2vonWv6HIULK94hREHt7S51oBNXo5QP4wn2F4PD3hg
P2DYBAw7guMy9wbGB+4=
"""
override func viewDidLoad() {
super.viewDidLoad()
let keyPair = KspKeyPair(privateKey: privateKey, publicKey: publicKey)
do {
let (b_arr, b_arr2) = try Decrypt.decodeTapoKey(key: secretKey, keyPair: keyPair)
print(b_arr.hexDescription)
print(b_arr2.hexDescription)
} catch {
print("decoding failed: \(error)")
}
}
}
The output is:
4907d77a736619e6338f7e88fbed565f
40decbd392295df33afa817d23ac2824
Python
Based on your Python script also a completely self-contained example:
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import base64
import logging
from collections import namedtuple
KspKeyPair = namedtuple('KspKeyPair', ['private_key', 'public_key'])
logger = logging.getLogger('root')
def decode_handshake_key(key: str, keyPair: KspKeyPair):
logger.debug(f"Will decode handshake key (...{key[5:]}) using current key pair")
decode: bytes = base64.b64decode(key.encode("UTF-8"))
decode2: bytes = base64.b64decode(keyPair.private_key)
cipher = PKCS1_v1_5.new(RSA.import_key(decode2))
do_final = cipher.decrypt(decode, None)
if do_final is None:
raise ValueError("Decryption failed!")
b_arr: bytearray = bytearray()
b_arr2: bytearray = bytearray()
for i in range(0, 16):
b_arr.insert(i, do_final[i])
for i in range(0, 16):
b_arr2.insert(i, do_final[i + 16])
print(b_arr.hex())
print(b_arr2.hex())
if __name__ == "__main__":
privateKey = """
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMY+gDb9hwm9gmYB+iDYxKPngQ3X
Rm3U4tzea9NLtmHgWipM44tE4zWkEeaBawWbFjrnT0lPriTXPn6OvvDRCY7eRejB9wP/aezoNMMr
A2XQ6Du8gcopM1ylUJCWjr9nftTb6wn75wY5IMPUKM2N0uZ+dvfQH4qEQwE05z6Iz1szAgMBAAEC
gYAW5UcHktZSwKlbwKSzwHVNfMJB5/gBXVHqMmH/oEHrIe8n7YNmJUmce1t55L6IgjXaDbbxf5tc
M+PK2A+jXnEc8+2snDbI1PpBepEwg5vzZ8RYnhGZT6P0/PsIqInkTIZnfXDnQ6wBapO1m9xJDe/B
fyWMVRa6JqJtgQ0XpfG76QJBAOgKPSF5wJKC69lCbvyh38fQoeJxEKrD03FGXGBDLLYOHqfijdMT
vcnE415994MI3fTy2dWm/yB8wZ7DvYVwVX8CQQDatuZyi6LhLhU47l5vpFsOnRWGhGrZaIN0o7N/
1v5Vwieujrwy/yW2uCvUeXVnmohJa+sFSr29HO4PEQwWtFxNAkBXSxrKUDJ5K9Wsa0izs/YrBrsQ
JDb/9yHBmJXCBSN57f/sateuE9wvXummr78AxcIyl3YJ4YRTZXu1za+r1qHjAkEAzjkavPKQ18W9
2PpZLOdJvFO9EiMVJH15Rad8/pNXKMFy7RJEvcj6ZHjvSt5jJxb8Xk5VQZ4hnYkDpk0qmtXhGQJB
AIO6TEXkHU3Sn0qTZsPmpu+EfCADLNKG43kiv74+cRzS7Th2A6E1yq5Y/lmbdpYaHqm0mKgvMHb2
ls7DtRTYoXo=
"""
publicKey = """
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGPoA2/YcJvYJmAfog2MSj54EN10Zt1OLc3mvT
S7Zh4FoqTOOLROM1pBHmgWsFmxY6509JT64k1z5+jr7w0QmO3kXowfcD/2ns6DTDKwNl0Og7vIHK
KTNcpVCQlo6/Z37U2+sJ++cGOSDD1CjNjdLmfnb30B+KhEMBNOc+iM9bMwIDAQAB
"""
secretKey = """
YyqsaPrgmVKcjS6UQY5aV0KtOdovqbuwGQMkZMoHtwygZQVy3fiLp0/03B4LKkYtIm0SBPYJw/cV
w2DyZYsPPvX71RbuU9Pp+AJkWTfReEWhSW2vonWv6HIULK94hREHt7S51oBNXo5QP4wn2F4PD3hg
P2DYBAw7guMy9wbGB+4=
"""
keyPair = KspKeyPair(privateKey, publicKey)
decode_handshake_key(secretKey, keyPair)
And the output of this Python program is exactly the same as that of the iOS Swift program. So the Swift part seems to work correctly.
Java
Also for the Java routine the same output should be given. Let's test it with this self-contained Java example:
package com.software7;
import org.apache.commons.codec.binary.Hex;
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
public class Main {
static String privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMY+gDb9hwm9gmYB+iDYxKPngQ3XRm3U4tzea9NLtmHgWipM44tE4zWkEeaBawWbFjrnT0lPriTXPn6OvvDRCY7eRejB9wP/aezoNMMrA2XQ6Du8gcopM1ylUJCWjr9nftTb6wn75wY5IMPUKM2N0uZ+dvfQH4qEQwE05z6Iz1szAgMBAAECgYAW5UcHktZSwKlbwKSzwHVNfMJB5/gBXVHqMmH/oEHrIe8n7YNmJUmce1t55L6IgjXaDbbxf5tcM+PK2A+jXnEc8+2snDbI1PpBepEwg5vzZ8RYnhGZT6P0/PsIqInkTIZnfXDnQ6wBapO1m9xJDe/BfyWMVRa6JqJtgQ0XpfG76QJBAOgKPSF5wJKC69lCbvyh38fQoeJxEKrD03FGXGBDLLYOHqfijdMTvcnE415994MI3fTy2dWm/yB8wZ7DvYVwVX8CQQDatuZyi6LhLhU47l5vpFsOnRWGhGrZaIN0o7N/1v5Vwieujrwy/yW2uCvUeXVnmohJa+sFSr29HO4PEQwWtFxNAkBXSxrKUDJ5K9Wsa0izs/YrBrsQJDb/9yHBmJXCBSN57f/sateuE9wvXummr78AxcIyl3YJ4YRTZXu1za+r1qHjAkEAzjkavPKQ18W92PpZLOdJvFO9EiMVJH15Rad8/pNXKMFy7RJEvcj6ZHjvSt5jJxb8Xk5VQZ4hnYkDpk0qmtXhGQJBAIO6TEXkHU3Sn0qTZsPmpu+EfCADLNKG43kiv74+cRzS7Th2A6E1yq5Y/lmbdpYaHqm0mKgvMHb2ls7DtRTYoXo=";
static String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGPoA2/YcJvYJmAfog2MSj54EN10Zt1OLc3mvTS7Zh4FoqTOOLROM1pBHmgWsFmxY6509JT64k1z5+jr7w0QmO3kXowfcD/2ns6DTDKwNl0Og7vIHKKTNcpVCQlo6/Z37U2+sJ++cGOSDD1CjNjdLmfnb30B+KhEMBNOc+iM9bMwIDAQAB";
static String secretKey = "YyqsaPrgmVKcjS6UQY5aV0KtOdovqbuwGQMkZMoHtwygZQVy3fiLp0/03B4LKkYtIm0SBPYJw/cVw2DyZYsPPvX71RbuU9Pp+AJkWTfReEWhSW2vonWv6HIULK94hREHt7S51oBNXo5QP4wn2F4PD3hgP2DYBAw7guMy9wbGB+4=";
public static void main(String[] args) {
KspKeyPair keyPair = new KspKeyPair(privateKey, publicKey);
C658a result = decodeTapoKey(secretKey, keyPair);
System.out.println(Hex.encodeHexString(result.bArr));
System.out.println(Hex.encodeHexString(result.bArr2));
}
public static C658a decodeTapoKey(String key, KspKeyPair keyPair) {
try {
byte[] decode = KspB64.decode(key.getBytes("UTF-8"));
byte[] decode2 = KspB64.decode(keyPair.getPrivateKey());
Cipher instance = Cipher.getInstance("RSA/ECB/PKCS1Padding");
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey p = kf.generatePrivate(new PKCS8EncodedKeySpec(decode2));
instance.init(Cipher.DECRYPT_MODE, p);
byte[] doFinal = instance.doFinal(decode);
byte[] bArr = new byte[16];
byte[] bArr2 = new byte[16];
System.arraycopy(doFinal, 0, bArr, 0, 16);
System.arraycopy(doFinal, 16, bArr2, 0, 16);
return new C658a(bArr, bArr2);
} catch (Exception ex) {
System.out.println("Something went wrong: " + ex.getMessage());
return null;
}
}
}
class C658a {
byte[] bArr;
byte[] bArr2;
public C658a(byte[] bArr, byte[] bArr2) {
this.bArr = bArr;
this.bArr2 = bArr2;
}
}
class KspKeyPair {
String privateKey;
String publicKey;
public KspKeyPair(String privateKey, String publicKey) {
this.privateKey = privateKey;
this.publicKey = publicKey;
}
public byte[] getPrivateKey() { return privateKey.getBytes(); }
public byte[] getPublicKey() { return publicKey.getBytes(); }
}
class KspB64 {
public static byte[] decode(byte[] bytes) {
return Base64.getMimeDecoder().decode(bytes);
}
}
And indeed the Java program delivers the same result.