Same vendorID for different iPads

Hi Team,


We are facing an issue while testing an app in different iPads registered with the same Apple IDs. Both the iPads running on iOS 13 and above.

The issue is both the devices returning the same "identifierForVendor" value when requested from the UIDevice API.


Below is our code:


@import UIKit;

@implementation DeviceUID

+ (NSString *)uid {
    return [[[DeviceUID alloc] initWithKey:@"deviceUID"] uid];
}

- (id)initWithKey:(NSString *)key {
    self = [super init];
    if (self) {
        _uidKey = key;
        _uid = nil;
    }
    return self;
}

/*! Returns the Device UID.
    The UID is obtained in a chain of fallbacks:
      - Keychain
      - NSUserDefaults
      - Apple IFV (Identifier for Vendor)
      - Generate a random UUID if everything else is unavailable
    At last, the UID is persisted if needed to.
*/
- (NSString *)uid {
    if (!_uid) _uid = [[self class] valueForKeychainKey:_uidKey service:_uidKey];
    if (!_uid) _uid = [[self class] valueForUserDefaultsKey:_uidKey];
    if (!_uid) _uid = [[self class] appleIFV];
    if (!_uid) _uid = [[self class] randomUUID];
    [self save];
    return _uid;
}

/*! Persist UID to NSUserDefaults and Keychain, if not yet saved
*/
- (void)save {
  if (![DeviceUID valueForUserDefaultsKey:_uidKey]) {
    [DeviceUID setValue:_uid forUserDefaultsKey:_uidKey];
  }
  if (![DeviceUID valueForKeychainKey:_uidKey service:_uidKey]) {
    [DeviceUID setValue:_uid forKeychainKey:_uidKey inService:_uidKey];
  }
}

/*! Create as generic NSDictionary to be used to query and update Keychain items.
*  param1
*  param2
*/
+ (NSMutableDictionary *)keychainItemForKey:(NSString *)key service:(NSString *)service {
    NSMutableDictionary *keychainItem = [[NSMutableDictionary alloc] init];
    keychainItem[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
    keychainItem[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAlways;
    keychainItem[(__bridge id)kSecAttrAccount] = key;
    keychainItem[(__bridge id)kSecAttrService] = service;
    return keychainItem;
}

/*! Sets
*  param1
*  param2
*/
+ (OSStatus)setValue:(NSString *)value forKeychainKey:(NSString *)key inService:(NSString *)service {
    NSMutableDictionary *keychainItem = [[self class] keychainItemForKey:key service:service];
    keychainItem[(__bridge id)kSecValueData] = [value dataUsingEncoding:NSUTF8StringEncoding];
    return SecItemAdd((__bridge CFDictionaryRef)keychainItem, NULL);
}

+ (NSString *)valueForKeychainKey:(NSString *)key service:(NSString *)service {
    OSStatus status;
    NSMutableDictionary *keychainItem = [[self class] keychainItemForKey:key service:service];
    keychainItem[(__bridge id)kSecReturnData] = (__bridge id)kCFBooleanTrue;
    keychainItem[(__bridge id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
    CFDictionaryRef result = nil;
    status = SecItemCopyMatching((__bridge CFDictionaryRef)keychainItem, (CFTypeRef *)&result);
    if (status != noErr) {
        return nil;
    }
    NSDictionary *resultDict = (__bridge_transfer NSDictionary *)result;
    NSData *data = resultDict[(__bridge id)kSecValueData];
    if (!data) {
        return nil;
    }
    return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}

@end


+ (NSString *)appleIFV {
    if(NSClassFromString(@"UIDevice") && [UIDevice instancesRespondToSelector:@selector(identifierForVendor)]) {
        // only available in iOS >= 6.0
        return [[UIDevice currentDevice].identifierForVendor UUIDString];
    }
    return nil;
}


Usage:


#import "DeviceUID.h";

NSString *deviceID = [DeviceUID uid];


This "deviceID" returned from the DeviceUID class is always the same for different devices registered with the same Apple IDs and running on iOS 13.


The DeviceID is saved in Keychain and UserDefaults once it is obtained from the UIDevice's "identifierForVendor" property. If it is not available from the property, then a random UUID is generated and saved. For subsequent installations of the app in the same device, the code checks for the DeviceID in Keychain and uses it if it is found.


According to the Docs, the identifierForVendor should return new value irrespective of the Apple ID used in the device. But somehow the same value is being returned for the devices all the time.


The keychain created doesn't provide the attribute "

kSecAttrSynchronizable", hence they keychains should not get synced across devices using same Apple IDs.


Could you please provide clarification to us why the same value is returned in this scenario?

Replies

Could you show what

identifierForVendor you get on your different iPad ?


------------------------

Here what the doc says:


Declaration

@property(nonatomic, readonly, strong) NSUUID *identifierForVendor;

Discussion


The value of this property is the same for apps that come from the same vendor running on the same device. A different value is returned for apps on the same device that come from different vendors, and for apps on different devices regardless of vendor.

Normally, the vendor is determined by data provided by the App Store. If the app was not installed from the app store (such as enterprise apps and apps still in development), then a vendor identifier is calculated based on the app’s bundle ID. The bundle ID is assumed to be in reverse-DNS format.

  • On iOS 6, the first two components of the bundle ID are used to generate the vendor ID. if the bundle ID only has a single component, then the entire bundle ID is used.
  • On IOS 7, all components of the bundle except for the last component are used to generate the vendor ID. If the bundle ID only has a single component, then the entire bundle ID is used.

Table 1 shows a collection of bundle IDs and which portions of the bundle ID the system uses to calculate the vendor ID.

Table 1

Example bundle identifiers

Bundle ID

iOS 6.x

iOS 7.x

com.example.app1

com.example.app1

com.example.app1

com.example.app2

com.example.app2

com.example.app2

com.example.app.app1

com.example.app.app1

com.example.app.app1

com.example.app.app2

com.example.app.app2

com.example.app.app2

example

example

example

For example,

com.example.app1
and
com.example.app2
would appear to have the same vendor ID.

If the value is

nil
, wait and get the value again later. This happens, for example, after the device has been restarted but before the user has unlocked the device.

The value in this property remains the same while the app (or another app from the same vendor) is installed on the iOS device. The value changes when the user deletes all of that vendor’s apps from the device and subsequently reinstalls one or more of them. The value can also change when installing test builds using Xcode or when installing an app on a device using ad-hoc distribution. Therefore, if your app stores the value of this property anywhere, you should gracefully handle situations where the identifier changes.

Hi,


I understand how the "identifierForVendor" is calculated for development builds and App Store builds. Ours' is a production application which was installed from App Store. The issue here is the keychain being synced even though explicitly not specified to sync the keychain data for the particular keychain.


Can you please let us know if you have found a solution?

Hi,
Is there any update on this ? I have similar issue just now, getting the same identifierForVendor on different iPhones (both are running on iOS 13.4.1) which has different AppleID and disabled keychain sync.