Skip to content

Instantly share code, notes, and snippets.

@dhoerl
Last active September 7, 2021 19:56
Show Gist options
  • Save dhoerl/f64a4cf3e6257ff27c41133fda97c824 to your computer and use it in GitHub Desktop.
Save dhoerl/f64a4cf3e6257ff27c41133fda97c824 to your computer and use it in GitHub Desktop.
Implement asymmetric cryptography for distributed app where keys can both be stored as strings
//
// Asymmetric.swift
//
// Created by David Hoerl on 12/23/18.
// Copyright © 2018 David Hoerl. All rights reserved.
//
/*
This is the portion you need in the distributed app, which uses the public key that can appear in plain text
*/
import Foundation
#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
struct Asymmetric {
// BGCnqt28i6sHMkERgMjkeb1LA8jZ5SLYXq+VBXWUVFInAl7INZZiwgspBgaY9TGTamcAo0mJ/sny
// x8Cy3njwV0l/XFyAYic8bUqTbbYlqzERIXOsh4q/RozPlE9x0MHmkA==
private static let publicStr = """
MIICCgKCAgEAqDJ9WzJabeHGp6xyqeTjSTwocp0so6MVun25f64Ty//ACeh249hlcUu9DRPfhkTE
qwXjvuPXvT+fDD8FCJvf+aJ2XmyDvpA2TTtixDa9O/w1edWRlVlGK/SOsebJBiIFM5qUzzB9pb7N
3JC5Hhx8FI4Bzi38hZuQAh1nPByVzHwqaaXekqmgFNzqqsPgkFtLi39bnv0UdHE043eWjgSZYZvn
uNusqZzJuC8qPDoDlF8ww4+yd5g407EjdPK8RIMKqmYyZB4i8GWCEtlYkMGS0P01XexZBq2yFzmg
mvUSViRmFGxctCM1+ZCBwaWy9b+uSMA8xanZ5kWkw7R+qI8bFc22GldB8CQRUNQnMHSU0zqO4Vk7
mvKzg25HH0VtiAeTiYP0kITitZHzbiQnaBDDWhgR7irKsWxdPvFzhy+hEiqMya5fO2DFZ/b7L+pl
00lYfbu2/e9zj8rEW9SbZqWT/MElISzj46m93IUq9z+56Xd2ySgDnsBxXYmYRPUSza0y9IOiJ7xa
lXFavQNoImNB4bXbF8GOerL1dZmzrCH0GVXKYXi8j2Gaxxo8GSauR9cUQC33Gv1HJL/sc13X4jLc
TNHK9r2/v+m6HEmG+IKibxhPXuGYJ4Foc+ORfwEqPCIEwwRY4MftVUb1nnAqy91az6f9O8GZMIjN
gN0mbnxaA00CAwEAAQ==
"""
static var publicKey: SecKey?
static func initialize() {
guard let key = Asymmetric.string2key(str: Asymmetric.publicStr) else { fatalError("FAILED TO CREATE PUBLIC KEY!") }
publicKey = key
}
private static func string2key(str: String) -> SecKey? {
guard
let data = Data(base64Encoded: str, options: [.ignoreUnknownCharacters]),
let key = data2secKey(keyData: data)
else { return nil }
return key
}
private static func data2secKey(keyData: Data) -> SecKey? {
var error:Unmanaged<CFError>?
let attrs: [CFString: Any] = [
kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecAttrKeyType: keyType,
//kSecAttrKeySizeInBits: keySize,
//kSecReturnPersistentRef: 1,
]
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
}
static func encryptData(data: Data) -> Data? {
guard let key = Asymmetric.publicKey else { fatalError("Impossible") }
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("Error \(err.localizedDescription)")
}
return nil
}
}
//
// AsymmetricTest.swift
//
// Created by David Hoerl on 12/23/18.
// Copyright © 2018 David Hoerl. All rights reserved.
//
/*
Apple explaines why to use these settings and why it works. In essense, Apple creates a Symmetric key
and embeds it in the payload, which is itself encrypted with the symmetric key.
See: https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/using_keys_for_encryption
Search for "In fact, the certificate, key, and trust services API provides a simple way to accomplish this."
https://stackoverflow.com/questions/53807792/how-to-implement-asymmetric-cryptography-for-distributed-app-where-keys-can-both
*/
#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 Asymmetric: NSObject {
// 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()
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 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
}
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
}
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
}
}
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
}
}
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
}
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
}
}
@dhoerl
Copy link
Author

dhoerl commented Dec 23, 2018

The AsymmetricTest.swift is a test and explore vehicle for public/private key encryption. The Asymmetric.swift file is what is needed in the distributed app, where the public key can be places in clear text.

See my post on StackOverflow for a deeper description.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment