File Coverage

lib/Data/Password/Entropy.pm
Criterion Covered Total %
statement 72 72 100.0
branch 23 24 95.8
condition 57 57 100.0
subroutine 10 10 100.0
pod 1 1 100.0
total 163 164 99.3


line stmt bran cond sub pod time code
1             package Data::Password::Entropy;
2             # coding: UTF-8
3              
4 3     3   80098 use utf8;
  3         22  
  3         17  
5 3     3   84 use strict;
  3         5  
  3         74  
6 3     3   12 use warnings;
  3         9  
  3         84  
7              
8 3     3   3304 use Encode;
  3         36848  
  3         277  
9 3     3   2614 use POSIX qw(floor);
  3         27580  
  3         21  
10              
11             our $VERSION = '0.08';
12              
13             # ==============================================================================
14              
15 3     3   3639 use Exporter;
  3         6  
  3         122  
16 3     3   15 use base qw(Exporter);
  3         7  
  3         475  
17             our @EXPORT = qw(
18             password_entropy
19             );
20             # ==============================================================================
21             use constant {
22 3         1302 CONTROL => 0,
23             NUMBER => 1,
24             UPPER => 2,
25             LOWER => 3,
26             PUNCT_1 => 4,
27             PUNCT_2 => 5,
28             EXTENDED => 6,
29 3     3   18 };
  3         4  
30              
31             my @CHAR_CLASSES;
32             my %CHAR_CAPACITY;
33              
34             BEGIN
35             {
36 3     3   11 for my $i (0..255) {
37 768         1189 my $cclass = 0;
38              
39 768 100 100     7614 if ($i < 32) {
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
40 96         122 $cclass = CONTROL;
41             }
42             elsif ($i >= ord('0') && $i <= ord('9')) {
43 30         30 $cclass = NUMBER;
44             }
45             elsif ($i >= ord('A') && $i <= ord('Z')) {
46 78         80 $cclass = UPPER;
47             }
48             elsif ($i >= ord('a') && $i <= ord('z')) {
49 78         80 $cclass = LOWER;
50             }
51             elsif ($i > 127) {
52 384         404 $cclass = EXTENDED;
53             }
54             elsif (
55             # Simple punctuation marks, which can be typed with first row of keyboard or numpad
56             $i == 32 || # space
57             $i == ord('!') || # 33
58             $i == ord('@') || # 64
59             $i == ord('#') || # 35
60             $i == ord('$') || # 36
61             $i == ord('%') || # 37
62             $i == ord('^') || # 94
63             $i == ord('&') || # 38
64             $i == ord('*') || # 42
65             $i == ord('(') || # 40
66             $i == ord(')') || # 41
67             $i == ord('_') || # 95
68             $i == ord('+') || # 43
69             $i == ord('-') || # 45
70             $i == ord('=') || # 61
71             $i == ord('/') # 47
72             ) {
73 48         51 $cclass = PUNCT_1;
74             }
75             else {
76             # Other punctuation marks
77 54         57 $cclass = PUNCT_2;
78             }
79              
80 768         967 $CHAR_CLASSES[$i] = $cclass;
81 768 100       1151 if (!$CHAR_CAPACITY{$cclass}) {
82 21         54 $CHAR_CAPACITY{$cclass} = 1;
83             }
84             else {
85 747         3521 $CHAR_CAPACITY{$cclass}++;
86             }
87             }
88             }
89             # ==============================================================================
90             sub password_entropy($)
91             {
92 16     16 1 9368 my ($passw) = @_;
93              
94 16         32 my $entropy = 0;
95              
96 16 100 100     103 if (defined($passw) && $passw ne '') {
97              
98             # Convert to octets
99 14         45 $passw = Encode::encode_utf8($passw);
100              
101 14         133 my $classes = +{};
102              
103 14         18 my $eff_len = 0.0; # the effective length
104 14         17 my $char_count = +{}; # to count characters quantities
105 14         20 my $distances = +{}; # to collect differences between adjacent characters
106              
107 14         25 my $len = length($passw);
108              
109 14         14 my $prev_nc = 0;
110              
111 14         38 for (my $i = 0; $i < $len; $i++) {
112 244         349 my $c = substr($passw, $i, 1);
113 244         267 my $nc = ord($c);
114 244         448 $classes->{$CHAR_CLASSES[$nc]} = 1;
115              
116 244         247 my $incr = 1.0; # value/factor for increment effective length
117              
118 244 100       467 if ($i > 0) {
119 230         257 my $d = $nc - $prev_nc;
120              
121 230 100       468 if (exists($distances->{$d})) {
122 113         149 $distances->{$d}++;
123 113         177 $incr /= $distances->{$d};
124             }
125             else {
126 117         260 $distances->{$d} = 1;
127             }
128             }
129              
130 244 100       471 if (exists($char_count->{$c})) {
131 123         136 $char_count->{$c}++;
132 123         208 $eff_len += $incr * (1.0 / $char_count->{$c});
133             }
134             else {
135 121         208 $char_count->{$c} = 1;
136 121         158 $eff_len += $incr;
137             }
138              
139 244         551 $prev_nc = $nc;
140             }
141              
142 14         18 my $pci = 0; # Password complexity index
143 14         54 for (keys(%$classes)) {
144 31         61 $pci += $CHAR_CAPACITY{$_};
145             }
146              
147 14 50       39 if ($pci != 0) {
148 14         51 my $bits_per_char = log($pci) / log(2.0);
149 14         128 $entropy = floor($bits_per_char * $eff_len);
150             }
151             }
152              
153 16         91 return $entropy;
154             }
155             # ==============================================================================
156             1;
157             __END__