swiftencryptionrsadecodecommoncrypto

Convert Java Decrypt to Swift


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?


Solution

  • It looks as if there is the following scenario:

    On client side we could:

    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.