1

我正在使用 NSValueTranformer 加密某些核心数据属性。这一切都很好,除了我需要能够根据 NSManagedObject 使用不同的加密密钥。无论如何我可以从我的变压器类中访问这个实体吗?

用例是我有多个具有不同密码的用户可以访问不同的 NSManagedObject 实体。如果我对所有对象使用相同的加密密钥,那么有人可以在 SQL 数据库中重新分配拥有它们的人,他们仍然会解密。

关于解决此问题的最佳方法的任何想法?

编辑:我应该提到我在 iOS 中这样做。

4

3 回答 3

1

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
于 2013-09-20T12:09:34.163 回答
1

第三次魅力?让我看看我是否可以解决您的 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设置当前,并且在保存和释放上下文之前不会更改。

希望这可以帮助。

于 2013-09-20T13:03:31.077 回答
0

好的。这让我很烦,所以我想了更多......我认为最简单的方法是拥有某种“会话”对象,然后在您的托管对象上拥有一个“派生属性”。假设您有一个名为 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方法。这会告诉运行时两个属性之间的关系,并有助于更无缝地工作。)

于 2013-09-20T12:57:22.257 回答