Skip to main content

Overview

SAMKeychain provides a simple migration path from other keychain wrapper libraries and older versions. This guide covers common migration scenarios and best practices.

Migrating from Other Libraries

From EMKeychain

SAMKeychain was originally inspired by EMKeychain (now discontinued). The API is similar but with some improvements:
// EMKeychain syntax
[EMKeychain setKeychainPassword:password 
                     forService:service 
                        account:account];

NSString *password = [EMKeychain keychainPasswordForService:service 
                                                     account:account];
The main difference is simplified method names. Your existing keychain data remains accessible - just update the method calls.

From SDKeychain

SDKeychain is another discontinued library that SAMKeychain replaces:
// SDKeychain syntax
[SDKeychain storePassword:password 
               forService:service 
                  account:account];

NSString *password = [SDKeychain passwordForService:service 
                                            account:account];

From Raw Security Framework

If you’re migrating from direct Security.framework usage, SAMKeychain dramatically simplifies your code:
// Direct Security framework - verbose and error-prone
NSDictionary *query = @{
    (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
    (__bridge id)kSecAttrService: service,
    (__bridge id)kSecAttrAccount: account,
    (__bridge id)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding],
    (__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlocked
};

OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
if (status == errSecDuplicateItem) {
    // Update existing item
    NSDictionary *attributesToUpdate = @{
        (__bridge id)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding]
    };
    status = SecItemUpdate((__bridge CFDictionaryRef)query, 
                          (__bridge CFDictionaryRef)attributesToUpdate);
}
When migrating from direct Security framework usage, ensure your kSecAttrService and kSecAttrAccount values match exactly to access existing keychain items.

ARC Requirements

SAMKeychain requires Automatic Reference Counting (ARC) to be enabled in your project.

If Your Project Uses ARC

No action needed - SAMKeychain will work out of the box.

If Your Project Uses Manual Reference Counting (MRC)

You have two options: Option 1: Enable ARC for SAMKeychain files only
  1. In Xcode, select your target
  2. Go to Build Phases > Compile Sources
  3. Find SAMKeychain files (SAMKeychain.m, SAMKeychainQuery.m)
  4. Add the -fobjc-arc compiler flag to each file
Option 2: Convert your entire project to ARC Xcode provides an automated migration tool:
  1. Edit > Convert > To Objective-C ARC…
  2. Follow the migration assistant prompts
  3. Review and test thoroughly after conversion
Most modern projects use ARC. Manual reference counting is only common in legacy codebases.

Version Updates

Updating from 1.x to Current Version

The current version maintains backward compatibility with 1.x releases. However, there are some improvements: Error Handling
// Old: No error handling
NSString *password = [SAMKeychain passwordForService:service account:account];

// New: With error handling (recommended)
NSError *error = nil;
NSString *password = [SAMKeychain passwordForService:service 
                                             account:account 
                                               error:&error];
if (!password && error) {
    NSLog(@"Failed to retrieve password: %@", error);
}
Swift Integration The library now includes __attribute__((swift_error(none))) for better Swift integration:
// Modern Swift usage
if let password = SAMKeychain.password(forService: "MyApp", 
                                       account: "user@example.com") {
    // Use password
}
Always check for errors when performing keychain operations. The keychain can fail for various reasons (device locked, insufficient permissions, etc.).

Breaking Changes Checklist

When updating to newer versions:
  • No breaking changes in the public API
  • ✅ Error handling is additive (old methods still work)
  • ✅ Existing keychain data remains accessible
  • ⚠️ Default accessibility setting behavior may have changed (explicitly set accessibility type)

Code Migration Examples

Complete Migration Example

Here’s a complete before/after example:
// Old code using manual Security framework
- (void)savePassword:(NSString *)password forAccount:(NSString *)account {
    NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
    
    NSDictionary *query = @{
        (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
        (__bridge id)kSecAttrService: @"MyApp",
        (__bridge id)kSecAttrAccount: account,
    };
    
    // Try to delete existing
    SecItemDelete((__bridge CFDictionaryRef)query);
    
    // Add new item
    NSMutableDictionary *newQuery = [query mutableCopy];
    newQuery[(__bridge id)kSecValueData] = passwordData;
    newQuery[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleWhenUnlocked;
    
    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)newQuery, NULL);
    if (status != errSecSuccess) {
        NSLog(@"Error saving password: %d", (int)status);
    }
}

- (NSString *)passwordForAccount:(NSString *)account {
    NSDictionary *query = @{
        (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
        (__bridge id)kSecAttrService: @"MyApp",
        (__bridge id)kSecAttrAccount: account,
        (__bridge id)kSecReturnData: @YES,
        (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne
    };
    
    CFDataRef dataRef = NULL;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&dataRef);
    
    if (status == errSecSuccess && dataRef != NULL) {
        NSData *data = (__bridge_transfer NSData *)dataRef;
        return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    }
    
    return nil;
}
Lines of Code Reduction: 50+ lines → 20 lines 🎉

Accessibility Type Migration

Critical Security Update: The default accessibility type (NULL) is highly insecure. Explicitly set accessibility type during migration.
// Set accessibility type globally (recommended)
#if TARGET_OS_IPHONE
    [SAMKeychain setAccessibilityType:kSecAttrAccessibleWhenUnlocked];
#endif

// Now all future passwords use this accessibility type
[SAMKeychain setPassword:password forService:service account:account];
Recommended Accessibility Types:
  • kSecAttrAccessibleWhenUnlocked - Most apps (requires device unlock)
  • kSecAttrAccessibleAfterFirstUnlock - Background apps that need access
  • kSecAttrAccessibleWhenUnlockedThisDeviceOnly - Sensitive data (no iCloud sync)

Testing Your Migration

After migrating, verify keychain operations:
- (void)testKeychainMigration {
    NSString *testService = @"MigrationTest";
    NSString *testAccount = @"test@example.com";
    NSString *testPassword = @"SecurePassword123";
    
    // 1. Save password
    NSError *error = nil;
    BOOL success = [SAMKeychain setPassword:testPassword 
                                 forService:testService 
                                    account:testAccount 
                                      error:&error];
    NSAssert(success, @"Failed to save: %@", error);
    
    // 2. Retrieve password
    NSString *retrieved = [SAMKeychain passwordForService:testService 
                                                  account:testAccount 
                                                    error:&error];
    NSAssert([retrieved isEqualToString:testPassword], @"Password mismatch");
    
    // 3. Delete password
    success = [SAMKeychain deletePasswordForService:testService 
                                            account:testAccount 
                                              error:&error];
    NSAssert(success, @"Failed to delete: %@", error);
    
    // 4. Verify deletion
    retrieved = [SAMKeychain passwordForService:testService 
                                        account:testAccount 
                                          error:&error];
    NSAssert(retrieved == nil, @"Password should be deleted");
    
    NSLog(@"✅ Migration test passed!");
}

Next Steps

Troubleshooting

Common issues and debugging tips

FAQ

Frequently asked questions