/* ========================================================================== ** * mcrypt_key_dx.c * * Copyright: * Copyright (C) 2011 by Christopher R. Hertel * * Email: crh@ubiqx.mn.org * * $Id: mcrypt_key_dx.c 29 2011-07-08 18:49:31Z crh $ * * -------------------------------------------------------------------------- ** * * Description: * Decrypt an exported BranchCache Server Secret and Passphrase pair. * * -------------------------------------------------------------------------- ** * * License: * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful. * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * -------------------------------------------------------------------------- ** * * Notes: * * This code was developed in participation * with the Protocol Freedom Information Foundation. * * This is example code, using libmcrypt and libmhash, that will decrypt * the Server Secret and Server Passphrase exported from a Windows system * running BranchCache[TM]. * * This code was developed using information from Microsoft's published * protocol documentation, particularly [MS-PCCRC], and help from * Microsoft's Documentation Support team (dochelp@microsoft.com). * * Terminology: * Export Passphrase: An arbitrary length binary string (typically * a text string in Unicode UTF-16-LE encoding) * used to encode and decode the extracted Server * Secret and Passphrase. * Server Passphrase: An arbitrary length binary string stored on the * server. The Server Passphrase is not used * directly. The SHA256 hash of the Passphrase is * used as a signing key to sign PeerDist segment * hashes. * Server Secret: As defined in section 1.1 of [MS-PCCRC], the * Server Secret is the SHA256 hash of what is * defined here as the Server Passphrase. * * To export the Server Secret & Passphrase, use the following command in * Microsoft Powershell: * * > netsh branchcache exportkey * * That will generate a file containing the both the Server Secret and the * Server Passphrase. The exported file will be AES encrypted, and will * need to be decrypted in order to be used by third-party software. * * This program performs the required decryption, and also allows the user * to validate the result by generating its own SHA256 of the extracted * Server Passphrase. The generated hash should be identical to the * extracted Server Secret. * * This program is intended as example code--not for production use. * * + To compile: * cc -o mcrypt_key_dx mcrypt_key_dx.c -lmcrypt -lmhash * * + These are the steps used to extract an exported PeerDist Server * Passphrase and Server Secret pair: * 1) Generate SHA256( export_passphrase ). * Note that the terminating NUL of the passphrase is NOT included * when calculating the hash. Also note that Windows systems use * UTF-16-LE encoding, not ASCII or UTF-8. * * 2) AES Decrypt the contents of the keyfile, passing the hashed * export_passphrase as the decryption key. * Use AES256. That is, AES with a keysize of 256 bits (32 bytes) * and a blocksize of 128 bits (16 bytes--standard for AES). The * Initialization Vector (IV) is 16 bytes of zeros (all NUL bytes). * * 3) Separate the first 32 bytes of the result of step 2 from the * remainder. * + The first 32 bytes are the Server Secret. * + The remainder is the Server Passphrase, padded per PKCS#7. See: * http://en.wikipedia.org/wiki/Padding_(cryptography)#Byte_padding * * 4) Remove the padding, and validate the extracted Server Passphrase by * calculating the SHA256 of the extracted Server Passhrase and * comparing that against the first 32 bytes of the decrypted value. * The two should match. * * FIX: * * + We currently make the assumption that the entire key file will fit * into a single input buffer. Not an unreasonable assumption, but * not good coding practice. * * + We take the user-supplied export_passphrase from the command line, * assume that it is in ASCII, and add NUL bytes to approximate UTF-16-LE * Unicode encoding. That's not the right way to do it. * * ========================================================================== ** */ #include /* Standard library. */ #include /* I/O stuff. */ #include /* For . */ #include /* For strerror(3). */ #include /* Variable arg lists.*/ #include /* Mcrypt decryption. */ #include /* Mcrypt hashes. */ /* -------------------------------------------------------------------------- ** * Macros: * * Err() - This is a conceited little macro that simply replaces * fprintf( stderr, ... ) with something shorter and easier * to type. * Say() - This is a conceited little macro that simply replaces * printf(3) with something shorter and easier to type. * ErrStr - Shorthand for the error message associated with . */ #define Err( ... ) (void)fprintf( stderr, __VA_ARGS__ ) #define Say( ... ) (void)printf( __VA_ARGS__ ) #define ErrStr (strerror( errno )) /* -------------------------------------------------------------------------- ** * Defined constants: * * bSIZE - The size of the input and output buffers we will create. * Note that we assume that the key file will fit in a single * buffer. * HASH_SIZE - The size, in bytes, of an SHA256 hash. * BLOCK_SIZE - The block-size used by the AES256 algorithm. * MCrypt implements Rijndael, which is a superset of the * AES standard algorithm set. AES uses a fixed block * size of 16 bytes, but Rijndael allows other block sizes. * See: * http://en.wikipedia.org/wiki/Advanced_Encryption_Standard * The Initialization Vector (IV) must be BLOCK_SIZE bytes in * length. */ #define bSIZE 2048 #define HASH_SIZE 32 #define BLOCK_SIZE 16 /* -------------------------------------------------------------------------- ** * Static Functions: */ void Fail( char *fmt, ... ) /* ------------------------------------------------------------------------ ** * Format and print a failure message on , then exit the process. * * Input: fmt - Format string, as used in printf(), etc. * ... - Variable parameter list. * * Output: none * * Notes: Exits the process returning EXIT_FAILURE. * * ------------------------------------------------------------------------ ** */ { va_list ap; va_start( ap, fmt ); (void)fprintf( stderr, "Failure: " ); (void)vfprintf( stderr, fmt, ap ); va_end( ap ); exit( EXIT_FAILURE ); } /* Fail */ static void hexlist( const unsigned char *bufr, int len ) /* ------------------------------------------------------------------------ ** * Output a hex string representing a block of bytes. * * Input: bufr - A pointer to a bunch of bytes. * len - Number of bytes of to output. * * Output: * * ------------------------------------------------------------------------ ** */ { int i; Err( "[" ); for( i = 0; i < len; i++ ) Err( "%.2x", bufr[i] ); Err( "]\n" ); } /* hexlist */ static unsigned char *calc_SHA256( const void *In, const size_t len, void *Out ) /* ------------------------------------------------------------------------ ** * Single-shot SHA256 calculation. * * Input: In - Pointer to the input buffer. * len - The number of bytes in to be hashed. * Out - A pointer to an output buffer which must be at least * HASH_SIZE bytes in length. * * Output: A pointer to the resulting SHA256 hash (same as ). * The result is returned as a pointer to unsigned char. * * Notes: This is just a wrapper around the libmhash functions used * to calculate an SHA256 hash. This function supports only * a single input buffer, rather than multiple buffers handled * in sequence. * * ------------------------------------------------------------------------ ** */ { MHASH ctx; ctx = mhash_init( MHASH_SHA256 ); (void)mhash( ctx, (const void *)In, len ); mhash_deinit( ctx, (void *)Out ); return( (unsigned char *)Out ); } /* calc_SHA256 */ /* -------------------------------------------------------------------------- ** * Program Mainline. */ int main( int argc, char *argv[] ) /* ------------------------------------------------------------------------ ** * Program Mainline. * * Input: argc - You know what this is. * argv - You know what to do. * * Output: EXIT_SUCCESS * or, if you are having a really bad day, EXIT_FAILURE. * * ------------------------------------------------------------------------ ** */ { int i; FILE *keyf; unsigned char *doublewide; MCRYPT td; size_t readlen; /* Bytes of cyphertext read. */ size_t phraselen; /* Length of the passphrase. */ unsigned char Bufr[bSIZE]; /* Cyphertext buffer. */ unsigned char ppHash[HASH_SIZE]; /* PassPhrase Hash */ unsigned char iv[BLOCK_SIZE]; /* Initialization Vector. */ /* Validate the command line. Somewhat. */ if( argc != 3 ) { Err( "Usage:\t%s \n", argv[0] ); Err( "\tWhere is the name of the file that contains the\n" ); Err( "\texported key, and is the phrase used as the\n" ); Err( "\tencryption key when the server secret was exported.\n\tIt " ); Err( "may be necessary to place the passphrase in quotation marks.\n" ); exit( EXIT_FAILURE ); } /* Open the keyfile. */ keyf = fopen( argv[1], "r" ); if( NULL == keyf ) { Err( "Failure opening file \"%s\"; %s.\n", argv[1], ErrStr ); return( EXIT_FAILURE ); } /* Read the keyfile. Assume it all fits into one buffer. */ readlen = fread( Bufr, 1, bSIZE, keyf ); if( 0 == readlen ) { Err( "Error reading input from %s; %s.\n", argv[1], ErrStr ); return( EXIT_FAILURE ); } /* Expand and hash the passphrase. * Windows uses Unicode UTF-16-LE, so plain ASCII must be expanded. * This is a kludge. We should be using real Unicode here. */ phraselen = strlen( argv[2] ); doublewide = (unsigned char *)calloc( 2*phraselen, 1 ); for( i = 0; i < phraselen; i++ ) { doublewide[i*2] = argv[2][i]; doublewide[1+(i*2)] = '\0'; } Err( "Passphrase: " ); phraselen *= 2; /* We've doubled the length of the user passphrase. */ hexlist( doublewide, phraselen ); (void)calc_SHA256( doublewide, phraselen, ppHash ); free( doublewide ); /* Decrypt the input. * Rijndael-128 is equivalent to AES with a block size * of 16 bytes (128 bits). * The mode is CBC (cypher-block chaining). */ td = mcrypt_module_open( "rijndael-128", NULL, "cbc", NULL ); /* Set the key and key size; 32-bytes == 256 bits. * Initialize the IV to all zeros. */ memset( iv, 0, BLOCK_SIZE ); i = mcrypt_generic_init( td, ppHash, HASH_SIZE, iv ); if( i ) Fail( "mcyrpt_generic_init() failed; [%d].\n", i ); /* Now decrypt the input. * mdecrypt_generic() re-uses the input buffer as the output buffer. */ mdecrypt_generic( td, Bufr, readlen ); mcrypt_generic_deinit( td ); mcrypt_module_close( td ); /* We now have a decrypted blob... * Trim away the PKCS#7 padding. */ readlen -= (int)Bufr[readlen-1]; /* Output the results. * The Server Passphrase starts at offset 32. */ Err( " Server\nPassphrase: " ); hexlist( &Bufr[HASH_SIZE], readlen-HASH_SIZE ); /* The first 32 bytes are the Server Secret (SHA256( Server_Passphrase )). */ Err( " Server\n Secret: " ); hexlist( Bufr, HASH_SIZE ); /* Calculate our own hash of the Server Passphrase so the user can compare. * This result should match the Server Secret. */ (void)calc_SHA256( &Bufr[HASH_SIZE], readlen-HASH_SIZE, ppHash ); Err( "Validation\n Hash: " ); hexlist( ppHash, HASH_SIZE ); return( EXIT_SUCCESS ); } /* main */ /* ========================================================================== */