我正在使用 NSValueTranformer 加密某些核心数据属性。这一切都很好,除了我需要能够根据 NSManagedObject 使用不同的加密密钥。无论如何我可以从我的变压器类中访问这个实体吗?
用例是我有多个具有不同密码的用户可以访问不同的 NSManagedObject 实体。如果我对所有对象使用相同的加密密钥,那么有人可以在 SQL 数据库中重新分配拥有它们的人,他们仍然会解密。
关于解决此问题的最佳方法的任何想法?
编辑:我应该提到我在 iOS 中这样做。
我正在使用 NSValueTranformer 加密某些核心数据属性。这一切都很好,除了我需要能够根据 NSManagedObject 使用不同的加密密钥。无论如何我可以从我的变压器类中访问这个实体吗?
用例是我有多个具有不同密码的用户可以访问不同的 NSManagedObject 实体。如果我对所有对象使用相同的加密密钥,那么有人可以在 SQL 数据库中重新分配拥有它们的人,他们仍然会解密。
关于解决此问题的最佳方法的任何想法?
编辑:我应该提到我在 iOS 中这样做。
NSValueTransformer
您可以创建具有状态(即加密密钥)的子类的自定义实例,并-bind:toObject:withKeyPath:options:
使用密钥将它们传递到选项字典中NSValueTransformerBindingOption
。
您将无法直接在 IB 中进行设置,因为 IB 按类名引用值转换器,但您可以在代码中进行。如果您感觉更加雄心勃勃,您可以在 IB 中设置绑定,然后稍后在代码中用不同的选项替换它们。
它可能看起来像这样:
@interface EncryptingValueTransformer : NSValueTransformer
@property (nonatomic,readwrite,copy) NSData* encryptionKey;
@end
@implementation EncryptingValueTransformer
- (void)dealloc
{
_encryptionKey = nil;
}
- (id)transformedValue:(id)value
{
if (!self.encryptionKey)
return nil;
// do the transformation
return value;
}
- (id)reverseTransformedValue:(id)value
{
if (!self.encryptionKey)
return nil;
// Do the reverse transformation
return value;
}
@end
@interface MyViewController : NSViewController
@property (nonatomic, readwrite, assign) IBOutlet NSControl* controlBoundToEncryptedValue;
@end
@implementation MyViewController
// Other stuff...
- (void)loadView
{
[super loadView];
// Replace IB's value tansformer binding settings (which will be by class and not instance) with specific,
// stateful instances.
for (NSString* binding in [self.controlBoundToEncryptedValue exposedBindings])
{
NSDictionary* bindingInfo = [self.controlBoundToEncryptedValue infoForBinding: binding];
NSDictionary* options = bindingInfo[NSOptionsKey];
if ([options[NSValueTransformerNameBindingOption] isEqual: NSStringFromClass([EncryptingValueTransformer class])])
{
// Out with the old
[self.controlBoundToEncryptedValue unbind: binding];
// In with the new
NSMutableDictionary* mutableOptions = [options mutableCopy];
mutableOptions[NSValueTransformerNameBindingOption] = nil;
mutableOptions[NSValueTransformerBindingOption] = [[EncryptingValueTransformer alloc] init];
[self.controlBoundToEncryptedValue bind: binding
toObject: bindingInfo[NSObservedObjectKey]
withKeyPath: bindingInfo[NSObservedKeyPathKey]
options: mutableOptions];
}
}
}
// Assuming you're using the standard representedObject pattern, this will get set every time you want
// your view to expose new model data. This is a good place to update the encryption key in the transformers'
// state...
- (void)setRepresentedObject:(id)representedObject
{
for (NSString* binding in [self.controlBoundToEncryptedValue exposedBindings])
{
id transformer = [self.controlBoundToEncryptedValue infoForBinding: NSValueBinding][NSOptionsKey][NSValueTransformerBindingOption];
EncryptingValueTransformer* encryptingTransformer = [transformer isKindOfClass: [EncryptingValueTransformer class]] ? (EncryptingValueTransformer*)transformer : nil;
encryptingTransformer.encryptionKey = nil;
}
[super setRepresentedObject:representedObject];
// Get key from model however...
NSData* encryptionKeySpecificToThisUser = /* Whatever it is... */ nil;
for (NSString* binding in [self.controlBoundToEncryptedValue exposedBindings])
{
id transformer = [self.controlBoundToEncryptedValue infoForBinding: NSValueBinding][NSOptionsKey][NSValueTransformerBindingOption];
EncryptingValueTransformer* encryptingTransformer = [transformer isKindOfClass: [EncryptingValueTransformer class]] ? (EncryptingValueTransformer*)transformer : nil;
encryptingTransformer.encryptionKey = encryptionKeySpecificToThisUser;
}
}
// ...Other stuff
@end
第三次魅力?让我看看我是否可以解决您的 only-transform-when-going-to-disk 要求。将此视为其他两种方法的混合。
@interface UserSession : NSObject
+ (UserSession*)currentSession;
+ (void)setCurrentSession: (UserSession*)session;
- (id)initWithUserName: (NSString*)username andEncryptionKey: (NSData*)key;
@property (nonatomic, readonly) NSString* userName;
@property (nonatomic, readonly) NSData* encryptionKey;
@end
@implementation UserSession
static UserSession* gCurrentSession = nil;
+ (UserSession*)currentSession
{
@synchronized(self)
{
return gCurrentSession;
}
}
+ (void)setCurrentSession: (UserSession*)userSession
{
@synchronized(self)
{
gCurrentSession = userSession;
}
}
- (id)initWithUserName: (NSString*)username andEncryptionKey: (NSData*)key
{
if (self = [super init])
{
_userName = [username copy];
_encryptionKey = [key copy];
}
return self;
}
- (void)dealloc
{
_userName = nil;
_encryptionKey = nil;
}
@end
@interface EncryptingValueTransformer : NSValueTransformer
@end
@implementation EncryptingValueTransformer
- (id)transformedValue:(id)value
{
UserSession* session = [UserSession currentSession];
NSAssert(session, @"No user session! Can't decrypt!");
NSData* key = session.encryptionKey;
NSData* decryptedData = Decrypt(value, key);
return decryptedData;
}
- (id)reverseTransformedValue:(id)value
{
UserSession* session = [UserSession currentSession];
NSAssert(session, @"No user session! Can't encrypt!");
NSData* key = session.encryptionKey;
NSData* encryptedData = Encrypt(value, key);
return encryptedData;
}
@end
这里唯一棘手的部分是您必须确保在创建托管对象上下文之前UserSession
设置当前,并且在保存和释放上下文之前不会更改。
希望这可以帮助。
好的。这让我很烦,所以我想了更多......我认为最简单的方法是拥有某种“会话”对象,然后在您的托管对象上拥有一个“派生属性”。假设您有一个名为 UserData 的实体,其属性名为encryptedData
,我编写了一些可能有助于说明的代码:
@interface UserData : NSManagedObject
@property (nonatomic, retain) NSData * unencryptedData;
@end
@interface UserData () // Private
@property (nonatomic, retain) NSData * encryptedData;
@end
// These functions defined elsewhere
NSData* Encrypt(NSData* clearData, NSData* key);
NSData* Decrypt(NSData* cipherData, NSData* key);
@interface UserSession : NSObject
+ (UserSession*)currentSession;
- (id)initWithUserName: (NSString*)username andEncryptionKey: (NSData*)key;
@property (nonatomic, readonly) NSString* userName;
@property (nonatomic, readonly) NSData* encryptionKey;
@end
@implementation UserData
@dynamic encryptedData;
@dynamic unencryptedData;
+ (NSSet*)keyPathsForValuesAffectingUnencryptedData
{
return [NSSet setWithObject: NSStringFromSelector(@selector(encryptedData))];
}
- (NSData*)unencryptedData
{
UserSession* session = [UserSession currentSession];
if (nil == session)
return nil;
NSData* key = session.encryptionKey;
NSData* encryptedData = self.encryptedData;
NSData* decryptedData = Decrypt(encryptedData, key);
return decryptedData;
}
- (void)setUnencryptedData:(NSData *)unencryptedData
{
UserSession* session = [UserSession currentSession];
NSAssert(session, @"No user session! Can't encrypt!");
NSData* key = session.encryptionKey;
NSData* encryptedData = Encrypt(unencryptedData, key);
self.encryptedData = encryptedData;
}
@end
@implementation UserSession
static UserSession* gCurrentSession = nil;
+ (UserSession*)currentSession
{
@synchronized(self)
{
return gCurrentSession;
}
}
+ (void)setCurrentSession: (UserSession*)userSession
{
@synchronized(self)
{
gCurrentSession = userSession;
}
}
- (id)initWithUserName: (NSString*)username andEncryptionKey: (NSData*)key
{
if (self = [super init])
{
_userName = [username copy];
_encryptionKey = [key copy];
}
return self;
}
-(void)dealloc
{
_userName = nil;
_encryptionKey = nil;
}
@end
这里的想法是,当给定用户登录时,您创建一个新的 UserSession 对象并调用+[UserSession setCurrentSession: [[UserSession alloc] initWithUserName: @"foo" andEncryptionKey: <whatever>]]
. 派生属性 ( unencryptedData
) 访问器和修改器获取当前会话并使用密钥将值来回转换为“真实”属性。(另外,不要跳过该+keyPathsForValuesAffectingUnencryptedData
方法。这会告诉运行时两个属性之间的关系,并有助于更无缝地工作。)