16

I try to make my own SecKey with

exponent:
let exponent = "10001"

modulus: 
let modulus = "D6250B831F82EC984513922E797283E4D3879E1F0AD52364EBDA5A5696F6E75CDCE0704A993F3F95AA557A6882A525EC4B8344DA3E7DFDECCBACCEF18131E461D3C5D3D7E1334C6AE27E5CDEF8A577857542BCBEF6CF021B0EE5604534E6C6CBAEFA6EFFC1AB93DEE7CE51A8C8F2B7345680BDF840841C3A6F654CD1F10BA2FD5CA1C6E782A8FAEC79BD22FA12116D75FFAEDB2DEC151E0B60DB91F2E74BA78EFBB45DF739AF9CDD41C482DC22FC76E03C8E2141BDAE5406C0DA230E2C7EFFC68C8811E1544496332B03BCFF0F627A8DF51D2E2B32B0771D1C6F87AD56010DCB7A3862C63B88B2CF7D7AD40CC53AF0CFEC0820777C9CCE95A58848D67779AE8D"

as publicKey to encrypt a text in Swift. Can someone help me?

Here is my code:

import Foundation
import Security

class Encryption {

var publicKeyPtr, privateKeyPtr: Unmanaged<SecKey>?
var publicKey, privateKey: SecKey?
let parameters: [String:String] = [kSecAttrKeyType: kSecAttrKeyTypeRSA, kSecAttrKeySizeInBits: "2048"]

init(){

}

func genKey() {

    let status = SecKeyGeneratePair(parameters, &publicKeyPtr, &privateKeyPtr)
    publicKey = publicKeyPtr!.takeRetainedValue()
    privateKey = privateKeyPtr!.takeRetainedValue()

}


func encrypt(plainText: String, publicKey: SecKey) -> [UInt8]{
    let blockSize = SecKeyGetBlockSize(publicKey)
    let plainTextData = [UInt8](plainText.utf8)
    let plainTextDataLength = UInt(countElements( plainText))
    var encryptedData = [UInt8](count: Int(blockSize), repeatedValue: 0)
    var encryptedDataLength = blockSize
    let result = SecKeyEncrypt(publicKey, SecPadding(kSecPaddingPKCS1),
        plainTextData, plainTextDataLength, &encryptedData, &encryptedDataLength)

    return encryptedData
}


func decrypt(data: [UInt8]) -> String{
    let blockSize = SecKeyGetBlockSize(publicKey)
    var decryptedData = [UInt8](count: Int(blockSize), repeatedValue: 0)
    var decryptedDataLength = blockSize
    let result = SecKeyDecrypt(privateKey, SecPadding(kSecPaddingPKCS1),
        data, blockSize,
        &decryptedData, &decryptedDataLength)

    let decryptedText = String(bytes: decryptedData,
        encoding:NSUTF8StringEncoding)

    return decryptedText!
}

}

Neeku
  • 3,646
  • 8
  • 33
  • 43
Will Cai
  • 161
  • 1
  • 4

2 Answers2

16

For anyone still looking for an answer to this maybe I can help.

SecKeyCreateWithData

Since iOS 10 you can use SecKeyCreateWithData to create a SecKey from an external representation of that key.

The format of this representation should be the same as the one returned by SecKeyCopyExternalRepresentation. As discussed in the docs, that format is PCKS#1 for an RSA key.

So in order to create a SecKey from a given modulus and exponent, we need to obtain the PKCS#1 representation of that key.

Quoting from PKCS#1:

An RSA public key should be represented with the ASN.1 type
RSAPublicKey:

  RSAPublicKey ::= SEQUENCE {
      modulus           INTEGER,  -- n
      publicExponent    INTEGER   -- e
  }

The fields of type RSAPublicKey have the following meanings:

  • modulus is the RSA modulus n.
  • publicExponent is the RSA public exponent e.

This ASN.1 type needs to be expressed using DER to obtain the data that SecKeyCreateWithData expects.

Getting The Data In The Right Format

Below you I'll try to discuss how to get the desired data format in Swift. Please note that you can probably also do that using OpenSSL from the command line which might be easier if you just need to do it once.

A good overview of what needs to be done is given in this article by Ignacio Nieto Carvajal.

Basically, you need to encode both the modulus and exponent as DER INTEGER and then combine them in a DER SEQUENCE.

You can find some code on how to do that here and here. (Disclamer: I am one of the contributors of that library.)

I'll try to summarize it below.

Assume we have an RSA modulus and public exponent as byte arrays. Obtaining a byte array from different representations of the modulus and exponent shouldn't be too hard.

let exponent: [UInt8] = [
    1, 0, 1
]

var modulus: [UInt8] = [
    136, 0, 243, 196, 194, 126, 151, 243, 72, 84, 246, 234, 207, 215, 168, 5, 233, 212, 8, 37, 34, 52, 215, 217, 223, 183, 58, 129, 66, 112, 88, 71, 201, 71, 33, 156, 132, 7, 189, 234, 110, 6, 46, 189, 233, 206, 61, 128, 220, 138, 56, 49, 34, 159, 245, 208, 214, 49, 169, 58, 170, 68, 127, 93, 137, 99, 74, 54, 65, 109, 112, 33, 65, 169, 246, 176, 128, 121, 171, 35, 214, 236, 210, 123, 94, 146, 86, 30, 134, 135, 116, 124, 4, 55, 208, 163, 219, 220, 203, 249, 107, 69, 147, 169, 66, 214, 179, 195, 152, 211, 209, 78, 100, 114, 209, 203, 120, 16, 254, 24, 39, 143, 79, 49, 202, 10, 37, 2, 155, 162, 14, 253, 194, 205, 74, 116, 60, 205, 25, 53, 85, 144, 72, 11, 7, 133, 78, 149, 111, 0, 215, 174, 36, 104, 175, 62, 196, 197, 49, 78, 172, 146, 82, 216, 160, 45, 48, 212, 50, 168, 208, 255, 205, 82, 22, 11, 13, 156, 197, 42, 159, 26, 124, 237, 178, 131, 239, 186, 37, 96, 24, 154, 243, 202, 252, 87, 102, 23, 19, 29, 73, 130, 95, 45, 219, 104, 13, 54, 30, 165, 144, 223, 1, 14, 169, 100, 111, 246, 54, 185, 47, 156, 238, 249, 88, 33, 244, 135, 233, 102, 36, 86, 196, 143, 178, 176, 62, 24, 178, 209, 163, 244, 116, 236, 81, 177, 190, 205, 140, 230, 6, 113, 158, 105, 111, 123
]

We then need to make sure that the modulus is prefixed with 0x00 to indicate that it is a non-negative number.

modulus.insert(0x00, at: 0)

Now we encode the modulus and exponent as INTEGERs.

var modulusEncoded: [UInt8] = []
modulusEncoded.append(0x02)
modulusEncoded.append(contentsOf: lengthField(of: modulus))
modulusEncoded.append(contentsOf: modulus)

var exponentEncoded: [UInt8] = []
exponentEncoded.append(0x02)
exponentEncoded.append(contentsOf: lengthField(of: exponent))
exponentEncoded.append(contentsOf: exponent)

And combine these INTEGERs to a SEQUENCE.

var sequenceEncoded: [UInt8] = []
sequenceEncoded.append(0x30)
sequenceEncoded.append(contentsOf: lengthField(of: (modulusEncoded + exponentEncoded)))
sequenceEncoded.append(contentsOf: (modulusEncoded + exponentEncoded))

The following is a helper function to compute the length field of a DER type used above:

func lengthField(of valueField: [UInt8]) -> [UInt8] {
    var count = valueField.count

    if count < 128 {
        return [ UInt8(count) ]
    }

    // The number of bytes needed to encode count.
    let lengthBytesCount = Int((log2(Double(count)) / 8) + 1)

    // The first byte in the length field encoding the number of remaining bytes.
    let firstLengthFieldByte = UInt8(128 + lengthBytesCount)

    var lengthField: [UInt8] = []
    for _ in 0..<lengthBytesCount {
        // Take the last 8 bits of count.
        let lengthByte = UInt8(count & 0xff)
        // Add them to the length field.
        lengthField.insert(lengthByte, at: 0)
        // Delete the last 8 bits of count.
        count = count >> 8
    }

    // Include the first byte.
    lengthField.insert(firstLengthFieldByte, at: 0)

    return lengthField
}

Now we finally have the data we want.

let keyData = Data(bytes: sequenceEncoded)

You can use this data create a SecKey.

// RSA key size is the number of bits of the modulus.
let keySize = (modulus.count * 8)

let attributes: [String: Any] = [
    kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
    kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
    kSecAttrKeySizeInBits as String: keySize
]

let publicKey = SecKeyCreateWithData(keyData as CFData, attributes as CFDictionary, nil)

I hope this helps! Let me know if you need any more info.

More Resources

Community
  • 1
  • 1
dlggr
  • 741
  • 7
  • 15
  • **Wow**, amazing and very detailed answer! – janniks Jun 05 '18 at 14:42
  • The publicKey is returning nil on XCode 10.1, any idea ? – ffabri Nov 06 '18 at 12:48
  • Are you saying `SecKeyCreateWithData` returns nil? – dlggr Nov 06 '18 at 12:59
  • 1
    Please update your code. When creating the `sequenceEncoded` array, replace `modulus` for `modulusEncoded` and `expoent` for `expoentEncoded` referenced in the last 2 lines otherwise the `SecKeyCreateWithData` will return nil. – ffabri Nov 06 '18 at 16:01
  • @ffabri you are right! These need to be the encoded values. Thanks for pointing it out. I updated the answer. Does it work for you now? – dlggr Nov 08 '18 at 18:58
  • Great! Yes it's working nicely, thank you very much for the codes !! Now I'm just trying to create a Private Key to encrypt data using `SecKeyCreateEncryptedData` but no lucky yet. – ffabri Nov 09 '18 at 10:43
  • I'm trying to reverse engineer an old licence generator software. I got the modulus and decryption in place and I'm able to decrypt the licence to it's source. Now trying to recreate it back. – ffabri Nov 09 '18 at 15:24
  • I see. Do you want to create a SecKey from an existing private key whose modulus, exponent, and private exponent you have or do you want to generate a new private key to encrypt with? – dlggr Nov 09 '18 at 15:45
  • I only have the modulus and expoent that I'm able to decrypt. I want to encrypt it back to get the same encrypted data. – ffabri Nov 09 '18 at 23:22
  • To encrypt, can't you just use the public key? Since you are able to decrypt you already seem to have a working private key. – dlggr Nov 09 '18 at 23:29
  • It gives me a different encrypted data if I use the same publicKey. Just didn't figure out why. Trying to create a private key using SecKeyCreateWithData gives me an error -50 – ffabri Nov 10 '18 at 01:12
  • Are you using RSA? While textbook RSA is deterministic, practical RSA implementations apply [random padding](https://en.wikipedia.org/wiki/RSA_(cryptosystem)#Padding_schemes) to the plaintext before encrypting it. This is why RSA produces different ciphertexts for the same plaintext and key when being run multiple times. You can still decrypt the different ciphertexts with the same private key, though. See also [PKCS#1](https://tools.ietf.org/html/rfc8017) and [this answer](https://stackoverflow.com/questions/16325057/why-does-rsa-encrypted-text-give-me-different-results-for-the-same-text). – dlggr Nov 10 '18 at 09:29
  • Yes, RSA.. Just can't get decrypted text correctly with same encrypted key. With your code, can you please give me an example of encrypting and decrypting a simple text ? – ffabri Nov 12 '18 at 08:03
  • The code simply imports a _public key_ from an external representation. See Apple's article about [Using Keys for Encryption](https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/using_keys_for_encryption) for code samples on how to use keys. If that doesn't help, it's probably best to submit a new question with a [minimal, complete, and verifiable example](https://stackoverflow.com/help/mcve). – dlggr Nov 12 '18 at 09:40
  • Thanks a lot @dnlggr ! – ffabri Nov 13 '18 at 11:06
  • Just submited new [question] (https://stackoverflow.com/questions/53329910/rsa-on-swift-4-2-using-modulus-and-exponent-to-encrypt-and-decrypt-data-getting) – ffabri Nov 16 '18 at 00:51
  • I‘ll have a look once I can. ;-) – dlggr Nov 16 '18 at 20:33
  • This is terrific, @dnlggr — with this I'm able to take a JWK (which has a public key modulus and exponent) and convert it into a `SecKey` and then use `.rsaSignatureMessagePKCS1v15SHA256` to verify the signature of a JWT. – Ben Kreeger Jul 19 '19 at 22:53
  • @ffabri did you succes to create a privatekey with modulus an exponent ? I have the same problem of error-50 you had. – brice cesarin Oct 11 '19 at 08:23
  • @bricecesarin yes. give me few days to get some old codes to share with you. – ffabri Oct 13 '19 at 09:07
  • @ffabri I implemented the same but I am getting corrupted cipher at the end. Please have a look on my code https://docs.google.com/document/d/1hMdTygk3TxN1A8QkSf8y5noABiLBRKGvaoDmWf1c2rU/edit?usp=sharing – Ratnesh Singh Apr 22 '20 at 00:07
  • @dnlggr The SecKeyCreateWithData always returns nil for me when I am using custom modulus and exponent. Could you help with this please ? – Burhan Shakir Oct 27 '20 at 23:31
  • @BurhanShakir If your data is correctly formatted as PKCS #1 RSA key it will work. – dlggr Oct 28 '20 at 15:23
  • 1
    Here is some working code based on this answer, in case anyone finds it useful: https://gist.github.com/invariant/67c1d71b54b0d7e4b5c665c6e305dc64 – Nick Moore Nov 17 '20 at 08:11
0

This is how I create RSA Key Pair just in case someone need it.

//tuple type for public/private key pair at class level
typealias KeyPair = (publicKey: SecKey, privateKey: SecKey)

func generate() {


    // In your code block

    let publicKeyTag: String = "com.example.inc.public"
    let privateKeyTag: String = "com.example,inc.private"
    let keyPair = generateKeyPair(publicKeyTag, privateTag: privateKeyTag, keySize: 1024)
    var pbError:Unmanaged<CFError>?
    var prError:Unmanaged<CFError>?

    if #available(OSX 10.12, *) {
        guard let pbData = SecKeyCopyExternalRepresentation((keyPair?.publicKey)!, &pbError) as Data? else {
            print("error: ", pbError!.takeRetainedValue() as Error)
            return
        }

        guard let prData = SecKeyCopyExternalRepresentation((keyPair?.privateKey)!, &prError) as Data? else {
            print("private key error: ")
            return
        }

        print("Public: \(pbData.bytes)")
        print("Private: \(prData.bytes)")

        let strPublicKey = appendPrefixSuffixTo(pbData.base64EncodedString(options: .lineLength64Characters), prefix: "-----BEGIN RSA PUBLIC KEY-----\n", suffix: "\n-----END RSA PUBLIC KEY-----")
        print("public key: \n", strPublicKey)

        let strPrivateKey = appendPrefixSuffixTo(prData.base64EncodedString(options: .lineLength64Characters), prefix: "-----BEGIN RSA PRIVATE KEY-----\n", suffix: "\n-----END RSA PRIVATE KEY-----")
        print("private key: \n", strPrivateKey)


    } else {
        // Fallback on earlier versions
    }



}

func generateKeyPair(_ publicTag: String, privateTag: String, keySize: Int) -> KeyPair? {

    var publicKey, privateKey: SecKey?

    let publicKeyAttr: [NSObject: NSObject] = [
        kSecAttrIsPermanent:true as NSObject,
        kSecAttrApplicationTag:publicTag.data(using: String.Encoding.utf8)! as NSObject,
        kSecClass: kSecClassKey, // added this value
        kSecReturnData: kCFBooleanFalse] // added this value
    let privateKeyAttr: [NSObject: NSObject] = [
        kSecAttrIsPermanent:true as NSObject,
        kSecAttrApplicationTag:privateTag.data(using: String.Encoding.utf8)! as NSObject,
        kSecClass: kSecClassKey, // added this value
        kSecReturnData: kCFBooleanFalse] // added this value


    var keyPairAttr = [NSObject: Any]()
    keyPairAttr[kSecAttrType] = kSecAttrKeyTypeRSA
    keyPairAttr[kSecAttrKeySizeInBits] = 1024
    keyPairAttr[kSecReturnData] = true
    keyPairAttr[kSecPublicKeyAttrs] = publicKeyAttr as NSObject
    keyPairAttr[kSecPrivateKeyAttrs] = privateKeyAttr as NSObject


    if SecKeyGeneratePair(keyPairAttr as CFDictionary, &publicKey, &privateKey) == 0 {
        print("RSA key pair generation Successful")
        return KeyPair(publicKey: publicKey!, privateKey: privateKey!)
    }

    return nil

}




func appendPrefixSuffixTo(_ string: String, prefix: String, suffix: String) -> String {
    return "\(prefix)\(string)\(suffix)"
}
ffabri
  • 657
  • 11
  • 18