Skip to content

Instantly share code, notes, and snippets.

@jbtule
Last active March 13, 2019 17:01
Show Gist options
  • Save jbtule/7dab1a1031590209bdcfac88ee71cb2b to your computer and use it in GitHub Desktop.
Save jbtule/7dab1a1031590209bdcfac88ee71cb2b to your computer and use it in GitHub Desktop.
haveibeenpwned.com script to check a password
#!/usr/bin/env dotnet-script
/*
* This work (haveibeenpwnded.csx by James Tuley),
* identified by James Tuley, is free of known copyright restrictions
* Source: https://gist.github.com/jbtule/7dab1a1031590209bdcfac88ee71cb2b
* http://creativecommons.org/publicdomain/mark/1.0/
*
* This script uses the Troy Hunt's HaveIBeenPwned.com range api, to search for passwords,
* without revealing what you are searching for.
*
* Requirements:
* .net core 2.1 installed.
* https://dotnet.microsoft.com/download/dotnet-core/2.1
* dotnet-script tool -- it can be installed via `dotnet tool install -g dotnet-script`
* https://github.com/filipw/dotnet-script
*
* Usage:
* linux & mac: ./haveibeenpwned.csx
* windows: dotnet script haveibeenpwned.csx
*/
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
/* ReadPassword
Reads keypresses into secure buffer.
*/
public static SecureString ReadPassword(bool showKeyPress = false) {
var password = new SecureString();
ConsoleKeyInfo nextKey;
var finished = false;
do {
nextKey = Console.ReadKey(intercept:true);
switch (nextKey.Key) {
case ConsoleKey.Backspace:
if (password.Length > 0) {
password.RemoveAt(password.Length - 1);
Console.SetCursorPosition(Console.CursorLeft-1,Console.CursorTop);
Console.Write(' ');
Console.SetCursorPosition(Console.CursorLeft-1,Console.CursorTop);
}
break;
case ConsoleKey.Enter:
finished = true;
Console.WriteLine();
break;
default:
password.AppendChar(nextKey.KeyChar);
Console.Write(showKeyPress ? nextKey.KeyChar : '*');
break;
}
} while(!finished);
password.MakeReadOnly();
return password;
}
/* GetPasswordHash
Tries to make a sha1 hash of a raw password while reducing allocations
Tries to clean up raw password copies afterward
*/
public static string GetPasswordHash() {
Console.Write("Password To Check: ");
//dispose of secure password when finsihed.
using(var securePassword = ReadPassword()) {
//Copies securePassword to unmanaged memory
IntPtr rawPassword = Marshal.SecureStringToGlobalAllocUnicode(securePassword);
try {
var charLength = securePassword.Length;
ReadOnlySpan<char> charPassword;
unsafe {
charPassword = new ReadOnlySpan<char>((void*)rawPassword, charLength);
}
//encode as utf8
var utf8Length = Encoding.UTF8.GetByteCount(charPassword);
Span<byte> utf8Password = stackalloc byte[utf8Length];
//compute sha1 hash
try{
var _ = Encoding.UTF8.GetBytes(charPassword, utf8Password);
using(var sha1Alg = HashAlgorithm.Create("SHA1")) {
var hashLength = sha1Alg.HashSize / 8;
var hashOfPassword = new byte[hashLength];
if (!sha1Alg.TryComputeHash(utf8Password, hashOfPassword, out var _)) {
throw new Exception("Failed to hash Password");
}
//convert to hexadecimal
string HexValue(byte b) => b.ToString("X2");
return String.Concat(Array.ConvertAll(hashOfPassword, HexValue));
}
} finally {
//zeroout utf8 byte password when finished
utf8Password.Clear();
}
} finally {
//zeros out password from unmanaged memory
Marshal.ZeroFreeGlobalAllocUnicode(rawPassword);
}
}
}
public enum Status{
Okay = 0,
Pwned,
Error
}
public static Status Run() {
Console.WriteLine("Have I been Pwned?");
using(var client = new HttpClient()) {
//Using https://haveibeenpwned.com/API/v2#PwnedPasswords
client.BaseAddress = new Uri("https://api.pwnedpasswords.com/range/");
var hash = GetPasswordHash();
var hashPrefix = hash.Substring(0,5);
var hashSuffix = hash.Substring(5);
/* CheckPwned
uses Password range search of haveibeenpwnded
to keep password from being shared with third party.
*/
async Task<Status> CheckPwned(int tries) {
var response = await client.GetAsync(hashPrefix);
if (!response.IsSuccessStatusCode)
{
if (response.StatusCode == HttpStatusCode.TooManyRequests && tries < 5)
{
var retryAfter = response.Headers.RetryAfter.Delta;
if (retryAfter is TimeSpan rA) {
System.Threading.Thread.Sleep(rA.Milliseconds);
return await CheckPwned(tries + 1);
}
}
Console.WriteLine($"Error {response.StatusCode} Checking Site. Network Problem?");
return Status.Error;
}
using (var body = await response.Content.ReadAsStreamAsync())
using (var reader = new StreamReader(body)) {
while (reader.Peek() > -1) {
var foundHash = await reader.ReadLineAsync();
if (foundHash.StartsWith(hashSuffix, StringComparison.OrdinalIgnoreCase)) {
var countString = foundHash.Substring(foundHash.IndexOf(':') + 1);
var count = Int32.Parse(countString);
Console.WriteLine($"Found {count:N0} times! This Password is Pwned!!");
return Status.Pwned;
}
}
}
Console.WriteLine("Not Found! Keep it secret, keep it safe!");
return Status.Okay;
}
return CheckPwned(tries:0).Result; //Call Synchronously
}
}
Environment.Exit((int)Run());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment