Wednesday, March 2, 2016

Store/Retrieve/Delete RSA public/private keys with keychain

This post shows how to implement the following function in iOS:

1. Get keys from keychain if available. (SecItemCopyMatching)
2. Remove keys in keychain. (SecItemDelete)
3. Generate an RSA private/public key pair and store in keychain. (SecKeyGeneratePair)
4. Store keys in keychain. (SecItemAdd) (SecKeyGeneratePair does it.)
5. Encrypt and decrypt a message with a key pair. (SecKeyEncrypt/SecKeyDecrypt)

The code below is developed with Xcode 7.2.1 (Swift 2.1.1) and tested with an iPhone and the simulator.

Just create a new Single View Application project in Xcode and modify ViewController.swift as:

import UIKit

class ViewController: UIViewController {
    
    var publicKey, privateKey: SecKey?
    
    let tagPrivate = "com.mycompany.tagPrivate"
    let tagPublic  = "com.mycompany.tagPublic"
    var keySourceStr = ""
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        if (GetKeysFromKeychain()) {
            keySourceStr = "from keychain"
            print("Key pair retrieved successfully!")
        } else {
            print("No valid key pair")
            deleteAllKeysInKeyChain()
            GenerateKeyPair()
            keySourceStr = "newly generated"
        }

        //deleteAllKeysInKeyChain() //Used to clear the keychain
        EncryptDecryptMessage()
        
    }
    
    //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
    }
    
    //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: 1024,
            kSecPrivateKeyAttrs as String: privateKeyAttr,
            kSecPublicKeyAttrs as String: publicKeyAttr
        ]
        
        let status = SecKeyGeneratePair(parameters, &publicKey, &privateKey)
        
        if status != noErr {
            print("SecKeyGeneratePair Error! \(status.description)")
            return
        }
        

                   print("Keys generated and added to keychain")
        //StoreInKeychain(tagPublic, key: publicKey!)
        //StoreInKeychain(tagPrivate, key: privateKey!)
    }
    
    //Store private and public keys in keychain
/*
    func StoreInKeychain(tag: String, key: SecKeyRef) {
        
        let attribute = [
            String(kSecClass)              : kSecClassKey,
            String(kSecAttrKeyType)        : kSecAttrKeyTypeRSA,
            String(kSecValueRef)           : key,
            String(kSecReturnPersistentRef): true
        ]
        
        let status = SecItemAdd(attribute, nil)
        
        if status != noErr {
            print("SecItemAdd Error!")
            return
        }
        
        print("\(keyTypeStr(tag)) key added to keychain")
    }*/
    
    //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)")
        }
    }
    
    //key type string
    func keyTypeStr(tag: String) -> String {
        return tag.stringByReplacingOccurrencesOfString("com.mycompany.tag", withString: "")
    }
    
    //Encrypt and decrypt message
    func EncryptDecryptMessage() {
        
        if ((publicKey == nil)||(privateKey == nil)) {
            print("Key pair is invalid. Encrypting/Decrypting message not allowed!")
            return
        }
        
        let message = "Raspberry Pi 3 is with Bluetooth & Wi-Fi and at US$35."
        let blockSize = SecKeyGetBlockSize(publicKey!)
        var messageEncrypted = [UInt8](count: blockSize, repeatedValue: 0)
        var messageEncryptedSize = blockSize
        
        var status: OSStatus!
        
        status = SecKeyEncrypt(publicKey!, SecPadding.PKCS1, message, message.characters.count, &messageEncrypted, &messageEncryptedSize)
        
        if status != noErr {
            print("Encryption Error!")
            return
        }
        
        //Decrypt the entrypted string with the private key
        var messageDecrypted = [UInt8](count: blockSize, repeatedValue: 0)
        var messageDecryptedSize = messageEncryptedSize
        
        status = SecKeyDecrypt(privateKey!, SecPadding.PKCS1, &messageEncrypted, messageEncryptedSize, &messageDecrypted, &messageDecryptedSize)
        
        if status != noErr {
            print("Decryption Error!")
            return
        }
        print("Encrypt and decrypt message successfully with keys \(keySourceStr). Message is:")
        print(NSString(bytes: &messageDecrypted, length: messageDecryptedSize, encoding: NSUTF8StringEncoding)!)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

}

Results

When no keys stored in keychain:

no Private key
no Public key
No valid key pair
No key in keychain
Keys generated and added to keychain
Encrypt and decrypt message successfully with keys newly generated. Message is:

Raspberry Pi 3 is with Bluetooth & Wi-Fi and at US$35.

When keys already stored in keychain:

Private Key existed!
Public Key existed!
Key pair retrieved successfully!
Encrypt and decrypt message successfully with keys from keychain. Message is:
Raspberry Pi 3 is with Bluetooth & Wi-Fi and at US$35.

References:
Encrypt/decrypt a string with code-generated RSA public/private keys in Swift
Encrypt/decrypt a string with public/private keys imported from PEM files (Swift)
OpenSSL RSA commands to encrypt/decrypt a message in terminal (Mac)

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

13 comments:

  1. Hi, how i can use this code to generate EC instead of RSA ???
    can i do that by replace kSecAttrKeyTypeRSA to kSecAttrKeyTypeEC
    and kSecAttrKeySizeInBits as String: 1024 to kSecAttrKeySizeInBits as String: 192 ???

    i search on the internet for any tutorial talking about EC but i found nothing
    please help me with that ??

    ReplyDelete
    Replies
    1. I hope I could answer you, but unfortunately I have no experience with EC. Can you ask in Stackoverflow or Apple Developer Forums?

      Delete
  2. Hi when we create a pair keychain, the keys are store automatically in the keychain? is for it that you commented the method StoreInKeychain?
    Thanks

    ReplyDelete
    Replies
    1. Yes. SecKeyGeneratePair() automatically stores generated keys in keychain when I called it. So SecItemAdd() is not required.

      Delete
  3. And Can i encrypt and decrypt a message much bigger the key size?
    Thanks

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Yes I have try encrypt a messaga >= key size, but return always errSecParam
      Do you have any idea why?
      Thanks

      Delete
    3. Perhaps the problem is with the block size, which should be the size of the encrypted message, but not be the size of the public key:

      let blockSize = SecKeyGetBlockSize(publicKey!)
      var messageEncrypted = [UInt8](count: blockSize, repeatedValue: 0)

      Please try define blockSize as a larger value.

      Delete
  4. Thx Enoch, very helpful. Any code sample on how to format the public Key in order to share it with a server (written in node.js) ?

    ReplyDelete
  5. Hi, delete function remove all keys? Even if these keys doesn't from my app?

    ReplyDelete