// convert an unsigned int to hex string const toHex = b => ( '00' + b.toString( 16 ) ).slice( -2 ); // return the SHA-1 hash of a given string // note: crypto.subtle is unavailable on non-HTTPS pages const sha1 = async str => { const msg = new TextEncoder('utf-8').encode( str ); const buf = await crypto.subtle.digest( 'SHA-1', msg ); return [ ...new Uint8Array( buf ) ].map( toHex ).join(''); }; // get a Map of hibp hash suffixes (suffix => count) const getMatches = async prefix => { const url = `https://api.pwnedpasswords.com/range/${ prefix }`; const res = await fetch( url ); const txt = await res.text(); return txt.split('\n').reduce( ( map, line ) => { const [ suffix, count ] = line.split(':'); return map.set( suffix.toLowerCase(), Number( count ) ); }, new Map ); }; // check how many matches hibp has for a password // (0 is good, everything else is obviously not) const checkPassword = async password => { const hash = await sha1( password ); const prefix = hash.substr( 0, 5 ); const suffix = hash.substr( 5 ); const matches = await getMatches( prefix ); return matches.get( suffix ) || 0; }; // usage checkPassword('badpassword').then( console.log.bind( console ) );