2
0

Reset from unlock, FAQ improvements.

[ADDED]     Ability to reset a master password from the unlock screen.
[FIXED]     Manually retain objects that live next to a VC in a
            storyboard within the VC to avoid an OS bug.
[FIXED]     Visibility of the deleteTip.
[ADDED]     An index to the FAQ.
[IMPROVED]  Improved and expanded the FAQ a bit more.
This commit is contained in:
Maarten Billemont 2012-06-15 11:16:02 +02:00
parent a67d9676ba
commit d4adafb448
10 changed files with 145 additions and 115 deletions

View File

@ -75,11 +75,14 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
- (void)signOut {
if (self.key)
self.key = nil;
self.activeUser = nil;
if (self.activeUser) {
self.activeUser = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedOut object:self];
}
}
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password {

View File

@ -18,6 +18,6 @@
- (void)showGuide;
- (void)export;
- (void)changeMP;
- (void)changeMasterPasswordFor:(MPUserEntity *)user;
@end

View File

@ -331,7 +331,7 @@
[TestFlight passCheckpoint:MPCheckpointDeactivated];
}
#pragma - mark Behavior
#pragma mark - Behavior
- (void)checkConfig {
@ -452,7 +452,7 @@
[self.window.rootViewController presentModalViewController:composer animated:YES];
}
- (void)changeMP {
- (void)changeMasterPasswordFor:(MPUserEntity *)user {
[PearlAlert showAlertWithTitle:@"Changing Master Password"
message:
@ -464,9 +464,9 @@
if (buttonIndex == [alert cancelButtonIndex])
return;
inf(@"Unsetting master password for: %@.", self.activeUser.userID);
self.activeUser.keyID = nil;
[self forgetSavedKeyFor:self.activeUser];
inf(@"Unsetting master password for: %@.", user.userID);
user.keyID = nil;
[self forgetSavedKeyFor:user];
[self signOut];
[TestFlight passCheckpoint:MPCheckpointChangeMP];

View File

@ -33,6 +33,7 @@
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
@property (weak, nonatomic) IBOutlet UIView *actionsTipContainer;
@property (weak, nonatomic) IBOutlet UIView *typeTipContainer;
@property (strong, nonatomic) IBOutlet UILongPressGestureRecognizer *resetPasswordCounterGesture;
@property (copy) void (^contentTipCleanup)(BOOL finished);

View File

@ -43,6 +43,7 @@
@synthesize searchTipContainer = _searchTipContainer;
@synthesize actionsTipContainer = _actionsTipContainer;
@synthesize typeTipContainer = _typeTipContainer;
@synthesize resetPasswordCounterGesture = _resetPasswordCounterGesture;
@synthesize contentField = _contentField;
@synthesize contentTipCleanup;
@ -149,6 +150,7 @@
[self setSearchTipContainer:nil];
[self setActionsTipContainer:nil];
[self setTypeTipContainer:nil];
[self setResetPasswordCounterGesture:nil];
[super viewDidUnload];
}

View File

@ -119,7 +119,7 @@
else
if (cell == self.changeMPCell)
[[MPAppDelegate get] changeMP];
[[MPAppDelegate get] changeMasterPasswordFor:[MPAppDelegate get].activeUser];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}

View File

@ -20,9 +20,10 @@
@property (weak, nonatomic) IBOutlet UILabel *deleteTip;
@property (weak, nonatomic) IBOutlet UIView *passwordTipView;
@property (weak, nonatomic) IBOutlet UILabel *passwordTipLabel;
@property (strong, nonatomic) IBOutlet UILongPressGestureRecognizer *targetedUserActionGesture;
@property (nonatomic, strong) UIColor *avatarShadowColor;
- (IBAction)deleteTargetedUser:(UILongPressGestureRecognizer *)sender;
- (IBAction)targetedUserAction:(UILongPressGestureRecognizer *)sender;
@end

View File

@ -32,6 +32,7 @@
@synthesize deleteTip;
@synthesize passwordTipView;
@synthesize passwordTipLabel;
@synthesize targetedUserActionGesture;
@synthesize avatarShadowColor = _avatarShadowColor;
@ -137,6 +138,7 @@
[self setDeleteTip:nil];
[self setPasswordTipView:nil];
[self setPasswordTipLabel:nil];
[self setTargetedUserActionGesture:nil];
[super viewDidUnload];
}
@ -356,7 +358,7 @@
self.nameLabel.backgroundColor = [UIColor clearColor];
self.oldNameLabel.center = self.nameLabel.center;
self.avatarShadowColor = [UIColor lightGrayColor];
self.deleteTip.alpha = [self.avatarToUser count] > 2? 1: 0;
self.deleteTip.alpha = 0.5;
}
MPUserEntity *targetedUser = self.selectedUser;
@ -590,13 +592,11 @@
*targetContentOffset = CGPointMake(middleAvatar.center.x - scrollView.bounds.size.width / 2, targetContentOffset->y);
[self updateLayoutAnimated:NO allowScroll:NO completion:nil];
// [self scrollToAvatar:middleAvatar animated:YES];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[self updateLayoutAnimated:YES allowScroll:YES completion:nil];
// [self scrollToAvatar:middleAvatar animated:YES];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
@ -606,7 +606,7 @@
#pragma mark - IBActions
- (IBAction)deleteTargetedUser:(UILongPressGestureRecognizer *)sender {
- (IBAction)targetedUserAction:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan)
return;
@ -618,18 +618,23 @@
if (!targetedUser)
return;
[PearlAlert showAlertWithTitle:@"Delete User" message:
PearlString(@"Do you want to delete all record of the following user?\n\n%@",
[PearlAlert showAlertWithTitle:@"Delete Or Reset User"
message:
PearlString(@"Do you want to reset the master password or delete all record of the following user?\n\n%@",
targetedUser.name)
viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
return;
if (buttonIndex == [alert firstOtherButtonIndex]) {
[[MPAppDelegate get].managedObjectContext deleteObject:targetedUser];
[[MPAppDelegate get] saveContext];
[self updateUsers];
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Delete", nil];
} else if (buttonIndex == [alert firstOtherButtonIndex] + 1) {
[[MPAppDelegate get] changeMasterPasswordFor:targetedUser];
}
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Delete", @"Reset", nil];
}
@end

View File

@ -734,6 +734,7 @@ L4m3P4sSw0rD</string>
<outlet property="passwordCounter" destination="Iuf-np-e9C" id="CIm-Mk-nJh"/>
<outlet property="passwordEdit" destination="9FS-fS-xH6" id="YeB-HF-ZPk"/>
<outlet property="passwordIncrementer" destination="jec-mu-nPt" id="i9B-lX-zzX"/>
<outlet property="resetPasswordCounterGesture" destination="cZr-Fj-eBw" id="St3-9p-J86"/>
<outlet property="searchDisplayController" destination="P8c-gf-nN3" id="CLs-YI-7NC"/>
<outlet property="searchResultsController" destination="0QO-2P-OhD" id="xEC-gV-lHp"/>
<outlet property="searchTipContainer" destination="zOR-Du-qRL" id="X7h-Vh-iCE"/>
@ -919,9 +920,9 @@ L4m3P4sSw0rD</string>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.5" contentMode="left" text="Tap and hold to delete." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="DBJ-Qi-ZcF">
<rect key="frame" x="32" y="460" width="256" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" alpha="0.5" contentMode="left" text="Tap and hold to delete or reset." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="DBJ-Qi-ZcF">
<rect key="frame" x="20" y="460" width="280" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" name="Copperplate" family="Copperplate" pointSize="12"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
@ -946,11 +947,12 @@ L4m3P4sSw0rD</string>
<outlet property="passwordTipView" destination="NvG-0R-eTZ" id="4Mx-TL-yfu"/>
<outlet property="passwordView" destination="7cc-yu-i0m" id="WoF-Ab-PPC"/>
<outlet property="spinner" destination="27q-lX-0vy" id="CGK-G9-PRI"/>
<outlet property="targetedUserActionGesture" destination="9WS-yS-aqQ" id="y74-cg-eat"/>
</connections>
</viewController>
<pongPressGestureRecognizer allowableMovement="10" minimumPressDuration="0.5" id="9WS-yS-aqQ">
<connections>
<action selector="deleteTargetedUser:" destination="Nbn-Rv-sP1" id="cBQ-oQ-c7g"/>
<action selector="targetedUserAction:" destination="Nbn-Rv-sP1" id="mgC-0X-heO"/>
</connections>
</pongPressGestureRecognizer>
</objects>
@ -1372,6 +1374,7 @@ L4m3P4sSw0rD</string>
<relationship kind="outlet" name="passwordCounter" candidateClass="UILabel"/>
<relationship kind="outlet" name="passwordEdit" candidateClass="UIButton"/>
<relationship kind="outlet" name="passwordIncrementer" candidateClass="UIButton"/>
<relationship kind="outlet" name="resetPasswordCounterGesture" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="outlet" name="searchResultsController" candidateClass="MPSearchDelegate"/>
<relationship kind="outlet" name="searchTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="siteName" candidateClass="UILabel"/>
@ -1409,7 +1412,7 @@ L4m3P4sSw0rD</string>
<class className="MPUnlockViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPUnlockViewController.h"/>
<relationships>
<relationship kind="action" name="deleteTargetedUser:" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="action" name="targetedUserAction:" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="outlet" name="avatarTemplate" candidateClass="UIButton"/>
<relationship kind="outlet" name="avatarsView" candidateClass="UIScrollView"/>
<relationship kind="outlet" name="deleteTip" candidateClass="UILabel"/>
@ -1420,6 +1423,7 @@ L4m3P4sSw0rD</string>
<relationship kind="outlet" name="passwordTipView" candidateClass="UIView"/>
<relationship kind="outlet" name="passwordView" candidateClass="UIView"/>
<relationship kind="outlet" name="spinner" candidateClass="UIImageView"/>
<relationship kind="outlet" name="targetedUserActionGesture" candidateClass="UILongPressGestureRecognizer"/>
</relationships>
</class>
</classes>

View File

@ -101,8 +101,25 @@
<h2 id="faq">&mdash; F.A.Q. &mdash;</h2>
<h3>What is this thing?<br />
How do I use it?</h3>
<ol>
<li><a href="#what" >What is it and how do I use it?</a></li>
<li><a href="#why" >Why do I need Master Password?</a></li>
<li><a href="#custom" >A password was given to me.</a></li>
<li><a href="#loss" >What if I loose my device?</a></li>
<li><a href="#device" >Am I dependant on my device?</a></li>
<li><a href="#paranoid" >How do I maximize my security?</a></li>
<li><a href="#hacked" >A website I use got hacked!</a></li>
<li><a href="#forgot" >I forgot my master password!</a></li>
<li><a href="#algorithm">How does Master Password work?</a></li>
<li><a href="#branded" >Do you offer enterprise solutions?</a></li>
</ol>
<h3 id="what">What is Master Password and how do I use it?</h3>
<p>
Master Password <b>creates secure and unique passwords for you</b>, so you don't have to.<br />
The human brain is not well suited for creating secure and random passwords, and it's also terrible at remembering lots of unique passwords.
Master Password does the work for you: all you need to do is remember a single long and secure master password to log into the app.
</p>
<p>
<b>Begin by entering the name</b> of the thing you want a password for. Naming is entirely up to you, but remember to be consistent.<br />
<i>Good names</i> could be:<br />
@ -115,7 +132,7 @@
<p>
<b>Tap the resulting password</b> to copy it for pasting in a different application or read it to type it in or use it manually elsewhere.
</p>
<p>
<p id="why">
The thought behind this application is to secure your online (and offline) life by <b>changing all of your passwords</b>
to passwords generated by this app.
</p>
@ -140,8 +157,7 @@
<i>other</i> site. Nothing is stopping them from trying to log into <i>your</i> GMail, Hotmail or Twitter
accounts using the same password that you used to register an account on their site. Even if you only give
your password to sites you trust, all it takes is for one of those sites to get hacked and lose their
passwords database. Those hackers now have all it takes to impersonate you. This is, in fact, so common,
that it's one of the main reasons people's accounts are getting hacked nowadays.
passwords database. Those hackers now have all it takes to impersonate you.
</p>
<p>
Some of you already try to remember unique-ish passwords for different sites. This causes problems too:
@ -158,7 +174,7 @@
or purpose you might need a password for.
</p>
<h3>I can't change all my passwords.<br />
<h3 id="custom">I can't change all my passwords.<br />
Some of them were assigned to me.</h3>
<p>
That's why this application allows you to change the password type to <code>Personal</code> or <code>Device
@ -171,15 +187,14 @@
password to one generated by the app, this is as good as it gets.
</p>
<h3>So, what if I lose my device?<br />
<h3 id="loss">So, what if I lose my device?<br />
I'm locked out of everything?</h3>
<p>
<b>Absolutely not!</b> In fact, generated passwords aren't even stored on your device. No, not in the
cloud either. They're not stored anywhere! What that basically means is, if you grab the iPhone of a
colleague or friend and open this app on it with your own master password, <i>it'll give you all your
generated passwords</i> (don't worry, it's perfectly safe). So, if you lose your iPhone or forget it,
just open the app on your iPad, or borrow a friend's phone, and you're back in business. No backups or
restores needed.
colleague or friend and open this app on it, re-create your user and log in, <i>it'll give you all your
generated passwords</i>. So, if you lose your iPhone or forget it, just open the app on your iPad,
or borrow a friend's device, and you're back in business. No backups or restores needed.
</p>
<p>
This also means that, unlike all those apps that store your passwords or send them off to be stored on the
@ -187,108 +202,107 @@
get at your passwords. There's also no cloud service that can be mis-managed or hacked.
</p>
<h3>Great, but that still means I need my phone to get my passwords.</h3>
<h3 id="device">Great, but that still means I need my device to get my passwords.</h3>
<p>
Correct. However, remember that usually you'll only need to use this app once for each site. After you log
into a site once using the password generated by this app, your browser will probably ask you to remember
the password for the future. Agree to that, and you won't need to bring up your phone again the next time
the password for the future. Agree to that, and you won't need to bring up your device again the next time
you log in to the account.
</p>
<p>
There is also a <b>Mac version</b> of Master Password available from the App Store. It allows you to
generate any of your passwords without even needing to take out your phone.
There is also a <b>Mac version</b> of Master Password that will be released on the Mac App Store.
It allows you to generate any of your passwords without the need to bring out your device.
</p>
<h3>I'm paranoid.<br />
<h3 id="paranoid">I'm paranoid.<br />
How do I maximize my security?</h3>
<p>
For starters, make sure you've changed the passwords of all your sites you have accounts for to those
generated by this app and make sure that you use this app when registering a new account somewhere, to
determine the password to use for the account.
The <b>most important</b> aspect to the security of your passwords is your <b>master password</b>. Make sure
you've chosen a <em>long and unique master password</em>. Master Password's algorithm makes it exceedingly
difficult for an attacker to try and guess your master password, but that doesn't make you invulnerable when
your master password is short or easy to guess. Ideally, your master password should be <em>longer than 10 characters</em>.
<b>An absurd sentence is a great idea</b>, especially if you add non-english or gibberish words to it.
Absurd sentences are long and high in entropy, but also particularly easy for the human brain to remember.
</p>
<p>
It's also important that you've chosen a long master password. Short master passwords, especially 4-digit
PIN codes, are trivial to guess by attackers. Using a <b>10-character master password</b> provides
sufficient entropy to protect against any modern-day attempt at brute-forcing, assuming the password is not
based on easily determined facts (names, birth dates, etc.).
Armed with a good master password, your next step is to assign generated passwords to all of your sites.
By default, Master Password creates passwords that are secure and still easy to copy from your device to a
computer by keyboard. If you prefer, you can go into Master Password's preferences (using the top-right icon)
and change the default password type to <code>Maximum Security</code>. Any new sites will now generate
passwords that are even higher in entropy. These types of passwords are nigh impossible for an attacker to
brute-force (though a <code>Long Password</code> really is secure enough for most any purpose, see
<a href="#hacked">What if a site I use gets hacked?</a>).
</p>
<p>
<b>A better idea yet</b> is to use a pass phrase, ideally <em>an absurd sentence</em>. These are usually
much easier to remember and much harder to guess by attackers.
</p>
<p>
Using the action icon on the top right, select <code>Settings</code> to find some advanced settings for
the application. Here, you can disable <code>Remember my password</code>. Doing so will force the
application to <b>ask for your master password each time</b> you open it. That way, when you show your
phone to somebody else after unlocking it, they can't go through your passwords.
Also check out the application's preferences (using the action icon on the top right, select <code>Preferences</code>).
Make sure that <code>Save Password</code> is disabled. Saving your password is a convenience feature that lets your
device save your master password so you don't need to enter it anymore. It also means that if somebody finds your device
somewhere or steals it, the only obstacle between them and your passwords are your device's PIN code (assuming you even
have one set).<br />
If you go into <code>Settings</code> from the <code>Preferences</code> page, you'll see some global application settings.
Make sure that <code>Stay logged in</code> is disabled here. If enabled, Master Password will not log you out when you
close the app. Your master password isn't saved on your device, but kept in memory for as long as your device remains
powered on. Again, a malignent person can easily get to your passwords if they find your device powered on and logged
into Master Password.
</p>
<h3>I forgot my master password. What are my options?</h3>
<h3 id="hacked">What if a site I use gets hacked?</h3>
<p>
There have been some high-profile password database leaks lately. LinkedIn, eHarmony, Last.fm, to name a few,
have lost millions of people's password hashes. In these cases, attackers have obtained a <q>hash</q> of
the passwords of all of these people, which makes it much easier for them to guess their real password.
A single sophisticated computer can be used to try about 200 million password combinations per second in an
attempt to find the real password behind a hash. That means these millions of people should be really worried
about their account's security.<br />
However, if your account is protected by a <code>Long Password</code> generated by Master Password, it would
take an attacker with ten sophisticated machines multiple lifetimes to find your actual password from a hash.
If the attacker knew beforehand that you had used Master Password to generate your password, he could make
his approach smarter and ten sophisticated machines would still take more than a year of constantly trying
millions of password combinations to find out your actual password.<br />
If instead you used a <code>Maximum Security</code> password to protect your account, the time it would take
for an attacker to brute-force your password goes completely off the scale: 10,000 sophisticated machines
would take up to 312409704477000000 years to try and find your password, even if the attacker knew you're
using Master Password.
</p>
<p>
If you're worried anyway or you need a new password for your site for some other reason, tap the password
counter button (the plus icon) to instantly create a new password for that site.
</p>
<p>
Long story short: When a website you use gets hacked and your password hashes are revealed to hackers, this
is a big problem for the security of your account, but only if you're <b>not</b> using Master Password.
</p>
<h3 id="forgot">I forgot my master password. What are my options?</h3>
<p>
Due to the nature of this app's algorithms and the decisions that were made to protect against brute-force
attacks, it is simply infeasible to recover your master password. If you really can't remember it, your
passwords are <b>gone</b>.
</p>
<p>
Where you go from here is, you log in with a new master password, and for each of your accounts, you go
through the password recovery procedure (which will usually involve sending a message to your email account)
Where you go from here is: on the unlock screen, tap and hold your user. A dialog will pop-up that will allow
you to reset your master password. Assign a new master password, log in, and for each of your accounts, go
through the password recovery procedure (which will usually involve the site sending a mail to your email account)
and reset the passwords of these accounts to passwords generated by your newly chosen master password.<br />
Just don't forget it again! :-)
Now don't forget it again! :-)
</p>
<h3>So how does this thing work internally?</h3>
<h3 id="algorithm">So how does this thing work internally?</h3>
<p>
The way Master Password works internally is <i>fully disclosed</i>. The source code for this application
is also available from <b>GitHub</b>. I invite anyone with a technical background to go through these
resources to make certain of the trustworthyness of Master Password.
</p>
<p>
This part will likely make sense to you only if you're well versed in computer security jargon. If you're
the kind of person who likes to know how the clock ticks before deciding that it can be trusted to keep
ticking, read on.
</p>
<p>
The user chooses a single master password, preferably sufficiently long to harden against brute-force
attacks. The application then creates a scrypt key derivative from the user's password. This process
takes quite a bit of processing time and memory. It makes brute-forcing the master password
<b>far more difficult</b>, to practically infeasible, even for otherwise vulnerable password strings.
</p>
<p>
When the user requests a password be generated for a site, the application composes a byte buffer
consisting of the site's name (<code>UTF-8</code> encoded), the key derived from the master password,
and a password counter, delimited in that order by a NUL byte. The bytes are hashed using the
<code>SHA-1</code> algorithm. The bytes resulting from this hashing operation are called the
<code>seed</code> in the next steps.
</p>
<p>
Next, we need the password type that the user has chosen to use for the site. Password types determine the
<q>cipher</q> that will be used to encrypt <code>seed</code> into a readable password. For
instance, the standard password type <q>Long Password</q> activates one of three pre-set ciphers:
<code>CvcvCvcvnoCvcv</code>, <code>CvcvnoCvcvCvcv</code> or <code>CvcvCvcvCvcvno</code>. Which of those
will be used, depends on the first of the <code>seed</code> bytes. Take the byte value modulo the amount of
pre-set ciphers (in this case, three), and the result tells you which of the three ciphers to use.
</p>
<p>
Now that we know what cipher to use for building our final password, all that's left is to iterate the
cipher, and produce a character of password output for each step. When you iterate the cipher, every
character in the cipher represents a set of possible output characters. For instance, a <code>C</code>
character in the cipher indicates that we need to choose a capital consonant character. An <code>o</code>
character in the cipher indicates that we need to choose an <q>other</q> (symbol) character. Exactly which
character to choose in that set for the password output depends on the next byte from <code>seed</code> bytes.
Like before, take the next unused <code>seed</code> byte's byte value modulo the amount of characters in the
set of possible output characters for the cipher iteration and use the result to choose the output
character. Repeat until you've iterated the whole cipher.
</p>
<p>
The result is a password whose format is dictated by the password type's ciphers and whose exact value is
filled in by feeding the algorithm some bytes from a hash operation on the user's givens.
The way Master Password works internally is <a href="http://masterpassword.lyndir.com/algorithm.html">fully disclosed</a>.
The source code for this application is also available from <a href="https://github.com/Lyndir/MasterPassword">GitHub</a>.
I invite anyone with a technical background to go through these resources to make certain of the trustworthiness of Master Password.
</p>
<h3>This stuff is gold.<br />
<h3 id="branded">This stuff is gold.<br />
I want one branded for our company.</h3>
<p>
Contact me directly for enterprise inquiries. I can provide branded clients and enterprise distribution
if your company is interested in deploying this solution internally.
<a href="mailto:masterpassword@lyndir.com">Contact me</a> directly for enterprise inquiries.
I can provide branded clients and enterprise distribution if your company is interested in deploying this solution internally.
</p>
<p>
Master Password can also be used as a One-Time Password token generator to secure your infrastructure and client access.
</p>
<footer>