Audio-M4P
view release on metacpan or search on metacpan
lib/Audio/M4P/Decrypt.pm view on Meta::CPAN
bless( $self, $class );
$self->{meta} = {};
foreach my $k (qw( strHome sPfix dirSep DEBUG DEBUGDUMPFILE forceclean )) {
$self->{$k} = $args{$k} if exists $args{$k};
}
unless ( exists $self->{strHome} ) {
if ( $ENV{APPDATA} ) { $self->{strHome} = $ENV{APPDATA} }
elsif ( $ENV{HOME} ) { $self->{strHome} = $ENV{HOME} }
else { $self->{strHome} = '~' }
}
unless ( exists $self->{sPfix} ) {
if ( $^O eq 'MSWin32' ) { $self->{sPfix} = '' }
else { $self->{sPfix} = '.' }
}
$self->{dirSep} = '/' unless exists $self->{dirSep};
$self->{DEBUG} = 0 unless exists $self->{DEBUG};
$self->{QTStream} = new Audio::M4P::QuickTime(%args);
return $self;
}
sub GetUserKey {
my ( $self, $userID, $keyID ) = @_;
my ( $userKey, $keyFile, $fh );
# default userkey if atoms are 0 is tr1-th3n.y00_by3
return "tr1-th3n.y00_by3" unless $userID && $keyID;
$keyFile = sprintf( "%s%s%sdrms%s%08X.%03d",
$self->{strHome}, $self->{dirSep}, $self->{sPfix}, $self->{dirSep},
$userID, $keyID );
open( $fh, '<', $keyFile ) or return;
binmode $fh;
print "Keyfile $keyFile\n" if $self->{DEBUG};
read( $fh, $userKey, -s $keyFile ) or return;
return $userKey;
}
sub Decrypt {
my ( $self, $cipherText, $offset, $count, $alg ) = @_;
my $len = int( $count / 16 ) * 16;
substr( $$cipherText, $offset, $len,
$alg->decrypt( substr( $$cipherText, $offset, $len ) ) );
}
sub DeDRMS {
my ( $self, $infile, $outfile ) = @_;
$self->{QTStream}->ReadFile($infile);
$self->{QTStream}->ParseBuffer();
my $sampleTable = $self->{QTStream}->GetSampleTable();
my $userKey = $self->GetUserKey( $self->{QTStream}->{userID},
$self->{QTStream}->{keyID} )
|| $self->GetSCInfoUserKey();
if ( !$userKey ) {
carp "Cannot find user key for $infile";
return;
}
else {
print "User key is $userKey\n" if $self->{DEBUG};
}
my $md5 = new Digest::MD5;
$md5->add( $self->{QTStream}->{name}, $self->{QTStream}->{iviv} );
my $alg = new Crypt::Rijndael( $userKey, Crypt::Rijndael::MODE_CBC );
$alg->set_iv( $md5->digest );
$self->Decrypt(
\$self->{QTStream}->{priv}, 0,
length( $self->{QTStream}->{priv} ), $alg
);
if ( $self->{QTStream}->{priv} !~ /^itun/ ) {
carp "Priv decryption if $infile failed.";
return;
}
my $key = substr( $self->{QTStream}->{priv}, 24, 16 );
$alg = new Crypt::Rijndael( $key, Crypt::Rijndael::MODE_CBC );
$alg->set_iv( substr( $self->{QTStream}->{priv}, 48, 16 ) );
my $mdata = $self->{QTStream}->FindAtom('mdat');
my $posit = $mdata->start + 8;
foreach my $samplesize ( @{$sampleTable} ) {
$self->Decrypt( $mdata->rbuf, $posit, $samplesize, $alg );
$posit += $samplesize;
}
$self->{QTStream}->ConvertDrmsToMp4a();
if ( $self->{forceclean} ) {
$self->{QTStream}
->CleanAppleM4aPersonalData( force => 1, zero_free_atoms => 1 );
}
$self->{QTStream}->WriteFile($outfile);
}
# DeDRMS is aliased to DecryptFile
sub DecryptFile { DeDRMS(@_) }
=head1 NAME
Audio::M4P::Decrypt -- DRMS decryption of Apple iTunes style MP4 player files
=head1 DESCRIPTION
Originally derived from the DeDRMS.cs program by Jon Lech Johansen
=head1 SYNOPSIS
use Audio::M4P::Decrypt;
my $outfile = 'mydecodedfile';
my $deDRMS = new Audio::M4P::Decrypt;
$deDRMS->DeDRMS($mp4file, $outfile);
=head1 METHODS
=over 4
=item B<new>
my $cs = new Audio::M4P::Decrypt;
my $cs_conparam = Audio::M4P::Decrypt->new(
strHome => '~', sPfix => '.', dirSep => '/' );
Optional arguments: strHome is the directory containing the keyfile directory.
After running VLC on a .m4p file under Windows, MacOS X, and Linux, this should
be found by the module automatically (APPDATA dir under Win32, ~/ under OS X and
Linux). sPfix is '.' for MacOS/*nix, nil with Windows. dirSep is the char that
separates directories, often /.
For debugging purposes, use eg:
my $cs_conparam = Audio::M4P::Decrypt->new(
DEBUG => 1, DEBUGDUMPFILE => 'm4ptree.html'
);
DEBUG turns on debugging output. DEBUGDUMPFILE is an output file name to dump
an html picture of the m4p data structure.
( run in 0.907 second using v1.01-cache-2.11-cpan-df04353d9ac )