Thursday, March 3, 2016

Sending RSA encrypted message - From iOS device to Python socket server (iOS Part)

Secure Data Transmission: iOS device (Swift) -> Raspberry Pi (Python)

Secure communication between a mobile device client and a server is important. This tutorial selects a Raspberry Pi as the server and uses an iPhone simulator as the client. The communication procedure is as below:

1. Server generates an RSA private / public key pair and sends the public key to iPhone
2. iPhone encrypts a message using the public key provided by server and sends the encrypted message to server
3. Server decrypts the encrypted message from iPhone using the private key

This tutorial is derived from the basic communication between an iPhone and a Python socket server built on a Raspberry Pi without encryption:

Connect an iPhone to a Simple Python Socket Server (Raspberry Pi Part) (iOS Part)

For an overview on client-server communications, see this:

Communication between iOS device (Client) and Raspberry Pi (Server)

Raspberry Pi Part

Setup a socket server in Python:

Sending RSA encrypted message - From iOS device to Python socket server (Raspberry Pi Part)

iOS Part

The Xcode version used is 7.2.1 (Swift 2.1.1).

Using external public key to encrypt a string in iOS is not easy. I have spent several days on this topic. Finally I found that Swift-RSAUtils is a useful tool. Here are the steps:

1. Download Swift-RSAUtils. Find out the RSAUtils.swift file in the downloaded folder.

2. Create a new Single View Application project in Xcode.

3. Add RSAUtils.swift to the project just created.

4. Modify ViewController.swift as below:

import UIKit

class ViewController: UIViewController, NSStreamDelegate {
    
    //Button
    var buttonConnect : UIButton!
    var buttonGetKey : UIButton!
    var buttonSendMsg : UIButton!
    var buttonQuit : UIButton!
    
    //Label
    var label : UILabel!
    var labelConnection : UILabel!
    
    //Socket server
    let addr = "192.168.xx.xx"
    let port = xxxx
    
    //Network variables
    var inStream : NSInputStream?
    var outStream: NSOutputStream?
    
    //Data received
    var buffer = [UInt8](count: 2000, repeatedValue: 0)
    
    var inStreamLength : Int!
    
    var keyString = ""
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        ButtonSetup()
        
        LabelSetup()
    }
    
    //Button Functions
    func ButtonSetup() {
        buttonConnect = UIButton(frame: CGRectMake(20, 50, 300, 30))
        buttonConnect.setTitle("Connect to server", forState: UIControlState.Normal)
        buttonConnect.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
        buttonConnect.setTitleColor(UIColor.cyanColor(), forState: UIControlState.Highlighted)
        buttonConnect.addTarget(self, action: "btnConnectPressed:", forControlEvents: UIControlEvents.TouchUpInside)
        view.addSubview(buttonConnect)
        
        buttonGetKey = UIButton(frame: CGRectMake(20, 100, 300, 30))
        buttonGetKey.setTitle("Get server's public key", forState: UIControlState.Normal)
        buttonGetKey.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
        buttonGetKey.setTitleColor(UIColor.cyanColor(), forState: UIControlState.Highlighted)
        buttonGetKey.addTarget(self, action: "btnGetKey:", forControlEvents: UIControlEvents.TouchUpInside)
        buttonGetKey.alpha = 0.3
        buttonGetKey.enabled = false
        view.addSubview(buttonGetKey)
        
        buttonSendMsg = UIButton(frame: CGRectMake(20, 150, 300, 30))
        buttonSendMsg.setTitle("Send encrypted message", forState: UIControlState.Normal)
        buttonSendMsg.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
        buttonSendMsg.setTitleColor(UIColor.cyanColor(), forState: UIControlState.Highlighted)
        buttonSendMsg.addTarget(self, action: "btnSendMsg:", forControlEvents: UIControlEvents.TouchUpInside)
        buttonSendMsg.alpha = 0.3
        buttonSendMsg.enabled = false
        view.addSubview(buttonSendMsg)
        
        buttonQuit = UIButton(frame: CGRectMake(20, 200, 300, 30))
        buttonQuit.setTitle("Send \"Quit\"", forState: UIControlState.Normal)
        buttonQuit.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
        buttonQuit.setTitleColor(UIColor.cyanColor(), forState: UIControlState.Highlighted)
        buttonQuit.addTarget(self, action: "btnQuitPressed:", forControlEvents: UIControlEvents.TouchUpInside)
        buttonQuit.alpha = 0.3
        buttonQuit.enabled = false
        view.addSubview(buttonQuit)
    }
    
    func btnConnectPressed(sender: UIButton) {
        NetworkEnable()
        
        buttonConnect.alpha = 0.3
        buttonConnect.enabled = false
    }
    func btnGetKey(sender: UIButton) {
        let data : NSData = "Client: OK".dataUsingEncoding(NSUTF8StringEncoding)!
        outStream?.write(UnsafePointer<UInt8>(data.bytes), maxLength: data.length)
    }
    func btnSendMsg(sender: UIButton) {
        
        let message = "Secret message from iPhone!!"
        
        let data = (message as NSString).dataUsingEncoding(NSUTF8StringEncoding)!
        
        // tag name to access the stored public key in keychain
        let TAG_PUBLIC_KEY = "com.mycompany.tag_public"
        
        let encryptStr = "encrypted_message="
        let encryptStrData = encryptStr.dataUsingEncoding(NSUTF8StringEncoding)!
        
        let encryptedData = RSAUtils.encryptWithRSAPublicKey(data, pubkeyBase64: keyString, keychainTag: TAG_PUBLIC_KEY)!
        
        let length = encryptStrData.length + encryptedData.length
        
        var array = [UInt8](count: length, repeatedValue: 0)
        encryptStrData.getBytes(&array, length: encryptStrData.length)
        encryptedData.getBytes(&array+encryptStrData.length, length: encryptedData.length)
        
        outStream?.write(UnsafePointer<UInt8>(array), maxLength: length)
        
        buttonSendMsg.alpha = 0.3
        buttonSendMsg.enabled = false
        buttonQuit.alpha = 1.0
        buttonQuit.enabled = true
        
        label.text = "Encrypted message sent"
    }
    func btnQuitPressed(sender: UIButton) {
        let data : NSData = "Quit".dataUsingEncoding(NSUTF8StringEncoding)!
        outStream?.write(UnsafePointer<UInt8>(data.bytes), maxLength: data.length)
        
        buttonQuit.alpha = 0.3
        buttonQuit.enabled = false
    }
    
    //Label setup function
    func LabelSetup() {
        label = UILabel(frame: CGRectMake(0,0,300,150))
        label.center = CGPointMake(view.center.x, view.center.y+100)
        label.textAlignment = NSTextAlignment.Center
        label.numberOfLines = 0 //Multi-lines
        label.font = UIFont(name: "Helvetica-Bold", size: 20)
        view.addSubview(label)
        
        labelConnection = UILabel(frame: CGRectMake(0,0,300,30))
        labelConnection.center = view.center
        labelConnection.textAlignment = NSTextAlignment.Center
        labelConnection.text = "Please connect to server"
        view.addSubview(labelConnection)
    }
    
    //Network functions
    func NetworkEnable() {
        
        print("NetworkEnable")
        NSStream.getStreamsToHostWithName(addr, port: port, inputStream: &inStream, outputStream: &outStream)
        
        inStream?.delegate = self
        outStream?.delegate = self
        
        inStream?.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
        outStream?.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
        
        inStream?.open()
        outStream?.open()
        
        buffer = [UInt8](count: 2000, repeatedValue: 0)
    }
    
    func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
        
        switch eventCode {
        case NSStreamEvent.EndEncountered:
            print("EndEncountered")
            labelConnection.text = "Connection stopped by server"
            label.text = ""

            inStream?.close()
            inStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
            outStream?.close()
            print("Stop outStream currentRunLoop")
            outStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
            buttonConnect.alpha = 1
            buttonConnect.enabled = true
            buffer.removeAll(keepCapacity: true)
            keyString = ""
        case NSStreamEvent.ErrorOccurred:
            print("ErrorOccurred")
            
            inStream?.close()
            inStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
            outStream?.close()
            outStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
            labelConnection.text = "Failed to connect to server"
            buttonConnect.alpha = 1
            buttonConnect.enabled = true
            label.text = ""
        case NSStreamEvent.HasBytesAvailable:
            print("HasBytesAvailable")
            
            if aStream == inStream {

                inStreamLength = inStream!.read(&buffer, maxLength: buffer.count)
                
                let bufferStr = NSString(bytes: &buffer, length: inStreamLength, encoding: NSUTF8StringEncoding) as! String
                
                if keyString == "" {
                    label.text = "Public key received"
                    keyString = getKeyStringFromPEMString(bufferStr)
                    buttonGetKey.alpha = 0.3
                    buttonGetKey.enabled = false
                    buttonSendMsg.alpha = 1.0
                    buttonSendMsg.enabled = true
                }
                else {
                    print(bufferStr)
                }
                
            }
            
        case NSStreamEvent.HasSpaceAvailable:
            print("HasSpaceAvailable")
        case NSStreamEvent.None:
            print("None")
        case NSStreamEvent.OpenCompleted:
            print("OpenCompleted")
            labelConnection.text = "Connected to server"
            buttonGetKey.alpha = 1.0
            buttonGetKey.enabled = true
        default:
            print("Unknown")
        }
    }
    
    //Key function - remove header and footer
    func getKeyStringFromPEMString(PEMString: String) -> String {

        let keyArray = PEMString.componentsSeparatedByString("\n") //Remove new line characters
        
        var keyOutput : String = ""
        
        for item in keyArray {
            if !item.containsString("-----") { //Example: -----BEGIN PUBLIC KEY-----
                keyOutput += item //Join the text together as a single string
            }
        }
        return keyOutput
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

Result

Run both the socket server on Raspberry Pi and iOS simulator on Mac. Press the four app buttons from top to bottom. The terminal of Raspberry Pi should display the "Secret message from iPhone!!" message:



For more information about importing external keys into iOS, see this:

No comments:

Post a Comment