brute-force_resistant_hashing.pl 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. #!/usr/bin/perl
  2. # Author: Trizen
  3. # Date: 21 December 2021
  4. # https://github.com/trizen
  5. # A concept for a brute-force resistant hashing method.
  6. # It requires a deterministic hash function, which is used in computing a
  7. # non-deterministic brute-force resistant hash, based on the processor speed
  8. # of the computer, taking about 2 seconds to hash a password, and about 1.5 seconds
  9. # to verify if the hash of a password is correct, given the password and the hash.
  10. # The method can be made deterministic, by providing a fixed number of iterations.
  11. # Otherwise, the method automatically computes a safe number of iterations based on hardware speed.
  12. # See also:
  13. # https://en.wikipedia.org/wiki/Bcrypt
  14. # https://en.wikipedia.org/wiki/Argon2
  15. use 5.020;
  16. use strict;
  17. use warnings;
  18. use Digest::SHA qw(sha512_hex);
  19. use experimental qw(signatures);
  20. sub bfr_hash ($password, $hash_function, $iterations = undef) {
  21. my $strength = 1; # delay time in seconds
  22. my $salt_hash = $hash_function->('');
  23. my $pass_hash = $hash_function->($password);
  24. my $hash_password = sub {
  25. $salt_hash = $hash_function->($salt_hash);
  26. $pass_hash = $hash_function->($salt_hash . $pass_hash);
  27. #$pass_hash = $hash_function->($pass_hash . $salt_hash);
  28. };
  29. if (defined $iterations) {
  30. for (1 .. $iterations) {
  31. $hash_password->();
  32. }
  33. }
  34. else {
  35. $iterations = 0;
  36. eval {
  37. local $SIG{ALRM} = sub { die "alarm\n" };
  38. alarm $strength;
  39. while (1) {
  40. $hash_password->();
  41. ++$iterations;
  42. }
  43. alarm 0;
  44. };
  45. say "[DEBUG] Iterations: $iterations";
  46. return __SUB__->($password, $hash_function, $iterations);
  47. }
  48. my $check_hash = $hash_function->($pass_hash . $salt_hash);
  49. return join('$', $pass_hash, $salt_hash, $check_hash);
  50. }
  51. sub check_bfr_hash ($password, $bfr_hash, $hash_function) {
  52. my ($pass_hash, $salt_hash, $check_hash) = split(/\$/, $bfr_hash);
  53. $salt_hash // return 0;
  54. $pass_hash // return 0;
  55. $check_hash // return 0;
  56. if ($hash_function->($pass_hash . $salt_hash) ne $check_hash) {
  57. return 0;
  58. }
  59. my $iterations = 0;
  60. my $hash = $hash_function->('');
  61. while (1) {
  62. $hash = $hash_function->($hash);
  63. ++$iterations;
  64. last if ($hash eq $salt_hash);
  65. }
  66. if (bfr_hash($password, $hash_function, $iterations) eq $bfr_hash) {
  67. return 1;
  68. }
  69. return 0;
  70. }
  71. my $password1 = 'foo';
  72. my $password2 = 'bar';
  73. my $hash1 = bfr_hash($password1, \&sha512_hex);
  74. my $hash2 = bfr_hash($password2, \&sha512_hex);
  75. say qq{bfr_hash("$password1", sha512) = $hash1};
  76. say qq{bfr_hash("$password2", sha512) = $hash2};
  77. say check_bfr_hash($password1, $hash1, \&sha512_hex); #=> 1
  78. say check_bfr_hash($password2, $hash2, \&sha512_hex); #=> 1
  79. say check_bfr_hash($password1, $hash2, \&sha512_hex); #=> 0
  80. say check_bfr_hash($password2, $hash1, \&sha512_hex); #=> 0
  81. __END__
  82. bfr_hash("foo", sha512) = d0cd2ed4ef19e55ea8d69212417e21d5723e41a716f74fea2bbc7d8e114108d1a439c763b2673c2e79ccc684b7558d42956982d6396abd6bcd99aca30b516787$a65bff3e58823c51d7a4a44bcebc8f5c8ba148e3eea81fc017ecd20eb94b5892f2112e397a48e5185ab500051ec285a0a9d104a6eed4828d04cc0661c0ea1885$03061c61439174d1a4f8f3fa73e53ff9b9480f02afa270544aaeacfc6cc08db27742f2d3721edc13a4cefabb0accbf476ef6c9596932fc81816c018e8fd6ca6e
  83. bfr_hash("bar", sha512) = 3eefe86bfbc36d7099625a3b3ab741c373435ab873d841eccbf9db465637b0c7a7e612cbc65fda0a9333c2065d10cbcb8120a8271b932234849753f899c4c396$906e9a62689d2bc012ff83f777432a2b1235faeff01a582d1fb3eb6b5201f1bca4174a4a983b6951fb211936d2040468c2a695f7b74ad45dcb76789ef267b9a9$e5bc95297be88c0b8003c731a052968ed6c2c75fceea2844e2584fdd05ae97ffa1795dc7f73e6b9c9c7c91d294dc7f435d687221fbf945d6d590fce7f54fcf7d