Create SecKey from modulus and exponent in swift
Asked Answered
V

2

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!
}

}

Verb answered 25/11, 2014 at 17:24 Comment(3)
Have you had any luck with this? I'm trying to do the sameLazaretto
Have you done with your goal?Sheilasheilah
developer.apple.com/documentation/security/seckeyCalistacalisthenics
W
17

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

Wildawildcat answered 18/5, 2018 at 15:6 Comment(24)
Wow, amazing and very detailed answer!Piston
The publicKey is returning nil on XCode 10.1, any idea ?Yeo
Are you saying SecKeyCreateWithData returns nil?Wildawildcat
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.Yeo
@Yeo 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?Wildawildcat
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.Yeo
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.Yeo
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?Wildawildcat
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.Yeo
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.Wildawildcat
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 -50Yeo
Are you using RSA? While textbook RSA is deterministic, practical RSA implementations apply random padding 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 and this answer.Wildawildcat
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 ?Yeo
The code simply imports a public key from an external representation. See Apple's article about 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.Wildawildcat
Thanks a lot @dnlggr !Yeo
Just submited new [question] (#53330410)Yeo
I‘ll have a look once I can. ;-)Wildawildcat
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.Marucci
@Yeo did you succes to create a privatekey with modulus an exponent ? I have the same problem of error-50 you had.Dabney
@bricecesarin yes. give me few days to get some old codes to share with you.Yeo
@Yeo I implemented the same but I am getting corrupted cipher at the end. Please have a look on my code docs.google.com/document/d/…Payne
@dnlggr The SecKeyCreateWithData always returns nil for me when I am using custom modulus and exponent. Could you help with this please ?Drub
@BurhanShakir If your data is correctly formatted as PKCS #1 RSA key it will work.Wildawildcat
Here is some working code based on this answer, in case anyone finds it useful: gist.github.com/invariant/67c1d71b54b0d7e4b5c665c6e305dc64Solomonsolon
Y
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)"
}
Yeo answered 23/4, 2020 at 1:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.