* Created Account->encrypt_password() for creating encrypted hashes of passwords. Set the default algorithm to sha256, a ~16 byte random salt and 500,000 re-encryptions (which takes a total of about 0.7 seconds on an Intel i7-6820HQ CPU).

* Added user_algorithm and user_hash_count to the new users database table so that we can remember how a hash was generated, should it be changed down the road.
* Made the salt length configurable by the user (as well as the algorithm and loop count).

Signed-off-by: Digimer <digimer@alteeve.ca>
main
Digimer 7 years ago
parent 9a37f66468
commit d110bff224
  1. 5
      Anvil/Tools.pm
  2. 134
      Anvil/Tools/Account.pm
  3. 2
      Anvil/Tools/Get.pm
  4. 3
      rpm/SPECS/anvil.spec
  5. 1
      share/words.xml
  6. 8
      tools/anvil-change-password
  7. 8
      tools/anvil.sql

@ -720,6 +720,11 @@ sub _set_defaults
host_type => "",
host_uuid => "",
log_file => "/var/log/anvil.log",
password => {
algorithm => "sha512",
hash_count => 500000,
salt_length => 16,
},
terminal => {
columns => 80,
stty => "",

@ -5,13 +5,14 @@ package Anvil::Tools::Account;
use strict;
use warnings;
use Digest::SHA qw(sha256_base64 sha384_base64 sha512_base64);
use Scalar::Util qw(weaken isweak);
our $VERSION = "3.0.0";
my $THIS_FILE = "Account.pm";
### Methods;
#
# encrypt_password
@ -73,6 +74,137 @@ sub parent
# Public methods #
#############################################################################################################
=head2 encrypt_password
This takes a string (a new password from a user), generates a salt, appends the salt to the string and hashes that using C<< sys::password::algorithm >>, the re-hashes the string C<< sys::password::hash_count >> times. The default algorithm is 'sha256' and the default rehashing count is '100,000' times.
This method returns a hash reference with the following keys;
* hash: The final encrypted hash.
* salt: The salt created (or used) to generate the hash.
* algorithm: The algorithm used to compute the hash.
* loops: The number of re-encryptions of the initial hash.
If anything goes wrong, all four keys will have empty strings.
Parameters
=head3 password (required)
This is the password (string) to encrypt.
=head3 salt (optional)
If passed, this string will be appended to the password to salt the string. If this is not passed, a random, new hash
=cut
sub encrypt_password
{
my $self = shift;
my $parameter = shift;
my $anvil = $self->parent;
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
my $password = defined $parameter->{password} ? $parameter->{password} : "";
my $salt = defined $parameter->{salt} ? $parameter->{target} : "";
my $hash = "";
my $loops = $anvil->data->{sys}{password}{hash_count} =~ /^\d+$/ ? $anvil->data->{sys}{password}{hash_count} : 500000;
my $algorithm = $anvil->data->{sys}{password}{algorithm} ? $anvil->data->{sys}{password}{algorithm} : "sha512";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
password => $anvil->Log->secure ? $password : "--",
salt => $salt,
}});
# We'll fill these out below if we succeed.
my $answer = {
hash => "",
salt => "",
loops => "",
algorithm => "",
};
# Make sure we got a string
if (not $password)
{
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Account->encrypt_password()", parameter => "password" }});
return($answer);
}
# If we weren't passed a salt, generate one node.
if (not $salt)
{
$salt = $anvil->Get->_salt;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { salt => $salt }});
}
# We support sha256, sha384 and sha512, possible new ones later.
if ($algorithm eq "sha256" )
{
$hash = sha256_base64($password.$salt);
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $hash }});
if ($loops > 0)
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { loops => $loops }});
for (1..$loops)
{
$hash = sha256_base64($hash);
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $hash }});
}
}
elsif ($algorithm eq "sha384" )
{
$hash = sha384_base64($password.$salt);
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $hash }});
if ($loops > 0)
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { loops => $loops }});
for (1..$loops)
{
$hash = sha384_base64($hash);
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $hash }});
}
}
elsif ($algorithm eq "sha512" )
{
$hash = sha512_base64($password.$salt);
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $hash }});
if ($loops > 0)
{
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { loops => $loops }});
for (1..$loops)
{
$hash = sha512_base64($hash);
}
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { hash => $hash }});
}
}
else
{
# Bash algorith.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0171", variables => { algorithm => $algorithm }});
return($answer);
}
$answer = {
hash => $hash,
salt => $salt,
loops => $loops,
algorithm => $algorithm,
};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
'answer->hash' => $answer->{hash},
'answer->salt' => $answer->{salt},
'answer->loops' => $answer->{loops},
'answer->algorithm' => $answer->{algorithm},
}});
return($answer);
}
1;

@ -821,7 +821,7 @@ sub _salt
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
my $salt = "";
my $salt_length = 16;
my $salt_length = $anvil->data->{sys}{password}{salt_length} =~ /^\d+$/ ? $anvil->data->{sys}{password}{salt_length} : 16;
my @seed = (" ", "~", "`", "!", "#", "^", "&", "*", "(", ")", "-", "_", "+", "=", "{", "[", "}", "]", "|", ":", ";", "'", ",", "<", ".", ">", "/");
my @alpha = ("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z");
my $seed_count = @seed;

@ -30,7 +30,8 @@ Requires: gpm
Requires: mlocate
Requires: perl-Data-Dumper
Requires: perl-DBD-Pg
Requires: perl-DBI
Requires: perl-DBI
Requires: perl-Digest-SHA
Requires: perl-JSON
Requires: perl-Log-Journald
Requires: perl-Net-SSH2

@ -250,6 +250,7 @@ The database connection error was:
<key name="log_0168">Failed to create the directory: [#!variable!directory!#]. The error (if any) was: [#!variable!error!#].</key>
<key name="log_0169">Failed to copy the file: [#!variable!source_file!#] to: [#!variable!target_file!#] on the target: [#!variable!target!#] as: [#!variable!remote_user!#]. The error (if any) was: [#!variable!error!#] and the output (if any) was: [#!variable!output!#].</key>
<key name="log_0170"><![CDATA[[ Note ] - The method Storage->copy_file() was asked to copy: [#!variable!source_file!#] to: [#!variable!target_file!#], but the target's parent directory doesn't exist and we were unable to create it.]]></key>
<key name="log_0171"><![CDATA[[ Error ] - The method: Account->encrypt_password() tried to use the algorithm: [#!variable!algorithm!#], which is not recognized. Only 'sha256', 'sha384' and 'sha512' are currently supported. The desired algorithm can be set via 'sys::password::algorithm'.]]></key>
<!-- Test words. Do NOT change unless you update 't/Words.t' or tests will needlessly fail. -->
<key name="t_0000">Test</key>

@ -48,6 +48,14 @@ if (($< != 0) && ($> != 0))
$anvil->nice_exit({code => 1});
}
use Time::HiRes qw(gettimeofday tv_interval);
my $start_time = [gettimeofday];
print "Start time: [".$start_time->[0].".".$start_time->[1]."]\n";
my $answer = $anvil->Account->encrypt_password({debug => 2, password => "Initial1"});
my $hash_time = tv_interval ($start_time, [gettimeofday]);
print "hash: [".$answer->{hash}."], salt: [".$answer->{salt}."], loops: [".$answer->{loops}."], algorithm: [".$answer->{algorithm}."], time: [".$hash_time."]\n";
exit;
# Connect
my $connections = $anvil->Database->connect();
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, secure => 0, key => "log_0132", variables => { connections => $connections }});

@ -45,6 +45,8 @@ CREATE TABLE users (
user_name text not null,
user_password text, -- A user without a password is disabled.
user_salt text, -- This is used to enhance the security of the user's password.
user_algorithm text, -- This is the algorithm used to encrypt the password and salt.
user_hash_count text, -- This is the number of times that the password+salt was re-hashed through the algorithm.
user_language text, -- If set, this will choose a different language over the default.
user_is_admin boolean not null default false, -- If true, all aspects of the program are available to the user.
user_is_experienced boolean not null default false, -- If true, user is allowed to delete a server, alter disk size, alter hardware and do other potentially risky things. They will also get fewer confirmation dialogues.
@ -59,6 +61,8 @@ CREATE TABLE history.users (
user_name text,
user_password text,
user_salt text,
user_algorithm text,
user_hash_count text,
user_language text,
user_is_admin boolean,
user_is_experienced boolean,
@ -78,6 +82,8 @@ BEGIN
user_name,
user_password,
user_salt,
user_algorithm,
user_hash_count,
user_language,
user_is_admin,
user_is_experienced,
@ -88,6 +94,8 @@ BEGIN
history_users.user_name,
history_users.user_password,
history_users.user_salt,
history_users.user_algorithm,
history_users.user_hash_count,
history_users.user_language,
history_users.user_is_admin,
history_users.user_is_experienced,

Loading…
Cancel
Save