Commit 88a15495 authored by Stéphane Borel's avatar Stéphane Borel
Browse files

New features for libdvdcss: we have three ways now to decode a title key.

1) Crack the title key (the method that was here before). The only change
here is that we search the key for the exact chapter we are seeking with
DVDSetArea (in case the key has changed within a title). It is maybe not a
good idea.

2) Crack the disc key, which allows us to decode instantly all title keys.
I've used an algorithm from Frank Stevenson ; it eats much memory (64MB),
and takes about 15 s at launch time.

3) Decode the disc key with player keys (libcss method). However, you need
licensed player keys at build time for that to work.

To choose between libdvdcss methods, a command line options is supplied:

        vlc --dvdcss <method> where method is one of title, disc, key.

Note that all these changes only work with linux now, since we have to add a
specific ioctl to read title key. I hope that I haven't broken too many things.
parent 563c5e17
This diff is collapsed.
......@@ -18,6 +18,9 @@ if test -r extras/libdvdcss/libdvdcss.c; then
HAVE_LIBDVDCSS=1
LIBDVDCSS_VERSION=0.0.3
AC_SUBST(LIBDVDCSS_VERSION)
if test -r extras/libdvdcss/csskeys.h; then
AC_DEFINE(HAVE_CSSKEYS,1,css decryption with player keys)
fi
fi
dnl Save CFLAGS and LDFLAGS
......
......@@ -2,7 +2,7 @@
* css.c: Functions for DVD authentification and unscrambling
*****************************************************************************
* Copyright (C) 1999-2001 VideoLAN
* $Id: css.c,v 1.9 2001/09/09 13:43:25 sam Exp $
* $Id: css.c,v 1.10 2001/10/13 15:34:21 stef Exp $
*
* Author: Stphane Borel <stef@via.ecp.fr>
* Hkan Hjort <d95hjort@dtek.chalmers.se>
......@@ -40,6 +40,18 @@
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <netinet/in.h>
#include <linux/cdrom.h>
#include "config.h"
#include "common.h"
......@@ -49,13 +61,20 @@
#include "csstables.h"
#include "ioctl.h"
#ifdef HAVE_CSSKEYS
# include "csskeys.h"
#endif
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static int CSSGetASF ( dvdcss_handle dvdcss );
static void CSSCryptKey ( int i_key_type, int i_varient,
u8 const * p_challenge, u8* p_key );
static int CSSCracker ( int i_start, unsigned char * p_crypted,
static void CSSDecryptKey( u8* p_crypted, u8* p_key, u8 );
static int CSSDiscCrack ( u8 * p_disc_key );
static int CSSTitleCrack( int i_start, unsigned char * p_crypted,
unsigned char * p_decrypted,
dvd_key_t * p_sector_key, dvd_key_t * p_key );
......@@ -81,21 +100,22 @@ int CSSTest( dvdcss_handle dvdcss )
}
/*****************************************************************************
* CSSInit : CSS Structure initialisation and DVD authentication.
* CSSAuth : CSS Structure initialisation and DVD authentication.
*****************************************************************************
* It simulates the mutual authentication between logical unit and host.
* Since we don't need the disc key to find the title key, we just run the
* basic unavoidable commands to authenticate device and disc.
*****************************************************************************/
int CSSInit( dvdcss_handle dvdcss )
int CSSAuth( dvdcss_handle dvdcss )
{
/* structures defined in cdrom.h or dvdio.h */
unsigned char p_buffer[2048 + 4 + 1];
unsigned char p_buffer[10];
char psz_warning[32];
int i_agid = 0;
int i_ret = -1;
int i;
dvdcss->css.i_agid = 0;
/* Test authentication success */
switch( CSSGetASF( dvdcss ) )
{
......@@ -104,10 +124,11 @@ int CSSInit( dvdcss_handle dvdcss )
case 1:
_dvdcss_debug( dvdcss, "already authenticated" );
return 0;
break;
case 0:
_dvdcss_debug( dvdcss, "need to authenticate" );
break;
}
/* Init sequence, request AGID */
......@@ -116,7 +137,7 @@ int CSSInit( dvdcss_handle dvdcss )
sprintf( psz_warning, "requesting AGID %d", i );
_dvdcss_debug( dvdcss, psz_warning );
i_ret = ioctl_ReportAgid( dvdcss->i_fd, &i_agid );
i_ret = ioctl_ReportAgid( dvdcss->i_fd, &dvdcss->css.i_agid );
if( i_ret != -1 )
{
......@@ -126,8 +147,8 @@ int CSSInit( dvdcss_handle dvdcss )
_dvdcss_error( dvdcss, "ioctl_ReportAgid failed, invalidating" );
i_agid = 0;
ioctl_InvalidateAgid( dvdcss->i_fd, &i_agid );
dvdcss->css.i_agid = 0;
ioctl_InvalidateAgid( dvdcss->i_fd, &dvdcss->css.i_agid );
}
/* Unable to authenticate without AGID */
......@@ -149,14 +170,14 @@ int CSSInit( dvdcss_handle dvdcss )
}
/* Send challenge to LU */
if( ioctl_SendChallenge( dvdcss->i_fd, &i_agid, p_buffer ) < 0 )
if( ioctl_SendChallenge( dvdcss->i_fd, &dvdcss->css.i_agid, p_buffer ) < 0 )
{
_dvdcss_error( dvdcss, "ioctl_SendChallenge failed" );
return -1;
}
/* Get key1 from LU */
if( ioctl_ReportKey1( dvdcss->i_fd, &i_agid, p_buffer ) < 0)
if( ioctl_ReportKey1( dvdcss->i_fd, &dvdcss->css.i_agid, p_buffer ) < 0)
{
_dvdcss_error( dvdcss, "ioctl_ReportKey1 failed" );
return -1;
......@@ -190,7 +211,7 @@ int CSSInit( dvdcss_handle dvdcss )
}
/* Get challenge from LU */
if( ioctl_ReportChallenge( dvdcss->i_fd, &i_agid, p_buffer ) < 0 )
if( ioctl_ReportChallenge( dvdcss->i_fd, &dvdcss->css.i_agid, p_buffer ) < 0 )
{
_dvdcss_error( dvdcss, "ioctl_ReportKeyChallenge failed" );
return -1;
......@@ -213,7 +234,7 @@ int CSSInit( dvdcss_handle dvdcss )
}
/* Send key2 to LU */
if( ioctl_SendKey2( dvdcss->i_fd, &i_agid, p_buffer ) < 0 )
if( ioctl_SendKey2( dvdcss->i_fd, &dvdcss->css.i_agid, p_buffer ) < 0 )
{
_dvdcss_error( dvdcss, "ioctl_SendKey2 failed" );
return -1;
......@@ -232,7 +253,7 @@ int CSSInit( dvdcss_handle dvdcss )
_dvdcss_debug( dvdcss, "received session key" );
if( i_agid < 0 )
if( dvdcss->css.i_agid < 0 )
{
return -1;
}
......@@ -249,12 +270,38 @@ int CSSInit( dvdcss_handle dvdcss )
case 0:
_dvdcss_debug( dvdcss, "need to get disc key" );
return 0;
}
return -1;
}
/*****************************************************************************
* CSSGetDiscKey : get disc key and optionnaly decrypts it.
*****************************************************************************
* This function should only be called if DVD ioctls are present.
* Two decryption methods are then offered:
* -disc key hash crack,
* -decryption with player keys if they are available.
*****************************************************************************/
int CSSGetDiscKey( dvdcss_handle dvdcss )
{
unsigned char p_buffer[2048 + 4 + 1];
#ifdef HAVE_CSSKEYS
dvd_key_t disc_key;
dvd_key_t test_key;
#endif
int i;
if( CSSAuth( dvdcss ) )
{
return -1;
}
/* Get encrypted disc key */
if( ioctl_ReadKey( dvdcss->i_fd, &i_agid, p_buffer ) < 0 )
if( ioctl_ReadDiscKey( dvdcss->i_fd, &dvdcss->css.i_agid, p_buffer ) < 0 )
{
_dvdcss_error( dvdcss, "ioctl_ReadKey failed" );
_dvdcss_error( dvdcss, "ioctl_ReadDiscKey failed" );
return -1;
}
......@@ -263,134 +310,195 @@ int CSSInit( dvdcss_handle dvdcss )
{
p_buffer[ i ] ^= dvdcss->css.disc.p_key_check[ 4 - (i % KEY_SIZE) ];
}
memcpy( dvdcss->css.disc.p_key_check, p_buffer, 2048 );
memcpy( dvdcss->css.disc.p_disc_key, p_buffer, 2048 );
/* Test authentication success */
switch( CSSGetASF( dvdcss ) )
switch( dvdcss->i_method )
{
case -1:
return -1;
case DVDCSS_KEY:
#ifdef HAVE_CSSKEYS
/* Decrypt disc key with player keys from csskeys.h */
_dvdcss_debug( dvdcss, "decrypting disc key with player keys" );
i = 0;
do
{
/* Take encrypted disc key and decrypt it */
memcpy( disc_key,
dvdcss->css.disc.p_disc_key
+ playerkeys[i].i_offset,
KEY_SIZE );
CSSDecryptKey( disc_key, playerkeys[i].p_key, 0 );
case 1:
_dvdcss_debug( dvdcss, "successfully authenticated" );
return 0;
/* Encrypt disc key hash with disc key to
* check we have disc key */
memcpy( test_key, dvdcss->css.disc.p_disc_key, KEY_SIZE );
CSSDecryptKey( test_key, disc_key, 0);
case 0:
_dvdcss_error( dvdcss, "no way to authenticate" );
return -1;
i++;
} while( ( playerkeys[i].i_offset != -1 ) &&
( memcmp( test_key, disc_key, KEY_SIZE ) ) );
/* The decrypted disk key will replace the disk key hash */
memcpy( dvdcss->css.disc.p_disc_key, disc_key, KEY_SIZE );
break;
#else
dvdcss->i_method = DVDCSS_DISC;
#endif
case DVDCSS_DISC:
/* Crack Disc key to be able to use it */
_dvdcss_debug( dvdcss, "cracking disc key with key hash" );
CSSDiscCrack( dvdcss->css.disc.p_disc_key );
break;
default:
_dvdcss_debug( dvdcss, "disc key won't be decrypted" );
}
return -1;
return 0;
}
/*****************************************************************************
* CSSGetKey : get title key.
*****************************************************************************
* The DVD should have been opened and authenticated before.
* CSSGetTitleKey : get title key.
*****************************************************************************/
int CSSGetKey( dvdcss_handle dvdcss, int i_pos, dvd_key_t p_titlekey )
int CSSGetTitleKey( dvdcss_handle dvdcss, int i_pos )
{
/*
* Title key cracking method from Ethan Hawke,
* with Frank A. Stevenson algorithm.
* Does not use any player key table and ioctls.
*/
u8 p_buf[0x800];
u8 p_packstart[4] = { 0x00, 0x00, 0x01, 0xba };
dvd_key_t p_key;
boolean_t b_encrypted;
boolean_t b_stop_scanning;
int i_blocks_read;
int i_best_plen;
int i_best_p;
int i,j;
for( i = 0 ; i < KEY_SIZE ; i++ )
if( ( dvdcss->i_method == DVDCSS_TITLE ) || ( dvdcss->b_ioctls == 0 ) )
{
p_key[i] = 0;
}
b_encrypted = 0;
b_stop_scanning = 0;
i_blocks_read = 0;
do
{
i_pos = dvdcss_seek( dvdcss, i_pos );
if( dvdcss_read( dvdcss, p_buf, 1, DVDCSS_NOFLAGS ) != 1 ) break;
/* Stop when we find a non MPEG stream block */
if( memcmp( p_buf, p_packstart, 4 ) )
/*
* Title key cracking method from Ethan Hawke,
* with Frank A. Stevenson algorithm.
* Does not use any player key table and ioctls.
*/
u8 p_buf[0x800];
u8 p_packstart[4] = { 0x00, 0x00, 0x01, 0xba };
boolean_t b_encrypted;
boolean_t b_stop_scanning;
int i_blocks_read;
int i_best_plen;
int i_best_p;
for( i = 0 ; i < KEY_SIZE ; i++ )
{
/* The title is unencrypted */
if( !b_encrypted )
break;
/* dvdcss some times fail to find/crack the key,
hope that it's the same as the one in the next title
_dvdcss_debug( dvdcss, "no key found at end of title" );
*/
p_key[i] = 0;
}
/* PES_scrambling_control on and make sure that the packet type
is one that can be scrambled */
if( p_buf[0x14] & 0x30 && ! ( p_buf[0x11] == 0xbb
|| p_buf[0x11] == 0xbe
|| p_buf[0x11] == 0xbf ) )
b_encrypted = 0;
b_stop_scanning = 0;
i_blocks_read = 0;
do
{
b_encrypted = 1;
i_best_plen = 0;
i_best_p = 0;
i_pos = dvdcss_seek( dvdcss, i_pos, DVDCSS_NOFLAGS );
if( dvdcss_read( dvdcss, p_buf, 1, DVDCSS_NOFLAGS ) != 1 ) break;
for( i = 2 ; i < 0x30 ; i++ )
/* Stop when we find a non MPEG stream block */
if( memcmp( p_buf, p_packstart, 4 ) )
{
for( j = i+1 ;
j < 0x80 && ( p_buf[0x7F - (j%i)] == p_buf[0x7F-j] );
j++ );
/* The title is unencrypted */
if( !b_encrypted )
break;
/* dvdcss some times fail to find/crack the key,
hope that it's the same as the one in the next title
_dvdcss_debug( dvdcss, "no key found at end of title" );
*/
}
/* PES_scrambling_control on and make sure that the packet type
is one that can be scrambled */
if( p_buf[0x14] & 0x30 && ! ( p_buf[0x11] == 0xbb
|| p_buf[0x11] == 0xbe
|| p_buf[0x11] == 0xbf ) )
{
b_encrypted = 1;
i_best_plen = 0;
i_best_p = 0;
for( i = 2 ; i < 0x30 ; i++ )
{
if( j > i_best_plen )
for( j = i+1 ;
j < 0x80 && ( p_buf[0x7F - (j%i)] == p_buf[0x7F-j] );
j++ );
{
i_best_plen = j;
i_best_p = i;
if( j > i_best_plen )
{
i_best_plen = j;
i_best_p = i;
}
}
}
}
if( ( i_best_plen > 20 ) && ( i_best_plen / i_best_p >= 2) )
{
i = CSSCracker( 0, &p_buf[0x80],
&p_buf[0x80 - ( i_best_plen / i_best_p) *i_best_p],
(dvd_key_t*)&p_buf[0x54],
&p_key );
b_stop_scanning = ( i >= 0 );
if( ( i_best_plen > 20 ) && ( i_best_plen / i_best_p >= 2) )
{
i = CSSTitleCrack( 0, &p_buf[0x80],
&p_buf[0x80 - ( i_best_plen / i_best_p) *i_best_p],
(dvd_key_t*)&p_buf[0x54],
&p_key );
b_stop_scanning = ( i >= 0 );
}
}
}
i_pos += 1;
i_blocks_read += 1;
i_pos += 1;
i_blocks_read += 1;
/* If we haven't seen any encrypted ones after 3000 blocks stop */
if( !b_encrypted && i_blocks_read >= 1000 ) break;
/* If we haven't seen any encrypted ones after 3000 blocks stop */
if( !b_encrypted && i_blocks_read >= 1000 ) break;
} while( !b_stop_scanning );
} while( !b_stop_scanning );
if( b_stop_scanning )
{
memcpy( p_titlekey, &p_key, sizeof(dvd_key_t) );
_dvdcss_debug( dvdcss, "vts key initialized" );
return 0;
}
if( b_stop_scanning )
{
memcpy( dvdcss->css.p_title_key, &p_key, sizeof(dvd_key_t) );
_dvdcss_debug( dvdcss, "vts key initialized" );
return 0;
}
if( !b_encrypted )
{
_dvdcss_debug( dvdcss, "file was unscrambled" );
return 0;
if( !b_encrypted )
{
_dvdcss_debug( dvdcss, "file was unscrambled" );
return 0;
}
return -1;
}
else
{
/*
* if we are here we have a decrypted disc key and ioctls are available
* so we can read the title key and decrypt it.
*/
/* We need to authenticate again for every key
* (to get a new session key ?) */
CSSAuth( dvdcss );
/* Get encrypted title key */
if( ioctl_ReadTitleKey( dvdcss->i_fd, &dvdcss->css.i_agid,
i_pos, p_key ) < 0 )
{
_dvdcss_error( dvdcss, "ioctl_ReadTitleKey failed" );
return -1;
}
/* Unencrypt title key using bus key */
for( i = 0 ; i < KEY_SIZE ; i++ )
{
p_key[ i ] ^= dvdcss->css.disc.p_key_check[ 4 - (i % KEY_SIZE) ];
}
return -1;
/* Title key decryption needs one inversion 0xff */
CSSDecryptKey( p_key, dvdcss->css.disc.p_disc_key, 0xff );
memcpy( dvdcss->css.p_title_key, p_key, sizeof(dvd_key_t) );
return 0;
} // ( dvdcss->i_method == DVDCSS_TITLECRACK ) || ( dvdcss->b_ioctls == 0 )
}
/*****************************************************************************
* CSSDescrambleSector
* CSSDescrambleSector: does the actual descrambling of data
*****************************************************************************
* sec : sector to descramble
* key : title key for this sector
......@@ -420,7 +528,7 @@ int CSSDescrambleSector( dvd_key_t p_key, u8* p_sec )
i_t1 = ( ( i_t1 & 1 ) << 8 ) ^ i_t4;
i_t4 = p_css_tab5[i_t4];
i_t6 = ((((((( i_t3 >> 3 ) ^ i_t3 ) >> 1 ) ^
i_t3 ) >> 8 ) ^ i_t3 ) >> 5) & 0xff;
i_t3 ) >> 8 ) ^ i_t3 ) >> 5 ) & 0xff;
i_t3 = (i_t3 << 8 ) | i_t6;
i_t6 = p_css_tab4[i_t6];
i_t5 += i_t6 + i_t4;
......@@ -473,7 +581,7 @@ static int CSSGetASF( dvdcss_handle dvdcss )
/*****************************************************************************
* CSSCryptKey : shuffles bits and unencrypt keys.
*****************************************************************************
* Used during authentication and disc key negociation in CSSInit.
* Used during authentication and disc key negociation in CSSAuth.
* i_key_type : 0->key1, 1->key2, 2->buskey.
* i_varient : between 0 and 31.
*****************************************************************************/
......@@ -678,15 +786,296 @@ static void CSSCryptKey( int i_key_type, int i_varient,
}
/*****************************************************************************
* CSSCracker : title key decryption by cracking
* CSSDecryptKey: decrypt p_crypted with p_key.
*****************************************************************************
* Decryption is slightly dependant on the type of key:
* -for disc key, invert is 0x00,
* -for title key, invert if 0xff.
*****************************************************************************/
static void CSSDecryptKey( u8* p_crypted, u8* p_key, u8 invert )
{
unsigned int i_lfsr1_lo;
unsigned int i_lfsr1_hi;
unsigned int i_lfsr0;
unsigned int i_combined;
byte_t o_lfsr0;
byte_t o_lfsr1;
byte_t k[5];
int i;
i_lfsr1_lo = p_key[0] | 0x100;
i_lfsr1_hi = p_key[1];
i_lfsr0 = ( ( p_key[4] << 17 )
| ( p_key[3] << 9 )
| ( p_key[2] << 1 ) )
+ 8 - ( p_key[2] & 7 );
i_lfsr0 = ( p_css_tab4[i_lfsr0 & 0xff] << 24 ) |
( p_css_tab4[( i_lfsr0 >> 8 ) & 0xff] << 16 ) |
( p_css_tab4[( i_lfsr0 >> 16 ) & 0xff] << 8 ) |
p_css_tab4[( i_lfsr0 >> 24 ) & 0xff];
i_combined = 0;
for( i = 0 ; i < KEY_SIZE ; ++i )
{
o_lfsr1 = p_css_tab2[i_lfsr1_hi] ^ p_css_tab3[i_lfsr1_lo];
i_lfsr1_hi = i_lfsr1_lo >> 1;
i_lfsr1_lo = ( ( i_lfsr1_lo & 1 ) << 8 ) ^ o_lfsr1;
o_lfsr1 = p_css_tab4[o_lfsr1];
o_lfsr0 = ((((((( i_lfsr0 >> 8 ) ^ i_lfsr0 ) >> 1 )
^ i_lfsr0 ) >> 3 ) ^ i_lfsr0 ) >> 7 );
i_lfsr0 = ( i_lfsr0 >> 8 ) | ( o_lfsr0 << 24 );
i_combined += ( o_lfsr0 ^ invert ) + o_lfsr1;
k[i] = i_combined & 0xff;
i_combined >>= 8;
}
p_crypted[4] = k[4] ^ p_css_tab1[p_crypted[4]] ^ p_crypted[3];
p_crypted[3] = k[3] ^ p_css_tab1[p_crypted[3]] ^ p_crypted[2];
p_crypted[2] = k[2] ^ p_css_tab1[p_crypted[2]] ^ p_crypted[1];
p_crypted[1] = k[1] ^ p_css_tab1[p_crypted[1]] ^ p_crypted[0];
p_crypted[0] = k[0] ^ p_css_tab1[p_crypted[0]] ^ p_crypted[4];
p_crypted[4] = k[4] ^ p_css_tab1[p_crypted[4]] ^ p_crypted[3];
p_crypted[3] = k[3] ^ p_css_tab1[p_crypted[3]] ^ p_crypted[2];
p_crypted[2] = k[2] ^ p_css_tab1[p_crypted[2]] ^ p_crypted[1];
p_crypted[1] = k[1] ^ p_css_tab1[p_crypted[1]] ^ p_crypted[0];
p_crypted[0] = k[0] ^ p_css_tab1[p_crypted[0]];
return;
}
/*****************************************************************************
* CSSDiscCrack: brute force disc key
* CSS hash reversal function designed by Frank Stevenson
*****************************************************************************
* This function is called by CSSGetKeys to find a key
* This function uses a big amount of memory to crack the disc key from the
* disc key hash, if player keys are not available.
*****************************************************************************/
static int CSSCracker( int i_start,
unsigned char * p_crypted,
unsigned char * p_decrypted,
dvd_key_t * p_sector_key,
dvd_key_t * p_key )
#define K1TABLEWIDTH 10
/*
* Simple function to test if a candidate key produces the given hash
*/
static int investigate( unsigned char* hash, unsigned char *ckey )
{
unsigned char key[5];
unsigned char pkey[5];
memcpy( key, hash, 5 );
memcpy( pkey, ckey, 5 );
CSSDecryptKey( key, pkey, 0 );
return memcmp( key, pkey, 5 );
}
static int CSSDiscCrack( u8 * p_disc_key )
{
unsigned char B[5] = { 0,0,0,0,0 }; /* Second Stage of mangle cipher */
unsigned char C[5] = { 0,0,0,0,0 }; /* Output Stage of mangle cipher
* IntermediateKey */
unsigned char k[5] = { 0,0,0,0,0 }; /* Mangling cipher key
* Also output from CSS( C ) */
unsigned char out1[5]; /* five first output bytes of LFSR1 */
unsigned char out2[5]; /* five first output bytes of LFSR2 */
unsigned