#!/usr/bin/env perl ## ## Author......: See docs/credits.txt ## License.....: MIT ## use strict; use warnings; use Convert::EBCDIC qw (ascii2ebcdic); use Digest::HMAC qw (hmac); use Digest::SHA qw(hmac_sha256); use Crypt::PBKDF2; use Crypt::DES; use Crypt::Rijndael; sub module_constraints { [[0, 8], [1, 8], [-1, -1], [-1, -1], [-1, -1]] } sub prepare_hmac_key { my ($username, $password) = @_; $username = substr ($username . " " x 8, 0, 8); $password = substr ($password . " " x 8, 0, 8); my $username_ebc = ascii2ebcdic ($username); my $password_ebc = ascii2ebcdic ($password); my @pw = split ("", $password_ebc); # split by char for (my $i = 0; $i < 8; $i++) { $pw[$i] = unpack ("C", $pw[$i]); $pw[$i] ^= 0x55; $pw[$i] <<= 1; $pw[$i] = pack ("C", $pw[$i] & 0xff); } my $key = join ("", @pw); my $cipher = new Crypt::DES $key; my $ciphertext = $cipher->encrypt ($username_ebc); return $ciphertext; } sub prepare_aes_key { my $mem_fac = (2 << (shift() - 1)) / 32; my $rep_fac = shift; my $hmac_key = shift; my $data_hex = shift; my $msg = pack ("H32", $data_hex) . pack('N', $mem_fac) . pack('N', 1); my $mem_buf = ""; # step 1: proprietary PBKDF2-HMAC-SHA256 (prepare $mem_buf) for my $n (0 .. $mem_fac - 1) { my $u_current = hmac_sha256($msg, $hmac_key); my $f_res = $u_current; my $h_prev = $u_current; # recalc hmac for my $i (0 .. $rep_fac * 100 - 2) { $h_prev = $u_current; $u_current = hmac_sha256($u_current, $hmac_key); my $f_res_tmp = ''; for my $j (0 .. length($f_res) - 1) { $f_res_tmp .= chr(ord(substr($f_res, $j, 1)) ^ ord(substr($u_current, $j, 1))); } $f_res = $f_res_tmp; } $msg = substr ($h_prev, 0, 16) . $f_res . pack('N', 1); $mem_buf .= $f_res } # step 2: mem_buf substitutions # Set new HMAC key (last block from mem_buf) my $mem_buf_len = length ($mem_buf); $hmac_key = substr ($mem_buf, $mem_buf_len - 32, 32); # Substitutions for my $n (0 .. $mem_fac - 1) { my $n_key = unpack ('N', substr ($hmac_key, 28, 4) ) & ($mem_fac - 1); my $mem_buf_blk = substr ($mem_buf, $n_key * 32, 32) . pack('N', 1); $mem_buf_blk = hmac_sha256($mem_buf_blk, $hmac_key); $mem_buf = substr ($mem_buf, 0, $n * 32) . $mem_buf_blk . substr ($mem_buf, ($n+1) * 32, $mem_buf_len - ($n+1) * 32); $hmac_key = $mem_buf_blk; } # step 3: PBKDF2-HMAC-SHA256(mem_buf, hmac_key, rep_fac) my $pbkdf2 = Crypt::PBKDF2->new ( hasher => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256), iterations => $rep_fac*100, output_len => 32 ); $msg = substr ($mem_buf, 0, ($mem_fac-1) * 32); my $aes_key = $pbkdf2->PBKDF2 ($msg, $hmac_key); return $aes_key; } sub module_generate_hash { my $word = shift; my $username = shift; my $mem_fac = shift // 0x08; my $rep_fac = shift // 0x32; my $salt_data = shift // uc random_hex_string (32); my $hmac_key = prepare_hmac_key (uc $username, $word); my $aes_key = prepare_aes_key ($mem_fac, $rep_fac, $hmac_key, $salt_data); my $plaint = ascii2ebcdic (substr (uc $username . " " x 8, 0, 8)) . "\x00" x 8; my $rijndael = Crypt::Rijndael->new($aes_key, Crypt::Rijndael::MODE_ECB()); my $ciphertext = $rijndael->encrypt($plaint); my $hash = sprintf ('$racf-kdfaes$*%s*E7D7E66D00018000%04X%04X00100010*%s*%s', uc $username, $mem_fac, $rep_fac, uc $salt_data, uc unpack("H32", $ciphertext)); return $hash; } sub module_verify_hash { my $line = shift; my @line_elements = split (":", $line); return if scalar @line_elements < 2; my $hash_in = shift @line_elements; my $word = join (":", @line_elements); # check signature my @hash_elements = split ('\*', $hash_in); return unless ($hash_elements[0] eq '$racf-kdfaes$'); my $username = $hash_elements[1]; my $mem_fac = hex (substr ($hash_elements[2], 16, 4)); my $rep_fac = hex (substr ($hash_elements[2], 20, 4)); my $salt = $hash_elements[3]; return unless defined $word; return unless defined $username; return unless defined $mem_fac; return unless defined $rep_fac; return unless defined $salt; $word = pack_if_HEX_notation ($word); my $new_hash = module_generate_hash ($word, $username, $mem_fac, $rep_fac, $salt); return ($new_hash, $word); } 1;