Balraj Singh
Balraj Singh

Reputation: 3471

iOS Elliptical curve key used for encryption decryption with Java Backend

I am a iOS developer and trying to use Secure enclave to generate ECC pair key. I am able to do that successfully using the sample app here: https://github.com/agens-no/EllipticCurveKeyPair. When I use this key along with a Python implementation to do encryption and decryption mentioned here: https://gist.github.com/dschuetz/2ff54d738041fc888613f925a7708a06 it works.

The problem is that i need a Java code to do the same. Can anyone help me to achieve this or point me to a code that does the same job of as Python code is doing.

On iOS side I am doing eciesEncryptionStandardX963SHA256AESGCM encrypt and decrypt logic.

I know i should have tried to solve this myself. But I am a iOS Engineer and trying my hands on Java backend. Would be really helpful if someone can guide me.


Created a sample Java code based on the answer. Link to code: https://gist.github.com/balrajOla/fa2f6030538b20a396c086377a6f7114

Using the sample iOS App provided here: https://github.com/agens-no/EllipticCurveKeyPair. I generated ECC keys. Then pass the public key to the Java code to create an encrypted message. This encrypted messages is passed back to sample iOS app mentioned above to be decrypted using eciesEncryptionStandardX963SHA256AESGCM algo. But we get an error mentioned below snapshot. enter image description here

Upvotes: 2

Views: 1663

Answers (2)

Minki
Minki

Reputation: 994

We had the same problem. We want to bring a EC key exchange working from iOS secure enclave with a Java Back-end.

After three days of trial&error, we finally found a Java implementation which is working.

Java code was taken from https://github.com/O2-Czech-Republic/BC-ECIES-for-iOS

And the iOS code, using eciesEncryptionCofactorVariableIVX963SHA256AESGCM algorithm:

  static func getExportableKeyFromECKey() -> String? {
        // If exists already a created EC Key, then export the public part
        if let privateKey = self.loadECPrivateKey() {
            if let publicKey = self.getECPublicKey(privateKey) {
                if self.algorithmAcceptedForEC(publicKey) {
                    var error: Unmanaged<CFError>?
                    // Get Public key External represenatation
                    guard let cfdata = SecKeyCopyExternalRepresentation(publicKey, &error) else {
                        return nil
                    }
                    let pubKeyData: Data = cfdata as Data
                    return pubKeyData.base64EncodedString()
                }
            }
        }
            // If no EC Key created, then first create one
        else {
            var error: Unmanaged<CFError>?
            let tag = Config.skName.data(using: .utf8) ?? Data()
            let attributes: [String: Any] = [kSecClass as String: kSecClassKey,
                                             kSecAttrKeyType as String: Config.skType,
                                             kSecAttrKeySizeInBits as String: Config.ecKeySize,
                                             kSecPrivateKeyAttrs as String: [ kSecAttrIsPermanent as String: true,
                                                                              kSecAttrApplicationTag as String: tag]]
            do {
                // Create Private Key
                guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
                    throw error!.takeRetainedValue() as Error
                }
                // Get Public Key
                guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
                    throw error!.takeRetainedValue() as Error
                }
                // Get Public key External represenatation
                guard let cfdata = SecKeyCopyExternalRepresentation(publicKey, &error) else {
                    throw error!.takeRetainedValue() as Error
                }
                let pubKeyData: Data = cfdata as Data
                return pubKeyData.base64EncodedString()
            } catch {
                print(error)
            }
        }
        return nil
    }

    static func loadECPrivateKey() -> SecKey? {
        let tag = Config.skName.data(using: .utf8)!
        let query: [String: Any] = [kSecClass as String: kSecClassKey,
                                    kSecAttrApplicationTag as String: tag,
                                    kSecAttrKeyType as String: Config.skType,
                                    kSecReturnRef as String: true]
        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)
        guard status == errSecSuccess else {
            return nil
        }
        print("LOAD PRIVATE KEY: \n \(item as! SecKey) \n")
        return (item as! SecKey)
    }

    static func getECPublicKey(_ privateKey: SecKey) -> SecKey? {
        guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
            // Can't get public key
            return nil
        }
        return publicKey
    }

    static func algorithmAcceptedForEC(_ publicKey: SecKey) -> Bool {
        guard SecKeyIsAlgorithmSupported(publicKey, .encrypt, Config.ecAlgorithm) else {
            // Algorith not supported
            print("\nEncrytion Algorithm not supported!!!\n")
            return false
        }
        return true
    }

    /// if let encryptedData = Data(base64Encoded: "BOqw779hxsGLMEV7X81Mphcx+SMtxSQs388s5CydkvJ4V2XuuWoyp48GCmgDMBnYlEIRqAdHxIc/Ts3ATxa9ENCDGdIZf5CjpWsOIVXYxLvupdap4w==", options:.ignoreUnknownCharacters) 
    static func decryptStr(_ encData: Data) {
        /// 1. Step: Get the Private Key and decrypt the symmetric key
        let privateKey = loadECPrivateKey()
        guard SecKeyIsAlgorithmSupported(privateKey!, .decrypt, Config.ecAlgorithm) else {
            print("Can't decrypt\nAlgorithm not supported")
            return
        }
        DispatchQueue.global().async {
            var error: Unmanaged<CFError>?
            let clearTextData = SecKeyCreateDecryptedData(privateKey!,
                                                          Config.ecAlgorithm,
                                                          encData as CFData,
                                                          &error) as Data?
            DispatchQueue.main.async {
                guard clearTextData != nil else {
                    print("Can't decrypt")
                    return
                }
                let clearText = String(decoding: clearTextData!, as: UTF8.self)
                print("Decrypted Info: \(clearText)")
                // clearText is our decrypted string
            }
        }
    }

Upvotes: 2

Tiago Peres
Tiago Peres

Reputation: 15602

In Java you have two interesting classes - ECGenParameterSpec and KeyPairGenerator. ECGenParameterSpec specifies parameters for generating elliptic curve domain parameters, and KeyPairGenerator is used to generate pairs of public and private keys.

In the book Android Security Internals by Nokilay Elenkov there's a good code example of their combination to generate the key pair.

KeyPairGenerator kpg = KeyPairGenerator.getInstance("ECDH");
ECGenParameterSpec ecParamSpec = new ECGenParameterSpec("secp256r1");
kpg.initialize(ecParamSpec);
KeyPair keyPair = kpg.generateKeyPair();

This is the explanation given about the previous code

There are two ways to initialize a KeyPairGenerator: by specifying the desired key size and by specifying algorithm-specific parameters. In both cases, you can optionally pass a SecureRandom instance to be used for key generation. If only a key size is specified, key generation will use default parameters (if any). To specify additional parameters, you must instantiate and configure an AlgorithmParameterSpec instance appropriate for the asymmetric algorithm you are using and pass it to the initialize() method, as shown in Example 5-15. In this example, the ECGenParameterSpec initialized in line 2 is an AlgorithmParameterSpec that allows you to specify the curve name used when generating Elliptic Curve (EC) cryptography keys. After it is passed to the initialize() method in line 3, the subsequent generateKeyPair() call in line 4 will use the specified curve (secp256r1) to generate the key pair.

Upvotes: 0

Related Questions