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 )