I am working on a static library that takes an input String, encrypts it with the private key then returns it. (Because the client request to use private key on the mobile side.)
I am modifying code from this post and this post to work with the private key, but the result string always returns nil.
Key for testing:
let privateTestingKey = "-----BEGIN RSA PRIVATE KEY-----MIICXwIBAAKBgQDPJfl0gC95kB3sS0SOfXW45UooiYjT3ZeHRDn/eG+So09x7cXLVbRILxxXfaU+9b5Yw2wETMvuzhP8C5Qd5PQy5lkH5LjcqJjDTvOBuBxNOSY4rmlqdy/skqM4FHjTGdwI2nZiVkjqOFXM+XZ94Ar+pszJifbSPElR1pO4NfT76QIDAQABAoGBAJjtoRdoFyR4yA6NlsRXTRS+ehwpRVGcc2TScrrvL/ejB2DFuFOgJyNvXE4fHWK4y9j+FP2rsJbRnyFhbu0O/VRNPa5Llnejea+5ov/3wxFqiae7pn6bjmiW/xdy0ycLWo90wLX9QqYjKBHomnh15FIajmSuRaUlBaL6DiZt010tAkEA0zLTPiH35meyXim7I7+mMmAN68zGu3cYpqq8i/xlHvXjWrBDV5saxRDsm97ktFqiRpvIoV3n0ZoT+3xjKhGINwJBAPsXM9hWdmWUXmaaAnXZDmKbNltRSZaaz4BktGRUJM0grt7kMndfBgd2JENNzYTfLpOWMIfHXpW9T4pVCjg5TN8CQQDGZf91ZbmYUx+HP5KSUY4R0pQhR/wEzSt2HfwDUPW5cOnEHsMUQBuUtoJfJrMYDfBVfjCqDiogh6pv2/jX4yJfAkEAqwsvQhwEI0Zi2DnpmyX1aq6Y5LQHERT8bVYsnHvFZgbxmNySlEai8MpGAaMqcW0naVpSTOw/PnnriSxM/efquQJBAM/3eRqzBHp/v3bRM+npSgEOaeOmtNiz+oNrJ09mUToyDTkuFOg7J9ojNkeviR4xqs9Hoos8lP3ho1uVcE3/tRQ=-----END RSA PRIVATE KEY-----"
Code for RSA encryption:
import Foundation
import Security
public class MPEncryption {
var publicKey: SecKey!
public init(publicKeyString: String) throws {
self.publicKey = try makePublicKey(from: publicKeyString)
}
private func makePublicKey(from keyString: String) throws -> SecKey {
let pubKeyData = Data(base64Encoded: sanitize(key: keyString))!
let query: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecAttrKeySizeInBits as String: 1024
]
var unmanagedError: Unmanaged<CFError>?
guard let pubKeyRef = SecKeyCreateWithData(pubKeyData as CFData, query as CFDictionary, &unmanagedError) else {
throw unmanagedError!.takeRetainedValue() as Error
}
return pubKeyRef
}
public func encrypt(value: String) throws -> String {
let valueData = value.data(using: .utf8)!
let bufferSize = SecKeyGetBlockSize(publicKey) - 11
let buffers = makeBuffers(fromData: valueData, bufferSize: bufferSize)
var encryptedData = Data()
for buffer in buffers {
var encryptionError: Unmanaged<CFError>?
guard let encryptedBuffer = SecKeyCreateEncryptedData(publicKey, .rsaEncryptionPKCS1, buffer as CFData, &encryptionError) as Data? else {
throw encryptionError!.takeRetainedValue() as Error
}
encryptedData.append(encryptedBuffer)
}
return encryptedData.base64EncodedString()
}
private func makeBuffers(fromData data: Data, bufferSize: Int) -> [Data] {
guard data.count > bufferSize else {
return [data]
}
var buffers: [Data] = []
for i in 0..<bufferSize {
let start = i * bufferSize
let lengthOffset = start + bufferSize
let length = lengthOffset < data.count ? bufferSize : data.count - start
let bufferRange = Range<Data.Index>(NSMakeRange(start, length))!
buffers.append(data.subdata(in: bufferRange))
}
return buffers
}
private func sanitize(key: String) -> String {
let headerRange = key.range(of: "-----BEGIN RSA PRIVATE KEY-----")
let footerRange = key.range(of: "-----END RSA PRIVATE KEY-----")
var sanitizedKey = key
if let headerRange = headerRange, let footerRange = footerRange {
let keyRange = Range<String.Index>(uncheckedBounds: (lower: headerRange.upperBound, upper: footerRange.lowerBound))
sanitizedKey = String(key[keyRange])
}
return sanitizedKey
.trimmingCharacters(in: .whitespacesAndNewlines)
.components(separatedBy: "\n")
.joined()
}
}
I can't locate the problem, any suggestion is appreciated.
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.
Related
I'm building a simple app, where users can add personal diary entries in the form of text only, and it gets saved to my cloud firestore database. Now, I want to build end-to-end encryption so the user's personal content is never readable by anyone but them.
To achieve this, I'm trying to use the CryptoSwift library to encrypt and decrypt content. For testing purposes, I'm starting with just a simple "Hello World" in my ViewModel. However, I'm unable to get it to work.
Couple of questions
I'm unable to get "Hello world" to show up from clear (hello world) > encrypted > cipher > decrypted (hello world). How can I get the same "hello world" result from my decrypt function?
In what format should I save my key to the keychain?
In what format should I save my encrypted text, and iv on firebase? Should I continue to use .toHexString() or should I be using .toBase64()?
I'm successfully able to generate a key, and save it as a "hexString". And I'm able to "run" through both the encrypt and the decrypt functions without errors, but they don't generate a "hello world" from cleartext(hello world) > encrypt > ciphertext > decrypt > helloworld.
class EncryptionService: EncryptionServicing {
private var keychainService = KeychainService()
func generateKey() throws -> String {
print("[EncryptionService] πππ Generate key called")
do {
if let userId = UserService.shared.userInfo?.userId {
let userId: [UInt8] = Array(userId.utf8)
// Salt added for randomness
let salt: [UInt8] = (1...5).map( {_ in UInt8(Int.random(in: 1...10))} )
let key = try PKCS5.PBKDF2(
password: userId,
salt: salt,
iterations: 4096,
keyLength: 32, /* AES-256 */
variant: .sha2(.sha256)
).calculate()
print("πππ UserId: \(userId)")
print("πππ Salt: \(salt)")
print("πππ KEY: \(key.toHexString())")
return key.toHexString()
}
}
catch {
print(error)
}
return ""
}
func encrypt(clearText: String, aesKey: String) throws -> (cipherText: String, iv: String) {
print("[EncryptionService] β
Encrypt called")
do {
let iv = AES.randomIV(AES.blockSize)
let keyArray = [UInt8](hex: aesKey)
let aes = try AES(key: keyArray, blockMode: CBC(iv: iv), padding: .pkcs7)
let cipherText = try aes.encrypt(Array(clearText.utf8))
return (cipherText.toHexString(), iv.toHexString())
}
catch {
print(error)
return ("", "")
}
}
func decrypt(cipherText: String, iv: String, aesKey: String) throws -> String {
print("[EncryptionService] β
Decryption called")
do {
print("Ciphertext: \(cipherText)")
print("Iv: \(iv)")
print("aesKey: \(aesKey)")
let keyArray = [UInt8](hex: aesKey)
let aes = try AES(key: keyArray, blockMode: CBC(iv: [UInt8](hex: iv)), padding: .pkcs7)
print("AES Key size: \(aes.keySize)")
let decryptedText = try aes.decrypt(cipherText.bytes)
return decryptedText.toHexString() // doesn't respond with "Hello world!"
}
}
}
This is what my ViewModel looks like.
#Published var dummyClearText: String = "Hello World!"
#Published var dummyCipherText: String = ""
#Published var dummyIv: String = ""
#Published var dummyDecryptedText: String = ""
// Have a "encrypt" button that calls this.
func generateCipherText() {
do {
let result = try EncryptionService().encrypt(clearText: self.dummyClearText, aesKey: self.aesKey)
self.dummyCipherText = result.cipherText
self.dummyIv = result.iv
}
catch {
print(error)
}
}
// Have a "decrypt" button that calls this, and takes the generated cipher text as input.
func generateClearText() {
do {
let result = try EncryptionService().decrypt(cipherText: self.dummyCipherText, iv: self.dummyIv, aesKey: self.aesKey)
self.dummyDecryptedText = result // doesn't respond with "Hello world!"
}
catch {
print(error)
}
}```
Thank you so much, still very much learning how to Swift.
Happy to provide more context.
After decryption, you don't return the decrypted text, you return its hex representation:
return decryptedText.toHexString()
You probably want something like this instead:
// Interpreting the data as UTF-8 can fail. You should handle this.
return String(data: decryptedText, encoding: .utf8) ?? ""
I'm trying attempting to create signature verification on iOS platform for when the signature and public key are known. The goal is to create something similar to Android's Signature class to where you pass in a key, some bytes from an unsigned entity, and a known signature, and then call verify() and it will tell you whether or not the signature is valid.
The public key string is known and is base64 encoded. The signature is also known and is base64 encoded. I know the algorithm is EC SHA 256 for encrypting. When I create the public SecKey it is 65 bytes and starts with 4. I am also 100% sure of the signature, I compare the byte array for the signature on both iOS and Android. They are the same BUT Android seems to validate the signature.
I'm pasting the code as to what I am doing. If anyone can point out what I am doing incorrectly, that'd be great.
Thanks
private var algorithm: SecKeyAlgorithm = .ecdsaSignatureMessageX962SHA256
typealias SecKeyPair = (publicKey: SecKey?, privateKey: SecKey?)
/**
Generates a public key using the 256 bit Elliptic Curve Signature
(a 256 bit EC public key is 65 bytes long and starts with an 04.)
*/
func getPublicKey(keyAsBytes: [UInt8]) -> SecKeyPair {
let keyType = kSecAttrKeyTypeECSECPrimeRandom
let keySize = 256
let privateKeyParams: [String: AnyObject] = [kSecAttrIsPermanent as String: false as AnyObject,
kSecAttrApplicationTag as String: keyAsBytes as AnyObject]
let publicKeyParams: [String: AnyObject] = [kSecAttrIsPermanent as String: false as AnyObject,
kSecAttrApplicationTag as String: keyAsBytes as AnyObject]
let parameters: [String: AnyObject] = [kSecAttrKeyType as String: keyType,
kSecAttrKeySizeInBits as String: keySize as AnyObject,
kSecPublicKeyAttrs as String: publicKeyParams as AnyObject,
kSecPrivateKeyAttrs as String: privateKeyParams as AnyObject]
var publicKey, privateKey: SecKey?
_ = SecKeyGeneratePair(parameters as CFDictionary, &publicKey, &privateKey)
return SecKeyPair(publicKey: publicKey, privateKey: privateKey)
}
private func verifyRaw(publicKey: SecKey,
updatePackage: Data,
signature: Data) -> Bool {
// #define CC_SHA256_DIGEST_LENGTH 32
// Creates an array of unsigned 8 bit integers that contains 32 zeros
var digest = [UInt8](repeating: 0, count:Int(CC_SHA256_DIGEST_LENGTH))
// CC_SHA256 performs digest calculation and places the result in the caller-supplied buffer for digest (md)
_ = updatePackage.withUnsafeBytes {
CC_SHA256($0.baseAddress, UInt32(updatePackage.count), &digest)
}
let result = SecKeyRawVerify(publicKey,
.PKCS1SHA256,
digest,
digest.count,
signature.bytes,
signature.bytes.count)
return result == errSecSuccess
}
private func verify(publicKey: SecKey,
updatePackage: CFData,
signature: CFData,
privateKey: SecKey? = nil) -> Bool {
var result = false
var pbError:Unmanaged<CFError>?
var digest = [UInt8](repeating: 0, count:Int(CC_SHA256_DIGEST_LENGTH))
// CC_SHA256 performs digest calculation and places the result in the caller-supplied buffer for digest (md)
let data = (updatePackage as Data)
_ = data.withUnsafeBytes {
CC_SHA256($0.baseAddress, UInt32(data.count), &digest)
}
let digestAsData = Data(bytes: digest, count: digest.count)
if SecKeyVerifySignature(publicKey,
algorithm,
digestAsData as CFData,
signature,
&pbError) {
result = true
} else {
Log.error(message: "[F_MANAGER] Unable to verify signature \(pbError!.takeRetainedValue() as Error)")
}
return result
}
private func isSignatureValid(updatePackage: [UInt8], decodedSignatureBytes: [UInt8], publicKey: String) -> Bool {
var result = false
// Generate the Public Key key
if let decodedKey = Data(base64Encoded: publicKey) {
let keys = getPublicKey(keyAsBytes: decodedKey.bytes)
if let pKey = keys.publicKey {
let decodedSignature = Data(decodedSignatureBytes)
let canVerify = SecKeyIsAlgorithmSupported(pKey, .verify, algorithm)
if canVerify {
let doRaw = false
let updatePackageAsData = Data(updatePackage)
if doRaw {
result = verifyRaw(publicKey: pKey,
updatePackage: updatePackageAsData,
signature: decodedSignature)
if !result {
let b64EncodedPackageAsData = updatePackageAsData.base64EncodedData()
result = verifyRaw(publicKey: pKey,
updatePackage: b64EncodedPackageAsData,
signature: decodedSignature)
}
} else {
result = verify(publicKey: pKey,
updatePackage: updatePackageAsData as CFData,
signature: decodedSignature as CFData)
if !result {
let b64EncodedPackageAsData = updatePackageAsData.base64EncodedData()
result = verify(publicKey: pKey,
updatePackage: b64EncodedPackageAsData as CFData,
signature: decodedSignature as CFData)
}
}
} else {
Log.error(message: "[F_MANAGER] Algorithm not supported")
}
}
}
return result
}
I posted this same question on Apple Developer forum, and there was a person who gave me some good info. He also indicated I could use CryptoKit which I wound up using.
He explained to me that my keys and signature were in the 'der' format. I had no idea. He also indicated that I didn't need to use the SecKeyCreateKeys.
I'm posting this solution in case there is someone else out there who is struggling like I was:
#available(iOS 14.0, *)
private func verifySignature(data: Data, key: Data, signature: Data) -> Bool {
var result = false
do {
let publicKey = try P384.Signing.PublicKey(derRepresentation: key.bytes)
let hash = SHA256.hash(data: data)
let signing384 = try P384.Signing.ECDSASignature(derRepresentation: signature)
if publicKey.isValidSignature(signing384, for: hash) {
result = true
}
} catch {
Log.error(message: "[F_MANAGER] Exception \(error)")
}
return result
}
I am new to iOS and Swift. I wanted to encrypt a string using the public key. The public key was like this:
"-----BEGIN PUBLIC KEY-----
Some String
-----END PUBLIC KEY-----"
I searched for it and got and implemented the following code:
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()
}
Thanks to StackOverflow again. Now the problem is i am not able to decrypt the encrypted string that is returned by this function. For encoding i wanted a 2048bit size.
For Android this is how it was done for encryption:
public String encryptKey() throws NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, InvalidKeyException, CertificateException {
publicKey = getPublicKey();
Cipher oaepFromAlgo = Cipher.getInstance("RSA/NONE/OAEPWITHSHA-256ANDMGF1PADDING");
oaepFromAlgo.init(Cipher.ENCRYPT_MODE, publicKey );
//byte[] ct = oaepFromAlgo.doFinal(secretKey.toString().getBytes(StandardCharsets.UTF_8));
byte[] ct = oaepFromAlgo.doFinal(secretKey.getEncoded());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return Base64.getEncoder().encodeToString(ct);
}
else{
return android.util.Base64.encodeToString(ct,android.util.Base64.DEFAULT);
}
}
private PublicKey getPublicKey() throws CertificateException {
CertificateFactory f = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate)f.generateCertificate(getTrustedCertificatesInputStream());
return certificate.getPublicKey();
}
It would be really helpful if anyone could guide me in the right path on how to decrypt and if my encryption process is similar to that of android.
A heartfelt thanks to everyone who tried to solve it. I managed to do it on my own. Instead of public key as string, i extracted public key as secKey by converting my crt file to der
I have what seems to be a simple need - I want to generate a public key that I can put into an app, and use to to encrypt data that may be exposed to others. Later, using a private key known only to myself, I want to be able to decrypt that data.
There are bits and pieces of the solution to this scattered all over, but I have yet to find a good explanation of how to:
generate keys that can be turned into string representations that then can be used to reconstruct the keys
pass Data objects into the encryption, and get a string representation of the encrypted data
turn the string representing the encrypted data back into a Data object, and then decrypt this data into its original form
do all the above only using Swift 4.1 or newer
I am aware that there are frameworks that can do this, but it seems like this should ultimately be a fairly small piece of code and so a framework is overkill.
Inspiration for this answer comes from Swift 3 export SecKey to String and NSString Crypt.m gist.
Don't know what PKI (Public Key Encryption)is? Then a nice tutorial: Everything you should know about certificates and PKI but are too afraid to ask
The recommended way to use public/private encryption for large data sets is to use public/private encryption to first share a temporary symmetric key, then use that key to encrypt, send the data, and allow the remote side to decrypt it.
That seemed like a complex task, but then it turned out that Apple already provides this capability for iOS/macOS (search for "In fact, the certificate, key, and trust services API provides a simple way to accomplish this.")
The code below is designed as a test vehicle to let anyone experiment with the settings. You exercise it twice - one with the #if set to true, the second to false. In the first run (using the Simulator for sure), you get string representations of the public and private keys. You then paste those into the class properties, change the #if setting, and re-run the test method.
The test method then re-creates both keys, encrypts the supplied data, then hands that encrypted data off to be decrypted. In the end, the original data is compared to the decrypted data and the result printed out. The raw code is available as gist on github.
The code is constructed as a test vehicle - you can run it as is to have it produce two strings representing the private and public key, then paste those in below to verify that the strings themselves perform exactly as a test with keys freshly generated. The test has four stages:
run an encrypt and decrypt with just the generated keys (SecKeys)
convert the public key to a string, then recreate a new public SecKey from the string, then do the encrypt/decrypt test
same as above except convert private key to string and back
same as above but both keys converted to strings and back
The gist above also contains a class Asymmetric that just uses the public key to encrypt data - this is what you would use in an app (but it's completely based on methods in this class).
You use either a RSA algorithm or an Elliptic Curve key pair. With the current key size, both use a AES 128-bit symmetric key for actual data encryption (see Apple headers).
#if true
private let keyType = kSecAttrKeyTypeRSA // kSecAttrKeyTypeEC
private let algorithm = SecKeyAlgorithm.rsaEncryptionOAEPSHA512AESGCM // EncryptionOAEPSHA512AESGCM
private let keySize = 4096 // SecKey.h states that with this size, you get AES 256-bit encoding of the payload
#else
private let keyType = kSecAttrKeyTypeECSECPrimeRandom // kSecAttrKeyTypeECSECPrimeRandom
private let algorithm = SecKeyAlgorithm.eciesEncryptionCofactorVariableIVX963SHA512AESGCM
private let keySize = 384 // SecKey.h states that with this size, you get AES 256-bit encoding of the payload
#endif
#objcMembers
final class AsymmetricTest: NSObject {
This is where you will paste in the keys you generate when running the program.
// Some Key pair I generated - replace with your own
private let publicStr = """
BFZjQQZVrcHitn13Af89ASrRT2VVPa4yGCreBJim52R/d3yJj3iTroanc7XW+YLJpijFBMei6ddf
lb2PjJLvXNJy8hQItCFRlpbGj7ddSCOuBNyjQP+cpmddgFhy8KCbgw==
"""
private let privateStr = """
BFZjQQZVrcHitn13Af89ASrRT2VVPa4yGCreBJim52R/d3yJj3iTroanc7XW+YLJpijFBMei6ddf
lb2PjJLvXNJy8hQItCFRlpbGj7ddSCOuBNyjQP+cpmddgFhy8KCbg+Sy8M4IjGDI5gdzNmWhDQp2
mggdySIqrjVobCL5NcAg5utA/2QdJGCy9mPw0GkFHg==
"""
var publicKey: Data = Data()
var privateKey: Data = Data()
Run the 4 tests. You provide the data - I tested with a few thousand bytes, but it should work for any data size.
func test(_ testData: Data) -> Bool {
func key2string(key: SecKey) -> String {
guard let keyData = secKey2data(key: key) else { fatalError("key2string FAILED!!!") }
let base64publicKey = keyData.base64EncodedString(options: [.lineLength76Characters, .endLineWithCarriageReturn])
return base64publicKey
}
func string2key(str: String, cfType: CFString) -> SecKey? {
let d = Data(base64Encoded: str, options: [.ignoreUnknownCharacters])
print("string2key: dataSize =", d?.count ?? "-1")
guard
let data = Data(base64Encoded: str, options: [.ignoreUnknownCharacters]),
let key = data2secKey(keyData: data, cfType: cfType)
else { return nil }
return key
}
func runTest(data testData: Data, keys: (public: SecKey, private: SecKey)) {
let d1 = Date()
let _ = self.encryptData(data: testData, key: keys.public)
print("Time:", -d1.timeIntervalSinceNow) // measure performance
if
let d1 = self.encryptData(data: testData, key: keys.public)
,
let d2 = self.decryptData(data: d1, key: keys.private)
{
print("Input len:", d1.count, "outputLen:", d2.count)
print("Reconstructed data is the same as input data:", testData == d2 ? "YES" : "NO")
} else {
print("TEST FAILED")
}
}
If you set the line below to false, then instead of generating keys, it will use the two strings at the top of the class.
#if true // set to true, then copy the two strings to publicStr and privateStr above and set this to false
guard let keys = createKey(keySize: keySize) else { print("WTF"); return false } // size is important smaller failed for me
print("PUBLIC:\n\(key2string(key: keys.public))\n")
print("PRIVATE:\n\(key2string(key: keys.private))\n")
runTest(data: testData, keys: keys) // Original Keys
do { // So suppose we have our public app - it gets the public key in base64 format
let base64key = key2string(key: keys.public)
guard let key = string2key(str: base64key, cfType: kSecAttrKeyClassPublic) else { fatalError("FAILED!") }
runTest(data: testData, keys: (key, keys.private)) // Reconstructed public
}
do { // So suppose we have our private app - it gets the private key in base64 format
let base64key = key2string(key: keys.private)
guard let key = string2key(str: base64key, cfType: kSecAttrKeyClassPrivate) else { fatalError("FAILED!") }
runTest(data: testData, keys: (keys.public, key)) // Reconstructed private
}
do {
let base64keyPublic = key2string(key: keys.public)
guard let keyPublic = string2key(str: base64keyPublic, cfType: kSecAttrKeyClassPublic) else { fatalError("FAILED!") }
let base64keyPrivate = key2string(key: keys.private)
guard let keyPrivate = string2key(str: base64keyPrivate, cfType: kSecAttrKeyClassPrivate) else { fatalError("FAILED!") }
runTest(data: testData, keys: (keyPublic, keyPrivate)) // Reconstructed private
}
#else
do {
guard let keyPublic = string2key(str: publicStr, cfType: kSecAttrKeyClassPublic) else { fatalError("FAILED!") }
guard let keyPrivate = string2key(str: privateStr, cfType: kSecAttrKeyClassPrivate) else { fatalError("FAILED!") }
runTest(data: testData, keys: (keyPublic, keyPrivate)) // Reconstructed private
}
#endif
return true
}
Encrypts the supplied data with the supplied key (which should be the public key):
func encryptData(data: Data, key: SecKey) -> Data? {
//var status: OSStatus = noErr
var error: Unmanaged<CFError>?
let cfData: CFData = data as NSData as CFData
guard SecKeyIsAlgorithmSupported(key, .encrypt, algorithm) else {
fatalError("Can't use this algorithm with this key!")
}
if let encryptedCFData = SecKeyCreateEncryptedData(key, algorithm, cfData, &error) {
return encryptedCFData as NSData as Data
}
if let err: Error = error?.takeRetainedValue() {
print("encryptData error \(err.localizedDescription)")
}
return nil
}
Decrypts the supplied data with the supplied key (which should be the private key):
func decryptData(data: Data, key: SecKey) -> Data? {
var error: Unmanaged<CFError>?
let cfData: CFData = data as NSData as CFData
guard SecKeyIsAlgorithmSupported(key, .decrypt, algorithm) else {
fatalError("Can't use this algorithm with this key!")
}
if let decryptedCFData = SecKeyCreateDecryptedData(key, algorithm, cfData, &error) {
return decryptedCFData as NSData as Data
} else {
if let err: Error = error?.takeRetainedValue() {
print("Error \(err.localizedDescription)")
}
return nil
}
}
Genreate a key - you should only need to do this one in a real world situation, then make sure the private key stays private:
func createKey(keySize: Int) -> (public: SecKey, private: SecKey)? {
var sanityCheck: OSStatus = 0
let publicKeyAttr:[CFString: Any] = [
kSecAttrIsPermanent : 0,
kSecAttrApplicationTag : "com.asymmetric.publickey".data(using: .ascii)!
]
let privateKeyAttr:[CFString: Any] = [
kSecAttrIsPermanent : 0,
kSecAttrApplicationTag : "com.asymmetric.privatekey".data(using: .ascii)!
]
let keyPairAttr:[CFString: Any] = [
kSecAttrKeyType : keyType,
kSecAttrKeySizeInBits : keySize,
kSecPrivateKeyAttrs : privateKeyAttr,
kSecPublicKeyAttrs : publicKeyAttr
]
var publicKey: SecKey? = nil
var privateKey: SecKey? = nil
sanityCheck = SecKeyGeneratePair(keyPairAttr as CFDictionary, &publicKey, &privateKey)
if sanityCheck == noErr {
return (publicKey!, privateKey!)
} else {
print("Fucked!")
return nil
}
}
Method that converts a SecKey to Data:
func secKey2data(key: SecKey) -> Data? {
var error:Unmanaged<CFError>?
guard let keyData = SecKeyCopyExternalRepresentation(key, &error) as Data? else { error?.release(); return nil }
//print("secKey2data size \(keyData.count)")
return keyData
}
Method that converts Data to a SecKey:
func data2secKey(keyData: Data, cfType: CFString) -> SecKey? {
var error:Unmanaged<CFError>?
let attrs: [CFString: Any] = [
kSecAttrKeyType: keyType,
kSecAttrKeyClass: cfType
]
let key = SecKeyCreateWithData(keyData as CFData, attrs as CFDictionary, &error)
if let err: Error = error?.takeRetainedValue() {
//let nsError: NSError = realErr
print("data2secKey ERR: \(err.localizedDescription)")
}
return key
}
}
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We donβt allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 6 years ago.
Improve this question
I want to see if is possible to generate a CSR (Certificate Signing Request) in iOS, and if there is a library for it. I want to generate a request, sign it with a private key from an extension, and then send the CSR request back to the server.
Is this possible, and is there a good library for it?
Thanks
Yes, it is possible but is not simple at all because iOS do not work with standard formats for keys as you could think
Generate CSR as PEM
I have used this library successfully to generate a CSR in PCKS#10 format with a key generated in KeyChain and encoded in DER format (binary).
https://github.com/ateska/ios-csr
func createCertificationRequest(keyId: String, C: String?, CN: String?, O:String?, SERIALNAME:String? ) -> String {
//Replace this with your mechanism to get the private and public key
let publicKey = loadKeyStringFromKeyChainAsNSData(PUBLIC_KEY + keyId)
let privateKey = loadKeySecKeyFromKeyChain(PRIVATE_KEY + keyId)
//SCCSR from ios-csr library
let sccsr : SCCSR = SCCSR()
sccsr.commonName = CN;
sccsr.organizationName = O;
sccsr.countryName = C;
// // aditional data you can set
// sccsr.countryName = #"";
// sccsr.organizationalUnitName = #"";
// sccsr.subjectDER = nil;
// //
//
let certificateRequest = sccsr.build(publicKey, privateKey: privateKey)
let certificateRequestB64 = certificateRequest.base64EncodedStringWithOptions(NSDataBase64EncodingOptions())
let certificateRequestPEM =
"-----BEGIN CERTIFICATE REQUEST-----\\n" + certificateRequestB64 + "\\n-----END CERTIFICATE REQUEST-----\\n"
return certificateRequestPEM
}
After this, you can send the CSR to server in DER (format) or encode in PEM format (base64) depending of the capabilities of your serv
I guess you are missed the final step, returning the X509 from server to device to be stored
EDITED
Generating keys with KeyChain
I include also the code to help generating keys using iOS-KeyChain
let KEY_SIZE = 2048
let PUBLIC_KEY = "mysystem.publickey."
let PRIVATE_KEY = "mysystem.privatekey."
// Generates a key pair in KeyChain using keyId as identifier and returns the public key
// publicKey -->PUBLIC_KEY + keyId
// privateKey --> PRIVATE_KEY + keyId
// KEY_SIZE is stablished globally
func generateKeyPairInKeyChain(keyId: String) -> String {
let privateAttributes = [String(kSecAttrIsPermanent): true,
String(kSecAttrApplicationTag): PRIVATE_KEY + keyId,
String(kSecAttrIsPermanent): kCFBooleanTrue]
let publicAttributes = [String(kSecAttrIsPermanent): true,
String(kSecAttrApplicationTag): PUBLIC_KEY + keyId,
String(kSecAttrIsPermanent): kCFBooleanTrue]
let pairAttributes = [String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecAttrKeySizeInBits): KEY_SIZE,
String(kSecPublicKeyAttrs): publicAttributes,
String(kSecPrivateKeyAttrs): privateAttributes]
//Ensure that keychain has no key with keyId identifier
deleteKeyPairFromKeyChain(keyId)
var publicRef: SecKeyRef?
var privateRef: SecKeyRef?
//Generate the keys and recover the references in publicRef and privateRf
switch SecKeyGeneratePair(pairAttributes, &publicRef, &privateRef) {
case noErr:
// Get the public key as a String
let publicKeyStr = loadKeyStringFromKeyChain(PUBLIC_KEY + keyId)
return publicKeyStr
default:
return ""
}
}
Utilities
The following includes the utility functions used generating CSR or keys. You will see that are basically the same changing the type of result (some extra work is needed to simplify...)
//Delete an existing keypair from keychain (public + private)
func deleteKeyPairFromKeyChain(keyId: String) {
deleteRSAKeyFromKeychain(PRIVATE_KEY + keyId)
deleteRSAKeyFromKeychain(PUBLIC_KEY + keyId)
}
// Delete existing RSA key from keychain
private func deleteRSAKeyFromKeychain(tagName: String) {
let queryFilter: [String: AnyObject] = [
String(kSecClass) : kSecClassKey,
String(kSecAttrKeyType) : kSecAttrKeyTypeRSA,
String(kSecAttrApplicationTag): tagName
]
let status: OSStatus = SecItemDelete(queryFilter)
NSLog("private or public deletion result is: " + status.description)
}
// Finds the SecKeyRef corresponding to the parameter key and returns it as a String
private func loadKeyStringFromKeyChain(key: String) -> String {
let query: Dictionary<String, AnyObject> = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecAttrKeySizeInBits): KEY_SIZE,
String(kSecClass): kSecClassKey,
String(kSecAttrApplicationTag): key,
kSecReturnData as String : kCFBooleanTrue ]
var dataTypeRef: AnyObject? = nil
var resultData: NSData? = nil
let status: OSStatus = withUnsafeMutablePointer(&dataTypeRef) { SecItemCopyMatching(query as NSDictionary, UnsafeMutablePointer($0)) }
NSLog("SecItemCopyMatching: " + status.description)
if status == errSecSuccess {
NSLog("private or public debug description is: " + dataTypeRef.debugDescription)
resultData = dataTypeRef as? NSData
let resultStr = resultData?.base64EncodedStringWithOptions([])
NSLog("private or public String is: " + resultStr!)
return resultStr!
} else {
NSLog("no key found!!!!")
return ""
}
}
// Finds the SecKeyRef corresponding to the parameter key and returns it
func loadKeySecKeyFromKeyChain(key: String) -> SecKeyRef {
let query: Dictionary<String, AnyObject> = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecAttrKeySizeInBits): KEY_SIZE,
String(kSecClass): kSecClassKey,
String(kSecAttrApplicationTag): key,
kSecReturnRef as String : kCFBooleanTrue ]
var dataTypeRef: Unmanaged<AnyObject>? = nil
var resultData: SecKeyRef? = nil
let status: OSStatus = withUnsafeMutablePointer(&dataTypeRef) { SecItemCopyMatching(query as NSDictionary, UnsafeMutablePointer($0)) }
NSLog("SecItemCopyMatching: " + status.description)
if status == errSecSuccess {
NSLog("private or public debug description is: " + dataTypeRef.debugDescription)
resultData = (dataTypeRef!.takeRetainedValue() as! SecKeyRef)
NSLog("SecItemCopyMatching returns SecKey: " + resultData.debugDescription)
return resultData!
} else {
return resultData!
}
}
// Finds the SecKeyRef corresponding to the parameter key and returns it as a NSData
private func loadKeyStringFromKeyChainAsNSData(key: String) -> NSData {
let query: Dictionary<String, AnyObject> = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecAttrKeySizeInBits): KEY_SIZE,
String(kSecClass): kSecClassKey,
String(kSecAttrApplicationTag): key,
kSecReturnData as String : kCFBooleanTrue ]
var dataTypeRef: AnyObject? = nil
var resultData: NSData? = nil
let status: OSStatus = withUnsafeMutablePointer(&dataTypeRef) { SecItemCopyMatching(query as NSDictionary, UnsafeMutablePointer($0)) }
NSLog("SecItemCopyMatching: " + status.description)
if status == errSecSuccess {
NSLog("private or public debug description is: " + dataTypeRef.debugDescription)
resultData = dataTypeRef as? NSData
return resultData!
} else {
NSLog("no key found!!!!")
return resultData!
}
}
EDITED
Export public key as DER
Note that this code will not provide the publickey in a readable format like DER
let publicKey = loadKeyStringFromKeyChainAsNSData(PUBLIC_KEY + keyId)
If you need to use the public key outside iOS (or importing a certificate and obtaining a valid key), extra actions are needed. Converting keys are supported using CryptoExportImportManager.swift of the following project
https://github.com/DigitalLeaves/CryptoExportImportManager
For example
func exportPublicKeyToDER(keyId:String) -> NSData?{
let publicKey = loadKeyStringFromKeyChainAsNSData(PUBLIC_KEY + keyId)
let keyType = kSecAttrKeyTypeRSA
let keySize = 2048
let exportImportManager = CryptoExportImportManager()
if let exportableDERKey = exportImportManager.exportPublicKeyToDER(publicKey, keyType: keyType as String, keySize: keySize) {
return exportableDERKey
} else {
return nil
}
}