NAME

Password::Policy::Rule::Pwned - Check passwords haven't been pwned

SYNOPSIS

use Password::Policy;
use Try::Tiny;

my $pass = 'password1';

my $pp = Password::Policy->new (config => 'policy.yaml');
try {
    $pp->process({ password => $pass });
} catch {
    if ($_->isa ("Password::Policy::Exception::PwnedError")) {
        warn "Unable to verify pwned status - try again later\n";
        return;
    }
    warn "'$pass' failed checks: $_ - don't use it\n";
    # Other actions
}

DESCRIPTION

Plug this rule into Password::Policy to validate potential passwords against the list from haveibeenpwned.com. It uses the recommended range function to ensure that neither the password nor its full hash is ever transferred over the wire.

The Password::Policy configuration file should set the "pwned" attribute to 1 in any policy where this rule should apply. A trivial example of such a policy might be:

---
default:
    length: 8
    algorithm: "Plaintext"
    pwned: 1

As with all other Password::Policy::Rule types, this will throw an exception to indicate an unsafe password. As it relies on a network service to operate it will also throw an exception if the service is unavailable for whatever reason. The two exceptions are different and may be interrogated to determine the difference.

try {
    $pp->process({ password => $pass });
} catch {
    if ($_->isa ("Password::Policy::Exception::Pwned")) {
        warn "This password '$pass' is pwned - don't use it";
    } elsif ($_->isa ("Password::Policy::Exception::PwnedError")) {
        warn "Could not check if password is pwned - use at own risk";
    } else {
        warn "Password not pwned but still bad: $_";
    }
    # Other actions
}

Alternatively the response may be stringified and the messages parsed for key phrases, although this will be less robust.

try {
    $pp->process({ password => $pass });
} catch {
    if (/has been pwned/) {
        warn "This password '$pass' is pwned - don't use it";
    } elsif (/Invalid response/) {
        warn "Could not check if password is pwned - use at own risk";
    } else {
        warn "Password not pwned but still bad: $_";
    }
    # Other actions
}

METHODS

check

$rule->check ($clearpw);

This method is not expected to be called directly but rather via Password::Policy->process. It takes one argument which is the password to be checked. The password must be either an encoded utf-8 bytestring or an unencoded string (which will be encoded internally prior to hashing).

The method will throw a Password::Policy::Exception::Pwned exception if the password is pwned. If the API server is unavailable it will warn and then throw a Password::Policy::Exception::PwnedError exception. It will return true if the password is verifiably not pwned.

DATA SOURCE

Note that this code is merely a user-friendly API client. It relies entirely upon the data held at api.pwnedpasswords.com and which is made available free of charge to end users such as your good self. If this data is useful to you then please consider making a donation to help fund this service and allow Troy's good work to continue.

SEE ALSO

To understand how to use this as part of a wider password policy enforcement program, see Password::Policy.

REPOSITORY

https://gitlab.com/openstrike/password-pwned

MAINTAINER

This module is written and maintained by Pete Houston of Openstrike <cpan@openstrike.co.uk>

COPYRIGHT

Copyright 2018, 2024 by Pete Houston. All Rights Reserved.

Permission to use, copy, and distribute is hereby granted, providing that the above copyright notice and this permission appear in all copies and in supporting documentation.

LICENCE

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

This means that you can, at your option, redistribute it and/or modify it under either the terms of the GNU Public License (GPL) version 1 or later, or under the Perl Artistic License.

See https://dev.perl.org/licenses/