1

I'm trying to convert a block of Java code to Swift but I'm not getting

This is the block that I want to convert:

https://github.com/K4CZP3R/tapo-p100-java-poc/blob/main/src/main/java/KspEncryption.java

public static C658a decodeTapoKey(String key, KspKeyPair keyPair) {
    KspDebug.out("Will try to decode the following key: " + key);
    
    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)
    {
        KspDebug.out("Something went wrong: " + ex.getMessage());
        return null;
    }

}

Here is the same code but this time in Python: https://github.com/K4CZP3R/tapo-p100-python/blob/21e4bf9b61c08a3eb215293198968f82cd80ab2d/encryption.py

def decode_handshake_key(key: str, key_pair: KeyPair) -> TpLinkCipher:
    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(key_pair.get_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])

And this is what I have right now in Swift that is not correct

func decodeTapoKey(key: String, keyPair: KspKeyPair) {
        
        
        let data = key.data(using: .utf8)!
        let b64 = data.base64EncodedString(options: .lineLength76Characters)
        
        let data2 = keyPair.privateKey.data(using: .utf8)!
        let b642 = data2.base64EncodedString(options: .lineLength76Characters)
        
        do {
            
            let privateKey = try PrivateKey(pemNamed: keyPair.getPrivateKey())
            let encrypted = try EncryptedMessage(base64Encoded: key)
            let clear = try encrypted.decrypted(with: privateKey, padding: .PKCS1)

            // Then you can use:
            let data = clear.data
            let base64String = clear.base64String
            let string = try clear.string(encoding: .utf8)
           

        } catch {
            print("error")
        }

I was trying to use the Pod SwiftyRSA.

Can someone help me with this?

Tiago Mendes
  • 4,572
  • 1
  • 29
  • 35
  • 1
    Stack Overflow is not a code writing service. It will help if you can provide some details about **what problem** you're currently having, what you have tried and where you're stuck. – jtbandes Nov 12 '20 at 23:30

1 Answers1

1

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.

Stephan Schlecht
  • 26,556
  • 1
  • 33
  • 47
  • 1
    Thank you Stephan for your super response and for the hard work that you had I am currently trying to implement your code and trying to do the next steps let's see if I can...fingers crossed!! :) – Tiago Mendes Nov 15 '20 at 21:52
  • 1
    I have the full flow working....once again thank you for the big help!!! – Tiago Mendes Nov 18 '20 at 13:25