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()
}
}
Reference:
CryptoExportImportManager on GitHub.
Go back to Communication between iOS device (Client) and Raspberry Pi (Server)
No comments:
Post a Comment