make crypt programs package variables
[chaz/groupsecret] / lib / App / GroupSecret / Crypt.pm
1 package App::GroupSecret::Crypt;
2 # ABSTRACT: Collection of crypto-related subroutines
3
4 use warnings;
5 use strict;
6
7 our $VERSION = '9999.999'; # VERSION
8
9 use Exporter qw(import);
10 use File::Temp;
11 use IPC::Open2;
12 use namespace::clean -except => [qw(import)];
13
14 our @EXPORT_OK = qw(
15 generate_secure_random_bytes
16 read_openssh_public_key
17 read_openssh_key_fingerprint
18 decrypt_rsa
19 encrypt_rsa
20 decrypt_aes_256_cbc
21 encrypt_aes_256_cbc
22 );
23
24 our $OPENSSL = 'openssl';
25 our $SSH_KEYGEN = 'ssh-keygen';
26
27 sub _croak { require Carp; Carp::croak(@_) }
28 sub _usage { _croak("Usage: @_\n") }
29
30 =func generate_secure_random_bytes
31
32 $bytes = generate_secure_random_bytes($num_bytes);
33
34 Get a certain number of secure random bytes.
35
36 =cut
37
38 sub generate_secure_random_bytes {
39 my $size = shift or _usage(q{generate_secure_random_bytes($num_bytes)});
40
41 my @cmd = ($OPENSSL, 'rand', $size);
42
43 my $out;
44 my $pid = open2($out, undef, @cmd);
45
46 waitpid($pid, 0);
47 my $status = $?;
48
49 my $exit_code = $status >> 8;
50 _croak 'Failed to generate secure random bytes' if $exit_code != 0;
51
52 return do { local $/; <$out> };
53 }
54
55 =func read_openssh_public_key
56
57 $pem_public_key = read_openssh_public_key($public_key_filepath);
58
59 Read a RFC4716 (SSH2) public key from a file, converting it to PKCS8 (PEM).
60
61 =cut
62
63 sub read_openssh_public_key {
64 my $filepath = shift or _usage(q{read_openssh_public_key($filepath)});
65
66 my @cmd = ($SSH_KEYGEN, qw{-e -m PKCS8 -f}, $filepath);
67
68 my $out;
69 my $pid = open2($out, undef, @cmd);
70
71 waitpid($pid, 0);
72 my $status = $?;
73
74 my $exit_code = $status >> 8;
75 _croak 'Failed to read OpenSSH public key' if $exit_code != 0;
76
77 return do { local $/; <$out> };
78 }
79
80 =func read_openssh_key_fingerprint
81
82 $fingerprint = read_openssh_key_fingerprint($filepath);
83
84 Get the fingerprint of an OpenSSH private or public key.
85
86 =cut
87
88 sub read_openssh_key_fingerprint {
89 my $filepath = shift or _usage(q{read_openssh_key_fingerprint($filepath)});
90
91 my @cmd = ($SSH_KEYGEN, qw{-l -E md5 -f}, $filepath);
92
93 my $out;
94 my $pid = open2($out, undef, @cmd);
95
96 waitpid($pid, 0);
97 my $status = $?;
98
99 my $exit_code = $status >> 8;
100 _croak 'Failed to read SSH2 key fingerprint' if $exit_code != 0;
101
102 my $line = do { local $/; <$out> };
103 chomp $line;
104
105 my ($bits, $fingerprint, $comment, $type) = $line =~ m!^(\d+) MD5:([^ ]+) (.*) \(([^\)]+)\)$!;
106
107 $fingerprint =~ s/://g;
108
109 return {
110 bits => $bits,
111 fingerprint => $fingerprint,
112 comment => $comment,
113 type => lc($type),
114 };
115 }
116
117 =func decrypt_rsa
118
119 $plaintext = decrypt_rsa($ciphertext_filepath, $private_key_filepath);
120 $plaintext = decrypt_rsa(\$ciphertext, $private_key_filepath);
121 decrypt_rsa($ciphertext_filepath, $private_key_filepath, $plaintext_filepath);
122 decrypt_rsa(\$ciphertext, $private_key_filepath, $plaintext_filepath);
123
124 Do RSA decryption. Turn ciphertext into plaintext.
125
126 =cut
127
128 sub decrypt_rsa {
129 my $filepath = shift or _usage(q{decrypt_rsa($filepath, $keypath)});
130 my $privkey = shift or _usage(q{decrypt_rsa($filepath, $keypath)});
131 my $outfile = shift;
132
133 my $temp;
134 if (ref $filepath eq 'SCALAR') {
135 $temp = File::Temp->new(UNLINK => 1);
136 print $temp $$filepath;
137 close $temp;
138 $filepath = $temp->filename;
139 }
140
141 my @cmd = ($OPENSSL, qw{rsautl -decrypt -oaep -in}, $filepath, '-inkey', $privkey);
142 push @cmd, ('-out', $outfile) if $outfile;
143
144 my $out;
145 my $pid = open2($out, undef, @cmd);
146
147 waitpid($pid, 0);
148 my $status = $?;
149
150 my $exit_code = $status >> 8;
151 _croak 'Failed to decrypt ciphertext' if $exit_code != 0;
152
153 return do { local $/; <$out> };
154 }
155
156 =func encrypt_rsa
157
158 $ciphertext = decrypt_rsa($plaintext_filepath, $public_key_filepath);
159 $ciphertext = decrypt_rsa(\$plaintext, $public_key_filepath);
160 decrypt_rsa($plaintext_filepath, $public_key_filepath, $ciphertext_filepath);
161 decrypt_rsa(\$plaintext, $public_key_filepath, $ciphertext_filepath);
162
163 Do RSA encryption. Turn plaintext into ciphertext.
164
165 =cut
166
167 sub encrypt_rsa {
168 my $filepath = shift or _usage(q{encrypt_rsa($filepath, $keypath)});
169 my $pubkey = shift or _usage(q{encrypt_rsa($filepath, $keypath)});
170 my $outfile = shift;
171
172 my $temp1;
173 if (ref $filepath eq 'SCALAR') {
174 $temp1 = File::Temp->new(UNLINK => 1);
175 print $temp1 $$filepath;
176 close $temp1;
177 $filepath = $temp1->filename;
178 }
179
180 my $key = read_openssh_public_key($pubkey);
181
182 my $temp2 = File::Temp->new(UNLINK => 1);
183 print $temp2 $key;
184 close $temp2;
185 my $keypath = $temp2->filename;
186
187 my @cmd = ($OPENSSL, qw{rsautl -encrypt -oaep -pubin -inkey}, $keypath, '-in', $filepath);
188 push @cmd, ('-out', $outfile) if $outfile;
189
190 my $out;
191 my $pid = open2($out, undef, @cmd);
192
193 waitpid($pid, 0);
194 my $status = $?;
195
196 my $exit_code = $status >> 8;
197 _croak 'Failed to encrypt plaintext' if $exit_code != 0;
198
199 return do { local $/; <$out> };
200 }
201
202 =func decrypt_aes_256_cbc
203
204 $plaintext = decrypt_aes_256_cbc($ciphertext_filepath, $secret);
205 $plaintext = decrypt_aes_256_cbc(\$ciphertext, $secret);
206 decrypt_aes_256_cbc($ciphertext_filepath, $secret, $plaintext_filepath);
207 decrypt_aes_256_cbc(\$ciphertext, $secret, $plaintext_filepath);
208
209 Do symmetric decryption. Turn ciphertext into plaintext.
210
211 =cut
212
213 sub decrypt_aes_256_cbc {
214 my $filepath = shift or _usage(q{decrypt_aes_256_cbc($ciphertext, $secret)});
215 my $secret = shift or _usage(q{decrypt_aes_256_cbc($ciphertext, $secret)});
216 my $outfile = shift;
217
218 my $temp;
219 if (ref $filepath eq 'SCALAR') {
220 $temp = File::Temp->new(UNLINK => 1);
221 print $temp $$filepath;
222 close $temp;
223 $filepath = $temp->filename;
224 }
225
226 my @cmd = ($OPENSSL, qw{aes-256-cbc -d -pass stdin -md sha256 -in}, $filepath);
227 push @cmd, ('-out', $outfile) if $outfile;
228
229 my ($in, $out);
230 my $pid = open2($out, $in, @cmd);
231
232 print $in $secret;
233 close($in);
234
235 waitpid($pid, 0);
236 my $status = $?;
237
238 my $exit_code = $status >> 8;
239 _croak 'Failed to decrypt ciphertext' if $exit_code != 0;
240
241 return do { local $/; <$out> };
242 }
243
244 =func encrypt_aes_256_cbc
245
246 $ciphertext = encrypt_aes_256_cbc($plaintext_filepath, $secret);
247 $ciphertext = encrypt_aes_256_cbc(\$plaintext, $secret);
248 encrypt_aes_256_cbc($plaintext_filepath, $secret, $ciphertext_filepath);
249 encrypt_aes_256_cbc(\$plaintext, $secret, $ciphertext_filepath);
250
251 Do symmetric encryption. Turn plaintext into ciphertext.
252
253 =cut
254
255 sub encrypt_aes_256_cbc {
256 my $filepath = shift or _usage(q{encrypt_aes_256_cbc($plaintext, $secret)});
257 my $secret = shift or _usage(q{encrypt_aes_256_cbc($plaintext, $secret)});
258 my $outfile = shift;
259
260 my $temp;
261 if (ref $filepath eq 'SCALAR') {
262 $temp = File::Temp->new(UNLINK => 1);
263 print $temp $$filepath;
264 close $temp;
265 $filepath = $temp->filename;
266 }
267
268 my @cmd = ($OPENSSL, qw{aes-256-cbc -pass stdin -md sha256 -in}, $filepath);
269 push @cmd, ('-out', $outfile) if $outfile;
270
271 my ($in, $out);
272 my $pid = open2($out, $in, @cmd);
273
274 print $in $secret;
275 close($in);
276
277 waitpid($pid, 0);
278 my $status = $?;
279
280 my $exit_code = $status >> 8;
281 _croak 'Failed to encrypt plaintext' if $exit_code != 0;
282
283 return do { local $/; <$out> };
284 }
285
286 1;
This page took 0.051566 seconds and 5 git commands to generate.