iOS verify digital signature - ios

In my application, I have a public key (represented as string), plain message and digital signature, represented as base64 encoded string, hashed with SHA256 and encrypted with RSA).
Now, I need to verify digital signature. I was trying to do as follows:
create SecKeyRef from NSString (taken from here)
create SHA256 digest from original message
verify signature using SecKeyRawVerify function
(I am trying to avoid using OpenSSL function)
Additionally, my digital signature was created using Java's SHA256withRSA method. I was reading here that SHA256WithRSA appends algorithm identifier with the actual hash. Now, I am not sure whether or not I need to append it to the hash.
Anyway, in both cases I get error -50, which according to Apple's documentations means One
or more parameters passed to a function were not valid.
Here is my code:
-(BOOL) verifySignature:(NSString*) rawData andKey:(NSString*) key andSignature:(NSString*)signature {
NSData* originalData = [rawData dataUsingEncoding:NSUTF8StringEncoding];
NSData *signatureData = [NSData dataFromBase64String:signature];
SecKeyRef publicKey = [self generatePublicKey:key];
uint8_t sha2HashDigest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256([originalData bytes], [originalData length], sha2HashDigest);
//DO I NEED THIS?
NSString *algIdentifier = #"1.3.14.3.2.26";
NSData *algData = [algIdentifier dataUsingEncoding:NSUTF8StringEncoding];
NSData* d_hash = [NSData dataWithBytes:sha2HashDigest length:CC_SHA256_DIGEST_LENGTH];
NSMutableData *concatenatedData = [NSMutableData data];
[concatenatedData appendData:algData];
[concatenatedData appendData:d_hash];
OSStatus verficationResult = SecKeyRawVerify (publicKey,
kSecPaddingPKCS1SHA256,
(const uint8_t *)[d_hash bytes],
(size_t)[d_hash length],
(const uint8_t *)[signatureData bytes],
(size_t)[signatureData length]
);
CFRelease(publicKey);
if (verficationResult == errSecSuccess){
NSLog(#"Verified");
return YES;
}
return NO;
}
- (SecKeyRef)generatePublicKey:(NSString *)key
{
// This will be base64 encoded, decode it.
NSData *d_key = [NSData dataFromBase64String:key];
d_key = [self stripPublicKeyHeader:d_key];
if (d_key == nil) return(nil);
NSData *d_tag = [NSData dataWithBytes:[#"pubKey" UTF8String] length:[#"pubKey" length]];
NSMutableDictionary *publicKey = [[NSMutableDictionary alloc] init];
[publicKey setObject:(id) kSecClassKey forKey:(id)kSecClass];
[publicKey setObject:(id) kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
[publicKey setObject:d_tag forKey:(id)kSecAttrApplicationTag];
SecItemDelete((CFDictionaryRef)publicKey);
CFTypeRef persistKey = nil;
// Add persistent version of the key to system keychain
[publicKey setObject:d_key forKey:(id)kSecValueData];
[publicKey setObject:(id) kSecAttrKeyClassPublic forKey:(id)
kSecAttrKeyClass];
[publicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)
kSecReturnPersistentRef];
OSStatus secStatus = SecItemAdd((CFDictionaryRef)publicKey, &persistKey);
if (persistKey != nil) CFRelease(persistKey);
if ((secStatus != noErr) && (secStatus != errSecDuplicateItem)) {
[publicKey release];
return(nil);
}
// Now fetch the SecKeyRef version of the key
SecKeyRef keyRef = nil;
[publicKey removeObjectForKey:(id)kSecValueData];
[publicKey removeObjectForKey:(id)kSecReturnPersistentRef];
[publicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnRef
];
[publicKey setObject:(id) kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
secStatus = SecItemCopyMatching((CFDictionaryRef)publicKey,
(CFTypeRef *)&keyRef);
[publicKey release];
return keyRef;
}

Maybe this answer is a little late but I had the same problem.
It turns out that Java handles the hashing for you, but iOS does not.
So if you have a plaintext called plainText you might generate a signature on it in Java doing this:
public static byte[] sign(PrivateKey key, byte[] plainText) {
try {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(key);
signature.update(plainText);
return signature.sign();
} catch (Exception e) {
return null;
}
}
But then to verify it in iOS you need to manually take a hash of the plaintext like so:
+ (BOOL)verifySignature:(uint8_t*)signature signatureLen:(size_t)sLen
withPlainText:(uint8_t*)plainText plainTextLen:(size_t)pLen
andKey:(SecKeyRef)key {
uint8_t hash[32];
CC_SHA256(plainText, pLen, hash);
OSStatus returnCode = SecKeyRawVerify(key,
kSecPaddingPKCS1SHA256,
hash,
32,
signature,
sLen);
return returnCode == 0;
}
In the above method, signature is the bytes generated by the Java method.
Of course, you may not want to hardcode parameters such as the the hash function used (and length of hash).

Related

Convert SecKeyRef to EC_KEY in iOS

I'm creating CSR from openSSL but due to OpenSSL isn't storing keys in secure enclave so I chooses objective C to create key pair (private key and public key) in secure enclave and send to OpenSSL for X509 certificate. I get successfully public key in NSData and then convert const unsigned char * bitsOfKeyDataPublicKey = (unsigned char *) [publicKey bytes]; and then create public key EC_KEY*_ec_keyPublic = d2i_EC_PUBKEY(NULL,&bitsOfKeyDataPublicKey, publicKeyLegnth);. But For Private key we get SecKeyRef from objective c so for creation EC_Key how can we convert private key or is this any way to convert or use private key ?
Looking for response.
Thanks
You can change private key from SecKeyRef to NSData
Example:
- (NSData *)getPrivateKeyBits {
OSStatus sanityCheck = noErr;
NSData * privateKeyBits = nil;
NSMutableDictionary * queryPrivateKey = [[NSMutableDictionary alloc] init];
// Set the public key query dictionary.
[queryPrivateKey setObject:(id)kSecClassKey forKey:(id)kSecClass];
[queryPrivateKey setObject:_privateTag forKey:(id)kSecAttrApplicationTag];
[queryPrivateKey setObject:(id)kSecAttrKeyTypeEC forKey:(id)kSecAttrKeyType];
[queryPrivateKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnData];
// Get the key bits.
sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef)queryPrivateKey, (void *)&privateKeyBits);
if (sanityCheck != noErr) {
privateKeyBits = nil;
}
else if (sanityCheck == errSecItemNotFound) {
privateKeyBits = nil;
}
return privateKeyBits;
}
Don't forget to use the _privateTag used for generating private key
now you can use:
const unsigned char *bitsOfKeyDataPrivateKey = (unsigned char *) [[self getPrivateKeyBits] bytes];
EC_KEY *_ec_keyPrivate = d2i_EC_PUBKEY(NULL,&bitsOfKeyDataPrivateKey, privateKeyLegnth);

RSA encryption using SecKeyEncrypt gives error -4 (errSecUnimplemented)

I'm trying to encrypt some data with RSA using the Security framework on iOS. I want to encrypt a simple base64-encoded string as follows:
NSData *data = [[NSData alloc] initWithBase64EncodedString:#"aGFsbG8=" options:0x0];
NSData *encrypted = [pair encrypt:data];
The pair variable holds references to a private key and a public key that were succesfully generated before using SecKeyGeneratePair.
The encrypt function looks like this:
- (NSData *)encrypt:(NSData *)data {
void *buffer = malloc([self blockSize] * sizeof(uint8_t));
memset(buffer, 0x0, [self blockSize]);
size_t ciphertextBufferLength = [data length];
OSStatus res = SecKeyEncrypt([self keyRef], 0x1, [data bytes], [data length], &buffer[0], &ciphertextBufferLength);
NSLog(#"Result of encryption: %d", res);
return [NSData dataWithBytesNoCopy:buffer length:[self blockSize] freeWhenDone:YES];
}
The implementation of [self blockSize] is rather straightforward:
- (unsigned long)blockSize {
return SecKeyGetBlockSize(keyRef);
}
I generate my keys with the following function:
- (BOOL)generateNewKeyPairOfSize:(unsigned int)keySize
{
SecKeyRef privKey = [[self publicKey] keyRef];
SecKeyRef pubKey = [[self publicKey] keyRef];
NSDictionary *privateKeyDict = #{ (__bridge id)kSecAttrIsPermanent : #(YES), (__bridge id)kSecAttrApplicationTag : [[self privateKey] tag] };
NSDictionary *publicKeyDict = #{ (__bridge id)kSecAttrIsPermanent : #(YES), (__bridge id)kSecAttrApplicationTag : [[self publicKey] tag] };
NSDictionary *keyDict = #{ (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeRSA, (__bridge id)kSecAttrKeySizeInBits : #(keySize), (__bridge id)kSecPublicKeyAttrs : publicKeyDict, (__bridge id)kSecPrivateKeyAttrs : privateKeyDict };
OSStatus res = SecKeyGeneratePair((__bridge CFDictionaryRef)keyDict, &privKey, &pubKey);
NSLog(#"Result of generating keys: %d", res);
[[self publicKey] setKeyRef:pubKey];
[[self privateKey] setKeyRef:privKey];
return YES;
}
The problem is that the value of res is -4, meaning errSecUnimplemented according to the documentation. I'm not sure what I'm doing wrong here since I have all parameters required. I'm not sure whether there is a mistake in the parameters and where. The call the [self blockSize] return 128.
Can anyone help me with this?
From documentation:
cipherTextLen - On entry, the size of the buffer provided in the
cipherText parameter. On return, the amount of data actually placed in
the buffer.
You don't set any value to ciphertextBufferLength.
Update #1
In SecKeyGeneratePair() you have wrong arguments: public key argument must be first, private key is the second. I think that is the reason why you have error code -4.
Update #2
When you fix problem from Update #1 you will see error code -50 (errSecParam) because your cipher text length is wrong. Here is how correct encryption/decryption looks like:
[self generateNewKeyPairOfSize:1024];
NSData *data = [[NSData alloc] initWithBase64EncodedString:#"aGFsbG8=" options:0x0];
size_t cipherTextSize = [self blockSize];
uint8_t *cipherText = malloc(cipherTextSize);
memset(cipherText, 0, cipherTextSize);
OSStatus res = SecKeyEncrypt(_publicKey, kSecPaddingPKCS1, [data bytes], data.length, cipherText, &cipherTextSize);
NSLog(#"Result of encryption: %d", res);
size_t plainTextSize = cipherTextSize;
uint8_t *plainText = malloc(plainTextSize);
res = SecKeyDecrypt(_privateKey, kSecPaddingPKCS1, cipherText, cipherTextSize, plainText, &plainTextSize);
NSLog(#"Result of decryption: %d", res);
In addition to the correct answer above, what solved it for me was the following knowledge:
You will get -4 if you are trying to encrypt anything using a SecKeyRef that refers to a private key.
Think about it. Nothing that was encrypted with a private key would be secure because the public key is, well, public. /facepalm
So yeah, Apple's framework does the responsible thing, and simply blocks you from encrypting something with a private key. Because if it allowed you to do something that stupid, then it would be giving you a false sense of security, which would be irresponsible.

2 level RSA Encryption

I have requirement in the app to do 2 level RSA encryption with 2 different public key.
1) Encrypt plainText with first public key.
2) Encrypt output of first with second public key.
I am using iOS Security framework to do the same. The first level of encryption works fine but when I try to encrypt again the output of step one with second public key the sanity check fails returning -50.
I assume this is due to the reason that buffer size is less for the required text to encrypt. But I am not sure what parameter to change to achieve the same. I tried changing padding from kSecPaddingPKCS1 to other types but it is not giving the required output.
Following is my function for the encryption:
+ (NSData*)getRSAEncryptedText:(NSString*)plaintext withPublicKeyIdSuffix:(NSString*)idSuffix {
SecKeyRef publicKey = NULL;
NSString *publicKeyIdentifier = [NSString stringWithFormat:#"%#.%#",[[NSBundle mainBundle] bundleIdentifier], idSuffix];
NSData * publicTag = [publicKeyIdentifier dataUsingEncoding:NSUTF8StringEncoding];
NSMutableDictionary *queryPublicKey = [[NSMutableDictionary alloc] init];
[queryPublicKey setObject:kSecClassKey forKey:kSecClass];
[queryPublicKey setObject:publicTag forKey:kSecAttrApplicationTag];
[queryPublicKey setObject:kSecAttrKeyTypeRSA forKey:kSecAttrKeyType];
[queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:kSecReturnRef];
SecItemCopyMatching((CFDictionaryRef)queryPublicKey, (CFTypeRef *)&publicKey);
if (!publicKey)
{
if(publicKey) CFRelease(publicKey);
return nil;
}
size_t cipherBufferSize = SecKeyGetBlockSize(publicKey);
SecPadding kTypeOfWrapPadding = kSecPaddingPKCS1;
// SecPadding kTypeOfWrapPadding = kSecPaddingNone;
uint8_t* cipherBuffer = malloc( cipherBufferSize * sizeof(uint8_t) );
memset((void *)cipherBuffer, 0x0, cipherBufferSize);
OSStatus sanityCheck = noErr;
// Encrypt using the public key.
sanityCheck = SecKeyEncrypt(publicKey,
kTypeOfWrapPadding,
(const uint8_t *)[[plaintext dataUsingEncoding:NSUTF8StringEncoding] bytes],
[[plaintext dataUsingEncoding:NSUTF8StringEncoding] length],
cipherBuffer,
&cipherBufferSize
);
if (sanityCheck != noErr) {
//NSLog(#"error with encryption");
free(cipherBuffer);
return nil;
}
NSData *encryptedData = [NSData dataWithBytes:cipherBuffer length:cipherBufferSize];
return encryptedData;
}
Please suggest how to achieve the second level of encryption.
The issue is resolved now, have altered the order in which keys were used for encryption. The one with smaller size is used first now for encryption and then the second one.

Decrypting string saved in keychain

in my app i store a NSString encrypted in the keychain with this method
NSUInteger fieldHash = [myStringToSave hash];
// Encrypt
NSString *fieldString = [KeychainWrapper securedSHA256DigestHashForPIN:fieldHash];
// Save in Keychain
if ([KeychainWrapper createKeychainValue:fieldString forIdentifier:PASSWORD]) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:PASSWORD];
[[NSUserDefaults standardUserDefaults] synchronize];
in the KeychainWrapper.m there is this method
+ (BOOL)createKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier
{
NSMutableDictionary *dictionary = [self setupSearchDirectoryForIdentifier:identifier];
NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding];
[dictionary setObject:valueData forKey:(__bridge id)kSecValueData];
// Protect the keychain entry so it's only valid when the device is unlocked.
[dictionary setObject:(__bridge id)kSecAttrAccessibleWhenUnlocked forKey:(__bridge id)kSecAttrAccessible];
// Add.
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)dictionary, NULL);
// If the addition was successful, return. Otherwise, attempt to update existing key or quit (return NO).
if (status == errSecSuccess) {
return YES;
} else if (status == errSecDuplicateItem){
return [self updateKeychainValue:value forIdentifier:identifier];
} else {
return NO;
}
}
and this
+ (NSString *)securedSHA256DigestHashForPIN:(NSUInteger)pinHash
{
// 1
NSString *name = [[NSUserDefaults standardUserDefaults] stringForKey:USERNAME];
name = [name stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
// 2
NSString *computedHashString = [NSString stringWithFormat:#"%#%i%#", name, pinHash, SALT_HASH];
// 3
NSString *finalHash = [self computeSHA256DigestForString:computedHashString];
//NSLog(#"** Computed hash: %# for SHA256 Digest: %#", computedHashString, finalHash);
return finalHash;
}
+ (NSString*)computeSHA256DigestForString:(NSString*)input
{
const char *cstr = [input cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:input.length];
uint8_t digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(data.bytes, data.length, digest);
// Setup our Objective-C output.
NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
// Parse through the CC_SHA256 results (stored inside of digest[]).
for(int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
[output appendFormat:#"%02x", digest[i]];
}
return output;
}
In order to get a value stored in the keychain i'm using this
+ (NSString *)keychainStringFromMatchingIdentifier:(NSString *)identifier
{
NSData *valueData = [self searchKeychainCopyMatchingIdentifier:identifier];
if (valueData) {
NSString *value = [[NSString alloc] initWithData:valueData
encoding:NSUTF8StringEncoding];
return value;
} else {
return nil;
}
}
passing the identifier PASSWORD like this
NSString *myNewString = [KeychainWrapper keychainStringFromMatchingIdentifier:PASSWORD];
The problem is that it come out a string encrypted which i cannot use. Any advice on how to decrypt it?
Thanks in advance
Nothing in the above code encrypts anything. You appear to be running your value through NSString hash and then SHA-256. Both of these are one-way hashes. By design, they cannot be reversed.
In general, this code is very confusing, and it's not clear what you're trying to achieve. You don't usually encrypt data that you're putting into keychain.
Note that your hash function will truncate multi-byte strings (Chinese, for example).
const char *cstr = [input cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:input.length];
This should be:
NSData *data = [input dataUsingEncoding:NSUTF8StringEncoding];
This code is from one of tutorials on Ray Wenderlich's website. See: http://www.raywenderlich.com/6475/basic-security-in-ios-5-tutorial-part-1 for more information
The basic idea is that you encrypt the password before you save it in the keychain, using the methods:
(NSString *)securedSHA256DigestHashForPIN:(NSUInteger)pinHash;
(NSString*)computeSHA256DigestForString:(NSString*)input;
Then when a user enters a password, you can encrypt it the same way and compare the two values to see if the user has entered the correct password. As the other answers says you cannot reverse the encryption to find the password, but you do not need to if you are just comparing the stored password with the entered password.
I think encrypting the password on the way into the keychain maybe overkill as the password should be encrypted and secure anyway, but cant see any harm in doing it anyway (unless you need to retrieve the original password).
If you are looking to encrypt and decrypt a string then check out this question:
AES Encryption for an NSString on the iPhone

Are there any known incompatibilities for SecKeyRawVerify with iOS 4.3 and iOS 5.0

I am trying to verify a data with a public key, using the exact method mentioned on http://blog.flirble.org/2011/01/05/rsa-public-key-openssl-ios/.
I tested my code with simulator iOS 6.1, iOS 4.3 and iOS 5.0. It is working on iOS 6.1 but it is not working on iOS 4.3 and iOS 5.0. On iOS 4.3 and iOS 5.0, the SecKeyRawVerify fails with error code -50(one of the input params is wrong).
Any one having any idea what is wrong here?
Below is the code of the verify function which I am using. Please see the definition of the functions used here
Code:
+ (SecKeyRef)getPublicKeyRef:(NSString*)key
{
NSString* tag = #"com.publickey";
NSString *s_key = [NSString string];
NSArray *a_key = [key componentsSeparatedByString:#"\n"];
BOOL f_key = FALSE;
for (NSString *a_line in a_key) {
if ([a_line isEqualToString:#"-----BEGIN PUBLIC KEY-----"]) {
f_key = TRUE;
}
else if ([a_line isEqualToString:#"-----END PUBLIC KEY-----"]) {
f_key = FALSE;
}
else if (f_key) {
s_key = [s_key stringByAppendingString:a_line];
}
}
if (s_key.length == 0) return(FALSE);
// This will be base64 encoded, decode it.
NSData *d_key = [Base64 decode:s_key];//[NSData dataFromBase64String:s_key];
d_key = [CryptoUtil stripPublicKeyHeader:d_key];
if (d_key == nil) return(FALSE);
NSData *d_tag = [NSData dataWithBytes:[tag UTF8String] length:[tag length]];
// Delete any old lingering key with the same tag
NSMutableDictionary *publicKey = [[NSMutableDictionary alloc] init];
[publicKey setObject:(id) kSecClassKey forKey:(id)kSecClass];
[publicKey setObject:(id) kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
[publicKey setObject:d_tag forKey:(id)kSecAttrApplicationTag];
SecItemDelete((CFDictionaryRef)publicKey);
CFTypeRef persistKey = nil;
// Add persistent version of the key to system keychain
[publicKey setObject:d_key forKey:(id)kSecValueData];
[publicKey setObject:(id) kSecAttrKeyClassPublic forKey:(id)kSecAttrKeyClass];
[publicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnPersistentRef];
OSStatus secStatus = SecItemAdd((CFDictionaryRef)publicKey, &persistKey);
if (persistKey != nil) CFRelease(persistKey);
if ((secStatus != noErr) && (secStatus != errSecDuplicateItem)) {
[publicKey release];
return(FALSE);
}
// Now fetch the SecKeyRef version of the key
SecKeyRef keyRef = nil;
[publicKey removeObjectForKey:(id)kSecValueData];
[publicKey removeObjectForKey:(id)kSecReturnPersistentRef];
[publicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnRef];
[publicKey setObject:(id) kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
secStatus = SecItemCopyMatching((CFDictionaryRef)publicKey,(CFTypeRef *)&keyRef);
[publicKey release];
if (keyRef == nil) return(FALSE);
return keyRef;
}
+ (BOOL)verifyMessage:(NSString *)msg forSignature:(NSString*)signature forPublicKey:(NSString*)publicKey
{
// Search for the two sections: Data and a signature.
NSString *s_data = msg, *s_signature = signature;
if ((s_data.length == 0) || (s_signature.length == 0)) return(FALSE);
// These will be base64 encoded, decode them.
NSData *d_data = [s_data dataUsingEncoding:NSUTF8StringEncoding];
if (d_data == nil) return(FALSE);
NSData *d_signature = [Base64 decode:s_signature];
if (d_signature == nil) return(FALSE);
// Make SHA-256 hash of the data
uint8_t h_data[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(d_data.bytes, d_data.length, h_data);
NSData* d_hash = [NSData dataWithBytes:h_data length:CC_SHA256_DIGEST_LENGTH];
// The signature is generated against the binary form of the data, validate
//it.
BOOL valid = FALSE;
OSStatus secStatus = SecKeyRawVerify([CryptoUtil getPublicKeyRef:publicKey],
kSecPaddingPKCS1SHA256,
d_hash.bytes, d_hash.length,
d_signature.bytes,
d_signature.length);
if (secStatus == errSecSuccess) {
valid = TRUE;
}
return(valid);
}
Error code:
The SecKeyRawVerify fails with error code -50(one of the input params is wrong) on iOS 5.0 and iOS 4.3, but the call succeeds on iOS 6.1.
Thanks in advance.
Found the reason why it was not working, kSecPaddingPKCS1SHA256 is supported iOS 6.0 above only.
http://developer.apple.com/library/ios/#releasenotes/General/iOS60APIDiffs/index.html

Resources