File Coverage

blib/lib/Audio/M4P/Decrypt.pm
Criterion Covered Total %
statement 34 80 42.5
branch 8 32 25.0
condition 0 6 0.0
subroutine 7 11 63.6
pod 5 5 100.0
total 54 134 40.3


line stmt bran cond sub pod time code
1             package Audio::M4P::Decrypt;
2              
3             require 5.006;
4 3     3   47285 use strict;
  3         9  
  3         105  
5 3     3   12 use warnings;
  3         4  
  3         74  
6 3     3   11 use Carp;
  3         6  
  3         256  
7             our $VERSION = '0.53';
8              
9 3     3   17 use Digest::MD5;
  3         3  
  3         81  
10 3     3   1320 use Crypt::Rijndael;
  3         1566  
  3         73  
11 3     3   1427 use Audio::M4P::QuickTime;
  3         6  
  3         2122  
12              
13             sub new {
14 3     3 1 32 my ( $class, %args ) = @_;
15 3         6 my $self = {};
16 3         7 bless( $self, $class );
17 3         17 $self->{meta} = {};
18 3         7 foreach my $k (qw( strHome sPfix dirSep DEBUG DEBUGDUMPFILE forceclean )) {
19 18 50       32 $self->{$k} = $args{$k} if exists $args{$k};
20             }
21 3 50       11 unless ( exists $self->{strHome} ) {
22 3 50       16 if ( $ENV{APPDATA} ) { $self->{strHome} = $ENV{APPDATA} }
  0 50       0  
23 3         10 elsif ( $ENV{HOME} ) { $self->{strHome} = $ENV{HOME} }
24 0         0 else { $self->{strHome} = '~' }
25             }
26 3 50       12 unless ( exists $self->{sPfix} ) {
27 3 50       13 if ( $^O eq 'MSWin32' ) { $self->{sPfix} = '' }
  0         0  
28 3         7 else { $self->{sPfix} = '.' }
29             }
30 3 50       10 $self->{dirSep} = '/' unless exists $self->{dirSep};
31 3 50       9 $self->{DEBUG} = 0 unless exists $self->{DEBUG};
32 3         27 $self->{QTStream} = new Audio::M4P::QuickTime(%args);
33 3         8 return $self;
34             }
35              
36             sub GetUserKey {
37 0     0 1   my ( $self, $userID, $keyID ) = @_;
38 0           my ( $userKey, $keyFile, $fh );
39              
40             # default userkey if atoms are 0 is tr1-th3n.y00_by3
41 0 0 0       return "tr1-th3n.y00_by3" unless $userID && $keyID;
42 0           $keyFile = sprintf( "%s%s%sdrms%s%08X.%03d",
43             $self->{strHome}, $self->{dirSep}, $self->{sPfix}, $self->{dirSep},
44             $userID, $keyID );
45 0 0         open( $fh, '<', $keyFile ) or return;
46 0           binmode $fh;
47 0 0         print "Keyfile $keyFile\n" if $self->{DEBUG};
48 0 0         read( $fh, $userKey, -s $keyFile ) or return;
49 0           return $userKey;
50             }
51              
52             sub Decrypt {
53 0     0 1   my ( $self, $cipherText, $offset, $count, $alg ) = @_;
54 0           my $len = int( $count / 16 ) * 16;
55 0           substr( $$cipherText, $offset, $len,
56             $alg->decrypt( substr( $$cipherText, $offset, $len ) ) );
57             }
58              
59             sub DeDRMS {
60 0     0 1   my ( $self, $infile, $outfile ) = @_;
61 0           $self->{QTStream}->ReadFile($infile);
62 0           $self->{QTStream}->ParseBuffer();
63 0           my $sampleTable = $self->{QTStream}->GetSampleTable();
64 0   0       my $userKey = $self->GetUserKey( $self->{QTStream}->{userID},
65             $self->{QTStream}->{keyID} )
66             || $self->GetSCInfoUserKey();
67 0 0         if ( !$userKey ) {
68 0           carp "Cannot find user key for $infile";
69 0           return;
70             }
71             else {
72 0 0         print "User key is $userKey\n" if $self->{DEBUG};
73             }
74 0           my $md5 = new Digest::MD5;
75 0           $md5->add( $self->{QTStream}->{name}, $self->{QTStream}->{iviv} );
76 0           my $alg = new Crypt::Rijndael( $userKey, Crypt::Rijndael::MODE_CBC );
77 0           $alg->set_iv( $md5->digest );
78 0           $self->Decrypt(
79             \$self->{QTStream}->{priv}, 0,
80             length( $self->{QTStream}->{priv} ), $alg
81             );
82 0 0         if ( $self->{QTStream}->{priv} !~ /^itun/ ) {
83 0           carp "Priv decryption if $infile failed.";
84 0           return;
85             }
86 0           my $key = substr( $self->{QTStream}->{priv}, 24, 16 );
87 0           $alg = new Crypt::Rijndael( $key, Crypt::Rijndael::MODE_CBC );
88 0           $alg->set_iv( substr( $self->{QTStream}->{priv}, 48, 16 ) );
89 0           my $mdata = $self->{QTStream}->FindAtom('mdat');
90 0           my $posit = $mdata->start + 8;
91              
92 0           foreach my $samplesize ( @{$sampleTable} ) {
  0            
93 0           $self->Decrypt( $mdata->rbuf, $posit, $samplesize, $alg );
94 0           $posit += $samplesize;
95             }
96 0           $self->{QTStream}->ConvertDrmsToMp4a();
97 0 0         if ( $self->{forceclean} ) {
98 0           $self->{QTStream}
99             ->CleanAppleM4aPersonalData( force => 1, zero_free_atoms => 1 );
100             }
101 0           $self->{QTStream}->WriteFile($outfile);
102             }
103              
104             # DeDRMS is aliased to DecryptFile
105 0     0 1   sub DecryptFile { DeDRMS(@_) }
106              
107             =head1 NAME
108              
109             Audio::M4P::Decrypt -- DRMS decryption of Apple iTunes style MP4 player files
110              
111             =head1 DESCRIPTION
112            
113             Originally derived from the DeDRMS.cs program by Jon Lech Johansen
114              
115             =head1 SYNOPSIS
116              
117             use Audio::M4P::Decrypt;
118              
119             my $outfile = 'mydecodedfile';
120             my $deDRMS = new Audio::M4P::Decrypt;
121             $deDRMS->DeDRMS($mp4file, $outfile);
122              
123              
124             =head1 METHODS
125              
126             =over 4
127              
128             =item B
129              
130             my $cs = new Audio::M4P::Decrypt;
131              
132             my $cs_conparam = Audio::M4P::Decrypt->new(
133             strHome => '~', sPfix => '.', dirSep => '/' );
134              
135             Optional arguments: strHome is the directory containing the keyfile directory.
136             After running VLC on a .m4p file under Windows, MacOS X, and Linux, this should
137             be found by the module automatically (APPDATA dir under Win32, ~/ under OS X and
138             Linux). sPfix is '.' for MacOS/*nix, nil with Windows. dirSep is the char that
139             separates directories, often /.
140              
141             For debugging purposes, use eg:
142             my $cs_conparam = Audio::M4P::Decrypt->new(
143             DEBUG => 1, DEBUGDUMPFILE => 'm4ptree.html'
144             );
145              
146             DEBUG turns on debugging output. DEBUGDUMPFILE is an output file name to dump
147             an html picture of the m4p data structure.
148              
149             =item B
150              
151             my $cs = new Audio::M4P::Decrypt( forceclean => 1 );
152             $cs->DeDRMS('infilename', 'outfilename');
153              
154             Decode infilename, write to outfilename. Reading slurps up an entire file,
155             so output can overwrite the same file without a problem, we hope. Backup first.
156             'forceclean => 1' will also attempt to remove residual personal data from the file.
157              
158             =item B
159              
160             $cs->DecryptFile('infilename', 'outfilename');
161              
162             More descriptive alias for the B method.
163              
164             =back
165              
166             =head1 B
167              
168             =over 4
169              
170             =item L
171              
172             =item L
173              
174             =back
175              
176             =head1 B
177              
178             This software is designed to allow different but fair use of music by the
179             purchaser of the music. In no way is this software intended to facilitate
180             use of digital music files by parties other than the purchaser of the
181             originally DRM-protected material. That is Fair Use, corporate entities.
182              
183             If you need to locate your iTMS keys, look for compatible versions of
184             JHymn or FairKeys, or use the LWP::UserAgent::iTMS_Client Perl module.
185              
186             =head2 Class Internal Functions
187              
188             =over 4
189              
190             =item B
191              
192             =item B
193              
194             =back
195              
196             =head1 AUTHOR
197              
198             Original C# version: copyright Jon Lech Johansen B.
199             Perl version: William Herrera B.
200              
201             =head1 SUPPORT
202              
203             Questions, feature requests and bug reports should go to
204             .
205              
206             =head1 COPYRIGHT
207              
208             =over 4
209              
210             Copyright (c) 2003-2005 William Herrera. All rights reserved.
211             This program is free software; you can redistribute it and/or modify
212             it under the same terms as Perl itself.
213              
214             =back
215              
216             =cut
217              
218             1;