How to find out the modulus and exponent of RSA Public Key on iPhone/Objective C
Busted my head on this, but here's a solution I've found (without using any external packages).
First, go to Apple's CryptoExercise example. Download the "SecKeyWrapper" class from there. The interesting function in that class is getPublicKeyBits.
Link to the example: http://developer.apple.com/library/ios/#samplecode/CryptoExercise/Introduction/Intro.html
The bits you will receive is a DER encoded (wiki it) public key containing both the modulus and exp. Here's a code that will decode it for you, pretty easy:
- (NSData *)getPublicKeyExp
{
NSData* pk = [self getPublicKeyBits];
if (pk == NULL) return NULL;
int iterator = 0;
iterator++; // TYPE - bit stream - mod + exp
[self derEncodingGetSizeFrom:pk at:&iterator]; // Total size
iterator++; // TYPE - bit stream mod
int mod_size = [self derEncodingGetSizeFrom:pk at:&iterator];
iterator += mod_size;
iterator++; // TYPE - bit stream exp
int exp_size = [self derEncodingGetSizeFrom:pk at:&iterator];
return [pk subdataWithRange:NSMakeRange(iterator, exp_size)];
}
- (NSData *)getPublicKeyMod
{
NSData* pk = [self getPublicKeyBits];
if (pk == NULL) return NULL;
int iterator = 0;
iterator++; // TYPE - bit stream - mod + exp
[self derEncodingGetSizeFrom:pk at:&iterator]; // Total size
iterator++; // TYPE - bit stream mod
int mod_size = [self derEncodingGetSizeFrom:pk at:&iterator];
return [pk subdataWithRange:NSMakeRange(iterator, mod_size)];
}
- (int)derEncodingGetSizeFrom:(NSData*)buf at:(int*)iterator
{
const uint8_t* data = [buf bytes];
int itr = *iterator;
int num_bytes = 1;
int ret = 0;
if (data[itr] > 0x80) {
num_bytes = data[itr] - 0x80;
itr++;
}
for (int i = 0 ; i < num_bytes; i++) ret = (ret * 0x100) + data[itr + i];
*iterator = itr + num_bytes;
return ret;
}
iOS: RSA Encrypt using public key (with modulus and exponent)
Wow, no wonder it's so hard to find an answer to this. I spent 2 days down the crypto-rabbit hole, and it's not pretty.
The easy way
Use Chilkat iOS RSA Library. One major downside: cost $189! :O
The hard way
Parse the XML, use SCZ-BasicEncodingRules-iOS to generate a public key data out of the modulus and exponent. If that works, create a dummy keychain using that public key (follow sample code here), extract the public key now in SecKeyRef
format and pass it to the encryptRSA
method in the question. Finally, cleanup, remove the dummy keychain. Sounds good in theory, but I have never tested this thoroughly, if you do, let me know!
Swift 3 export SecKey to String
Export Key (iOS 10 only)
var error:Unmanaged<CFError>?
if let cfdata = SecKeyCopyExternalRepresentation(publicKey!, &error) {
let data:Data = cfdata as Data
let b64Key = data.base64EncodedString()
}
See https://stackoverflow.com/a/30662270/5276890 and https://stackoverflow.com/a/27935528/5276890 for longer ways which probably support iOS < 10.
Reimport Key
guard let data2 = Data.init(base64Encoded: b64Key) else {
return
}
let keyDict:[NSObject:NSObject] = [
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecAttrKeySizeInBits: NSNumber(value: 512),
kSecReturnPersistentRef: true as NSObject
]
guard let publicKey = SecKeyCreateWithData(data2 as CFData, keyDict as CFDictionary, nil) else {
return
}
Note: This generates a base64 key and not a certificate. A lot of code samples online deal with how to generate a public key from a certificate using SecCertificateCreateWithData
Also: 512 bit is fast to generate but worthless. Pick a longer and secure value once you're satisfied with the results.
I got valid results back when importing the key I generated and exported, so I assume it works, but I did not try to encrypt and decrypt with it.
iOS SecKeyRef to NSData
- use of undeclared identifier 'publicTag'
The publicTag
is just some unique identifier added to the Keychain items. In the CryptoExercise sample project it is defined as
#define kPublicKeyTag "com.apple.sample.publickey"
static const uint8_t publicKeyIdentifier[] = kPublicKeyTag;
NSData *publicTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:sizeof(publicKeyIdentifier)];
- Cast of an indirect pointer to an Objective-C pointer to 'CFTypeRef ' (aka 'const void *') is disallowed with ARC
This can be solved by using a temporary CFTypeRef
variable:
CFTypeRef result;
sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef)queryPublicKey, &result);
if (sanityCheck == errSecSuccess) {
publicKeyBits = CFBridgingRelease(result);
}
- I do not want to build a query or whatnot to extract the key from the keychain. I have it in a variable and I wish to extract it from there ...
As far as I know, you have to store the SecKeyRef to the Keychain temporarily. SecItemAdd
has the option to return the added item as data. From the documentation:
To obtain the data of the added item as an object of type
CFDataRef
,
specify the return type keykSecReturnData
with a value of
kCFBooleanTrue
.
Putting all that together, the following code should do what you want:
- (NSData *)getPublicKeyBitsFromKey:(SecKeyRef)givenKey {
static const uint8_t publicKeyIdentifier[] = "com.your.company.publickey";
NSData *publicTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:sizeof(publicKeyIdentifier)];
OSStatus sanityCheck = noErr;
NSData * publicKeyBits = nil;
NSMutableDictionary * queryPublicKey = [[NSMutableDictionary alloc] init];
[queryPublicKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
[queryPublicKey setObject:publicTag forKey:(__bridge id)kSecAttrApplicationTag];
[queryPublicKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
// Temporarily add key to the Keychain, return as data:
NSMutableDictionary * attributes = [queryPublicKey mutableCopy];
[attributes setObject:(__bridge id)givenKey forKey:(__bridge id)kSecValueRef];
[attributes setObject:@YES forKey:(__bridge id)kSecReturnData];
CFTypeRef result;
sanityCheck = SecItemAdd((__bridge CFDictionaryRef) attributes, &result);
if (sanityCheck == errSecSuccess) {
publicKeyBits = CFBridgingRelease(result);
// Remove from Keychain again:
(void)SecItemDelete((__bridge CFDictionaryRef) queryPublicKey);
}
return publicKeyBits;
}
I hope that this works, I cannot test it at the moment.
- Follow-up: How can I export a private key to NSData, since I've read several time that the function I'm trying to work with only works for public keys.
I don't know.
Related Topics
Set<Nsobject>' Does Not Have a Member Named 'Anyobject." - Xcode 6.3
Itms-90809: Deprecated API Usage - Existing App That Use Uiwebview Are No Longer Accepted
How to Use Multiple Segues with One Uitableviewdelegate
Static VS Class as Class Variable/Method (Swift)
Draggesture on a VStack with Lot of Buttons, How to Detect When the Drag Is Inside a Button
Multiple Uicollectionview in One Controller
How to Truncate Decimals to X Places in Swift
Convert Unicode (E.G. 1F564) to Emoji in Swift
Codesign Returned Errsecinternalcomponent in High Sierra
How to Programmatically Get iOS's Alphanumeric Version String
Libz.Dylib Versus Libz.1.2.3.Dylib Versus Libz.1.2.5.Dylib
Rotate Sprite by Touch with a Limited Rotation Speed
Dyld: Library Not Loaded for a Framework Within a Framework
Usage of String.Range in Swift 3.0