Exposed and Insecure Passwords API

Protect your users from credential stuffing.

Credential stuffing is a cyberattack in which credentials obtained from a data breach are used to access user accounts.
SHA1 Prefix 
Checking against our database of more than 300,000,000 exposed password hashes is a secure way to force your users to use stronger passwords.
Note: because the lookup is based on a partial hash there is no way for us to tell what password is being entered.
Under construction.
Work in progress.
Found me!
import hashlib
import requests

_API_KEY = "SIGN-UP-TO-GET-API-KEY"

def is_exposed(password):
    """Returns True if password is exposed False otherwise."""
    password_hash = hashlib.sha1(password.encode('utf-8'))
    prefix = password_hash.hexdigest()[:6]
    suffix = password_hash.hexdigest()[6:]
    url = "https://api.spidey-sense.com/v1/lookup/prefix/%s"
    auth = ("key", _API_KEY)
    r = requests.get(url % prefix, auth=auth)
    suffixes = r.json()["suffixes"]
    return suffix in suffixes

if __name__ == "__main__":
    print (is_exposed("querty"))

require 'digest'
require 'open-uri'
require 'JSON'

SPIDEY_KEY = 'SIGN-UP-TO-GET-API-KEY'.freeze

def exposed?(password)
  password_hash = Digest::SHA1.hexdigest(password)
  prefix = password_hash[0..5]
  suffix = password_hash[6..40]
  resp = open(
    "https://api.spidey-sense.com/v1/lookup/prefix/#{prefix}",
    http_basic_authentication: ['key', SPIDEY_KEY]
  ).read
  JSON.parse(resp)['suffixes'].include?(suffix)
end

puts exposed?('qwerty')

package main

import (
    "crypto/sha1"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

func isExposed(password string) (bool, error) {
    sha1Hash := sha1.Sum([]byte(password))
    prefix := fmt.Sprintf("%x", sha1Hash[0:3])
    suffix := fmt.Sprintf("%x", sha1Hash[3:20])

	key := "SIGN-UP-TO-GET-API-KEY"
	url := fmt.Sprintf("https://key:%s@api.spidey-sense.com/v1/lookup/prefix/%s", key, prefix)

    resp, err := http.Get(url)
    if err != nil {
        return false, err
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return false, err
    }

    r := struct {
        Suffixes []string `json:"suffixes"`
    }{}
    err = json.Unmarshal(body, &r)
    if err != nil {
        return false, err
    }

    for _, s := range r.Suffixes {
        if s == suffix {
            return true, nil
        }
    }
    return false, nil
}

func main() {
    exp, err := isExposed("qwerty")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(exp)
}

< ? php
require_once 'HTTP/Request2.php';
define('_API_KEY', 'SIGN-UP-TO-GET-API-KEY');
function isExposed($pwd) {
  $pwd_hash = sha1($pwd);
  $hash_prefix = substr($pwd_hash, 0, 6);
  $hash_suffix = substr($pwd_hash, 6, strlen($pwd_hash));
  $request = new HTTP_Request2();
  $request->setUrl('https://api.spidey-sense.com/v1/lookup/prefix/'.$hash_prefix);
  $request->setMethod(HTTP_Request2::METHOD_GET);
  $request->setConfig(array(
    'follow_redirects' => TRUE
  ));
  $auth = base64_encode('key:'._API_KEY);
  $request->setHeader(array(
    'Authorization' => 'Basic '.$auth
  ));
  $response = $request->send();
  if ($response->getStatus() == 200) {
          $jsonBody = json_decode($response->getBody());
          $suffixes = $jsonBody->suffixes;
          return in_array($hash_suffix, $suffixes);
  }
  else {
    echo 'Unexpected HTTP status: ' . $response->getStatus() . ' ' .
    $response->getReasonPhrase();
  }
}
if (isExposed('qwerty')) {
  echo 'Oh snap, your password has been exposed'
} else  {
  echo 'All good'
}
const crypto = require('crypto');
const request = require('request');

const _API_KEY = "SIGN-UP-TO-GET-API-KEY"

var isExposed = function(pwd, success, error) {
    shasum = crypto.createHash('sha1');
    shasum.update(pwd);
    pwd_hash = shasum.digest('hex');
    hash_prefix = pwd_hash.slice(0,6);
    hash_suffix = pwd_hash.slice(6, pwd_hash.length) 
    api_key = "SIGN-UP-TO-GET-API-KEY";
    request({ 
            url: `https://api.spidey-sense.com/v1/lookup/prefix/${hash_prefix}`,
            auth: {
                'username':'key',
                'password': _API_KEY
            }},
            (err, _, body) => {
                if (err) { 
                  error(err)
                }
                else {
                    let bodyJson = JSON.parse(body);
                    let suffixes = bodyJson.suffixes;
                    success(suffixes.includes(hash_suffix));
                }
            }
    );
}

isExposed('qwerty', 
    (exposed) => {
        if (exposed) {
            console.log('Oh snap, your password has been exposed');
        }
        else {
            console.log('All clear!');
        }
    },
    (err) => {
        console.log(err);
    });

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Net.Http;
using System.Security.Cryptography;
using System.Net.Http.Headers;


public static async Task IsExposed(string password)
{
    var apiKey = "SIGN-UP-TO-GET-API-KEY"

    bool isExposed = true;
    using(SHA1 sha = new SHA1Managed())
    using(var client = new HttpClient())
    {
        byte[] pwd_bytes = Encoding.UTF8.GetBytes(password);
        var pwdHash = HexStringFromBytes(sha.ComputeHash(pwd_bytes));
        var hashPrefix = pwdHash.Substring(0, 6);
        var hashSuffix = pwdHash.Substring(6);
        client.DefaultRequestHeaders.Authorization = 
            new AuthenticationHeaderValue("Basic",
                                          Convert.ToBase64String(
                                          Encoding.ASCII.GetBytes($"key:" + apiKey)));
        var res = await client.GetAsync("https://api.spidey-sense.com/v1/lookup/prefix/" + prefix);
        var data = await res.Content.ReadAsStringAsync();
        var jsonObject = JsonConvert.DeserializeObject(data);
        var suffixes = jsonObject.suffixes.ToObject>() as List;
        isExposed = suffixes.Any(s => hashSuffix.Equals(s, StringComparison.InvariantCultureIgnoreCase));
    }
    return isExposed;
}

public string HexStringFromBytes(byte[] bytes)
{
    var sb = new StringBuilder();
    foreach (byte b in bytes)
    {
        var hex = b.ToString("x2");
        sb.Append(hex);
    }
    return sb.ToString();
}

var isExposed = await IsExposed("qwerty");

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.util.Base64;
import org.json.JSONArray;
import org.json.JSONObject;


public static Boolean isExposed(String password) {
    HttpURLConnection connection = null;

    try {
        String apiKey = "SIGN-UP-TO-GET-API-KEY";

	    // Hash password and extract 
	    // hash prefix (first 6 characters)
	    // and suffix (the rest of the hash)
        MessageDigest digest = MessageDigest.getInstance("SHA-1");
        digest.reset();
        digest.update(password.getBytes("utf8"));
        String pwdHash = String.format("%040x", new BigInteger(1, digest.digest()));
        String hashPrefix = pwdHash.substring(0, 6);
        String hashSuffix = pwdHash.substring(6, pwdHash.length());

        // Create connection
        URL url = new URL("https://api.spidey-sense.com/v1/lookup/prefix/" + hashPrefix);
        connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");

        String auth = "key:" + apiKey;
        String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());

	    // Add authorization header
        connection.setRequestProperty("Authorization", "Basic " + encodedAuth);

	    // Execute request
        int code = connection.getResponseCode();

        // Get Response
        InputStream is = connection.getInputStream();
        BufferedReader rd = new BufferedReader(new InputStreamReader(is));
        StringBuilder response = new StringBuilder(); // or StringBuffer if Java version 5+
        String line;
        while ((line = rd.readLine()) != null) {
            response.append(line);
            response.append('\r');
        }
        rd.close();
        String res = response.toString();
        JSONObject json = new JSONObject(res);
        JSONArray suffixes = json.getJSONArray("suffixes");
        Boolean isExposed = suffixes.toList().contains(hashSuffix);

        return isExposed;

    } catch (Exception e) {
        e.printStackTrace();
        return false;
    } finally {
        if (connection != null) {
            connection.disconnect();
        }
    }
}

$ prefix=$(echo -n "qwerty"  | sha1sum | awk '{print substr($0,0,6)}')

$ echo $prefix
b1b377

$ curl -s --user 'SIGN-UP-TO-GET-API-KEY' \
  https://api.spidey-sense.com/v1/lookup/prefix/$prefix
{
  "prefix": "b1b377",
  "suffixes": [
    "1fdcc58395495fc20406e23cf916c2bb2f",
    "237a42bf3d2ed2092e1961bb59d48f3c16",
    "2f5d7afe3944d66d018e5c7ee2af28d683",
    "3678f196de938f721cd408ed190330f5db",
    "377ba15b8d5e12fccba32b074d45503d67",
    "387376afd1b3dab553d439c8a7d7cdded1",
    "3a05c0ed0176787a4f1574ff0075f7521e",
    "48186f058da83745b80e70b66d36b216a4",
    "5fec591927a596b6114ed5dac4e4c22e04",
    "6004e5282c5384de32afc2148bad032450",
    "69a96ded7a904fbe8f130508b2bfddaeb1",
    "6b8a2a14a15a8c22a49ec451de9778581a",
    "6c507d6248060841d4b4a4d444947e28a8",
    "82c978c9120cf75be0d93be1330c2705e5",
    "83f271cecc5f9bbc1e56b0585568c80248",
    "8d8bc3ee5c0b06bc8866cdca4bea62adaf",
    "906e1edbd8acdb06bf86fdf6fd9ca4575f",
    "a3a51160df4013ab79a8844e41d1498e3f",
    "ab6cd7920b3cff9f048ae147fc5774ca5b",
    "beeab93b9630b5d308eace498ac7b47cff",
    "c705a7d7ac712135606dc5741b4b3cf0fb",
    "c76fbf9557cf0d36b967a83bb6329765dc",
    "d7618d169131201146aa3b68a12621b066",
    "dd99ff2c2a249ea22db7d1360ccb9ba04c",
    "ecd0e2c0152db98585b54b0161e05d5823",
    "f14f4258243863575cbf33215358357c61",
    "f25bd03816993b76154bbe82fc939cf63a",
    "ff32ecf384a7dbd7f1325f2aa9421747d8",
    "01eee3eb6ce29db12ab39d4e4c1e579372"
  ]
}

Sign up  to get your API key.

Low Latency

Built for performance.

K-Anonymity

We don't want to know your secrets.