Compute Diffie-Hellman key pair and shared secret in iOS with Swift - ios

I need on iOS with Swift to:
Generate a Diffie–Hellman key pair with a pre-agreed modulus P and a base G
Compute a shared secret with a local private key, a remote public key and pre-agreed modulus P
And this without Elliptic-curve Diffie–Hellman (ECDH).
I wrote this code using BigInt repository.
/// Diffie–Hellman key pair
struct DHKeyPair {
var privateKey: Data
var publicKey: Data
init(privateKey: Data, publicKey: Data) {
self.privateKey = privateKey
self.publicKey = publicKey
}
}
/// Generate a Diffie–Hellman key pair
///
/// - Parameter modulus: Diffie-Hellman modulus P
/// - Parameter base: Diffie-Hellman base G
/// - Returns: DH key pair, or nil on error
static func generateDhKeyPair(modulus: Data, base: Data) -> DHKeyPair? {
// Modulus
let modulusBigUInt = BigUInt(modulus)
// Base
let baseBigUInt = BigUInt(base)
// Generate random private key
guard let privateKey = Crypto.randomBytes(length: 512) else {
return nil
}
let privateKeyBigUInt = BigUInt(privateKey)
// Compute public key
let publicKeyBigUInt = baseBigUInt.power(privateKeyBigUInt, modulus: modulusBigUInt)
var publicKey = publicKeyBigUInt.serialize()
// Set Diffie-Hellman key pair
let dhKeypair = DHKeyPair(privateKey: privateKey, publicKey: publicKey)
return dhKeypair
}
/// Compute a shared secret based on local pair key (public/private) and remote public key
///
/// - Parameter privateKey: Private key
/// - Parameter remotePublicKey: Remote public key
/// - Parameter modulus: Diffie-Hellman modulus P
/// - Returns: the computed shared secret
static func computeSharedSecret(privateKey: Data, remotePublicKey: Data, modulus: Data) -> Data {
// Private key
let privateKeyBigUInt = BigUInt(privateKey)
// Remote public key
let remotePublicKeyBigUInt = BigUInt(remotePublicKey)
// Modulus
let modulusBigUInt = BigUInt(modulus)
// Compute shared secret
let sharedSecretBigUInt = remotePublicKeyBigUInt.power(privateKeyBigUInt, modulus: modulusBigUInt)
var sharedSecret = sharedSecretBigUInt.serialize()
return sharedSecret
}
It's well working but it takes too long, about 50 seconds to generate the key pair, and the same to compute the shared secret. This is because of the BigUInt modular exponentiation function (see doc).
I couldn't find anything for these operations in the Keys documentation of the API for Swift.
So how can I do these Diffie-Hellman operations faster? Can it be done also with Swift API?
PS: we've the same function on the JDK which takes just a few seconds.
EDIT 1:
These 2 operations take about 4 seconds with a build in release, which is acceptable. But the execution time is still too long in debug (on a real device or a simulator), and also the unit tests are commented because the execution time is too long when running the unit tests.

Related

How can I create a Public Key from a PEM File (String) in Swift?

I'm pretty new to RSA Keys and Certificates and all of that. So I'm getting a PEM File from a network request, I understand the PEM file is basically the certificate without header and footer, so essentially I have a string. I need to create a PublicKey from that in order to Encrypt some text, but I think I'm doing something wrong. This is my code so far, I have talked to other teams (Android) and when they print out the Encrypted Data, they are getting a regular String and I'm getting lots of weird characters. Thanks in advance guys! (:
func encryptBase64(text: String, certificateStr: String) throws -> Data {
let encryptionError = EncrpytionError(message: "There has been an issue with encryption")
guard
let certData = Data(base64Encoded: certificateStr),
let certificate = SecCertificateCreateWithData(nil, certData as CFData),
let publicKey = SecCertificateCopyKey(certificate)
else {
throw encryptionError
}
let algorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA256AESGCM
guard SecKeyIsAlgorithmSupported(publicKey, .encrypt, algorithm) else {
throw encryptionError
}
guard let cipherText = SecKeyCreateEncryptedData(
publicKey,
algorithm,
text.data(using: .utf8)! as CFData, nil)
else {
throw encryptionError
}
return cipherText as Data
}
And when I try to print cipherText as a String, I get this weird thing:
"D�aj�\\m꒤h,�A�{��8�~�\nY\u0003F�Cˤ�#��\"�\u0018�\u0007\u001fX#VC�U_��E\u0005dž1���X��\/4Px��P\u0016�8}% ��<��#�I_�K\u000e�\bR*�� ���斋�7,�%���F^q�\u0000\\�'�ZTD\u0013Q�_\u0010\u001f>i]&��B���#1\u0006\b��E\u0004�F���yS\u0013�3����SB)��m\u0017%��5ʲ����s\u0003��r�&�?�8b��W#\u001e��؞ۡ��8�s~��ӹ�u\"�2��U�&\b�3XV���˔Y��xt[\fm&P:\\�\f� y��6jy"
Android team is doing something like this on Kotlin:
private fun extractPublicKey(certificateString: String): PublicKey {
val encodedCertificateBytes = certificateString.toByteArray()
val decodedCertificateBytes = Base64.decode(encodedCertificateBytes, Base64.DEFAULT)
val inStream = decodedCertificateBytes.inputStream()
val certificateFactory = CertificateFactory.getInstance("X.509")
val certificate = certificateFactory.generateCertificate(inStream)
inStream.close()
return certificate.publicKey
}

How to Create random key in secure enclave in Xamarin.iOS

I'm trying to create an RSA key for signing data in my Xamarin.iOS app on a physical device. Below is my code
using (var access = new SecAccessControl(SecAccessible.WhenUnlockedThisDeviceOnly, SecAccessControlCreateFlags.BiometryCurrentSet|SecAccessControlCreateFlags.PrivateKeyUsage))
{
var keyParameters = new SecKeyGenerationParameters
{
KeyType = SecKeyType.RSA,
KeySizeInBits = 2048,
Label = AppInfo.PackageName,
TokenID = SecTokenID.SecureEnclave,
PrivateKeyAttrs = new SecKeyParameters
{
IsPermanent = true,
ApplicationTag = NSData.FromString(AppInfo.PackageName, NSStringEncoding.UTF8),
AccessControl = access,
CanSign = true,
CanVerify = true
}
};
var privateKey = SecKey.CreateRandomKey(keyParameters.Dictionary, out NSError nsError);
var publicKey = privateKey.GetPublicKey();
NSData keyData = publicKey.GetExternalRepresentation();
}
the above code is giving below NSError and null private key
{The operation couldn’t be completed. (OSStatus error -50 - Key generation failed, error -50)}.
The above code is working fine without SecAccessControlCreateFlags.PrivateKeyUsage and TokenID = SecTokenID.SecureEnclave
Please let me know how to resolve this.
The Secure Enclave does not support RSA keys. It can only create elliptic curve keys with curve P256 (aka NIST P-256 or secp256r1). If you want to use RSA, you're left with the default Security implementation (without the Secure Enclave flag).
If you insist on using the secure enclave, you'll have to use the above mentioned curve.

Chilkat iOS10+ RSA compatibility issue

I'm using native RSA in one iOS app and Chilkat RSA library in another. On a native iOS side I encrypt (OAEP SHA256) data with the following function:
static func encryptWithKey(_ data: Data, rsaKey: SecKey) -> Data? {
let algorithm = SecKeyAlgorithm.rsaEncryptionOAEPSHA256
guard SecKeyIsAlgorithmSupported(rsaKey, .encrypt, algorithm) else { return nil }
var error: Unmanaged<CFError>?
let encryptedData = SecKeyCreateEncryptedData(rsaKey, algorithm, data as CFData, &error)
if let encryptionError = error {
print(encryptionError.takeRetainedValue())
}
return encryptedData as Data?
}
and then in another app, I decrypt this data with Chilkat library:
static func decrypt(base64 encryptedText: String, with xmlKey: String) -> Data? {
let privateKey = CkoPrivateKey()
guard let success = privateKey?.loadXml(xmlKey), success else { return nil }
guard let rsa = CkoRsa() else { return nil }
rsa.unlockComponent(Chilkat.key)
rsa.oaepPadding = true
rsa.littleEndian = false
rsa.oaepHash = "sha256"
rsa.encodingMode = "base64"
rsa.importPrivateKeyObj(privateKey)
if let decryptedData = rsa.decryptBytesENC(encryptedText, bUsePrivateKey: true) {
return decryptedData
} else {
print(rsa.lastErrorText)
}
return nil
}
Even I used one private / public keys pair I'm getting an error (from rsa.lastErrorText):
"ChilkatLog:
DecryptBytesENC(7ms):
DllDate: Feb 1 2018
ChilkatVersion: 9.5.0.72
UnlockPrefix: XXXXXXXXXXXX
Architecture: Little Endian; 64-bit
Language: IOS C/C++/Swift/Objective-C
VerboseLogging: 1
usePrivateKey: 1
Component successfully unlocked using purchased unlock code.
rsaDecryptBytes(7ms):
rsa_decrypt(7ms):
KeyType: Private
InputSize: 256
Padding: OAEP
OaepHashAlg: SHA-256
MgfHashAlg: SHA-1
ParamLen: 0
ModulusBitLen: 2048
inlen: 256
modulus_bytelen: 256
modulus_bitlen: 2048
bigEndian: 0
Byte swapping from big-endian to little-endian
padding: OAEP
No leading zero byte for OAEP decoding.
OAEP decoding failed.
--rsa_decrypt
--rsaDecryptBytes
Failed.
--DecryptBytesENC
--ChilkatLog"
Any ideas?
I added some extra logging so we can see the bytes (in hex) after the RSA decrypt but before OAEP unpadding. After RSA decrypt (but before OAEP unpadding), we should see bytes in this format:
0x00 || maskedseed || maskedDB
In other words, the 1st byte of the result SHOULD be a 0 byte. If the RSA private key used to decrypt did not correspond to the public key used to encrypt, then we would see random bytes. (i.e. the 1st byte would not be 0). The maskedseed and maskedDB will still appear as random bytes.
The problem was in OAEP Mgf hash function. If you set oaepMgfHash = "sha256" it becomes compatible with native iOS RSA implementation. In addition, Chilkat library provides an automatic fallback to a proper oaepMgfHash function in case you've specified it incorrectly. Thanks, Matt!

Encrypt RSA/ECB/OAEPWithSHA-256AndMGF1Padding Swift

I am going to say in advance i don't know too much about cryptography (Basics only). I am trying to Implement a Credential OpenHome Service and I want to encrypt a password to send it to the device.
The device provides a function written in C that returns a public key String that looks like that:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCzjFGuEKD0uWxzb47oRbiSP2uDwVJPeWU7m9VXi626V6lameTzdtwj2eYVZTIAsAW7yW4or2skn7oHqFG4GvhMzgMwoQjKFxeCPPFXRSotnt26AN1DhvFJp3V/d+MpmkzI07iWcD5eNe4EVNK9GSE4JOEHhJ/JYBVMiu04XE5aqwIDAQAB
The Android implementation has been already done and the specs given are
RSA/ECB/OAEPWithSHA-256AndMGF1Padding
also there is a web site that gives "instructions" when encrypting
http://wiki.openhome.org/wiki/Av:Developer:CredentialsService
I have tried so far these libraries:
SwiftyRSA, Heimdall, SwCrypt
I really thing that one of my main failures are I don't understand what I have, what do I need and finally how to achieve it using swift.
ideally at the end i will have a functions like
func encryptMessage(message:String, whithPublicKey key:String)->String
thank you very much.
After a long research i have just implemented my own solution rather than using libraries and not understanding what was going on. It is always good to know what happens and it this case it is not rocket science.
On iOS if you want to encrypt/decrypt you need to use a key stored on the keychain. If, in my case, i have been given the public key i can import it and also I can do the same with the private key. Please see my HelperClass Here.
Then, and from only from iOS 10, you can call this 2 methods for encrypting and decrypting
let error:UnsafeMutablePointer<Unmanaged<CFError>?>? = nil
let plainData = "A Plain text...".data(using: .utf8)
if let encryptedMessageData:Data = SecKeyCreateEncryptedData(publicSecKey, .rsaEncryptionOAEPSHA256, plainData! as CFData,error) as Data?{
print("We have an encrypted message")
let encryptedMessageSigned = encryptedMessageData.map { Int8(bitPattern: $0) }
print(encryptedMessageSigned)
if let decryptedMessage:Data = SecKeyCreateDecryptedData(privateSecKey, .rsaEncryptionOAEPSHA256, encryptedMessageData as CFData,error) as Data?{
print("We have an decrypted message \(String.init(data: decryptedMessage, encoding: .utf8)!)")
}
else{
print("Error decrypting")
}
}
else{
print("Error encrypting")
}
Also, if you want before iOS 10 you have the functions:
func SecKeyEncrypt(_ key: SecKey,
_ padding: SecPadding,
_ plainText: UnsafePointer<UInt8>,
_ plainTextLen: Int,
_ cipherText: UnsafeMutablePointer<UInt8>,
_ cipherTextLen: UnsafeMutablePointer<Int>) -> OSStatus
And
func SecKeyDecrypt(_ key: SecKey,
_ padding: SecPadding,
_ cipherText: UnsafePointer<UInt8>,
_ cipherTextLen: Int,
_ plainText: UnsafeMutablePointer<UInt8>,
_ plainTextLen: UnsafeMutablePointer<Int>) -> OSStatus
But these give less options and They are quite resticted.
Worth mentioning that my public and private key where generate on android using
public static String createStringFromPublicKey(Key key) throws Exception {
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(key.getEncoded());
return new String(Base64.encode(x509EncodedKeySpec.getEncoded(), Base64.NO_WRAP), "UTF-8");
}
and
public static String createStringFromPrivateKey(Key key) throws Exception {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(key.getEncoded());
return new String(Base64.encode(pkcs8EncodedKeySpec.getEncoded(), Base64.NO_WRAP), "UTF-8");
}

How to construct Data/NSData from UnsafeMutablePointer<T>

Background: I am using OpenSSL library to create PKCS12 file in Swift. I have the certificate and private key stored in Keychain.
Problem: I am able to create a PKCS12 file and store it in the app bundle successfully. When I want to use the PKCS12 file (e.g. Receive a auth challenge from a HTTPS server), I can load the file by using SecPKCS12Import() function provided by Apple. Now, instead of generating a physical file, I want to generate the PKCS12 object in flight whenever I need it. It will be stored in memory. Since I am new to Swift, I am seeking help for converting from UnsafeMutablePointer to Data.
You will understand more when you read my following code:
Previously, I have my createP12 function implemented as:
createP12(pemCert: String, pemPK: String) {
// .......
// Code to load certificate and private key Object..
guard let p12 = PKCS12_create(passPhrase, name, privateKey, certificate, nil, NID_pbe_WithSHA1And3_Key_TripleDES_CBC, NID_pbe_WithSHA1And3_Key_TripleDES_CBC, 0, 0, 0) else {
ERR_print_errors_fp(stderr)
return
}
// Save p12 to file
let fileManager = FileManager.default
let tempDirectory = NSTemporaryDirectory() as NSString
let path = tempDirectory.appendingPathComponent("ssl.p12")
fileManager.createFile(atPath: path, contents: nil, attributes: nil)
guard let fileHandle = FileHandle(forWritingAtPath: path) else {
LogUtils.logError("Cannot open file handle: \(path)")
return
}
let p12File = fdopen(fileHandle.fileDescriptor, "w")
i2d_PKCS12_fp(p12File, p12)
fclose(p12File)
fileHandle.closeFile()
}
Then when I want to read the p12 file, I can call
let p12Data = NSData(contentsOfFile: Bundle.main.path(forResource: mainBundleResource, ofType:resourceType)!)! as Data
var items: CFArray?
let certOptions: NSDictionary = [kSecImportExportPassphrase as NSString: passwordStr as NSString]
self.securityError = SecPKCS12Import(p12Data as NSData, certOptions, &items)
// Code to read attributes
From createP12() function, I firstly got a p12 object in type of UnsafeMutablePointer<PKCS12> then store it in the file. Instead, now I want to pass p12 directly to the pkcs12 reader function. To do so, I have to firstly convert p12 object into a Data/NSData object since that's what required by SecPKCS12Import() function.
So, long story short, how can I construct a Data/NSData object from the p12 object which is in type of UnsafaMutablePointer<PKCS12> thus I can pass it into SecPKCS12Import()?
This should work (it compiles but I could not test it). The idea is to write the PKCS12 object to a memory buffer and then create Data
from the buffer:
func p12ToData(p12: UnsafeMutablePointer<PKCS12>) -> Data {
// Write PKCS12 to memory buffer:
let mbio = BIO_new(BIO_s_mem())
i2d_PKCS12_bio(mbio, p12)
// Get pointer to memory buffer and number of bytes. The
// # define BIO_get_mem_data(b,pp) BIO_ctrl(b,BIO_CTRL_INFO,0,(char *)pp)
// macro is not imported to Swift.
var ptr = UnsafeRawPointer(bitPattern: 1)!
let cnt = BIO_ctrl(mbio, BIO_CTRL_INFO, 1, &ptr)
// Create data from pointer and count:
let data = Data(bytes: ptr, count: cnt)
// Release memory buffer:
BIO_free(mbio)
return data
}

Resources