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