diff --git a/External/Pearl b/External/Pearl index 82753ec5..5af61226 160000 --- a/External/Pearl +++ b/External/Pearl @@ -1 +1 @@ -Subproject commit 82753ec57f65343bb86a3db86d84d933a7aa9caa +Subproject commit 5af61226a78398de136dd1663f9e6ac8624e7df5 diff --git a/MasterPassword/ObjC/MPAlgorithm.h b/MasterPassword/ObjC/MPAlgorithm.h index f34365c4..2c8d3b11 100644 --- a/MasterPassword/ObjC/MPAlgorithm.h +++ b/MasterPassword/ObjC/MPAlgorithm.h @@ -19,7 +19,7 @@ #import "MPStoredSiteEntity.h" #import "MPGeneratedSiteEntity.h" -#define MPAlgorithmDefaultVersion 1 +#define MPAlgorithmDefaultVersion 2 #define MPAlgorithmDefault MPAlgorithmForVersion(MPAlgorithmDefaultVersion) id MPAlgorithmForVersion(NSUInteger version); @@ -43,8 +43,8 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack); @required - (NSUInteger)version; -- (BOOL)migrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc; -- (BOOL)migrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit; +- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc; +- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit; - (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName; - (MPKey *)keyFromKeyData:(NSData *)keyData; diff --git a/MasterPassword/ObjC/MPAlgorithm.m b/MasterPassword/ObjC/MPAlgorithm.m index 39e9ad90..788210f2 100644 --- a/MasterPassword/ObjC/MPAlgorithm.m +++ b/MasterPassword/ObjC/MPAlgorithm.m @@ -35,6 +35,9 @@ id MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion) { if (PearlCFBundleVersionCompare( bundleVersion, @"1.3" ) == NSOrderedAscending) // Pre-1.3 return MPAlgorithmForVersion( 0 ); + if (PearlCFBundleVersionCompare( bundleVersion, @"2.1" ) == NSOrderedAscending) + // Pre-2.1 + return MPAlgorithmForVersion( 1 ); return MPAlgorithmDefault; } diff --git a/MasterPassword/ObjC/MPAlgorithmV0.m b/MasterPassword/ObjC/MPAlgorithmV0.m index 396ae937..f847791f 100644 --- a/MasterPassword/ObjC/MPAlgorithmV0.m +++ b/MasterPassword/ObjC/MPAlgorithmV0.m @@ -73,7 +73,7 @@ return [(id)other version] == [self version]; } -- (BOOL)migrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc { +- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc { NSError *error = nil; NSFetchRequest *migrationRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )]; @@ -84,15 +84,15 @@ return NO; } - BOOL requiresExplicitMigration = NO; + BOOL success = YES; for (MPSiteEntity *migrationSite in migrationSites) - if (![migrationSite migrateExplicitly:NO]) - requiresExplicitMigration = YES; + if (![migrationSite tryMigrateExplicitly:NO]) + success = NO; - return requiresExplicitMigration; + return success; } -- (BOOL)migrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit { +- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit { if (site.version != [self version] - 1) // Only migrate from previous version. @@ -364,11 +364,11 @@ - (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key { return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedName withCounter:1 - variant:MPSiteVariantLogin context:nil usingKey:key]; + variant:MPSiteVariantLogin context:nil usingKey:key]; } - (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter - usingKey:(MPKey *)key { + usingKey:(MPKey *)key { return [self generateContentForSiteNamed:name ofType:type withCounter:counter variant:MPSiteVariantPassword context:nil usingKey:key]; @@ -381,7 +381,7 @@ } - (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter - variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key { + variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key { // Determine the seed whose bytes will be used for calculating a password uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length ), ncontextLength = htonl( context.length ); @@ -396,9 +396,9 @@ nameLengthBytes, [name dataUsingEncoding:NSUTF8StringEncoding], counterBytes, - context? contextLengthBytes: nil, + context? contextLengthBytes: nil, [context dataUsingEncoding:NSUTF8StringEncoding], - nil] + nil] hmacWith:PearlHashSHA256 key:key.keyData]; trc( @"seed is: %@", [seed encodeHex] ); const char *seedBytes = seed.bytes; @@ -678,7 +678,7 @@ } - (void)importProtectedPassword:(NSString *)protectedContent protectedByKey:(MPKey *)importKey - intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { + intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." ); switch (site.type) { diff --git a/MasterPassword/ObjC/MPAlgorithmV1.m b/MasterPassword/ObjC/MPAlgorithmV1.m index 5dd04284..22e2ef0f 100644 --- a/MasterPassword/ObjC/MPAlgorithmV1.m +++ b/MasterPassword/ObjC/MPAlgorithmV1.m @@ -25,7 +25,7 @@ return 1; } -- (BOOL)migrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit { +- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit { if (site.version != [self version] - 1) // Only migrate from previous version. @@ -46,12 +46,13 @@ } - (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter - variant:(MPSiteVariant)variant usingKey:(MPKey *)key { + variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key { // Determine the seed whose bytes will be used for calculating a password - uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length ); + uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length ), ncontextLength = htonl( context.length ); NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )]; NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )]; + NSData *contextLengthBytes = [NSData dataWithBytes:&ncontextLength length:sizeof( ncontextLength )]; NSString *scope = [self scopeForVariant:variant]; trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@)", [[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex] ); @@ -60,6 +61,8 @@ nameLengthBytes, [name dataUsingEncoding:NSUTF8StringEncoding], counterBytes, + context? contextLengthBytes: nil, + [context dataUsingEncoding:NSUTF8StringEncoding], nil] hmacWith:PearlHashSHA256 key:key.keyData]; trc( @"seed is: %@", [seed encodeHex] ); diff --git a/MasterPassword/ObjC/MPAlgorithmV2.h b/MasterPassword/ObjC/MPAlgorithmV2.h new file mode 100644 index 00000000..ec76634a --- /dev/null +++ b/MasterPassword/ObjC/MPAlgorithmV2.h @@ -0,0 +1,21 @@ +/** + * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) + * + * See the enclosed file LICENSE for license information (LGPLv3). If you did + * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt + * + * @author Maarten Billemont + * @license http://www.gnu.org/licenses/lgpl-3.0.txt + */ + +// +// MPAlgorithmV1 +// +// Created by Maarten Billemont on 17/07/12. +// Copyright 2012 lhunath (Maarten Billemont). All rights reserved. +// + +#import "MPAlgorithmV1.h" + +@interface MPAlgorithmV2 : MPAlgorithmV1 +@end diff --git a/MasterPassword/ObjC/MPAlgorithmV2.m b/MasterPassword/ObjC/MPAlgorithmV2.m new file mode 100644 index 00000000..b5db03a2 --- /dev/null +++ b/MasterPassword/ObjC/MPAlgorithmV2.m @@ -0,0 +1,97 @@ +/** +* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) +* +* See the enclosed file LICENSE for license information (LGPLv3). If you did +* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt +* +* @author Maarten Billemont +* @license http://www.gnu.org/licenses/lgpl-3.0.txt +*/ + +// +// MPAlgorithmV1 +// +// Created by Maarten Billemont on 17/07/12. +// Copyright 2012 lhunath (Maarten Billemont). All rights reserved. +// + +#import +#import "MPAlgorithmV2.h" +#import "MPEntities.h" + +@implementation MPAlgorithmV2 + +- (NSUInteger)version { + + return 2; +} + +- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit { + + if (site.version != [self version] - 1) + // Only migrate from previous version. + return NO; + + if (!explicit) { + if (site.type & MPSiteTypeClassGenerated && site.name.length != [site.name dataUsingEncoding:NSUTF8StringEncoding].length) { + // This migration requires explicit permission for types of the generated class. + site.requiresExplicitMigration = YES; + return NO; + } + } + + // Apply migration. + site.requiresExplicitMigration = NO; + site.version = [self version]; + return YES; +} + +- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter + variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key { + + // Determine the seed whose bytes will be used for calculating a password + NSData *nameBytes = [name dataUsingEncoding:NSUTF8StringEncoding]; + NSData *contextBytes = [context dataUsingEncoding:NSUTF8StringEncoding]; + uint32_t ncounter = htonl( counter ), nnameLength = htonl( nameBytes.length ), ncontextLength = htonl( contextBytes.length ); + NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )]; + NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )]; + NSData *contextLengthBytes = [NSData dataWithBytes:&ncontextLength length:sizeof( ncontextLength )]; + NSString *scope = [self scopeForVariant:variant]; + NSData *scopeBytes = [scope dataUsingEncoding:NSUTF8StringEncoding]; + trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@)", + [[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex] ); + NSData *seed = [[NSData dataByConcatenatingDatas: + scopeBytes, + nameLengthBytes, + nameBytes, + counterBytes, + context? contextLengthBytes: nil, + contextBytes, + nil] + hmacWith:PearlHashSHA256 key:key.keyData]; + trc( @"seed is: %@", [seed encodeHex] ); + const unsigned char *seedBytes = seed.bytes; + + // Determine the cipher from the first seed byte. + NSAssert( [seed length], @"Missing seed." ); + NSArray *typeCiphers = [self ciphersForType:type]; + NSString *cipher = typeCiphers[seedBytes[0] % [typeCiphers count]]; + trc( @"type %@ (%lu), ciphers: %@, selected: %@", [self nameOfType:type], (unsigned long)type, typeCiphers, cipher ); + + // Encode the content, character by character, using subsequent seed bytes and the cipher. + NSAssert( [seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher." ); + NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]]; + for (NSUInteger c = 0; c < [cipher length]; ++c) { + uint16_t keyByte = seedBytes[c + 1]; + NSString *cipherClass = [cipher substringWithRange:NSMakeRange( c, 1 )]; + NSString *cipherClassCharacters = [self charactersForCipherClass:cipherClass]; + NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange( keyByte % [cipherClassCharacters length], 1 )]; + + trc( @"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character ); + [content appendString:character]; + } + + return content; +} + +@end diff --git a/MasterPassword/ObjC/MPEntities.h b/MasterPassword/ObjC/MPEntities.h index 596ea536..0d1930f3 100644 --- a/MasterPassword/ObjC/MPEntities.h +++ b/MasterPassword/ObjC/MPEntities.h @@ -36,7 +36,7 @@ @property(readonly) id algorithm; - (NSUInteger)use; -- (BOOL)migrateExplicitly:(BOOL)explicit; +- (BOOL)tryMigrateExplicitly:(BOOL)explicit; - (NSString *)resolveLoginUsingKey:(MPKey *)key; - (NSString *)resolvePasswordUsingKey:(MPKey *)key; - (void)resolveLoginUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result; diff --git a/MasterPassword/ObjC/MPEntities.m b/MasterPassword/ObjC/MPEntities.m index 302cbae2..1c6a6c37 100644 --- a/MasterPassword/ObjC/MPEntities.m +++ b/MasterPassword/ObjC/MPEntities.m @@ -134,18 +134,20 @@ self.loginName, self.requiresExplicitMigration ); } -- (BOOL)migrateExplicitly:(BOOL)explicit { +- (BOOL)tryMigrateExplicitly:(BOOL)explicit { - while (self.version < MPAlgorithmDefaultVersion) - if ([MPAlgorithmForVersion( self.version + 1 ) migrateSite:self explicit:explicit]) - inf( @"%@ migration to version: %ld succeeded for site: %@", - explicit? @"Explicit": @"Automatic", (long)self.version + 1, self ); - else { + while (self.version < MPAlgorithmDefaultVersion) { + NSUInteger toVersion = self.version + 1; + if (![MPAlgorithmForVersion( toVersion ) tryMigrateSite:self explicit:explicit]) { wrn( @"%@ migration to version: %ld failed for site: %@", - explicit? @"Explicit": @"Automatic", (long)self.version + 1, self ); + explicit? @"Explicit": @"Automatic", (long)toVersion, self ); return NO; } + inf( @"%@ migration to version: %ld succeeded for site: %@", + explicit? @"Explicit": @"Automatic", (long)toVersion, self ); + } + return YES; } diff --git a/MasterPassword/ObjC/iOS/MPPasswordCell.m b/MasterPassword/ObjC/iOS/MPPasswordCell.m index 26eadc78..4540fe13 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordCell.m +++ b/MasterPassword/ObjC/iOS/MPPasswordCell.m @@ -317,7 +317,7 @@ - (IBAction)doUpgrade:(UIButton *)sender { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { - if (![[self siteInContext:context] migrateExplicitly:YES]) { + if (![[self siteInContext:context] tryMigrateExplicitly:YES]) { [PearlOverlay showTemporaryOverlayWithTitle:@"Couldn't Upgrade Site" dismissAfter:2]; return; } diff --git a/MasterPassword/ObjC/iOS/MPPasswordsViewController.m b/MasterPassword/ObjC/iOS/MPPasswordsViewController.m index 6389ac2c..d3760fd6 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordsViewController.m +++ b/MasterPassword/ObjC/iOS/MPPasswordsViewController.m @@ -74,6 +74,18 @@ [self updatePasswords]; } +- (void)viewDidAppear:(BOOL)animated { + + [super viewDidAppear:animated]; + + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context]; + if (![MPAlgorithmDefault tryMigrateUser:activeUser inContext:context]) + [PearlOverlay showTemporaryOverlayWithTitle:@"Some Sites Need Upgrade" dismissAfter:2]; + [context saveToStore]; + }]; +} + - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; diff --git a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj index e3a693d3..9d1850bf 100644 --- a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj +++ b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 93D3992FA1546E01F498F665 /* PearlNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */; }; 93D399433EA75E50656040CB /* Twitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D394077F8FAB8167647187 /* Twitter.framework */; }; 93D399D7E08A142776A74CB8 /* MPOverlayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D395105935859D71679931 /* MPOverlayViewController.m */; }; + 93D39A27F2506C6FEEF9C588 /* MPAlgorithmV2.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399A8E3181B442D347CD7 /* MPAlgorithmV2.m */; }; 93D39A53D76CA70786423458 /* UICollectionView+PearlReloadFromArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */; }; 93D39A5FF670957C0AF8298D /* MPPasswordCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */; }; 93D39A8EA1C49CE43B63F47B /* PearlUICollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D8A953779B35403AF6E /* PearlUICollectionView.m */; }; @@ -448,12 +449,14 @@ 93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCoachmarkViewController.m; sourceTree = ""; }; 93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordCell.h; sourceTree = ""; }; 93D3999693660C89A7465F4E /* MPCoachmarkViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCoachmarkViewController.h; sourceTree = ""; }; + 93D399A8E3181B442D347CD7 /* MPAlgorithmV2.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV2.m; sourceTree = ""; }; 93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUsersViewController.m; sourceTree = ""; }; 93D399F244BB522A317811BB /* MPFixable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPFixable.h; sourceTree = ""; }; 93D39A1DDFA09AE2E14D26DC /* UIResponder+PearlFirstResponder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIResponder+PearlFirstResponder.m"; sourceTree = ""; }; 93D39A28369954D147E239BA /* MPSetupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSetupViewController.m; sourceTree = ""; }; 93D39A4759186F6D2D34AA6B /* PearlSizedTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlSizedTextView.h; sourceTree = ""; }; 93D39A813CA9D7E192261ED2 /* MPFixable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPFixable.m; sourceTree = ""; }; + 93D39A97A7D48CB3B784194D /* MPAlgorithmV2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAlgorithmV2.h; sourceTree = ""; }; 93D39AA10CD00D05937671B1 /* UITextView+PearlAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITextView+PearlAttributes.h"; sourceTree = ""; }; 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = ""; }; 93D39ACBA9F4878B6A1CC33B /* MPEmergencyViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEmergencyViewController.m; sourceTree = ""; }; @@ -2343,6 +2346,8 @@ 93D39A813CA9D7E192261ED2 /* MPFixable.m */, 93D394C78C7B879C9AD9152C /* MPAppDelegate_InApp.m */, 93D39CECA10BCCB0BA581BF1 /* MPAppDelegate_InApp.h */, + 93D399A8E3181B442D347CD7 /* MPAlgorithmV2.m */, + 93D39A97A7D48CB3B784194D /* MPAlgorithmV2.h */, ); name = ObjC; path = ..; @@ -3325,6 +3330,7 @@ 93D39D38356F59DBEF934D70 /* MPAppDelegate_InApp.m in Sources */, 93D390C1B93F9D3AE37DD0A5 /* MPAnswersViewController.m in Sources */, 93D399D7E08A142776A74CB8 /* MPOverlayViewController.m in Sources */, + 93D39A27F2506C6FEEF9C588 /* MPAlgorithmV2.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/MasterPassword/Resources/Media/thumb_generated_answers.png b/MasterPassword/Resources/Media/thumb_generated_answers.png index 0674b676..32db5649 100644 Binary files a/MasterPassword/Resources/Media/thumb_generated_answers.png and b/MasterPassword/Resources/Media/thumb_generated_answers.png differ diff --git a/MasterPassword/Resources/Media/thumb_generated_answers@2x.png b/MasterPassword/Resources/Media/thumb_generated_answers@2x.png index 5842273f..a644a320 100644 Binary files a/MasterPassword/Resources/Media/thumb_generated_answers@2x.png and b/MasterPassword/Resources/Media/thumb_generated_answers@2x.png differ diff --git a/MasterPassword/Resources/Media/thumb_generated_answers@3x.png b/MasterPassword/Resources/Media/thumb_generated_answers@3x.png index 1e366a26..64724312 100644 Binary files a/MasterPassword/Resources/Media/thumb_generated_answers@3x.png and b/MasterPassword/Resources/Media/thumb_generated_answers@3x.png differ