File Coverage

blib/lib/Crypt/Juniper.pm
Criterion Covered Total %
statement 69 69 100.0
branch 7 8 87.5
condition 5 5 100.0
subroutine 11 11 100.0
pod 2 2 100.0
total 94 95 98.9


line stmt bran cond sub pod time code
1             package Crypt::Juniper;
2              
3 4     4   114584 use warnings;
  4         9  
  4         116  
4 4     4   22 use strict;
  4         8  
  4         128  
5 4     4   24 use Carp;
  4         10  
  4         372  
6              
7 4     4   21 use base 'Exporter';
  4         19  
  4         4534  
8             our @EXPORT = qw( juniper_encrypt juniper_decrypt );
9              
10             =head1 NAME
11              
12             Crypt::Juniper - Encrypt/decrypt Juniper $9$ secrets
13              
14             =head1 VERSION
15              
16             Version 0.02
17              
18             =cut
19              
20             our $VERSION = '0.02';
21              
22              
23             =head1 SYNOPSIS
24              
25             use Crypt::Juniper;
26             my $secret = juniper_decrypt('$9$LbHX-wg4Z'); ## $secret="lc";
27             my $crypt = juniper_encrypt('lc'); ## encrypt it
28              
29             =cut
30              
31             #################################################################
32             ## globals
33              
34             my $MAGIC = q{$9$};
35              
36             ###################################
37             ## letter families
38              
39             my @FAMILY = qw[ QzF3n6/9CAtpu0O B1IREhcSyrleKvMW8LXx 7N-dVbwsY2g4oaJZGUDj iHkq.mPf5T ];
40             my %EXTRA;
41              
42             for my $fam (0..$#FAMILY)
43             {
44             for my $c (split //, $FAMILY[$fam])
45             {
46             $EXTRA{$c} = (3-$fam);
47             }
48             }
49              
50             my $VALID = do {
51             my $letters = join '', @FAMILY;
52             my $end = "[$letters]{4,}\$";
53             $end =~ s/-/\\-/;
54             qr/^\Q$MAGIC\E$end/;
55             };
56              
57             ###################################
58             ## forward and reverse dictionaries
59              
60             my @NUM_ALPHA = split //, join '', @FAMILY;
61             my %ALPHA_NUM = map { $NUM_ALPHA[$_] => $_ } 0..$#NUM_ALPHA;
62              
63             ###################################
64             ## encoding moduli by position
65              
66             my @ENCODING = (
67             [ 1, 4, 32 ],
68             [ 1, 16, 32 ],
69             [ 1, 8, 32 ],
70             [ 1, 64 ],
71             [ 1, 32 ],
72             [ 1, 4, 16, 128 ],
73             [ 1, 32, 64 ],
74             );
75              
76             #################################################################
77              
78             =head1 EXPORTED FUNCTIONS
79              
80             =head2 juniper_decrypt($crypt)
81              
82             Decrypt the string C<$crypt>, returning the corresponding plain-text.
83             Input string must be of the format "$9$blahblah". This function will
84             die() if there any processing errors.
85              
86             =cut
87              
88             sub juniper_decrypt {
89 3690     3690 1 1246310 my ($crypt) = @_;
90              
91 3690 100 100     38284 croak "Invalid Juniper crypt string!"
92             unless (defined $crypt and $crypt =~ $VALID);
93              
94 3686         21985 my ($chars) = $crypt =~ /^\Q$MAGIC\E(\S+)/;
95              
96 3686         8648 my $first = _nibble(\$chars, 1);
97 3686         9964 _nibble(\$chars, $EXTRA{$first});
98              
99 3686         4645 my $prev = $first;
100 3686         4419 my $decrypt = '';
101              
102 3686         8680 while ($chars)
103             {
104 69830         105142 my $decode = $ENCODING[ length($decrypt) % @ENCODING ];
105 69830         83657 my $len = @$decode;
106              
107 69830         130020 my @nibble = split //, _nibble(\$chars, $len);
108 69828         119912 my @gaps = map { my $g = _gap($prev, $_); $prev = $_ ; $g } @nibble;
  200080         280945  
  200080         229474  
  200080         307565  
109              
110 69828         120122 $decrypt .= _gap_decode(\@gaps, $decode);
111             }
112              
113 3684         16184 return $decrypt;
114             }
115              
116             sub _nibble {
117 77202     77202   88739 my ($cref, $len) = @_;
118 77202         132637 my $nib = substr($$cref, 0, $len, '');
119 77202 100       154444 length($nib) == $len
120             or croak "Ran out of characters: hit '$nib', expecting $len chars";
121 77200         237289 return $nib;
122             }
123              
124             ###################################
125             ## calculate the distance between two characters
126             sub _gap {
127 200080     200080   243529 my ($c1, $c2) = @_;
128              
129 200080         416556 return ($ALPHA_NUM{$c2} - $ALPHA_NUM{$c1}) % @NUM_ALPHA - 1;
130             };
131              
132             ###################################
133             ## given a series of gaps and moduli, calculate the resulting plaintext
134             sub _gap_decode {
135 69828     69828   77793 my ($gaps, $dec) = @_;
136 69828         68081 my $num = 0;
137 69828 50       147088 @$gaps == @$dec or die "Nibble and decode size not the same!";
138 69828         119217 for (0..$#$gaps)
139             {
140 200080         311432 $num += $gaps->[$_] * $dec->[$_];
141             }
142 69828         277753 chr( $num % 256 );
143             }
144              
145             =head2 juniper_encrypt($secret)
146              
147             Encrypt the plain text C<$secret>, returning a result suitable for
148             inclusion in a Juniper configuration.
149              
150             =cut
151              
152             sub juniper_encrypt {
153 3684     3684 1 743589 my ($plain, $salt) = @_;
154              
155 3684 100       9649 defined $salt or $salt = _randc(1);
156 3684         8161 my $rand = _randc($EXTRA{$salt});
157              
158 3684         4735 my $pos = 0;
159 3684         4105 my $prev = $salt;
160 3684         5862 my $crypt = "$MAGIC$salt$rand";
161              
162 3684         21347 for my $p (split //, $plain)
163             {
164 69815         104029 my $encode = $ENCODING[ $pos % @ENCODING ];
165 69815         112919 $crypt .= _gap_encode($p, $prev, $encode);
166 69815         103633 $prev = substr($crypt, -1);
167 69815         96364 $pos++;
168             }
169              
170 3684         16954 return $crypt;
171             }
172              
173             ## return a random number of characters from our alphabet
174             sub _randc {
175 4684   100 4684   12105 my $cnt = shift || 0;
176 4684         5945 my $r = '';
177              
178 4684         25427 $r .= $NUM_ALPHA[ int rand $#NUM_ALPHA ]
179             while ($cnt-- > 0);
180              
181 4684         10544 $r;
182             }
183              
184             ## encode a plain-text character with a series of gaps,
185             ## according to the current encoder.
186             sub _gap_encode {
187 69815     69815   101941 my ($pc, $prev, $enc) = @_;
188 69815         80341 my $ord = ord($pc);
189              
190 69815         74242 my $crypt = '';
191 69815         62013 my @gaps;
192              
193 69815         91148 for my $mod (reverse @$enc)
194             {
195 200043         273231 unshift @gaps, int($ord/$mod);
196 200043         274607 $ord %= $mod;
197             }
198              
199 69815         103115 for my $gap (@gaps)
200             {
201 200043         237127 $gap += $ALPHA_NUM{$prev} + 1;
202 200043         275187 my $c = $prev = $NUM_ALPHA[ $gap % @NUM_ALPHA ];
203 200043         309056 $crypt .= $c;
204             }
205              
206 69815         153802 return $crypt;
207             }
208              
209             =head1 AUTHOR
210              
211             kevin brintnall, C<< >>
212              
213             =head1 COPYRIGHT & LICENSE
214              
215             Copyright 2008 kevin brintnall, all rights reserved.
216              
217             This program is free software; you can redistribute it and/or modify it
218             under the same terms as Perl itself.
219              
220             =cut
221              
222             1; # End of Crypt::Juniper