Friday, March 4, 2016

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

Secure Data Transmission: Raspberry Pi -> iOS device

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

1. iPhone generates an RSA private / public key pair and sends the public key to server.
2. Server encrypts a message using the public key provided by iPhone and sends the encrypted message to iPhone
3. iPhone decrypts the encrypted message from server 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 Python socket server to iOS device (Raspberry Pi Part)

iOS Part

The Xcode version used is 7.2.1 (Swift 2.1.1).

1. Download CryptoExportImportManager. Find out the CryptoExportImportManager.swift file in the downloaded folder.

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

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

4. Modify ViewController.swift as below:

import UIKit

class ViewController: UIViewController, NSStreamDelegate {
    
    //Button
    var buttonConnect    : UIButton!
    var buttonSendKey     : UIButton!
    var buttonDecryptMsg : 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: 200, repeatedValue: 0)
    
    var inStreamLength : Int!
    
    //Key variables and constants
    var publicKey, privateKey: SecKey?
    var publicKeyString = ""
    let keySize = 1024
    let tagPrivate = "com.mycompany.tagPrivate"
    let tagPublic  = "com.mycompany.tagPublic"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        ButtonSetup()
        
        LabelSetup()
        
        KeySetup()
    }
    
    //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)
        
        buttonSendKey = UIButton(frame: CGRectMake(20, 100, 300, 30))
        buttonSendKey.setTitle("Send public key to server", forState: UIControlState.Normal)
        buttonSendKey.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
        buttonSendKey.setTitleColor(UIColor.cyanColor(), forState: UIControlState.Highlighted)
        buttonSendKey.addTarget(self, action: "btnSendKey:", forControlEvents: UIControlEvents.TouchUpInside)
        buttonSendKey.alpha = 0.3
        buttonSendKey.enabled = false
        view.addSubview(buttonSendKey)
        
        buttonDecryptMsg = UIButton(frame: CGRectMake(20, 150, 300, 30))
        buttonDecryptMsg.setTitle("Decrypt message", forState: UIControlState.Normal)
        buttonDecryptMsg.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
        buttonDecryptMsg.setTitleColor(UIColor.cyanColor(), forState: UIControlState.Highlighted)
        buttonDecryptMsg.addTarget(self, action: "btnDecryptMsg:", forControlEvents: UIControlEvents.TouchUpInside)
        buttonDecryptMsg.alpha = 0.3
        buttonDecryptMsg.enabled = false
        view.addSubview(buttonDecryptMsg)
        
        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 btnSendKey(sender: UIButton) {
        let publicKeyTypeStr = "public_key=\(publicKeyString)"
        let data : NSData = publicKeyTypeStr.dataUsingEncoding(NSUTF8StringEncoding)!
        outStream?.write(UnsafePointer<UInt8>(data.bytes), maxLength: data.length)
    }
    func btnDecryptMsg(sender: UIButton) {

        let dataEncryptedSize : Int = inStreamLength

        var arrayDecrypted = [UInt8](count: dataEncryptedSize, repeatedValue: 0)
        var arrayDecryptedSize = dataEncryptedSize

        let status = SecKeyDecrypt(privateKey!, SecPadding.PKCS1, &buffer, dataEncryptedSize, &arrayDecrypted, &arrayDecryptedSize)

        if status == errSSLCrypto {
            print("errSSLCrypto") //-9809 error
            return
        } else if status != noErr {
            print("Decryption Error! \(status.description)")
            return
        }
        let messageStr = NSString(bytes: &arrayDecrypted, length: arrayDecryptedSize, encoding: NSUTF8StringEncoding)!

        label.text = "Server sent:\n\(messageStr)"
        print("Decrypt successfully:\n\(messageStr)")
        
        buttonDecryptMsg.alpha = 0.3
        buttonDecryptMsg.enabled = false
        buttonQuit.alpha = 1
        buttonQuit.enabled = true
    }
    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)
    }
    
    //Get keys from keychain or generate new keys
    func KeySetup() {
        
        //Reset keychain
        //deleteAllKeysInKeyChain()
        
        if (GetKeysFromKeychain()) {
            print("Key pair retrieved successfully!")
        } else {
            print("No valid key pair")
            deleteAllKeysInKeyChain()
            GenerateKeyPair()
        }
        GetPublicKeyStr()
    }

    //Other key functions - Check Keychain and get keys

    func GetKeysFromKeychain() -> Bool {
        privateKey = GetKeyTypeInKeyChain(tagPrivate)
        publicKey = GetKeyTypeInKeyChain(tagPublic)
        return ((privateKey != nil)&&(publicKey != nil))
    }
    
    func GetKeyTypeInKeyChain(tag : String) -> SecKey? {
        let query: [String: AnyObject] = [
            String(kSecClass)             : kSecClassKey,
            String(kSecAttrKeyType)       : kSecAttrKeyTypeRSA,
            String(kSecAttrApplicationTag): tag,
            String(kSecReturnRef)         : true
        ]
        
        var result : AnyObject?
        
        let status = SecItemCopyMatching(query, &result)
        
        if status == errSecSuccess {
            print("\(keyTypeStr(tag)) Key existed!")
            return result as! SecKey?
        }
        print("no \(keyTypeStr(tag)) key")
        
        return nil
    }
    
    //Get public key string from keychain
    func GetPublicKeyStr() {
        
        var unsafeData : AnyObject?
        
        let parameters = [
            kSecClass as String: kSecClassKey,
            kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
            kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
            kSecReturnData as String: true
        ]
        
        //Get public key string
        let status = SecItemCopyMatching(parameters, &unsafeData)
        
        if status != noErr {
            print("Get Key Error!")
            return
        }
        
        //Need CryptoExportImportManager.swift from GitHub.
        let manager = CryptoExportImportManager()
        publicKeyString = manager.exportRSAPublicKeyToPEM(unsafeData as! NSData, keyType: kSecAttrKeyTypeRSA as String, keySize: keySize)
        //print(publicKeyString) //Check this with server
    }
    
    //Generate private and public keys
    func GenerateKeyPair() {
        let privateKeyAttr: [NSString: AnyObject] = [
            kSecAttrIsPermanent: true,
            kSecAttrApplicationTag: tagPrivate
        ]
        let publicKeyAttr: [NSString: AnyObject] = [
            kSecAttrIsPermanent: true,
            kSecAttrApplicationTag: tagPublic
        ]
        
        let parameters: [String: AnyObject] = [
            kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
            kSecAttrKeySizeInBits as String: keySize,
            kSecPrivateKeyAttrs as String: privateKeyAttr,
            kSecPublicKeyAttrs as String: publicKeyAttr
        ]
        
        //Generate new keys and store them in keychain
        let status = SecKeyGeneratePair(parameters, &publicKey, &privateKey)
        
        if status != noErr {
            print("SecKeyGeneratePair Error! \(status.description)")
            return
        }
        
        print("Key pair generated successfully")
    }
    
    //key type string
    func keyTypeStr(tag: String) -> String {
        return tag.stringByReplacingOccurrencesOfString("com.mycompany.tag", withString: "")
    }
    
    //Delete keys when required.
    func deleteAllKeysInKeyChain() {
        
        let query : [String: AnyObject] = [
            String(kSecClass)             : kSecClassKey
        ]
        let status = SecItemDelete(query)
        
        switch status {
        case errSecItemNotFound:
            print("No key in keychain")
        case noErr:
            print("All Keys Deleted!")
        default:
            print("SecItemDelete error! \(status.description)")
        }
    }
    
    //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: 200, 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)

        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)
                
                //If data received
                if inStreamLength > 0 {
                    label.text = "Encrypted message received!"
                
                    buttonSendKey.alpha = 0.3
                    buttonSendKey.enabled = false
                    buttonDecryptMsg.alpha = 1.0
                    buttonDecryptMsg.enabled = true
                }
            }
            
        case NSStreamEvent.HasSpaceAvailable:
            print("HasSpaceAvailable")
        case NSStreamEvent.None:
            print("None")
        case NSStreamEvent.OpenCompleted:
            print("OpenCompleted")
            labelConnection.text = "Connected to server"
            buttonSendKey.alpha = 1.0
            buttonSendKey.enabled = true
        default:
            print("Unknown")
        }
    }
    
    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 message "Secret message from RPi server" sent by Raspberry Pi should be displayed on the iOS app and Xcode debug console:


Reference:

CryptoExportImportManager on GitHub.

Go back to Communication between iOS device (Client) and Raspberry Pi (Server)

No comments:

Post a Comment