Skip to content

Latest commit

 

History

History
145 lines (107 loc) · 8.43 KB

README-RSAUtils.md

File metadata and controls

145 lines (107 loc) · 8.43 KB

RSAUtils

Utility class to encrypt/decrypt data using RSA private/public key.

Objective-C version can be found at https://github.com/ideawu/Objective-C-RSA

Note: if you receive error status -34018 on Xcode8 / iOS 10, enable Keychain Sharing. See: http://stackoverflow.com/questions/38456471/secitemadd-always-returns-error-34018-in-xcode-8-in-ios-10-simulator/38543243#38543243

Introduction

This library is to solve the following problem (actually mine):

  • We already had a server that provides APIs for clients to call. Data has to be encrypted using RSA, and the server had already generated a pair of RSA public/private key for each client.
  • We had to implement an iOS client for the server's APIs. It seemed to be a simple task at first, but it soon turned out not so:
    1. In Apple's Security framework all key management happens at the keychain level. It means existing key(s) must be imported to keychain in order to be usable. Using the key-pair generated by SecKeyGeneratePair was easy. Importing an existing key (in file, or as a string), however, turned out to be tricky.
    2. There was a solution in Objective-C but we needed a pure-Swift one and there was no good pure-Swift implementation at the time.
    3. The amount of data that can be encrypted in one go is limitted by the key's length. If a large amount of data needs to be encrypted, it has to be processed chunk by chunk.

This small library addresses all 3 problems above.

Usage

Notes:

Public key: only X509 public key is supported. On disk, X509 public key file begins with -----BEGIN PUBLIC KEY----- and ends with -----END PUBLIC KEY-----.

Private key: PKCS#1 and PKCS#8 private keys are supported. On disk, PKCS#8 private key file begins with -----BEGIN PRIVATE KEY----- and ends with -----END PRIVATE KEY-----, PKCS#1 private key file begins with -----BEGIN RSA PRIVATE KEY----- and ends with -----END RSA PRIVATE KEY-----.

Obtain PKCS#8 private key and X509 public key:

Usually you have the PKCS#1 private key in a .pem file (this file starts with -----BEGIN RSA PRIVATE KEY----- and ends with -----END RSA PRIVATE KEY-----). To convert to PKCS#8 format, use the following command:

openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in privkey.pem -out privkey.pkcs8.pem

You can extract X509 public key from a PEM private key by using the following command:

openssl rsa -pubout -in privkey.pem -out pubkey.x509.pem

or

openssl rsa -pubout -in privkey.pkcs8.pem -out pubkey.x509.pem

Example of a PKCS#1 private key:

-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDAF4nCtUoppj4taUW7T4+aGhf45lGR8mAvVnKHufTzPaNinYzl
mkLF7I7dk0Tgc3KoACJ6I2NC//mhEpNErX0MAqVlCMbAgWvUNSFvHByaqC/5o5hu
k3CwvERFho2IHmSMMTasMG6PoOhto3KPF9AxszkRCsY3xnS5JURE/4h4DQIDAQAB
AoGAcwOk8Hgr15Q6VmZZ4jVY/iPxho4g+QnunWldWfb1u06ErV84JaGfqXJieDjd
XKbDV+P8wLS/kjZB+TduBgj6fVTqTdL26sBsAhTiYTBK4UoXwRMg14dbXJrsnH5n
KUU9GFslFQefRaWn4mjvaMFkUqRsrLkM2nmgBXDnmJLdmwECQQDd40S4EuwoLFYW
xDiWpdCn2nA6n7SBp2xa26Az7bZog6KcDxGgNPY1Z1+PGnnOw6Lcgmx2LOZS4g4S
WwzjnuQ1AkEA3Z+cFFaGll9di5J2BWlQNKzv5UeSMZqvspxsd2VDJXmWs2PvqEHd
BzU4WW8WsHWyoycOY0R710t8JcWCrMGPeQJBAIl6rTNYFhZ2EgkdHurIZlX6FBte
pJrIv2w0NDi4ipKLLQ+Ajq0y43IHUL/76Yjg0mHKNaWrADJOeeWJoJzP8BkCQQCo
zHSYmP6RcblSIQ97N6c7N6zQOR8EYQkJRVLn7VyjImTB5ZAX23J5lvOASrhBVqPk
2E6BFRwt8vRv4GuTISohAkAImxaeC1+jiRGN+EtsNDSmgNjILg5Tyteor6t3guTR
uMv2Y8tJiCh7UTguUnWiDo/RLrDjuvoE4lLpbyAvariY
-----END RSA PRIVATE KEY-----

It's PKCS#8 private key format:

-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMAXicK1SimmPi1p
RbtPj5oaF/jmUZHyYC9Wcoe59PM9o2KdjOWaQsXsjt2TROBzcqgAInojY0L/+aES
k0StfQwCpWUIxsCBa9Q1IW8cHJqoL/mjmG6TcLC8REWGjYgeZIwxNqwwbo+g6G2j
co8X0DGzOREKxjfGdLklRET/iHgNAgMBAAECgYBzA6TweCvXlDpWZlniNVj+I/GG
jiD5Ce6daV1Z9vW7ToStXzgloZ+pcmJ4ON1cpsNX4/zAtL+SNkH5N24GCPp9VOpN
0vbqwGwCFOJhMErhShfBEyDXh1tcmuycfmcpRT0YWyUVB59FpafiaO9owWRSpGys
uQzaeaAFcOeYkt2bAQJBAN3jRLgS7CgsVhbEOJal0KfacDqftIGnbFrboDPttmiD
opwPEaA09jVnX48aec7DotyCbHYs5lLiDhJbDOOe5DUCQQDdn5wUVoaWX12LknYF
aVA0rO/lR5Ixmq+ynGx3ZUMleZazY++oQd0HNThZbxawdbKjJw5jRHvXS3wlxYKs
wY95AkEAiXqtM1gWFnYSCR0e6shmVfoUG16kmsi/bDQ0OLiKkostD4COrTLjcgdQ
v/vpiODSYco1pasAMk555YmgnM/wGQJBAKjMdJiY/pFxuVIhD3s3pzs3rNA5HwRh
CQlFUuftXKMiZMHlkBfbcnmW84BKuEFWo+TYToEVHC3y9G/ga5MhKiECQAibFp4L
X6OJEY34S2w0NKaA2MguDlPK16ivq3eC5NG4y/Zjy0mIKHtROC5SdaIOj9EusOO6
+gTiUulvIC9quJg=
-----END PRIVATE KEY-----

And it's X509 public key:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAF4nCtUoppj4taUW7T4+aGhf4
5lGR8mAvVnKHufTzPaNinYzlmkLF7I7dk0Tgc3KoACJ6I2NC//mhEpNErX0MAqVl
CMbAgWvUNSFvHByaqC/5o5huk3CwvERFho2IHmSMMTasMG6PoOhto3KPF9AxszkR
CsY3xnS5JURE/4h4DQIDAQAB
-----END PUBLIC KEY-----

Functions:

Key management functions:

  • static func getRSAKeyFromKeychain(_ tagName: String) -> SecKey?: Gets an existing RSA key specified by a tag from keychain.
  • static func deleteRSAKeyFromKeychain(_ tagName: String): Deletes an existing RSA key specified by a tag from keychain.
  • static func addRSAPrivateKey(_ privkeyBase64: String, tagName: String) throws -> SecKey?: Adds a RSA private key to keychain and returns its SecKey reference.
    • The private key is in base-64 PKCS#1 or PKCS#8 format.
    • Throws RSAUtilsError if the input key is not a valid PKCS#8 private key.
    • -----BEGIN PRIVATE KEY-----, -----BEGIN RSA PRIVATE KEY-----, -----END PRIVATE KEY----- and -----END RSA PRIVATE KEY----- can be omitted.
  • static func addRSAPublicKey(_ pubkeyBase64: String, tagName: String) throws -> SecKey?: Adds a RSA public key to keychain and returns its SecKey reference.
    • The public key is in base-64 X509 format.
    • Throws RSAUtilsError if the input key is not a valid X509 public key.
    • -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- can be omitted.

Encryption/Decryption functions:

  • static func encryptWithRSAKey(data: Data, tagName: String) -> Data?: Encrypts data using a RSA key from keychain specified by tagName.
  • static func encryptWithRSAKey(str: String, tagName: String) -> Data?: Encrypts a string using a RSA key from keychain specified by tagName.
  • static func decryptWithRSAKey(encryptedData: Data, tagName: String) -> Data?: Decrypts an encrypted data using a RSA key from keychain specified by tagName.
  • static func encryptWithRSAPublicKey(data: Data, pubkeyBase64: String) throws -> Data?: Encrypts data using RSA public key.
    • The public key is in base-64 X509 format.
    • Throws RSAUtilsError if the input key is not a valid X509 public key.
    • -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- can be omitted.
  • static func encryptWithRSAPublicKey(str: String, pubkeyBase64: String) throws -> Data?: Encrypts a string using RSA public key.
  • static func encryptWithRSAPublicKey(data: Data, pubkeyBase64: String, tagName: String) throws -> Data?: Encrypts data using RSA public key. Also, the public key will be stored in keychain specified by tagName.
  • static func encryptWithRSAPublicKey(str: String, pubkeyBase64: String, tagName: String) throws -> Data?: Encrypts a string using RSA public key. Also, the public key will be stored in keychain specified by tagName.
  • static func decryptWithRSAPrivateKey(encryptedData: Data, privkeyBase64: String) throws -> Data?: Decrypts an encrypted data using a RSA private key.
  • static func decryptWithRSAPrivateKey(encryptedData: Data, privkeyBase64: String, tagName: String) throws -> Data?: Decrypts an encrypted data using a RSA private key. Also, the private key will be stored in keychain specified by tagName.

Encrypt/Decrypt Large Amount of Data

The amount of data that can be encrypted in one go is key's size - 11. For example, if the key's size is 256 bytes (2048 bits), maximum amount of data that can be encrypted is 245 bytes.

In order to encrypt a large amount of data, encryptXXX() function splits the large data into chunks of size key's size - 11, encrypts each chunk and concatenates encrypted chunks into the final result.

Decryption performs similarly: encrypted data is splitted into chunks of size key's size, each chunk is then decrypted and concatenated into the final result.

Changes