Swift: Rsa Encrypt a String with a Specific Private Key

RSA Encryption function in Swift 4 from Public Key string

You can do like this way...

static func encrypt(string: String, publicKey: String?) -> String? {
guard let publicKey = publicKey else { return nil }

let keyString = publicKey.replacingOccurrences(of: "-----BEGIN PUBLIC KEY-----\n", with: "").replacingOccurrences(of: "\n-----END PUBLIC KEY-----", with: "")
guard let data = Data(base64Encoded: keyString) else { return nil }

var attributes: CFDictionary {
return [kSecAttrKeyType : kSecAttrKeyTypeRSA,
kSecAttrKeyClass : kSecAttrKeyClassPublic,
kSecAttrKeySizeInBits : 2048,
kSecReturnPersistentRef : kCFBooleanTrue] as CFDictionary
}

var error: Unmanaged<CFError>? = nil
guard let secKey = SecKeyCreateWithData(data as CFData, attributes, &error) else {
print(error.debugDescription)
return nil
}
return encrypt(string: string, publicKey: secKey)
}

static func encrypt(string: String, publicKey: SecKey) -> String? {
let buffer = [UInt8](string.utf8)

var keySize = SecKeyGetBlockSize(publicKey)
var keyBuffer = [UInt8](repeating: 0, count: keySize)

// Encrypto should less than key length
guard SecKeyEncrypt(publicKey, SecPadding.PKCS1, buffer, buffer.count, &keyBuffer, &keySize) == errSecSuccess else { return nil }
return Data(bytes: keyBuffer, count: keySize).base64EncodedString()
}

Encrypt using RSA Certificate in Swift

The code provided in question doesn't extract the Key from certificate. Rather, it tries to create the SecKey using the certificate string itself. The correct way to do the same is to create a SecCertificate object from the certificate data, then create a SecTrust using the certificate data. Then using the trust, copy the public key to make SecKey object.

The final code will look something like this:

import Foundation
import Security

struct RSA {

static func encrypt(string: String, certificate: String?) -> String? {
guard let certificate = certificate else { return nil }

let certificateString = certificate.replacingOccurrences(of: "-----BEGIN CERTIFICATE-----", with: "").replacingOccurrences(of: "-----END CERTIFICATE-----", with: "").replacingOccurrences(of: "\r", with: "").replacingOccurrences(of: "\n", with: "").replacingOccurrences(of: "\t", with: "").replacingOccurrences(of: " ", with: "")
print(certificateString)

// Convert the certificate string to Data
guard let data = Data(base64Encoded: certificateString) else { return nil }
print(data)

// Create SecCertificate object using certificate data
guard let cer = SecCertificateCreateWithData(nil, data as NSData) else { return nil }

var trust: SecTrust?

// Retrieve a SecTrust using the SecCertificate object. Provide X509 as policy
let status = SecTrustCreateWithCertificates(cer, SecPolicyCreateBasicX509(), &trust)

// Check if the trust generation is success
guard status == errSecSuccess else { return nil }

// Retrieve the SecKey using the trust hence generated
guard let secKey = SecTrustCopyPublicKey(trust!) else { return nil }

return encrypt(string: string, publicKey: secKey)
}

static func encrypt(string: String, publicKey: SecKey) -> String? {
let buffer = [UInt8](string.utf8)

var keySize = SecKeyGetBlockSize(publicKey)
var keyBuffer = [UInt8](repeating: 0, count: keySize)

// Encrypto should less than key length
guard SecKeyEncrypt(publicKey, SecPadding.PKCS1, buffer, buffer.count, &keyBuffer, &keySize) == errSecSuccess else { return nil }
return Data(bytes: keyBuffer, count: keySize).base64EncodedString()
}
}

var pemString = "-----BEGIN CERTIFICATE-----##Base 64 encoded certificate string##-----END CERTIFICATE-----"

let password = "abcde"
let encryptedPassword = RSA.encrypt(string: password, certificate: pemString)
print(encryptedPassword as Any)

The only change is in the static func encrypt(string: String, certificate: String?) -> String? function.

Swift Load RSA Public Key from String (MacOS)

The key data you have is PEM encoded. However, Apple supports DER encoding for their security API. So first we'll have to transform your key data to the correct format. The example below is created with a random RSA key exporting the public key to PEM format. PEM headers can differ from library to library, so be sure you remove the tags, before continuing to the DER transformation.

var pem = "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAs6AofVx+UAXcVjnIU0Z5SAGO/LPlTunA9zi7jNDcIrZTR8ULHTrm\naSAg/ycNR1/wUeac617RrFeQuoPSWjhZPRJrMa3faVMCqTgV2AmaPgKnPWBrY2ir\nGhnCnIAvD3sitCEKultjCstrTA71Jo/BuVaj6BVgaA/Qn3U9mQ+4JiEFiTxy4kOF\nes1/WwTLjRQYVf42oG350bTKw9F0MklTTZdiZKCQtc3op86A7VscFhwusY0CaZfB\nlRDnTgTMoUhZJpKSLZae93NVFSJY1sUANPZg8TzujqhRKt0g5HR/Ud61icvBbcx8\n+a3NzmuwPylvp5m6hz/l14Y7UZ8UT5deywIDAQAB\n-----END RSA PUBLIC KEY-----\n"

// Remove headers and footers from the PEM, leaving us with DER encoded data split by new lines
[
"-----BEGIN RSA PUBLIC KEY-----", "-----END RSA PUBLIC KEY-----",
"-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----"
].forEach { pem = pem.replacingOccurrences(of: $0, with: "") }

// Construct DER data from the remaining PEM data
let der = Data(base64Encoded: pem, options: .ignoreUnknownCharacters)!

Now that we have our DER encoded data, it's time to construct our key. Firstly, create the attributes describing the key, after that create the key from the DER data. Note here how der is of type Data not of type String. Generally speaking, crypto operations occur on Data. The security API however uses CFData, but one can easily exchange them (as CFData or as Data). The same goes for the attribute dictionary.

// Key generation attributes
let attributes: [String: Any] = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecAttrKeyClass): kSecAttrKeyClassPublic,
String(kSecAttrKeySizeInBits): der.count * 8
]

// For simplicity I force unwrap here. The nil parameter can be used to extract an error
let key = SecKeyCreateWithData(der as CFData, attributes as CFDictionary, nil)!

Now that we have our key, we can use it to encrypt. Be aware however that RSA cannot encrypt huge amounts of data (not technically true, you could create chunks of data and do it that way, but RSA is not intended for this. If you want to do such thing, read up on key exchange and symmetric encryption. RSA is not intended for that behaviour). Also note that OAEP (as used in the example) has a random factor to it. Meaning the cipher text output will differ each time you run this code. This doesn't mean it's not working, it's simply a property OAEP has. I'd also like to point out that PKCS1 padding should be avoided when able in favour of OAEP.

// An example message to encrypt
let plainText = "This is my secret".data(using: .utf8)!

// Perform the actual encryption
// Again force unwrapping for simplicity
let cipherText = SecKeyCreateEncryptedData(key, .rsaEncryptionOAEPSHA256, plainText as CFData, nil)! as Data

In the example above .rsaEncryptionOAEPSHA256 is used. This is one of the available encryption algorithms for RSA:

  • .rsaEncryptionRaw
  • .rsaEncryptionPKCS1
  • .rsaEncryptionOAEPSHA1
  • .rsaEncryptionOAEPSHA224
  • .rsaEncryptionOAEPSHA256
  • .rsaEncryptionOAEPSHA384
  • .rsaEncryptionOAEPSHA512

I highly recommend using one of the OAEP variants, but that is up to you. I hope this helps. The SecKeyCreateEncryptedData is available for macOS since 10.12. You can read more about it here.

Swift encrypt String using RSA Private key with Security framework

When the encrypt() method is called, the following error message is displayed on my machine:

encrypt:RSA:PKCS1: algorithm not supported by the key <SecKeyRef algorithm id: 1, key type: RSAPrivateKey, ...>

This is not surprising, since the code tries to encrypt with a private key (labeled in the code as public key, which is misleading!). However, a private key is applied only for signing, while a public key is used for encryption.

Encryption with the private key is not consistently implemented by libraries. Some extract the public key from the private key and encrypt with the public key, some create a signature with the private key and others do not support this at all (like this library here).

It seems most likely to me that your requirement is to create a signature. Technically, encryption and signing are similar and differ in that encryption uses the public key for modular exponentiation and signing uses the private key. In addition, the padding variants differ. However, the purposes are completely different; encryption is about confidentiality, while signing is about verifying authenticity.

So you could try signing instead of encrypting. For this, the code has to be changed only slightly (however, sooner or later the variable names should also be adapted):

guard let encryptedBuffer = SecKeyCreateSignature(publicKey, .rsaSignatureDigestPKCS1v15Raw, buffer as CFData, &encryptionError) as Data? else {
throw encryptionError!.takeRetainedValue() as Error
}

encryptedData now contains the signature (and publicKey denotes the private key).

Note that for rsaSignatureDigestPKCS1v15Raw the message is signed directly, i.e. without hashing or considering the digest ID. Since no digest ID is specified, this might be the correct variant.

However, be aware that the message length is limited (analogous to encryption). Compliant with the standard is a hashing and the consideration of the digest ID, which is achieved e.g. with rsaSignatureMessagePKCS1v15SHA256 for SHA-256.

how to encrypt and decrypt a String(Plain Text) with RSA public key in ios, swift

Hope this will help you:

let serverPublicKey = "Some text with key"

let data2 = Data.init(base64Encoded: serverPublicKey)

let keyDict:[NSObject:NSObject] = [
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecAttrKeySizeInBits: NSNumber(value: 2048),
kSecReturnPersistentRef: true as NSObject
]

let publickeysi = SecKeyCreateWithData(data2! as CFData, keyDict as CFDictionary, nil)

//Encrypt a string with the public key
let message = "This is my message."
let blockSize = SecKeyGetBlockSize(publickeysi!)
var messageEncrypted = [UInt8](repeating: 0, count: blockSize)
var messageEncryptedSize = blockSize

var status: OSStatus!

status = SecKeyEncrypt(publickeysi!, SecPadding.PKCS1, message, message.characters.count, &messageEncrypted, &messageEncryptedSize)

if status != noErr {
print("Encryption Error!")
return
}


Related Topics



Leave a reply



Submit