Skip to content

Commit

Permalink
Support for password references
Browse files Browse the repository at this point in the history
  • Loading branch information
mihaifm committed Jan 21, 2019
1 parent c86ca43 commit 51e5abd
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 72 deletions.
118 changes: 54 additions & 64 deletions HIBPOfflineCheckExt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
using System.Diagnostics;
using KeePassLib.Utility;
using KeePass.Util;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using KeePass.Util.Spr;

Expand All @@ -37,13 +36,6 @@ public override bool Initialize(IPluginHost host)
if (host == null) return false;

PluginHost = host;

ToolStripItemCollection tsMenu = PluginHost.MainWindow.ToolsMenu.DropDownItems;
tsMenu.Add(new ToolStripSeparator());
ToolStripMenuItem tsMenuItem = new ToolStripMenuItem("HIBP Offline Check...");
tsMenuItem.Click += new EventHandler(ToolsMenuItemClick);
tsMenu.Add(tsMenuItem);

prov = new HIBPOfflineColumnProv() { Host = host };

options = LoadOptions();
Expand All @@ -54,6 +46,18 @@ public override bool Initialize(IPluginHost host)
return true;
}

public override ToolStripMenuItem GetMenuItem(PluginMenuType t)
{
if (t == PluginMenuType.Main)
{
ToolStripMenuItem tsMenuItem = new ToolStripMenuItem("HIBP Offline Check...");
tsMenuItem.Click += new EventHandler(ToolsMenuItemClick);
return tsMenuItem;
}

return null;
}

private void ToolsMenuItemClick(object sender, EventArgs e)
{
HIBPOfflineCheckOptions optionsForm = new HIBPOfflineCheckOptions(this);
Expand Down Expand Up @@ -183,14 +187,14 @@ public override bool SupportsCellAction(string strColumnName)
return (strColumnName == PluginOptions.ColumnName);
}

[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Dispose is idempotent")]
private void GetPasswordStatus()
{
var pwd_sha_str = String.Empty;

using (var sha1 = new SHA1CryptoServiceProvider())
{
var password = SprEngine.Compile(PasswordEntry.Strings.GetSafe(PwDefs.PasswordField).ReadString(), new SprContext(PasswordEntry,Host.Database, SprCompileFlags.All));
var context = new SprContext(PasswordEntry, Host.Database, SprCompileFlags.All);
var password = SprEngine.Compile(PasswordEntry.Strings.GetSafe(PwDefs.PasswordField).ReadString(), context);

var pwd_sha_bytes = sha1.ComputeHash(UTF8Encoding.UTF8.GetBytes(password));
var sb = new StringBuilder(2 * pwd_sha_bytes.Length);
Expand All @@ -210,75 +214,61 @@ private void GetPasswordStatus()
return;
}

FileStream fs = null;
StreamReader sr = null;

try
using (FileStream fs = File.OpenRead(latestFile))
using (StreamReader sr = new StreamReader(fs))
{
fs = File.OpenRead(latestFile);
sr = new StreamReader(fs);

string line;
Status = PluginOptions.SecureText;
int sha_len = pwd_sha_str.Length;

var low = 0L;
var high = fs.Length;

while (low <= high)
try
{
var middle = (low + high + 1) / 2;
fs.Seek(middle, SeekOrigin.Begin);
string line;
Status = PluginOptions.SecureText;
int sha_len = pwd_sha_str.Length;

// Resync with base stream after seek
sr.DiscardBufferedData();
var low = 0L;
var high = fs.Length;

line = sr.ReadLine();
while (low <= high)
{
var middle = (low + high + 1) / 2;
fs.Seek(middle, SeekOrigin.Begin);

if (sr.EndOfStream) break;
// Resync with base stream after seek
sr.DiscardBufferedData();

// We may have read only a partial line so read again to make sure we get a full line
if (middle > 0) line = sr.ReadLine() ?? String.Empty;
line = sr.ReadLine();

int compare = String.Compare(pwd_sha_str, line.Substring(0, sha_len), StringComparison.Ordinal);
if (sr.EndOfStream) break;

if (compare < 0)
{
high = middle - 1;
}
else if (compare > 0)
{
low = middle + 1;
}
else
{
string[] tokens = line.Split(':');
Status = PluginOptions.InsecureText;
insecureWarning = true;
// We may have read only a partial line so read again to make sure we get a full line
if (middle > 0) line = sr.ReadLine() ?? String.Empty;

if (PluginOptions.BreachCountDetails)
int compare = String.Compare(pwd_sha_str, line.Substring(0, sha_len), StringComparison.Ordinal);

if (compare < 0)
{
high = middle - 1;
}
else if (compare > 0)
{
Status += " (password count: " + tokens[1].Trim() + ")";
low = middle + 1;
}
else
{
string[] tokens = line.Split(':');
Status = PluginOptions.InsecureText;
insecureWarning = true;

break;
if (PluginOptions.BreachCountDetails)
{
Status += " (password count: " + tokens[1].Trim() + ")";
}

break;
}
}
}
}
catch
{
Status = "Failed to read HIBP file";
}
finally
{
if (sr != null)
{
sr.Dispose();
}

if (fs != null)
catch
{
fs.Dispose();
Status = "Failed to read HIBP file";
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.3.1.0")]
[assembly: AssemblyFileVersion("1.3.1.0")]
[assembly: AssemblyVersion("1.3.2.0")]
[assembly: AssemblyFileVersion("1.3.2.0")]
10 changes: 5 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,26 @@ for all selected passwords.
While it does provide an API for securely checking the passwords online, some bits of a hashed password still need
to be sent to the service when performing this type of check.

This plugin offers the alternative of an offline check, by using the 30GB file provided by [Have I been pwned?](https://haveibeenpwned.com/).
This plugin offers the alternative of an offline check, by using the downloadable file provided by [Have I been pwned?](https://haveibeenpwned.com/).

The plugin adds a new column to KeePass. When double-clicking the column for a specific entry, the sha1 hash is calculated for the password,
which is then searched in the file. A status will be displayed on the column for that specific password.

## Features

- binary search in the 30GB file gives an instant result
- binary search in the large password file gives an instant result
- the status (Pwned or Secure) is saved in the KeePass database and will be retrieved when reopening the app, and updated if the password entry changes
- each password is individually checked only on user request
- multiple passwords can be checked in bulk by using the right click menu (under "Selected Entries")

## Prerequisites

- Download the [pwned-passwords-ordered-by-hash.txt](https://downloads.pwnedpasswords.com/passwords/pwned-passwords-ordered-by-hash.7z.torrent) file from
- Download the [pwned-passwords-sha1-ordered-by-hash-v4.txt](https://haveibeenpwned.com/Passwords) file from
haveibeenpwned.com [password list](https://haveibeenpwned.com/Passwords). Use the torrent if possible, as suggested by the author.

__It's important that you get the -ordered-by-hash- version of the file, the plugin uses it for fast searching__.
__It's important that you get the SHA-1 (ordered by hash) version of the file, the plugin uses it for fast searching__.
- Extract the file from the 7zip archive
- Place the `pwned-passwords-ordered-by-hash.txt` in the same location as `KeePass.exe`
- Place the `pwned-passwords-sha1-ordered-by-hash-v4.txt` file in the same location as `KeePass.exe` (file location is configurable in the options)

## Installation

Expand Down
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
:
HIBPOfflineCheck:1.3.1.0
HIBPOfflineCheck:1.3.2.0
:

0 comments on commit 51e5abd

Please sign in to comment.